diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index 5db00014..97c4fde8 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -394,11 +394,16 @@ private: // Vulkan context VkContext* vkCtx_ = nullptr; - // Vulkan pipelines (one per blend mode) + // Vulkan pipelines — two-sided (VK_CULL_MODE_NONE) variants VkPipeline opaquePipeline_ = VK_NULL_HANDLE; // blend mode 0 VkPipeline alphaTestPipeline_ = VK_NULL_HANDLE; // blend mode 1 VkPipeline alphaPipeline_ = VK_NULL_HANDLE; // blend mode 2 VkPipeline additivePipeline_ = VK_NULL_HANDLE; // blend mode 3+ + // Backface-culled variants for one-sided materials (materialFlags & 0x04 == 0) + VkPipeline opaqueCulledPipeline_ = VK_NULL_HANDLE; + VkPipeline alphaTestCulledPipeline_ = VK_NULL_HANDLE; + VkPipeline alphaCulledPipeline_ = VK_NULL_HANDLE; + VkPipeline additiveCulledPipeline_ = VK_NULL_HANDLE; VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE; // Shadow rendering (Phase 7) diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 5281aad6..2351fa63 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -570,13 +570,14 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout // Pipeline derivatives — opaque is the base, others derive from it for shared state optimization auto buildM2Pipeline = [&](VkPipelineColorBlendAttachmentState blendState, bool depthWrite, + VkCullModeFlags cullMode = VK_CULL_MODE_NONE, VkPipelineCreateFlags flags = 0, VkPipeline basePipeline = VK_NULL_HANDLE) -> VkPipeline { return PipelineBuilder() .setShaders(m2Vert.stageInfo(VK_SHADER_STAGE_VERTEX_BIT), m2Frag.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT)) .setVertexInput({m2Binding}, m2Attrs) .setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) - .setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE) + .setRasterization(VK_POLYGON_MODE_FILL, cullMode) .setDepthTest(true, depthWrite, VK_COMPARE_OP_LESS_OR_EQUAL) .setColorBlendAttachment(blendState) .setMultisample(vkCtx_->getMsaaSamples()) @@ -588,15 +589,26 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout .build(device, vkCtx_->getPipelineCache()); }; - opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true, + // Two-sided pipelines (VK_CULL_MODE_NONE) — for materials with TwoSided flag (0x04) + opaquePipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true, VK_CULL_MODE_NONE, VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT); - alphaTestPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), true, + alphaTestPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), true, VK_CULL_MODE_NONE, VK_PIPELINE_CREATE_DERIVATIVE_BIT, opaquePipeline_); - alphaPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), false, + alphaPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), false, VK_CULL_MODE_NONE, VK_PIPELINE_CREATE_DERIVATIVE_BIT, opaquePipeline_); - additivePipeline_ = buildM2Pipeline(PipelineBuilder::blendAdditive(), false, + additivePipeline_ = buildM2Pipeline(PipelineBuilder::blendAdditive(), false, VK_CULL_MODE_NONE, VK_PIPELINE_CREATE_DERIVATIVE_BIT, opaquePipeline_); + // Backface-culled pipelines — default for one-sided materials + opaqueCulledPipeline_ = buildM2Pipeline(PipelineBuilder::blendDisabled(), true, VK_CULL_MODE_BACK_BIT, + VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT); + alphaTestCulledPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), true, VK_CULL_MODE_BACK_BIT, + VK_PIPELINE_CREATE_DERIVATIVE_BIT, opaqueCulledPipeline_); + alphaCulledPipeline_ = buildM2Pipeline(PipelineBuilder::blendAlpha(), false, VK_CULL_MODE_BACK_BIT, + VK_PIPELINE_CREATE_DERIVATIVE_BIT, opaqueCulledPipeline_); + additiveCulledPipeline_ = buildM2Pipeline(PipelineBuilder::blendAdditive(), false, VK_CULL_MODE_BACK_BIT, + VK_PIPELINE_CREATE_DERIVATIVE_BIT, opaqueCulledPipeline_); + // --- Build particle pipelines --- if (particleVert.isValid() && particleFrag.isValid()) { VkVertexInputBindingDescription pBind{}; @@ -861,6 +873,10 @@ void M2Renderer::shutdown() { destroyPipeline(alphaTestPipeline_); destroyPipeline(alphaPipeline_); destroyPipeline(additivePipeline_); + destroyPipeline(opaqueCulledPipeline_); + destroyPipeline(alphaTestCulledPipeline_); + destroyPipeline(alphaCulledPipeline_); + destroyPipeline(additiveCulledPipeline_); destroyPipeline(particlePipeline_); destroyPipeline(particleAdditivePipeline_); destroyPipeline(smokePipeline_); diff --git a/src/rendering/m2_renderer_render.cpp b/src/rendering/m2_renderer_render.cpp index d013fb50..58a0a458 100644 --- a/src/rendering/m2_renderer_render.cpp +++ b/src/rendering/m2_renderer_render.cpp @@ -1147,16 +1147,25 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const } if (forceCutout) effectiveBlendMode = 1; + const bool twoSided = (batch.materialFlags & 0x04) != 0; VkPipeline desiredPipeline; if (forceCutout) { + // Foliage / ground-detail cards are effectively two-sided desiredPipeline = opaquePipeline_; - } else { + } else if (twoSided) { switch (effectiveBlendMode) { case 0: desiredPipeline = opaquePipeline_; break; case 1: desiredPipeline = alphaTestPipeline_; break; case 2: desiredPipeline = alphaPipeline_; break; default: desiredPipeline = additivePipeline_; break; } + } else { + switch (effectiveBlendMode) { + case 0: desiredPipeline = opaqueCulledPipeline_; break; + case 1: desiredPipeline = alphaTestCulledPipeline_; break; + case 2: desiredPipeline = alphaCulledPipeline_; break; + default: desiredPipeline = additiveCulledPipeline_; break; + } } if (desiredPipeline != currentPipeline) { vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, desiredPipeline); @@ -1348,10 +1357,18 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const else if (effectiveBlendMode == 4 || effectiveBlendMode == 5) effectiveBlendMode = 3; } + const bool twoSided = (batch.materialFlags & 0x04) != 0; VkPipeline desiredPipeline; - switch (effectiveBlendMode) { - case 2: desiredPipeline = alphaPipeline_; break; - default: desiredPipeline = additivePipeline_; break; + if (twoSided) { + switch (effectiveBlendMode) { + case 2: desiredPipeline = alphaPipeline_; break; + default: desiredPipeline = additivePipeline_; break; + } + } else { + switch (effectiveBlendMode) { + case 2: desiredPipeline = alphaCulledPipeline_; break; + default: desiredPipeline = additiveCulledPipeline_; break; + } } if (desiredPipeline != currentPipeline) { vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, desiredPipeline);