diff --git a/source/ps/CStrInternStatic.h b/source/ps/CStrInternStatic.h index d9e8e5300e..441ddfde1f 100644 --- a/source/ps/CStrInternStatic.h +++ b/source/ps/CStrInternStatic.h @@ -49,14 +49,6 @@ X(DISABLE_RECEIVE_SHADOWS) X(IGNORE_LOS) X(MINIMAP_BASE) X(MINIMAP_POINT) -X(MODE_SHADOWCAST) -X(MODE_SILHOUETTEDISPLAY) -X(MODE_SILHOUETTEOCCLUDER) -X(MODE_WIREFRAME) -X(MODE_WIREFRAME_SOLID) -X(PASS_REFLECTIONS) -X(PASS_REFRACTIONS) -X(PASS_SHADOWS) X(RENDER_DEBUG_MODE) X(RENDER_DEBUG_MODE_AO) X(RENDER_DEBUG_MODE_ALPHA) @@ -140,6 +132,7 @@ X(particle_multiply) X(particle_overlay) X(particle_solid) X(particle_subtract) +X(particle_wireframe) X(playerColor) X(projInvTransform) X(qualityLevel) @@ -169,11 +162,18 @@ X(spaceTransform) X(sunColor) X(sunDir) X(terrain_base) +X(terrain_base_reflections) +X(terrain_base_wireframe) X(terrain_blend) +X(terrain_blend_reflections) +X(terrain_blend_wireframe) X(terrain_decal) +X(terrain_decal_reflections) +X(terrain_decal_wireframe) X(terrain_shadow_caster) X(terrain_silhouette_occluder) X(terrain_solid) +X(terrain_wireframe) X(tex) X(texSize) X(textureTransform) @@ -187,6 +187,7 @@ X(vertexCount) X(viewInvTransform) X(water_high) X(water_simple) +X(water_simple_wireframe) X(water_waves) X(waterEffectsTex) X(waterTex) diff --git a/source/renderer/DecalRData.cpp b/source/renderer/DecalRData.cpp index bd3f54bbe0..46711fcff3 100644 --- a/source/renderer/DecalRData.cpp +++ b/source/renderer/DecalRData.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -138,7 +138,8 @@ void CDecalRData::Update(CSimulation2* simulation) void CDecalRData::RenderDecals( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IVertexInputLayout* vertexInputLayout, - const std::vector& decals, const CShaderDefines& context, ShadowMap* shadow) + const std::vector& decals, const CShaderDefines& context, + ShadowMap* shadow, const CMaterial::Pass materialPass) { PROFILE3("render terrain decals"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain decals"); @@ -156,7 +157,7 @@ void CDecalRData::RenderDecals( { CMaterial& material = decal->m_Decal->m_Decal.m_Material; - if (material.GetShaderEffect().empty()) + if (material.GetShaderEffect(materialPass).empty()) { LOGERROR("Terrain renderer failed to load shader effect.\n"); continue; @@ -167,7 +168,7 @@ void CDecalRData::RenderDecals( SDecalBatch batch; batch.decal = decal; - batch.shaderEffect = material.GetShaderEffect(); + batch.shaderEffect = material.GetShaderEffect(materialPass); batch.shaderDefines = material.GetShaderDefines(); batch.vertices = decal->m_VBDecals.Get(); batch.indices = decal->m_VBDecalsIndices.Get(); @@ -193,8 +194,16 @@ void CDecalRData::RenderDecals( CShaderDefines defines = contextDecal; defines.SetMany(itTechBegin->shaderDefines); // TODO: move enabling blend to XML. - CShaderTechniquePtr techBase = g_Renderer.GetShaderManager().LoadEffect( - itTechBegin->shaderEffect == str_terrain_base ? str_terrain_decal : itTechBegin->shaderEffect, defines); + CShaderTechniquePtr techBase{g_Renderer.GetShaderManager().LoadEffect([](const CStrIntern shaderEffect) + { + if (shaderEffect == str_terrain_base) + return str_terrain_decal; + if (shaderEffect == str_terrain_base_reflections) + return str_terrain_decal_reflections; + if (shaderEffect == str_terrain_base_wireframe) + return str_terrain_decal_wireframe; + return shaderEffect; + }(itTechBegin->shaderEffect), defines)}; if (!techBase) { LOGERROR("Terrain renderer failed to load shader effect (%s)\n", diff --git a/source/renderer/DecalRData.h b/source/renderer/DecalRData.h index ffc00cba8f..26abd90de1 100644 --- a/source/renderer/DecalRData.h +++ b/source/renderer/DecalRData.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ #ifndef INCLUDED_DECALRDATA #define INCLUDED_DECALRDATA +#include "graphics/Material.h" #include "graphics/RenderableObject.h" #include "lib/code_annotation.h" #include "maths/Vector2D.h" @@ -45,7 +46,8 @@ public: static void RenderDecals( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IVertexInputLayout* vertexInputLayout, - const std::vector& decals, const CShaderDefines& context, ShadowMap* shadow); + const std::vector& decals, const CShaderDefines& context, + ShadowMap* shadow, const CMaterial::Pass materialPass); CModelDecal* GetDecal() { return m_Decal; } diff --git a/source/renderer/ModelRenderer.cpp b/source/renderer/ModelRenderer.cpp index c08e04937b..f4e335a0ed 100644 --- a/source/renderer/ModelRenderer.cpp +++ b/source/renderer/ModelRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -64,8 +64,43 @@ #include #include -/////////////////////////////////////////////////////////////////////////////////////////////// -// ModelRenderer implementation + +namespace +{ + +CMaterial::Pass GetMaterialPassFromCullGroup(const int cullGroup, const ERenderMode renderMode) +{ + switch (renderMode) + { + case ERenderMode::WIREFRAME: + return CMaterial::Pass::WIREFRAME; + case ERenderMode::EDGED_FACES: + return CMaterial::Pass::WIREFRAME_SOLID; + case ERenderMode::SOLID: + break; + } + + switch (cullGroup) + { + case CSceneRenderer::CULL_SHADOWS_CASCADE_0: [[fallthrough]]; + case CSceneRenderer::CULL_SHADOWS_CASCADE_1: [[fallthrough]]; + case CSceneRenderer::CULL_SHADOWS_CASCADE_2: [[fallthrough]]; + case CSceneRenderer::CULL_SHADOWS_CASCADE_3: + return CMaterial::Pass::SHADOW_CASTER; + case CSceneRenderer::CULL_SILHOUETTE_OCCLUDER: + return CMaterial::Pass::SILHOUETTE_OCCLUDER; + case CSceneRenderer::CULL_SILHOUETTE_CASTER: + return CMaterial::Pass::SILHOUETTE_CASTER; + case CSceneRenderer::CULL_DEFAULT: + return CMaterial::Pass::MAIN; + default: + break; + } + + return CMaterial::Pass::MAIN; +} + +} void ModelRenderer::Init() { @@ -380,7 +415,8 @@ struct SMRCompareTechBucket void ShaderModelRenderer::Render( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags) + const RenderModifierPtr& modifier, const CShaderDefines& context, + int cullGroup, int flags, const ERenderMode renderMode) { if (m->submissions[cullGroup].empty()) return; @@ -388,6 +424,8 @@ void ShaderModelRenderer::Render( CMatrix3D worldToCam; g_Renderer.GetSceneRenderer().GetViewCamera().GetOrientation().GetInverse(worldToCam); + const CMaterial::Pass materialPass{GetMaterialPassFromCullGroup(cullGroup, renderMode)}; + /* * Rendering approach: * @@ -462,8 +500,10 @@ void ShaderModelRenderer::Render( for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i) { CModel* model = m->submissions[cullGroup][i]; - const CShaderDefines& defines = model->GetMaterial().GetShaderDefines(); - SMRMaterialBucketKey key(model->GetMaterial().GetShaderEffect(), defines); + const CMaterial material{model->GetMaterial()}; + const CShaderDefines& defines{material.GetShaderDefines()}; + const CStrIntern shaderEffect{material.GetShaderEffect(materialPass)}; + SMRMaterialBucketKey key(shaderEffect, defines); MaterialBuckets_t::iterator it = materialBuckets.find(key); if (it == materialBuckets.end()) diff --git a/source/renderer/ModelRenderer.h b/source/renderer/ModelRenderer.h index daacebe2e8..019ceaffc9 100644 --- a/source/renderer/ModelRenderer.h +++ b/source/renderer/ModelRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -26,6 +26,7 @@ #include "graphics/MeshManager.h" #include "graphics/RenderableObject.h" +#include "renderer/SceneRenderer.h" #include "lib/types.h" #include @@ -173,7 +174,8 @@ public: */ virtual void Render( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags) = 0; + const RenderModifierPtr& modifier, const CShaderDefines& context, + int cullGroup, int flags, const ERenderMode renderMode) = 0; /** * CopyPositionAndNormals: Copy unanimated object-space vertices and @@ -282,7 +284,8 @@ public: void EndFrame() override; void Render( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags) override; + const RenderModifierPtr& modifier, const CShaderDefines& context, + int cullGroup, int flags, const ERenderMode renderMode) override; private: struct ShaderModelRendererInternals; diff --git a/source/renderer/PatchRData.cpp b/source/renderer/PatchRData.cpp index 68d87f2f0c..9598fb5f78 100644 --- a/source/renderer/PatchRData.cpp +++ b/source/renderer/PatchRData.cpp @@ -883,7 +883,8 @@ using ShaderTechniqueBatches = PooledBatchMap& patches, const CShaderDefines& context, ShadowMap* shadow) + const std::vector& patches, const CShaderDefines& context, + ShadowMap* shadow, const CMaterial::Pass materialPass) { PROFILE3("render terrain bases"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain bases"); @@ -902,7 +903,7 @@ void CPatchRData::RenderBases( { SSplat& splat = patch->m_Splats[j]; const CMaterial& material = splat.m_Texture->GetMaterial(); - if (material.GetShaderEffect().empty()) + if (material.GetShaderEffect(materialPass).empty()) { LOGERROR("Terrain renderer failed to load shader effect.\n"); continue; @@ -911,7 +912,9 @@ void CPatchRData::RenderBases( BatchElements& batch = PooledPairGet( PooledMapGet( PooledMapGet( - PooledMapGet(batches, std::make_pair(material.GetShaderEffect(), material.GetShaderDefines()), scopedLinearAllocator), + PooledMapGet( + batches, std::make_pair(material.GetShaderEffect(materialPass), material.GetShaderDefines()), + scopedLinearAllocator), splat.m_Texture, scopedLinearAllocator ), patch->m_VBBase->m_Owner, scopedLinearAllocator @@ -1043,7 +1046,8 @@ struct SBlendStackItem void CPatchRData::RenderBlends( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IVertexInputLayout* vertexInputLayout, - const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow) + const std::vector& patches, const CShaderDefines& context, + ShadowMap* shadow, const CMaterial::Pass materialPass) { PROFILE3("render terrain blends"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain blends"); @@ -1130,11 +1134,19 @@ void CPatchRData::RenderBlends( CShaderDefines defines = contextBlend; defines.SetMany(bestTex->GetMaterial().GetShaderDefines()); // TODO: move enabling blend to XML. - const CStrIntern shaderEffect = bestTex->GetMaterial().GetShaderEffect(); - if (shaderEffect != str_terrain_base) + const CStrIntern shaderEffect = bestTex->GetMaterial().GetShaderEffect(materialPass); + if (shaderEffect != str_terrain_base && shaderEffect != str_terrain_base_reflections && shaderEffect != str_terrain_base_wireframe) ONCE(LOGWARNING("Shader effect '%s' doesn't support semi-transparent terrain rendering.", shaderEffect.c_str())); - layer.m_ShaderTech = g_Renderer.GetShaderManager().LoadEffect( - shaderEffect == str_terrain_base ? str_terrain_blend : shaderEffect, defines); + layer.m_ShaderTech = g_Renderer.GetShaderManager().LoadEffect([](const CStrIntern shaderEffect) + { + if (shaderEffect == str_terrain_base) + return str_terrain_blend; + if (shaderEffect == str_terrain_base_reflections) + return str_terrain_blend_reflections; + if (shaderEffect == str_terrain_base_wireframe) + return str_terrain_blend_wireframe; + return shaderEffect; + }(shaderEffect), defines); } batches.push_back(layer); } diff --git a/source/renderer/PatchRData.h b/source/renderer/PatchRData.h index 3db47b207f..4442c4d4f8 100644 --- a/source/renderer/PatchRData.h +++ b/source/renderer/PatchRData.h @@ -18,6 +18,7 @@ #ifndef INCLUDED_PATCHRDATA #define INCLUDED_PATCHRDATA +#include "graphics/Material.h" #include "graphics/Patch.h" #include "graphics/RenderableObject.h" #include "lib/code_annotation.h" @@ -76,11 +77,13 @@ public: static void RenderBases( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IVertexInputLayout* vertexInputLayout, - const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow); + const std::vector& patches, const CShaderDefines& context, + ShadowMap* shadow, const CMaterial::Pass materialPass); static void RenderBlends( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IVertexInputLayout* vertexInputLayout, - const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow); + const std::vector& patches, const CShaderDefines& context, + ShadowMap* shadow, const CMaterial::Pass materialPass); static void RenderStreams( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IVertexInputLayout* vertexInputLayout, diff --git a/source/renderer/SceneRenderer.cpp b/source/renderer/SceneRenderer.cpp index 38e2f04394..8911c1e3bf 100644 --- a/source/renderer/SceneRenderer.cpp +++ b/source/renderer/SceneRenderer.cpp @@ -154,18 +154,18 @@ public: */ void CallModelRenderers( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const CShaderDefines& context, int cullGroup, int flags) + const CShaderDefines& context, int cullGroup, int flags, const ERenderMode renderMode) { CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) contextSkinned.Add(str_USE_INSTANCING, str_1); - Model.NormalSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags); + Model.NormalSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags, renderMode); if (Model.NormalUnskinned != Model.NormalSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); - Model.NormalUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags); + Model.NormalUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode); } } @@ -174,18 +174,18 @@ public: */ void CallTranspModelRenderers( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const CShaderDefines& context, int cullGroup, int flags) + const CShaderDefines& context, int cullGroup, int flags, const ERenderMode renderMode) { CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) contextSkinned.Add(str_USE_INSTANCING, str_1); - Model.TranspSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags); + Model.TranspSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags, renderMode); if (Model.TranspUnskinned != Model.TranspSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); - Model.TranspUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags); + Model.TranspUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode); } } }; @@ -294,18 +294,11 @@ void CSceneRenderer::SetSimulation(CSimulation2* simulation) } void CSceneRenderer::RenderShadowMap( - Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const CShaderDefines& context) + Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { PROFILE3_GPU(deviceCommandContext, "shadow map"); GPU_SCOPED_LABEL(deviceCommandContext, "Render shadow map"); - CShaderDefines shadowsContext = context; - shadowsContext.Add(str_PASS_SHADOWS, str_1); - - CShaderDefines contextCast = shadowsContext; - contextCast.Add(str_MODE_SHADOWCAST, str_1); - m->shadow.BeginRender(deviceCommandContext); const int cascadeCount = m->shadow.GetCascadeCount(); @@ -317,17 +310,18 @@ void CSceneRenderer::RenderShadowMap( const int cullGroup = CULL_SHADOWS_CASCADE_0 + cascade; { PROFILE("render patches"); - m->terrainRenderer.RenderPatches(deviceCommandContext, cullGroup, {}); + m->terrainRenderer.RenderPatches( + deviceCommandContext, cullGroup, {}, CColor(0.0f, 0.0f, 0.0f, 1.0f), false); } { PROFILE("render models"); - m->CallModelRenderers(deviceCommandContext, contextCast, cullGroup, ModelFlag::CAST_SHADOWS); + m->CallModelRenderers(deviceCommandContext, {}, cullGroup, ModelFlag::CAST_SHADOWS, ERenderMode::SOLID); } { PROFILE("render transparent models"); - m->CallTranspModelRenderers(deviceCommandContext, contextCast, cullGroup, ModelFlag::CAST_SHADOWS); + m->CallTranspModelRenderers(deviceCommandContext, {}, cullGroup, ModelFlag::CAST_SHADOWS, ERenderMode::SOLID); } } @@ -341,23 +335,17 @@ void CSceneRenderer::RenderPatches( PROFILE3("patches"); GPU_SCOPED_LABEL(deviceCommandContext, "Render patches"); - // Switch on wireframe if we need it. - CShaderDefines localContext = context; - if (m_TerrainRenderMode == WIREFRAME) - localContext.Add(str_MODE_WIREFRAME, str_1); - // Render all the patches, including blend pass. - m->terrainRenderer.RenderTerrainShader(deviceCommandContext, localContext, cullGroup, - g_RenderingOptions.GetShadows() ? &m->shadow : nullptr); + m->terrainRenderer.RenderTerrainShader(deviceCommandContext, context, cullGroup, + g_RenderingOptions.GetShadows() ? &m->shadow : nullptr, m_TerrainRenderMode == WIREFRAME); if (m_TerrainRenderMode == EDGED_FACES) { - localContext.Add(str_MODE_WIREFRAME, str_1); // Edged faces: need to make a second pass over the data. // Render tiles edges. m->terrainRenderer.RenderPatches( - deviceCommandContext, cullGroup, localContext, CColor(0.5f, 0.5f, 1.0f, 1.0f)); + deviceCommandContext, cullGroup, context, CColor(0.5f, 0.5f, 1.0f, 1.0f), true); // Render outline of each patch. m->terrainRenderer.RenderOutlines(deviceCommandContext, cullGroup); @@ -373,18 +361,13 @@ void CSceneRenderer::RenderModels( int flags = 0; - CShaderDefines localContext = context; + const ERenderMode modelRenderMode{ + m_ModelRenderMode == WIREFRAME ? WIREFRAME : SOLID}; - if (m_ModelRenderMode == WIREFRAME) - localContext.Add(str_MODE_WIREFRAME, str_1); - - m->CallModelRenderers(deviceCommandContext, localContext, cullGroup, flags); + m->CallModelRenderers(deviceCommandContext, context, cullGroup, flags, modelRenderMode); if (m_ModelRenderMode == EDGED_FACES) - { - localContext.Add(str_MODE_WIREFRAME_SOLID, str_1); - m->CallModelRenderers(deviceCommandContext, localContext, cullGroup, flags); - } + m->CallModelRenderers(deviceCommandContext, {}, cullGroup, flags, EDGED_FACES); } void CSceneRenderer::RenderTransparentModels( @@ -402,25 +385,17 @@ void CSceneRenderer::RenderTransparentModels( CShaderDefines contextBlend = context; contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1); - if (m_ModelRenderMode == WIREFRAME) - { - contextOpaque.Add(str_MODE_WIREFRAME, str_1); - contextBlend.Add(str_MODE_WIREFRAME, str_1); - } + const ERenderMode modelRenderMode{ + m_ModelRenderMode == WIREFRAME ? WIREFRAME : SOLID}; if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE) - m->CallTranspModelRenderers(deviceCommandContext, contextOpaque, cullGroup, flags); + m->CallTranspModelRenderers(deviceCommandContext, contextOpaque, cullGroup, flags, modelRenderMode); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND) - m->CallTranspModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags); + m->CallTranspModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags, modelRenderMode); if (m_ModelRenderMode == EDGED_FACES) - { - CShaderDefines contextWireframe = contextOpaque; - contextWireframe.Add(str_MODE_WIREFRAME, str_1); - - m->CallTranspModelRenderers(deviceCommandContext, contextWireframe, cullGroup, flags); - } + m->CallTranspModelRenderers(deviceCommandContext, {}, cullGroup, flags, EDGED_FACES); } // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) @@ -602,13 +577,10 @@ void CSceneRenderer::RenderReflections( scissorRect.height = screenScissor.y2 - screenScissor.y1; deviceCommandContext->SetScissors(1, &scissorRect); - CShaderDefines reflectionsContext = context; - reflectionsContext.Add(str_PASS_REFLECTIONS, str_1); - // Render terrain and models - RenderPatches(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS); - RenderModels(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS); - RenderTransparentModels(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS, TRANSPARENT); + RenderPatches(deviceCommandContext, context, CULL_REFLECTIONS); + RenderModels(deviceCommandContext, context, CULL_REFLECTIONS); + RenderTransparentModels(deviceCommandContext, context, CULL_REFLECTIONS, TRANSPARENT); // Particles are always oriented to face the camera in the vertex shader, // so they don't need the inverted cull face. @@ -713,18 +685,11 @@ void CSceneRenderer::RenderRefractions( } void CSceneRenderer::RenderSilhouettes( - Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const CShaderDefines& context) + Renderer::Backend::IDeviceCommandContext* deviceCommandContext) { PROFILE3("silhouettes"); GPU_SCOPED_LABEL(deviceCommandContext, "Render silhouettes"); - CShaderDefines contextOccluder = context; - contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1); - - CShaderDefines contextDisplay = context; - contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1); - // Render silhouettes of units hidden behind terrain or occluders. // To avoid breaking the standard rendering of alpha-blended objects, this // has to be done in a separate pass. @@ -739,29 +704,30 @@ void CSceneRenderer::RenderSilhouettes( { PROFILE("render patches"); - m->terrainRenderer.RenderPatches(deviceCommandContext, CULL_SILHOUETTE_OCCLUDER, {}); + m->terrainRenderer.RenderPatches( + deviceCommandContext, CULL_SILHOUETTE_OCCLUDER, {}, CColor(0.0f, 0.0f, 0.0f, 1.0f), false); } { PROFILE("render model occluders"); - m->CallModelRenderers(deviceCommandContext, contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); + m->CallModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_OCCLUDER, 0, ERenderMode::SOLID); } { PROFILE("render transparent occluders"); - m->CallTranspModelRenderers(deviceCommandContext, contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); + m->CallTranspModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_OCCLUDER, 0, ERenderMode::SOLID); } // Since we can't sort, we'll use the stencil buffer to ensure we only draw // a pixel once (using the color of whatever model happens to be drawn first). { PROFILE("render model casters"); - m->CallModelRenderers(deviceCommandContext, contextDisplay, CULL_SILHOUETTE_CASTER, 0); + m->CallModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_CASTER, 0, ERenderMode::SOLID); } { PROFILE("render transparent casters"); - m->CallTranspModelRenderers(deviceCommandContext, contextDisplay, CULL_SILHOUETTE_CASTER, 0); + m->CallTranspModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_CASTER, 0, ERenderMode::SOLID); } } @@ -832,7 +798,7 @@ void CSceneRenderer::PrepareSubmissions( if (g_RenderingOptions.GetShadows()) { - RenderShadowMap(deviceCommandContext, context); + RenderShadowMap(deviceCommandContext); } if (m->waterManager.m_RenderWater) @@ -1164,7 +1130,7 @@ void CSceneRenderer::RenderSceneOverlays( if (!g_RenderingOptions.GetCutsceneMode()) { if (g_RenderingOptions.GetSilhouettes()) - RenderSilhouettes(deviceCommandContext, m->globalContext); + RenderSilhouettes(deviceCommandContext); m->silhouetteRenderer.RenderDebugOverlays(deviceCommandContext); diff --git a/source/renderer/SceneRenderer.h b/source/renderer/SceneRenderer.h index fc111dc8d9..6120fbbd85 100644 --- a/source/renderer/SceneRenderer.h +++ b/source/renderer/SceneRenderer.h @@ -230,8 +230,7 @@ protected: const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode); void RenderSilhouettes( - Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const CShaderDefines& context); + Renderer::Backend::IDeviceCommandContext* deviceCommandContext); void RenderParticles( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, @@ -239,8 +238,7 @@ protected: // shadow rendering stuff void RenderShadowMap( - Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const CShaderDefines& context); + Renderer::Backend::IDeviceCommandContext* deviceCommandContext); // render water reflection and refraction textures void RenderReflections( diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp index 523ae29ff4..35bd2d4ebc 100644 --- a/source/renderer/TerrainRenderer.cpp +++ b/source/renderer/TerrainRenderer.cpp @@ -65,6 +65,9 @@ #include #include +namespace +{ + /** * TerrainRenderer keeps track of which phase it is in, to detect * when Submit, PrepareForRendering etc. are called in the wrong order. @@ -75,6 +78,17 @@ enum Phase Phase_Render }; +CMaterial::Pass GetMaterialPassFromCullGroup(const int cullGroup, const bool wireframe) +{ + if (wireframe) + return CMaterial::Pass::WIREFRAME; + + return cullGroup == CSceneRenderer::CULL_REFLECTIONS + ? CMaterial::Pass::REFLECTIONS + : CMaterial::Pass::MAIN; +} + +} /** * Struct TerrainRendererInternals: Internal variables used by the TerrainRenderer class. @@ -93,7 +107,7 @@ struct TerrainRendererInternals /// Fancy water shader CShaderTechniquePtr fancyWaterTech; - CShaderTechniquePtr shadowCasterTech, silhouettteOccluderTech; + CShaderTechniquePtr shadowCasterTech, silhouettteOccluderTech, wireframeTech; CShaderTechniquePtr shaderTechniqueSolid, shaderTechniqueSolidDepthTest; @@ -156,6 +170,7 @@ void TerrainRenderer::Initialize() CShaderManager& shaderManager{g_Renderer.GetShaderManager()}; m->shadowCasterTech = shaderManager.LoadEffect(str_terrain_shadow_caster); m->silhouettteOccluderTech = shaderManager.LoadEffect(str_terrain_silhouette_occluder); + m->wireframeTech = shaderManager.LoadEffect(str_terrain_wireframe); } void TerrainRenderer::SetSimulation(CSimulation2* simulation) @@ -316,7 +331,8 @@ void TerrainRenderer::PrepareShader( void TerrainRenderer::RenderTerrainShader( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const CShaderDefines& context, int cullGroup, ShadowMap* shadow) + const CShaderDefines& context, int cullGroup, ShadowMap* shadow, + const bool wireframe) { ENSURE(m->phase == Phase_Render); @@ -353,22 +369,25 @@ void TerrainRenderer::RenderTerrainShader( deviceCommandContext->EndPass(); + const CMaterial::Pass materialPass{GetMaterialPassFromCullGroup(cullGroup, wireframe)}; + CPatchRData::RenderBases( - deviceCommandContext, m->baseVertexInputLayout, visiblePatches, context, shadow); + deviceCommandContext, m->baseVertexInputLayout, visiblePatches, context, shadow, materialPass); // render blend passes for each patch CPatchRData::RenderBlends( - deviceCommandContext, m->blendVertexInputLayout, visiblePatches, context, shadow); + deviceCommandContext, m->blendVertexInputLayout, visiblePatches, context, shadow, materialPass); CDecalRData::RenderDecals( - deviceCommandContext, m->decalsVertexInputLayout, visibleDecals, context, shadow); + deviceCommandContext, m->decalsVertexInputLayout, visibleDecals, context, shadow, materialPass); } /////////////////////////////////////////////////////////////////// // Render un-textured patches as polygons void TerrainRenderer::RenderPatches( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - int cullGroup, const CShaderDefines& defines, const CColor& color) + int cullGroup, const CShaderDefines& defines, const CColor& color, + const bool wireframe) { ENSURE(m->phase == Phase_Render); @@ -379,22 +398,29 @@ void TerrainRenderer::RenderPatches( GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain patches"); CShaderTechniquePtr solidTech; - switch (cullGroup) + if (wireframe) { - case CSceneRenderer::CULL_SHADOWS_CASCADE_0: [[fallthrough]]; - case CSceneRenderer::CULL_SHADOWS_CASCADE_1: [[fallthrough]]; - case CSceneRenderer::CULL_SHADOWS_CASCADE_2: [[fallthrough]]; - case CSceneRenderer::CULL_SHADOWS_CASCADE_3: - ENSURE(defines.GetMap().empty()); - solidTech = m->shadowCasterTech; - break; - case CSceneRenderer::CULL_SILHOUETTE_OCCLUDER: - ENSURE(defines.GetMap().empty()); - solidTech = m->silhouettteOccluderTech; - break; - default: - solidTech = g_Renderer.GetShaderManager().LoadEffect(str_terrain_solid, defines); - break; + solidTech = m->wireframeTech; + } + else + { + switch (cullGroup) + { + case CSceneRenderer::CULL_SHADOWS_CASCADE_0: [[fallthrough]]; + case CSceneRenderer::CULL_SHADOWS_CASCADE_1: [[fallthrough]]; + case CSceneRenderer::CULL_SHADOWS_CASCADE_2: [[fallthrough]]; + case CSceneRenderer::CULL_SHADOWS_CASCADE_3: + ENSURE(defines.GetMap().empty()); + solidTech = m->shadowCasterTech; + break; + case CSceneRenderer::CULL_SILHOUETTE_OCCLUDER: + ENSURE(defines.GetMap().empty()); + solidTech = m->silhouettteOccluderTech; + break; + default: + solidTech = g_Renderer.GetShaderManager().LoadEffect(str_terrain_solid, defines); + break; + } } deviceCommandContext->SetGraphicsPipelineState( solidTech->GetGraphicsPipelineState()); diff --git a/source/renderer/TerrainRenderer.h b/source/renderer/TerrainRenderer.h index d2cdb5382a..71617f5b18 100644 --- a/source/renderer/TerrainRenderer.h +++ b/source/renderer/TerrainRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -104,7 +104,8 @@ public: */ void RenderTerrainShader( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const CShaderDefines& context, int cullGroup, ShadowMap* shadow); + const CShaderDefines& context, int cullGroup, ShadowMap* shadow, + const bool wireframe); /** * RenderPatches: Render all patches un-textured as polygons. @@ -118,7 +119,7 @@ public: void RenderPatches( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup, const CShaderDefines& defines, - const CColor& color = CColor(0.0f, 0.0f, 0.0f, 1.0f)); + const CColor& color, const bool wireframe); /** * RenderOutlines: Render the outline of patches as lines.