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++; }