From 4d80b92c396962aec2cc7b5d6e47de50c0fadd67 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 6 Feb 2026 01:54:25 -0800 Subject: [PATCH] Parse M2 render flags and apply per-batch blend modes Water/lava batches in fountain and Ironforge M2 models use non-opaque blend modes (alpha, additive) defined in the M2 material table. Without parsing these, they rendered as solid surfaces extending visibly beyond their containers. Now each batch looks up its blend mode from the material array and sets the appropriate GL blend function. --- include/pipeline/m2_loader.hpp | 7 ++++ include/rendering/m2_renderer.hpp | 1 + src/pipeline/m2_loader.cpp | 14 ++++++++ src/rendering/m2_renderer.cpp | 55 ++++++++++++++++++++++++++++++- 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/include/pipeline/m2_loader.hpp b/include/pipeline/m2_loader.hpp index e7f7dfe9..c8e74873 100644 --- a/include/pipeline/m2_loader.hpp +++ b/include/pipeline/m2_loader.hpp @@ -109,6 +109,12 @@ struct M2Batch { uint16_t submeshLevel = 0; // Submesh level (0=base, 1+=LOD/alternate mesh) }; +// Material / render flags (per-batch blend mode) +struct M2Material { + uint16_t flags; // Render flags (unlit, unfogged, two-sided, etc.) + uint16_t blendMode; // 0=Opaque, 1=AlphaKey, 2=Alpha, 3=Add, 4=Mod, 5=Mod2x, 6=BlendAdd, 7=Screen +}; + // Texture transform (UV animation) data struct M2TextureTransform { M2AnimationTrack translation; // UV translation keyframes @@ -145,6 +151,7 @@ struct M2Model { std::vector batches; std::vector textures; std::vector textureLookup; // Batch texture index lookup + std::vector materials; // Render flags / blend modes // Texture transforms (UV animation) std::vector textureTransforms; diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index bf48e3c5..fe4baa88 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -32,6 +32,7 @@ struct M2ModelGPU { uint32_t indexCount = 0; bool hasAlpha = false; uint16_t textureAnimIndex = 0xFFFF; // 0xFFFF = no texture animation + uint16_t blendMode = 0; // 0=Opaque, 1=AlphaKey, 2=Alpha, 3=Add, etc. }; GLuint vao = 0; diff --git a/src/pipeline/m2_loader.cpp b/src/pipeline/m2_loader.cpp index d33afb1c..edcf58e1 100644 --- a/src/pipeline/m2_loader.cpp +++ b/src/pipeline/m2_loader.cpp @@ -518,6 +518,20 @@ M2Model M2Loader::load(const std::vector& m2Data) { model.textureLookup = readArray(m2Data, header.ofsTexLookup, header.nTexLookup); } + // Read render flags / materials (blend modes) + if (header.nRenderFlags > 0 && header.ofsRenderFlags > 0) { + struct M2MaterialDisk { uint16_t flags; uint16_t blendMode; }; + auto diskMats = readArray(m2Data, header.ofsRenderFlags, header.nRenderFlags); + model.materials.reserve(diskMats.size()); + for (const auto& dm : diskMats) { + M2Material mat; + mat.flags = dm.flags; + mat.blendMode = dm.blendMode; + model.materials.push_back(mat); + } + core::Logger::getInstance().debug(" Materials: ", model.materials.size()); + } + // Read texture transforms (UV animation data) if (header.nUVAnimation > 0 && header.ofsUVAnimation > 0) { // Build per-sequence flags for skipping external .anim data diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index f711ea6b..83f8ef0a 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -729,6 +729,11 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { gpuModel.hasTextureAnimation = true; } + // Store blend mode from material + if (batch.materialIndex < model.materials.size()) { + bgpu.blendMode = model.materials[batch.materialIndex].blendMode; + } + // Resolve texture: batch.textureIndex → textureLookup → allTextures GLuint tex = whiteTexture; if (batch.textureIndex < model.textureLookup.size()) { @@ -1253,9 +1258,49 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm:: } shader->setUniform("uUVOffset", uvOffset); + // Apply per-batch blend mode from M2 material + // 0=Opaque, 1=AlphaKey, 2=Alpha, 3=Add, 4=Mod, 5=Mod2x, 6=BlendAdd, 7=Screen + bool batchTransparent = false; + switch (batch.blendMode) { + case 0: // Opaque + glBlendFunc(GL_ONE, GL_ZERO); + break; + case 1: // Alpha Key (alpha test, handled by uAlphaTest) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case 2: // Alpha + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + batchTransparent = true; + break; + case 3: // Additive + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + batchTransparent = true; + break; + case 4: // Mod + glBlendFunc(GL_DST_COLOR, GL_ZERO); + batchTransparent = true; + break; + case 5: // Mod2x + glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + batchTransparent = true; + break; + case 6: // BlendAdd + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + batchTransparent = true; + break; + default: // Fallback + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + } + + // Disable depth writes for transparent/additive batches + if (batchTransparent && fadeAlpha >= 1.0f) { + glDepthMask(GL_FALSE); + } + bool hasTexture = (batch.texture != 0); shader->setUniform("uHasTexture", hasTexture); - shader->setUniform("uAlphaTest", batch.hasAlpha); + shader->setUniform("uAlphaTest", batch.blendMode == 1); if (hasTexture) { glActiveTexture(GL_TEXTURE0); @@ -1266,6 +1311,14 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm:: glDrawElements(GL_TRIANGLES, batch.indexCount, GL_UNSIGNED_SHORT, (void*)(batch.indexStart * sizeof(uint16_t))); + // Restore depth writes and blend func after transparent batch + if (batchTransparent && fadeAlpha >= 1.0f) { + glDepthMask(GL_TRUE); + } + if (batch.blendMode != 0 && batch.blendMode != 2) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + lastDrawCallCount++; }