From a24fe4cc45aaa1762f01546a8ac81810fc377394 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 7 Mar 2026 00:48:04 -0800 Subject: [PATCH] Ironforge Great Forge lava, magma water rendering, LavaSteam particle effects - Add magma/slime rendering path to water shader (fbm noise, crust/molten/core coloring) - Fix WMO liquid height filter rejecting high-altitude zones like Ironforge (Z>300) - Allow interior WMO magma/slime MLIQ groups to load (skip only water/ocean) - Mark LAVASTEAM.m2 as spell effect for proper additive blend, hide emission mesh - Add isLavaModel flag for M2 ForgeLava/LavaPots UV scroll fallback - Add isLava material detection in WMO renderer for lava texture UV animation - Fix WMO material UBO colors for magma (was blue, now orange-red) --- assets/shaders/water.frag.glsl | 46 ++++++++++++++++++++++++ assets/shaders/water.frag.spv | Bin 34440 -> 37836 bytes assets/shaders/wmo.frag.glsl | 14 +++++++- assets/shaders/wmo.frag.spv | Bin 20156 -> 21120 bytes include/rendering/m2_renderer.hpp | 1 + include/rendering/wmo_renderer.hpp | 5 ++- src/rendering/m2_renderer.cpp | 56 +++++++++++++++++++++++++++++ src/rendering/terrain_manager.cpp | 20 +++++++++-- src/rendering/water_renderer.cpp | 13 ++++--- src/rendering/wmo_renderer.cpp | 15 ++++++-- 10 files changed, 158 insertions(+), 12 deletions(-) diff --git a/assets/shaders/water.frag.glsl b/assets/shaders/water.frag.glsl index c7dbc5b4..5d3af519 100644 --- a/assets/shaders/water.frag.glsl +++ b/assets/shaders/water.frag.glsl @@ -155,6 +155,52 @@ void main() { float time = fogParams.z; float basicType = push.liquidBasicType; + // ============================================================ + // Magma / Slime — self-luminous flowing surfaces, skip water path + // ============================================================ + if (basicType > 1.5) { + float dist = length(viewPos.xyz - FragPos); + vec2 flowUV = FragPos.xy; + + bool isMagma = basicType < 2.5; + + // Multi-octave flowing noise for organic lava look + float n1 = fbmNoise(flowUV * 0.06 + vec2(time * 0.02, time * 0.03), time * 0.4); + float n2 = fbmNoise(flowUV * 0.10 + vec2(-time * 0.015, time * 0.025), time * 0.3); + float n3 = noiseValue(flowUV * 0.25 + vec2(time * 0.04, -time * 0.02)); + float flow = n1 * 0.45 + n2 * 0.35 + n3 * 0.20; + + // Dark crust vs bright molten core + vec3 crustColor, hotColor, coreColor; + if (isMagma) { + crustColor = vec3(0.15, 0.04, 0.01); // dark cooled rock + hotColor = vec3(1.0, 0.45, 0.05); // orange molten + coreColor = vec3(1.0, 0.85, 0.3); // bright yellow-white core + } else { + crustColor = vec3(0.05, 0.15, 0.02); + hotColor = vec3(0.3, 0.8, 0.15); + coreColor = vec3(0.5, 1.0, 0.3); + } + + // Three-tier color: crust → molten → hot core + float crustMask = smoothstep(0.25, 0.50, flow); + float coreMask = smoothstep(0.60, 0.80, flow); + vec3 color = mix(crustColor, hotColor, crustMask); + color = mix(color, coreColor, coreMask); + + // Subtle pulsing emissive glow + float pulse = 1.0 + 0.15 * sin(time * 1.5 + flow * 6.0); + color *= pulse; + + // Emissive brightening for hot areas + color *= 1.0 + coreMask * 0.6; + + float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0); + color = mix(fogColor.rgb, color, fogFactor); + outColor = vec4(color, 0.97); + return; + } + vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(SceneColor, 0)); // --- Normal computation --- diff --git a/assets/shaders/water.frag.spv b/assets/shaders/water.frag.spv index 6fe7f2a6449a2a30df1299673bfdd8b027c567bb..a5b91695bce51f42f74950164e1da6098a7e99ed 100644 GIT binary patch literal 37836 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 V+$Z^aa({<=E!RhzIjNb`{{!U*RZ;)| literal 34440 zcmZ{t2b^71)xHm8CiLDxdXX*=s~8u2FEPD`7%|tRJB62 zQgvBxRUfNWOQTek^)A#aRAXyCb@~z0cN|^NzvHgE?WE(%)flVVwo0{P)dRN7>l>J_ zzRc29wGL$i$}t!g5&vD3ODU_;R$n>?jiINidVo%Qs^zPx(@&my=%G`mPd<6p@bJl_ z3(g*Fojf=&zjbo|z({M>f`Kzy`Yl`d4a}c4xUj!;=*6SVrI)xX(?|cpzQH4AjSLMA z9@BS5>yV+5d3}RhpD}xvy=U*dH|DCBTvntWu5k~uQdJ8E=Fy|J<*K!)4;UC-FfuT6 z;evso`BSGJOY?DiTMS|Q)$uu`_8(KNK|QU;z15o3BSQ=4%$?sF9j&!xs|~29wua`l z7L1&I#Jqt8bGIf+`;S;X)w#24HFJA9utG(c} zT7!cN2m3~*4E4?HZo5a(c2DrB0}B?k7-=s%d!PT+7R^5GygkM-)qddqo!k0EF(;iq zQ^X`EehYYZXMtxmKIe%WpG(Az&*k9$X=l~>f2PJ~*Z488o6T|kNL;t=sh$M8*W2eyPxW+o zV}BOj-p&n`QE7YgexTpFT^_)3pk)3{A2bQWiQQGY$!v`8X4eS_=cNDm< z#y!>1;NiZJzVvk*d}d=ismpdUd{$$d)n#kJa~=$I+0KEV(X_i@30qHfWf%YG5`0W` zBRpfc1>7D(PjxGJ-oSh>QT3Q^ho4>dF{Zi)?7Dg0d99t_8kjS8=8#i}(o;Q%=3YOL zgyj#zhld8{FKFzK$UAe=Q~h8GKBjsKp0U2TgsrD~xr_e=zW6-rtzLo8XpKx^#_MbC zSTa!aIAfr7ruA6<-pqm4`~{6gTRX3@*vxCj&}b*V_p;eTa|+*fzRR}t z8BX0OIPsSQkIwDuA3C#RBKnx>?+%c|b@~r@X5Z+*tRv4Jc4AzY*U@Gy9G$C=`N%!y z4fVGMsiP^M$z5*3yx}@W{hTAUDI=}ZfgRnsQ9ok1)p8BZQUCVboW5|Ne_|&Kb1=|w zlY@Eo!bfL~v|96z@)CnW1hhGWC$px^$J9N9<`uOy7pBfl!dzpvN7Ixq|$lm}j_vpr5d^7m`P9MG1HrfaK&Tfqup`G{6 zXd?r|!-K6O7L2s!&so4syDqz;I|%cAbH``${PsEATTNB#>mTSFY;Q30Im|ZG`a9>? zFx-8khxW7KgFM${%!}aV{r=*Hx9|5o)n)L0PwqO8p6Y6NbFU}f*ye0P z&YnHmYM;Bk)h8G0^#bX83)-mbaCo~xJ7#ZnTlD(g-X8DmX!V+Pw4UmVXytwVOYru6 zeQb4CVoc{^`{ULG&>#kr5Io<#4PIXW~l>~&|-P>V?Jg%_gF ztWO=3WvZ9Z7S8W)jnv6c>0A|z|1!2?921wQ)(G2$x$#=0?ol`CT#MRz`&`C-!r<`S zzV`X4zfDHx4$Pa^8tH7-#PXc()BOkr$i>4w{O6_*b9@cFM2=JX`rFqSt|{2Kmd$DP z*Q-w3u~iQNxn2zp4xQP##yS4w(feDi;fQLh)*AC Rlh9^1sB{`Ng-Y_*{=-GdWH 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 diff --git a/assets/shaders/wmo.frag.glsl b/assets/shaders/wmo.frag.glsl index b8db595d..c04e1a93 100644 --- a/assets/shaders/wmo.frag.glsl +++ b/assets/shaders/wmo.frag.glsl @@ -28,6 +28,7 @@ layout(set = 1, binding = 1) uniform WMOMaterial { int pomMaxSamples; float heightMapVariance; float normalMapStrength; + int isLava; }; layout(set = 1, binding = 2) uniform sampler2D uNormalHeightMap; @@ -120,6 +121,14 @@ void main() { // Compute final UV (with POM if enabled) vec2 finalUV = TexCoord; + // Lava/magma: scroll UVs for flowing effect + if (isLava != 0) { + float time = fogParams.z; + // Scroll both axes — pools get horizontal flow, waterfalls get vertical flow + // (UV orientation depends on mesh, so animate both) + finalUV += vec2(time * 0.04, time * 0.06); + } + // Build TBN matrix vec3 T = normalize(Tangent); vec3 B = normalize(Bitangent); @@ -170,7 +179,10 @@ void main() { shadow = mix(1.0, shadow, shadowParams.y); } - if (unlit != 0) { + if (isLava != 0) { + // Lava is self-luminous — bright emissive, no shadows + result = texColor.rgb * 1.5; + } else if (unlit != 0) { result = texColor.rgb * shadow; } else if (isInterior != 0) { vec3 mocv = max(VertColor.rgb, vec3(0.5)); diff --git a/assets/shaders/wmo.frag.spv b/assets/shaders/wmo.frag.spv index a7b9ef940c38550f2f1e033f8f4ee5c56700db4e..2453f0ff763aa7053f9ce3d164e1646cb97d2d03 100644 GIT binary patch literal 21120 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 Wo&{T=^Hb((JY(qRyPY=Q`~MGgd{}q@ literal 20156 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 2 bool isWindow = false; // F_SIDN or F_WINDOW material + bool isLava = false; // lava/magma texture (UV scroll) // For multi-draw: store index ranges struct DrawRange { uint32_t firstIndex; uint32_t indexCount; }; std::vector draws; diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 690a5234..acf2816d 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1131,10 +1131,32 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { (lowerName.find("hazardlight") != std::string::npos) || (lowerName.find("lavasplash") != std::string::npos) || (lowerName.find("lavabubble") != std::string::npos) || + (lowerName.find("lavasteam") != std::string::npos) || (lowerName.find("wisps") != std::string::npos); gpuModel.isSpellEffect = effectByName || (hasParticles && model.vertices.size() <= 200 && model.particleEmitters.size() >= 3); + gpuModel.isLavaModel = + (lowerName.find("forgelava") != std::string::npos) || + (lowerName.find("lavapot") != std::string::npos) || + (lowerName.find("lavaflow") != std::string::npos); + if (lowerName.find("lava") != std::string::npos || lowerName.find("steam") != std::string::npos) { + LOG_WARNING("M2 LAVA/STEAM: '", model.name, "' isSpellEffect=", gpuModel.isSpellEffect ? "Y" : "N", + " effectByName=", effectByName ? "Y" : "N", + " particles=", model.particleEmitters.size(), + " verts=", model.vertices.size(), + " batches=", model.batches.size(), + " texTransforms=", model.textureTransforms.size(), + " texTransformLookup=", model.textureTransformLookup.size(), + " isLavaModel=", gpuModel.isLavaModel ? "Y" : "N"); + for (size_t bi = 0; bi < model.batches.size(); bi++) { + const auto& b = model.batches[bi]; + uint8_t bm = (b.materialIndex < model.materials.size()) ? model.materials[b.materialIndex].blendMode : 255; + uint16_t mf = (b.materialIndex < model.materials.size()) ? model.materials[b.materialIndex].flags : 0; + LOG_WARNING(" batch[", bi, "]: blend=", (int)bm, " matFlags=0x", std::hex, mf, std::dec, + " texAnimIdx=", b.textureAnimIndex, " idxCount=", b.indexCount); + } + } gpuModel.isInstancePortal = (lowerName.find("instanceportal") != std::string::npos) || (lowerName.find("instancenewportal") != std::string::npos) || @@ -2357,6 +2379,9 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const } const bool foliageLikeModel = model.isFoliageLike; + // Particle-dominant spell effects: mesh is emission geometry, render dim + const bool particleDominantEffect = model.isSpellEffect && + !model.particleEmitters.empty() && model.batches.size() <= 2; for (const auto& batch : model.batches) { if (batch.indexCount == 0) continue; if (!model.isGroundDetail && batch.submeshLevel != targetLOD) continue; @@ -2421,6 +2446,12 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const } } } + // Lava M2 models: fallback UV scroll if no texture animation + if (model.isLavaModel && uvOffset == glm::vec2(0.0f)) { + static auto startTime = std::chrono::steady_clock::now(); + float t = std::chrono::duration(std::chrono::steady_clock::now() - startTime).count(); + uvOffset = glm::vec2(t * 0.03f, -t * 0.08f); + } // Foliage/card-like batches render more stably as cutout (depth-write on) // instead of alpha-blended sorting. @@ -2498,6 +2529,10 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const pc.useBones = useBones ? 1 : 0; pc.isFoliage = model.shadowWindFoliage ? 1 : 0; pc.fadeAlpha = instanceFadeAlpha; + // Particle-dominant effects: mesh is emission geometry, don't render + if (particleDominantEffect && batch.blendMode <= 1) { + continue; + } vkCmdPushConstants(cmd, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc); vkCmdDrawIndexed(cmd, batch.indexCount, 1, batch.indexStart, 0, 0); @@ -2948,8 +2983,23 @@ void M2Renderer::emitParticles(M2Instance& inst, const M2ModelGPU& gpu, float dt std::uniform_real_distribution distN(-1.0f, 1.0f); std::uniform_int_distribution distTile; + static uint32_t steamDiagCounter = 0; + bool steamDiag = (gpu.isSpellEffect && gpu.particleEmitters.size() >= 6 && steamDiagCounter < 3); + for (size_t ei = 0; ei < gpu.particleEmitters.size(); ei++) { const auto& em = gpu.particleEmitters[ei]; + if (steamDiag) { + float rate = interpFloat(em.emissionRate, inst.animTime, inst.currentSequenceIndex, + gpu.sequences, gpu.globalSequenceDurations); + float life = interpFloat(em.lifespan, inst.animTime, inst.currentSequenceIndex, + gpu.sequences, gpu.globalSequenceDurations); + LOG_WARNING("STEAM PARTICLE DIAG emitter[", ei, "]: enabled=", em.enabled ? "Y" : "N", + " rate=", rate, " life=", life, + " animTime=", inst.animTime, " seq=", inst.currentSequenceIndex, + " bone=", em.bone, " blendType=", (int)em.blendingType, + " globalSeq=", em.emissionRate.globalSequence, + " rateSeqs=", em.emissionRate.sequences.size()); + } if (!em.enabled) continue; float rate = interpFloat(em.emissionRate, inst.animTime, inst.currentSequenceIndex, @@ -3038,6 +3088,12 @@ void M2Renderer::emitParticles(M2Instance& inst, const M2ModelGPU& gpu, float dt inst.emitterAccumulators[ei] = 0.0f; } } + if (steamDiag) { + LOG_WARNING("STEAM PARTICLE DIAG: totalParticles=", inst.particles.size(), + " sequences=", gpu.sequences.size(), + " globalSeqDurations=", gpu.globalSequenceDurations.size()); + steamDiagCounter++; + } } void M2Renderer::updateParticles(M2Instance& inst, float dt) { diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index 5fbca940..b0454ffa 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -835,10 +835,24 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) { modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)); modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.y, glm::vec3(0.0f, 1.0f, 0.0f)); modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.x, glm::vec3(1.0f, 0.0f, 0.0f)); - for (const auto& group : wmoReady.model.groups) { + for (size_t gi = 0; gi < wmoReady.model.groups.size(); gi++) { + const auto& group = wmoReady.model.groups[gi]; if (!group.liquid.hasLiquid()) continue; - // Skip interior groups — their liquid is for indoor areas - if (group.flags & 0x2000) continue; + uint16_t lt = group.liquid.materialId; + uint8_t basicType = (lt == 0) ? 0 : ((lt - 1) % 4); + bool isInterior = (group.flags & 0x2000) != 0; + LOG_WARNING("WMO MLIQ group", gi, ": flags=0x", std::hex, group.flags, std::dec, + " materialId=", lt, " basicType=", (int)basicType, + " interior=", isInterior ? "Y" : "N", + " xVerts=", group.liquid.xVerts, " yVerts=", group.liquid.yVerts); + // Skip interior water/ocean but keep magma/slime (e.g. Ironforge lava) + if (isInterior) { + if (basicType < 2) { + LOG_WARNING(" -> SKIPPED (interior water/ocean)"); + continue; + } + LOG_WARNING(" -> LOADING (interior magma/slime)"); + } waterRenderer->loadFromWMO(group.liquid, modelMatrix, wmoInstId); loadedLiquids++; } diff --git a/src/rendering/water_renderer.cpp b/src/rendering/water_renderer.cpp index 6a5c310d..a01cfedb 100644 --- a/src/rendering/water_renderer.cpp +++ b/src/rendering/water_renderer.cpp @@ -544,9 +544,14 @@ void WaterRenderer::updateMaterialUBO(WaterSurface& surface) { // WMO liquid material override if (surface.wmoId != 0) { const uint8_t basicType = (surface.liquidType == 0) ? 0 : ((surface.liquidType - 1) % 4); - if (basicType == 2 || basicType == 3) { - color = glm::vec4(0.2f, 0.4f, 0.6f, 1.0f); - alpha = 0.45f; + if (basicType == 2) { + // Magma — bright orange-red, opaque + color = glm::vec4(1.0f, 0.35f, 0.05f, 1.0f); + alpha = 0.95f; + } else if (basicType == 3) { + // Slime — green, semi-opaque + color = glm::vec4(0.2f, 0.6f, 0.1f, 1.0f); + alpha = 0.85f; } } @@ -935,7 +940,7 @@ void WaterRenderer::loadFromWMO([[maybe_unused]] const pipeline::WMOLiquid& liqu surface.origin.z = adjustedZ; surface.position.z = adjustedZ; - if (surface.origin.z > 300.0f || surface.origin.z < -100.0f) return; + if (surface.origin.z > 2000.0f || surface.origin.z < -500.0f) return; // Build tile mask from MLIQ flags and per-vertex heights size_t tileCount = static_cast(surface.width) * static_cast(surface.height); diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 5bae174f..8e72bd0d 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -596,20 +596,26 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) { // so we additionally check for "window" or "glass" in the texture path to // distinguish actual glass from lamp post geometry. bool isWindow = false; + bool isLava = false; if (batch.materialId < modelData.materialTextureIndices.size()) { uint32_t ti = modelData.materialTextureIndices[batch.materialId]; if (ti < modelData.textureNames.size()) { const auto& texName = modelData.textureNames[ti]; - // Case-insensitive search for "window" or "glass" + // Case-insensitive search for material types std::string texNameLower = texName; std::transform(texNameLower.begin(), texNameLower.end(), texNameLower.begin(), ::tolower); isWindow = (texNameLower.find("window") != std::string::npos || texNameLower.find("glass") != std::string::npos); + isLava = (texNameLower.find("lava") != std::string::npos || + texNameLower.find("molten") != std::string::npos || + texNameLower.find("magma") != std::string::npos); + if (isLava) { + LOG_WARNING("WMO LAVA BATCH: tex='", texName, "' matId=", batch.materialId, + " blend=", blendMode, " flags=0x", std::hex, matFlags, std::dec); + } } } - - BatchKey key{ reinterpret_cast(tex), alphaTest, unlit, isWindow }; auto& mb = batchMap[key]; if (mb.draws.empty()) { @@ -619,6 +625,7 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) { mb.unlit = unlit; mb.isTransparent = (blendMode >= 2); mb.isWindow = isWindow; + mb.isLava = isLava; // Look up normal/height map from texture cache if (hasTexture && tex != whiteTexture_.get()) { for (const auto& [cacheKey, cacheEntry] : textureCache) { @@ -668,6 +675,7 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) { } matData.heightMapVariance = mb.heightMapVariance; matData.normalMapStrength = normalMapStrength_; + matData.isLava = mb.isLava ? 1 : 0; if (matBuf.info.pMappedData) { memcpy(matBuf.info.pMappedData, &matData, sizeof(matData)); } @@ -789,6 +797,7 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) { doodadTemplate.m2Path = m2Path; doodadTemplate.localTransform = localTransform; modelData.doodadTemplates.push_back(doodadTemplate); + } if (!modelData.doodadTemplates.empty()) {