Moves model flags to ModelAbstract.
[0ad.git] / source / renderer / SceneRenderer.cpp
blobb48905a281e0c44ed1829e1e7ac3a9868327b69b
1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "SceneRenderer.h"
22 #include "graphics/Camera.h"
23 #include "graphics/Decal.h"
24 #include "graphics/GameView.h"
25 #include "graphics/LightEnv.h"
26 #include "graphics/LOSTexture.h"
27 #include "graphics/MaterialManager.h"
28 #include "graphics/MiniMapTexture.h"
29 #include "graphics/Model.h"
30 #include "graphics/ModelDef.h"
31 #include "graphics/ParticleManager.h"
32 #include "graphics/Patch.h"
33 #include "graphics/ShaderManager.h"
34 #include "graphics/TerritoryTexture.h"
35 #include "graphics/Terrain.h"
36 #include "graphics/Texture.h"
37 #include "graphics/TextureManager.h"
38 #include "maths/Matrix3D.h"
39 #include "maths/MathUtil.h"
40 #include "ps/CLogger.h"
41 #include "ps/ConfigDB.h"
42 #include "ps/CStrInternStatic.h"
43 #include "ps/Game.h"
44 #include "ps/Profile.h"
45 #include "ps/World.h"
46 #include "renderer/backend/IDevice.h"
47 #include "renderer/DebugRenderer.h"
48 #include "renderer/HWLightingModelRenderer.h"
49 #include "renderer/InstancingModelRenderer.h"
50 #include "renderer/ModelRenderer.h"
51 #include "renderer/OverlayRenderer.h"
52 #include "renderer/ParticleRenderer.h"
53 #include "renderer/Renderer.h"
54 #include "renderer/RenderingOptions.h"
55 #include "renderer/RenderModifiers.h"
56 #include "renderer/ShadowMap.h"
57 #include "renderer/SilhouetteRenderer.h"
58 #include "renderer/SkyManager.h"
59 #include "renderer/TerrainOverlay.h"
60 #include "renderer/TerrainRenderer.h"
61 #include "renderer/WaterManager.h"
63 #include <algorithm>
65 struct SScreenRect
67 int x1, y1, x2, y2;
70 /**
71 * Struct CSceneRendererInternals: Truly hide data that is supposed to be hidden
72 * in this structure so it won't even appear in header files.
74 class CSceneRenderer::Internals
76 NONCOPYABLE(Internals);
77 public:
78 Internals(Renderer::Backend::IDevice* device)
79 : waterManager(device), shadow(device)
83 ~Internals() = default;
85 /// Water manager
86 WaterManager waterManager;
88 /// Sky manager
89 SkyManager skyManager;
91 /// Terrain renderer
92 TerrainRenderer terrainRenderer;
94 /// Overlay renderer
95 OverlayRenderer overlayRenderer;
97 /// Particle manager
98 CParticleManager particleManager;
100 /// Particle renderer
101 ParticleRenderer particleRenderer;
103 /// Material manager
104 CMaterialManager materialManager;
106 /// Shadow map
107 ShadowMap shadow;
109 SilhouetteRenderer silhouetteRenderer;
111 /// Various model renderers
112 struct Models
114 // NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer,
115 // RenderModifier, etc) is mostly a relic of an older design that implemented
116 // the different materials and rendering modes through extensive subclassing
117 // and hooking objects together in various combinations.
118 // The new design uses the CShaderManager API to abstract away the details
119 // of rendering, and uses a data-driven approach to materials, so there are
120 // now a small number of generic subclasses instead of many specialised subclasses,
121 // but most of the old infrastructure hasn't been refactored out yet and leads to
122 // some unwanted complexity.
124 // Submitted models are split on two axes:
125 // - Normal vs Transp[arent] - alpha-blended models are stored in a separate
126 // list so we can draw them above/below the alpha-blended water plane correctly
127 // - Skinned vs Unskinned - with hardware lighting we don't need to
128 // duplicate mesh data per model instance (except for skinned models),
129 // so non-skinned models get different ModelVertexRenderers
131 ModelRendererPtr NormalSkinned;
132 ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported
133 ModelRendererPtr TranspSkinned;
134 ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported
136 ModelVertexRendererPtr VertexRendererShader;
137 ModelVertexRendererPtr VertexInstancingShader;
138 ModelVertexRendererPtr VertexGPUSkinningShader;
140 LitRenderModifierPtr ModShader;
141 } Model;
143 CShaderDefines globalContext;
146 * Renders all non-alpha-blended models with the given context.
148 void CallModelRenderers(
149 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
150 const CShaderDefines& context, int cullGroup, int flags)
152 CShaderDefines contextSkinned = context;
153 if (g_RenderingOptions.GetGPUSkinning())
155 contextSkinned.Add(str_USE_INSTANCING, str_1);
156 contextSkinned.Add(str_USE_GPU_SKINNING, str_1);
158 Model.NormalSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags);
160 if (Model.NormalUnskinned != Model.NormalSkinned)
162 CShaderDefines contextUnskinned = context;
163 contextUnskinned.Add(str_USE_INSTANCING, str_1);
164 Model.NormalUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags);
169 * Renders all alpha-blended models with the given context.
171 void CallTranspModelRenderers(
172 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
173 const CShaderDefines& context, int cullGroup, int flags)
175 CShaderDefines contextSkinned = context;
176 if (g_RenderingOptions.GetGPUSkinning())
178 contextSkinned.Add(str_USE_INSTANCING, str_1);
179 contextSkinned.Add(str_USE_GPU_SKINNING, str_1);
181 Model.TranspSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags);
183 if (Model.TranspUnskinned != Model.TranspSkinned)
185 CShaderDefines contextUnskinned = context;
186 contextUnskinned.Add(str_USE_INSTANCING, str_1);
187 Model.TranspUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags);
192 CSceneRenderer::CSceneRenderer(Renderer::Backend::IDevice* device)
194 m = std::make_unique<Internals>(device);
196 m_TerrainRenderMode = SOLID;
197 m_WaterRenderMode = SOLID;
198 m_ModelRenderMode = SOLID;
199 m_OverlayRenderMode = SOLID;
201 m_DisplayTerrainPriorities = false;
203 m_LightEnv = nullptr;
205 m_CurrentScene = nullptr;
208 CSceneRenderer::~CSceneRenderer()
210 // We no longer UnloadWaterTextures here -
211 // that is the responsibility of the module that asked for
212 // them to be loaded (i.e. CGameView).
213 m.reset();
216 void CSceneRenderer::ReloadShaders(Renderer::Backend::IDevice* device)
218 m->globalContext = CShaderDefines();
220 if (g_RenderingOptions.GetShadows())
222 m->globalContext.Add(str_USE_SHADOW, str_1);
223 if (device->GetBackend() == Renderer::Backend::Backend::GL_ARB &&
224 device->GetCapabilities().ARBShadersShadow)
226 m->globalContext.Add(str_USE_FP_SHADOW, str_1);
228 if (g_RenderingOptions.GetShadowPCF())
229 m->globalContext.Add(str_USE_SHADOW_PCF, str_1);
230 const int cascadeCount = m->shadow.GetCascadeCount();
231 ENSURE(1 <= cascadeCount && cascadeCount <= 4);
232 const CStrIntern cascadeCountStr[5] = {str_0, str_1, str_2, str_3, str_4};
233 m->globalContext.Add(str_SHADOWS_CASCADE_COUNT, cascadeCountStr[cascadeCount]);
234 #if !CONFIG2_GLES
235 m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1);
236 #endif
239 m->globalContext.Add(str_RENDER_DEBUG_MODE,
240 RenderDebugModeEnum::ToString(g_RenderingOptions.GetRenderDebugMode()));
242 if (device->GetBackend() != Renderer::Backend::Backend::GL_ARB && g_RenderingOptions.GetFog())
243 m->globalContext.Add(str_USE_FOG, str_1);
245 m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier());
247 ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED);
248 m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer());
249 m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, device->GetBackend() != Renderer::Backend::Backend::GL_ARB));
251 if (g_RenderingOptions.GetGPUSkinning()) // TODO: should check caps and GLSL etc too
253 m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, device->GetBackend() != Renderer::Backend::Backend::GL_ARB));
254 m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
255 m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
257 else
259 m->Model.VertexGPUSkinningShader.reset();
260 m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
261 m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
264 m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
265 m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
268 void CSceneRenderer::Initialize()
270 // Let component renderers perform one-time initialization after graphics capabilities and
271 // the shader path have been determined.
272 m->waterManager.Initialize();
273 m->terrainRenderer.Initialize();
274 m->overlayRenderer.Initialize();
277 // resize renderer view
278 void CSceneRenderer::Resize(int UNUSED(width), int UNUSED(height))
280 // need to recreate the shadow map object to resize the shadow texture
281 m->shadow.RecreateTexture();
283 m->waterManager.RecreateOrLoadTexturesIfNeeded();
286 void CSceneRenderer::BeginFrame()
288 // choose model renderers for this frame
289 m->Model.ModShader->SetShadowMap(&m->shadow);
290 m->Model.ModShader->SetLightEnv(m_LightEnv);
293 void CSceneRenderer::SetSimulation(CSimulation2* simulation)
295 // set current simulation context for terrain renderer
296 m->terrainRenderer.SetSimulation(simulation);
299 void CSceneRenderer::RenderShadowMap(
300 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
301 const CShaderDefines& context)
303 PROFILE3_GPU("shadow map");
304 GPU_SCOPED_LABEL(deviceCommandContext, "Render shadow map");
306 CShaderDefines shadowsContext = context;
307 shadowsContext.Add(str_PASS_SHADOWS, str_1);
309 CShaderDefines contextCast = shadowsContext;
310 contextCast.Add(str_MODE_SHADOWCAST, str_1);
312 m->shadow.BeginRender(deviceCommandContext);
314 const int cascadeCount = m->shadow.GetCascadeCount();
315 ENSURE(0 <= cascadeCount && cascadeCount <= 4);
316 for (int cascade = 0; cascade < cascadeCount; ++cascade)
318 m->shadow.PrepareCamera(deviceCommandContext, cascade);
320 const int cullGroup = CULL_SHADOWS_CASCADE_0 + cascade;
322 PROFILE("render patches");
323 m->terrainRenderer.RenderPatches(deviceCommandContext, cullGroup, shadowsContext);
327 PROFILE("render models");
328 m->CallModelRenderers(deviceCommandContext, contextCast, cullGroup, ModelFlag::CAST_SHADOWS);
332 PROFILE("render transparent models");
333 m->CallTranspModelRenderers(deviceCommandContext, contextCast, cullGroup, ModelFlag::CAST_SHADOWS);
337 m->shadow.EndRender(deviceCommandContext);
340 void CSceneRenderer::RenderPatches(
341 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
342 const CShaderDefines& context, int cullGroup)
344 PROFILE3_GPU("patches");
345 GPU_SCOPED_LABEL(deviceCommandContext, "Render patches");
347 // Switch on wireframe if we need it.
348 CShaderDefines localContext = context;
349 if (m_TerrainRenderMode == WIREFRAME)
350 localContext.Add(str_MODE_WIREFRAME, str_1);
352 // Render all the patches, including blend pass.
353 m->terrainRenderer.RenderTerrainShader(deviceCommandContext, localContext, cullGroup,
354 g_RenderingOptions.GetShadows() ? &m->shadow : nullptr);
356 if (m_TerrainRenderMode == EDGED_FACES)
358 localContext.Add(str_MODE_WIREFRAME, str_1);
359 // Edged faces: need to make a second pass over the data.
361 // Render tiles edges.
362 m->terrainRenderer.RenderPatches(
363 deviceCommandContext, cullGroup, localContext, CColor(0.5f, 0.5f, 1.0f, 1.0f));
365 // Render outline of each patch.
366 m->terrainRenderer.RenderOutlines(deviceCommandContext, cullGroup);
370 void CSceneRenderer::RenderModels(
371 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
372 const CShaderDefines& context, int cullGroup)
374 PROFILE3_GPU("models");
375 GPU_SCOPED_LABEL(deviceCommandContext, "Render models");
377 int flags = 0;
379 CShaderDefines localContext = context;
381 if (m_ModelRenderMode == WIREFRAME)
382 localContext.Add(str_MODE_WIREFRAME, str_1);
384 m->CallModelRenderers(deviceCommandContext, localContext, cullGroup, flags);
386 if (m_ModelRenderMode == EDGED_FACES)
388 localContext.Add(str_MODE_WIREFRAME_SOLID, str_1);
389 m->CallModelRenderers(deviceCommandContext, localContext, cullGroup, flags);
393 void CSceneRenderer::RenderTransparentModels(
394 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
395 const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode)
397 PROFILE3_GPU("transparent models");
398 GPU_SCOPED_LABEL(deviceCommandContext, "Render transparent models");
400 int flags = 0;
402 CShaderDefines contextOpaque = context;
403 contextOpaque.Add(str_ALPHABLEND_PASS_OPAQUE, str_1);
405 CShaderDefines contextBlend = context;
406 contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1);
408 if (m_ModelRenderMode == WIREFRAME)
410 contextOpaque.Add(str_MODE_WIREFRAME, str_1);
411 contextBlend.Add(str_MODE_WIREFRAME, str_1);
414 if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE)
415 m->CallTranspModelRenderers(deviceCommandContext, contextOpaque, cullGroup, flags);
417 if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND)
418 m->CallTranspModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags);
420 if (m_ModelRenderMode == EDGED_FACES)
422 CShaderDefines contextWireframe = contextOpaque;
423 contextWireframe.Add(str_MODE_WIREFRAME, str_1);
425 m->CallTranspModelRenderers(deviceCommandContext, contextWireframe, cullGroup, flags);
429 // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space)
430 // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html
431 // - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test)
432 void CSceneRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const
434 // First, we'll convert the given clip plane to camera space, then we'll
435 // Get the view matrix and normal matrix (top 3x3 part of view matrix)
436 CMatrix3D normalMatrix = camera.GetOrientation().GetTranspose();
437 CVector4D camPlane = normalMatrix.Transform(worldPlane);
439 CMatrix3D matrix = camera.GetProjection();
441 // Calculate the clip-space corner point opposite the clipping plane
442 // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and
443 // transform it into camera space by multiplying it
444 // by the inverse of the projection matrix
446 CVector4D q;
447 q.X = (Sign(camPlane.X) - matrix[8] / matrix[11]) / matrix[0];
448 q.Y = (Sign(camPlane.Y) - matrix[9] / matrix[11]) / matrix[5];
449 q.Z = 1.0f / matrix[11];
450 q.W = (1.0f - matrix[10] / matrix[11]) / matrix[14];
452 // Calculate the scaled plane vector
453 CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q));
455 // Replace the third row of the projection matrix
456 matrix[2] = c.X;
457 matrix[6] = c.Y;
458 matrix[10] = c.Z - matrix[11];
459 matrix[14] = c.W;
461 // Load it back into the camera
462 camera.SetProjection(matrix);
465 void CSceneRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const
467 WaterManager& wm = m->waterManager;
469 CMatrix3D projection;
470 if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
472 const float aspectRatio = 1.0f;
473 // Expand fov slightly since ripples can reflect parts of the scene that
474 // are slightly outside the normal camera view, and we want to avoid any
475 // noticeable edge-filtering artifacts
476 projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane());
478 else
479 projection = m_ViewCamera.GetProjection();
481 camera = m_ViewCamera;
483 // Temporarily change the camera to one that is reflected.
484 // Also, for texturing purposes, make it render to a view port the size of the
485 // water texture, stretch the image according to our aspect ratio so it covers
486 // the whole screen despite being rendered into a square, and cover slightly more
487 // of the view so we can see wavy reflections of slightly off-screen objects.
488 camera.m_Orientation.Scale(1, -1, 1);
489 camera.m_Orientation.Translate(0, 2 * wm.m_WaterHeight, 0);
490 camera.UpdateFrustum(scissor);
491 // Clip slightly above the water to improve reflections of objects on the water
492 // when the reflections are distorted.
493 camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight + 2.0f));
495 SViewPort vp;
496 vp.m_Height = wm.m_RefTextureSize;
497 vp.m_Width = wm.m_RefTextureSize;
498 vp.m_X = 0;
499 vp.m_Y = 0;
500 camera.SetViewPort(vp);
501 camera.SetProjection(projection);
502 CMatrix3D scaleMat;
503 scaleMat.SetScaling(g_Renderer.GetHeight() / static_cast<float>(std::max(1, g_Renderer.GetWidth())), 1.0f, 1.0f);
504 camera.SetProjection(scaleMat * camera.GetProjection());
506 CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight + 0.5f);
507 SetObliqueFrustumClipping(camera, camPlane);
510 void CSceneRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const
512 WaterManager& wm = m->waterManager;
514 CMatrix3D projection;
515 if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
517 const float aspectRatio = 1.0f;
518 // Expand fov slightly since ripples can reflect parts of the scene that
519 // are slightly outside the normal camera view, and we want to avoid any
520 // noticeable edge-filtering artifacts
521 projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane());
523 else
524 projection = m_ViewCamera.GetProjection();
526 camera = m_ViewCamera;
528 // Temporarily change the camera to make it render to a view port the size of the
529 // water texture, stretch the image according to our aspect ratio so it covers
530 // the whole screen despite being rendered into a square, and cover slightly more
531 // of the view so we can see wavy refractions of slightly off-screen objects.
532 camera.UpdateFrustum(scissor);
533 camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight + 0.5f)); // add some to avoid artifacts near steep shores.
535 SViewPort vp;
536 vp.m_Height = wm.m_RefTextureSize;
537 vp.m_Width = wm.m_RefTextureSize;
538 vp.m_X = 0;
539 vp.m_Y = 0;
540 camera.SetViewPort(vp);
541 camera.SetProjection(projection);
542 CMatrix3D scaleMat;
543 scaleMat.SetScaling(g_Renderer.GetHeight() / static_cast<float>(std::max(1, g_Renderer.GetWidth())), 1.0f, 1.0f);
544 camera.SetProjection(scaleMat * camera.GetProjection());
547 // RenderReflections: render the water reflections to the reflection texture
548 void CSceneRenderer::RenderReflections(
549 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
550 const CShaderDefines& context, const CBoundingBoxAligned& scissor)
552 PROFILE3_GPU("water reflections");
553 GPU_SCOPED_LABEL(deviceCommandContext, "Render water reflections");
555 WaterManager& wm = m->waterManager;
557 // Remember old camera
558 CCamera normalCamera = m_ViewCamera;
560 ComputeReflectionCamera(m_ViewCamera, scissor);
561 const CBoundingBoxAligned reflectionScissor =
562 m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
563 if (reflectionScissor.IsEmpty())
565 m_ViewCamera = normalCamera;
566 return;
569 // Save the model-view-projection matrix so the shaders can use it for projective texturing
570 wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection();
571 if (deviceCommandContext->GetDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN)
573 CMatrix3D flip;
574 flip.SetIdentity();
575 flip._22 = -1.0f;
576 wm.m_ReflectionMatrix = flip * wm.m_ReflectionMatrix;
579 float vpHeight = wm.m_RefTextureSize;
580 float vpWidth = wm.m_RefTextureSize;
582 SScreenRect screenScissor;
583 screenScissor.x1 = static_cast<int>(floor((reflectionScissor[0].X * 0.5f + 0.5f) * vpWidth));
584 screenScissor.y1 = static_cast<int>(floor((reflectionScissor[0].Y * 0.5f + 0.5f) * vpHeight));
585 screenScissor.x2 = static_cast<int>(ceil((reflectionScissor[1].X * 0.5f + 0.5f) * vpWidth));
586 screenScissor.y2 = static_cast<int>(ceil((reflectionScissor[1].Y * 0.5f + 0.5f) * vpHeight));
588 deviceCommandContext->BeginFramebufferPass(wm.m_ReflectionFramebuffer.get());
590 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
591 viewportRect.width = vpWidth;
592 viewportRect.height = vpHeight;
593 deviceCommandContext->SetViewports(1, &viewportRect);
595 Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
596 scissorRect.x = screenScissor.x1;
597 scissorRect.y = screenScissor.y1;
598 scissorRect.width = screenScissor.x2 - screenScissor.x1;
599 scissorRect.height = screenScissor.y2 - screenScissor.y1;
600 deviceCommandContext->SetScissors(1, &scissorRect);
602 CShaderDefines reflectionsContext = context;
603 reflectionsContext.Add(str_PASS_REFLECTIONS, str_1);
605 // Render terrain and models
606 RenderPatches(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS);
607 RenderModels(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS);
608 RenderTransparentModels(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS, TRANSPARENT);
610 // Particles are always oriented to face the camera in the vertex shader,
611 // so they don't need the inverted cull face.
612 if (g_RenderingOptions.GetParticles())
614 RenderParticles(deviceCommandContext, CULL_REFLECTIONS);
617 deviceCommandContext->SetScissors(0, nullptr);
618 deviceCommandContext->EndFramebufferPass();
620 // Reset old camera
621 m_ViewCamera = normalCamera;
624 // RenderRefractions: render the water refractions to the refraction texture
625 void CSceneRenderer::RenderRefractions(
626 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
627 const CShaderDefines& context, const CBoundingBoxAligned &scissor)
629 PROFILE3_GPU("water refractions");
630 GPU_SCOPED_LABEL(deviceCommandContext, "Render water refractions");
632 WaterManager& wm = m->waterManager;
634 // Remember old camera
635 CCamera normalCamera = m_ViewCamera;
637 ComputeRefractionCamera(m_ViewCamera, scissor);
638 const CBoundingBoxAligned refractionScissor =
639 m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
640 if (refractionScissor.IsEmpty())
642 m_ViewCamera = normalCamera;
643 return;
646 CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f);
647 SetObliqueFrustumClipping(m_ViewCamera, camPlane);
649 // Save the model-view-projection matrix so the shaders can use it for projective texturing
650 wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection();
651 wm.m_RefractionProjInvMatrix = m_ViewCamera.GetProjection().GetInverse();
652 wm.m_RefractionViewInvMatrix = m_ViewCamera.GetOrientation();
654 if (deviceCommandContext->GetDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN)
656 CMatrix3D flip;
657 flip.SetIdentity();
658 flip._22 = -1.0f;
659 wm.m_RefractionMatrix = flip * wm.m_RefractionMatrix;
660 wm.m_RefractionProjInvMatrix = wm.m_RefractionProjInvMatrix * flip;
663 float vpHeight = wm.m_RefTextureSize;
664 float vpWidth = wm.m_RefTextureSize;
666 SScreenRect screenScissor;
667 screenScissor.x1 = static_cast<int>(floor((refractionScissor[0].X * 0.5f + 0.5f) * vpWidth));
668 screenScissor.y1 = static_cast<int>(floor((refractionScissor[0].Y * 0.5f + 0.5f) * vpHeight));
669 screenScissor.x2 = static_cast<int>(ceil((refractionScissor[1].X * 0.5f + 0.5f) * vpWidth));
670 screenScissor.y2 = static_cast<int>(ceil((refractionScissor[1].Y * 0.5f + 0.5f) * vpHeight));
672 deviceCommandContext->BeginFramebufferPass(wm.m_RefractionFramebuffer.get());
674 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
675 viewportRect.width = vpWidth;
676 viewportRect.height = vpHeight;
677 deviceCommandContext->SetViewports(1, &viewportRect);
679 Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
680 scissorRect.x = screenScissor.x1;
681 scissorRect.y = screenScissor.y1;
682 scissorRect.width = screenScissor.x2 - screenScissor.x1;
683 scissorRect.height = screenScissor.y2 - screenScissor.y1;
684 deviceCommandContext->SetScissors(1, &scissorRect);
686 // Render terrain and models
687 RenderPatches(deviceCommandContext, context, CULL_REFRACTIONS);
689 // Render debug-related terrain overlays to make it visible under water.
690 ITerrainOverlay::RenderOverlaysBeforeWater(deviceCommandContext);
692 RenderModels(deviceCommandContext, context, CULL_REFRACTIONS);
693 RenderTransparentModels(deviceCommandContext, context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE);
695 deviceCommandContext->SetScissors(0, nullptr);
696 deviceCommandContext->EndFramebufferPass();
698 // Reset old camera
699 m_ViewCamera = normalCamera;
702 void CSceneRenderer::RenderSilhouettes(
703 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
704 const CShaderDefines& context)
706 PROFILE3_GPU("silhouettes");
707 GPU_SCOPED_LABEL(deviceCommandContext, "Render silhouettes");
709 CShaderDefines contextOccluder = context;
710 contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1);
712 CShaderDefines contextDisplay = context;
713 contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1);
715 // Render silhouettes of units hidden behind terrain or occluders.
716 // To avoid breaking the standard rendering of alpha-blended objects, this
717 // has to be done in a separate pass.
718 // First we render all occluders into depth, then render all units with
719 // inverted depth test so any behind an occluder will get drawn in a constant
720 // color.
722 // TODO: do we need clear here?
723 deviceCommandContext->ClearFramebuffer(false, true, true);
725 // Render occluders:
728 PROFILE("render patches");
729 m->terrainRenderer.RenderPatches(deviceCommandContext, CULL_SILHOUETTE_OCCLUDER, contextOccluder);
733 PROFILE("render model occluders");
734 m->CallModelRenderers(deviceCommandContext, contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
738 PROFILE("render transparent occluders");
739 m->CallTranspModelRenderers(deviceCommandContext, contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
742 // Since we can't sort, we'll use the stencil buffer to ensure we only draw
743 // a pixel once (using the color of whatever model happens to be drawn first).
745 PROFILE("render model casters");
746 m->CallModelRenderers(deviceCommandContext, contextDisplay, CULL_SILHOUETTE_CASTER, 0);
750 PROFILE("render transparent casters");
751 m->CallTranspModelRenderers(deviceCommandContext, contextDisplay, CULL_SILHOUETTE_CASTER, 0);
755 void CSceneRenderer::RenderParticles(
756 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
757 int cullGroup)
759 PROFILE3_GPU("particles");
760 GPU_SCOPED_LABEL(deviceCommandContext, "Render particles");
762 m->particleRenderer.RenderParticles(
763 deviceCommandContext, cullGroup, m_ModelRenderMode == WIREFRAME);
765 if (m_ModelRenderMode == EDGED_FACES)
767 m->particleRenderer.RenderParticles(
768 deviceCommandContext, cullGroup, true);
769 m->particleRenderer.RenderBounds(cullGroup);
773 void CSceneRenderer::PrepareSubmissions(
774 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
775 const CBoundingBoxAligned& waterScissor)
777 PROFILE3("prepare submissions");
778 GPU_SCOPED_LABEL(deviceCommandContext, "Prepare submissions");
780 m->skyManager.LoadAndUploadSkyTexturesIfNeeded(deviceCommandContext);
782 GetScene().GetLOSTexture().InterpolateLOS(deviceCommandContext);
783 GetScene().GetTerritoryTexture().UpdateIfNeeded(deviceCommandContext);
784 GetScene().GetMiniMapTexture().Render(
785 deviceCommandContext, GetScene().GetLOSTexture(), GetScene().GetTerritoryTexture());
787 CShaderDefines context = m->globalContext;
789 // Prepare model renderers
791 PROFILE3("prepare models");
792 m->Model.NormalSkinned->PrepareModels();
793 m->Model.TranspSkinned->PrepareModels();
794 if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
795 m->Model.NormalUnskinned->PrepareModels();
796 if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
797 m->Model.TranspUnskinned->PrepareModels();
800 m->terrainRenderer.PrepareForRendering();
802 m->overlayRenderer.PrepareForRendering();
804 m->particleRenderer.PrepareForRendering(context);
807 PROFILE3("upload models");
808 m->Model.NormalSkinned->UploadModels(deviceCommandContext);
809 m->Model.TranspSkinned->UploadModels(deviceCommandContext);
810 if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
811 m->Model.NormalUnskinned->UploadModels(deviceCommandContext);
812 if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
813 m->Model.TranspUnskinned->UploadModels(deviceCommandContext);
816 m->overlayRenderer.Upload(deviceCommandContext);
818 m->particleRenderer.Upload(deviceCommandContext);
820 if (g_RenderingOptions.GetShadows())
822 RenderShadowMap(deviceCommandContext, context);
825 if (m->waterManager.m_RenderWater)
827 if (waterScissor.GetVolume() > 0 && m->waterManager.WillRenderFancyWater())
829 m->waterManager.UpdateQuality();
831 PROFILE3_GPU("water scissor");
832 if (g_RenderingOptions.GetWaterReflection())
833 RenderReflections(deviceCommandContext, context, waterScissor);
835 if (g_RenderingOptions.GetWaterRefraction())
836 RenderRefractions(deviceCommandContext, context, waterScissor);
838 if (g_RenderingOptions.GetWaterFancyEffects())
839 m->terrainRenderer.RenderWaterFoamOccluders(deviceCommandContext, CULL_DEFAULT);
844 void CSceneRenderer::RenderSubmissions(
845 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
846 const CBoundingBoxAligned& waterScissor)
848 PROFILE3("render submissions");
849 GPU_SCOPED_LABEL(deviceCommandContext, "Render submissions");
851 CShaderDefines context = m->globalContext;
853 constexpr int cullGroup = CULL_DEFAULT;
855 m->skyManager.RenderSky(deviceCommandContext);
857 // render submitted patches and models
858 RenderPatches(deviceCommandContext, context, cullGroup);
860 // render debug-related terrain overlays
861 ITerrainOverlay::RenderOverlaysBeforeWater(deviceCommandContext);
863 // render other debug-related overlays before water (so they can be seen when underwater)
864 m->overlayRenderer.RenderOverlaysBeforeWater(deviceCommandContext);
866 RenderModels(deviceCommandContext, context, cullGroup);
868 // render water
869 if (m->waterManager.m_RenderWater && g_Game && waterScissor.GetVolume() > 0)
871 if (m->waterManager.WillRenderFancyWater())
873 // Render transparent stuff, but only the solid parts that can occlude block water.
874 RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT_OPAQUE);
876 m->terrainRenderer.RenderWater(deviceCommandContext, context, cullGroup, &m->shadow);
878 // Render transparent stuff again, but only the blended parts that overlap water.
879 RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT_BLEND);
881 else
883 m->terrainRenderer.RenderWater(deviceCommandContext, context, cullGroup, &m->shadow);
885 // Render transparent stuff, so it can overlap models/terrain.
886 RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT);
889 else
891 // render transparent stuff, so it can overlap models/terrain
892 RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT);
895 // render debug-related terrain overlays
896 ITerrainOverlay::RenderOverlaysAfterWater(deviceCommandContext, cullGroup);
898 // render some other overlays after water (so they can be displayed on top of water)
899 m->overlayRenderer.RenderOverlaysAfterWater(deviceCommandContext);
901 // particles are transparent so render after water
902 if (g_RenderingOptions.GetParticles())
904 RenderParticles(deviceCommandContext, cullGroup);
907 // render debug lines
908 if (g_RenderingOptions.GetDisplayFrustum())
909 DisplayFrustum();
911 if (g_RenderingOptions.GetDisplayShadowsFrustum())
912 m->shadow.RenderDebugBounds();
914 m->silhouetteRenderer.RenderDebugBounds(deviceCommandContext);
917 void CSceneRenderer::EndFrame()
919 // empty lists
920 m->terrainRenderer.EndFrame();
921 m->overlayRenderer.EndFrame();
922 m->particleRenderer.EndFrame();
923 m->silhouetteRenderer.EndFrame();
925 // Finish model renderers
926 m->Model.NormalSkinned->EndFrame();
927 m->Model.TranspSkinned->EndFrame();
928 if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
929 m->Model.NormalUnskinned->EndFrame();
930 if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
931 m->Model.TranspUnskinned->EndFrame();
934 void CSceneRenderer::DisplayFrustum()
936 g_Renderer.GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 0.25f), 2);
937 g_Renderer.GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 1.0f), 2, true);
940 // Text overlay rendering
941 void CSceneRenderer::RenderTextOverlays(CCanvas2D& canvas)
943 PROFILE3_GPU("text overlays");
945 if (m_DisplayTerrainPriorities)
946 m->terrainRenderer.RenderPriorities(canvas, CULL_DEFAULT);
949 // SetSceneCamera: setup projection and transform of camera and adjust viewport to current view
950 // The camera always represents the actual camera used to render a scene, not any virtual camera
951 // used for shadow rendering or reflections.
952 void CSceneRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera)
954 m_ViewCamera = viewCamera;
955 m_CullCamera = cullCamera;
957 if (g_RenderingOptions.GetShadows())
958 m->shadow.SetupFrame(m_CullCamera, m_LightEnv->GetSunDir());
961 void CSceneRenderer::Submit(CPatch* patch)
963 if (m_CurrentCullGroup == CULL_DEFAULT)
965 m->shadow.AddShadowReceiverBound(patch->GetWorldBounds());
966 m->silhouetteRenderer.AddOccluder(patch);
969 if (CULL_SHADOWS_CASCADE_0 <= m_CurrentCullGroup && m_CurrentCullGroup <= CULL_SHADOWS_CASCADE_3)
971 const int cascade = m_CurrentCullGroup - CULL_SHADOWS_CASCADE_0;
972 m->shadow.AddShadowCasterBound(cascade, patch->GetWorldBounds());
975 m->terrainRenderer.Submit(m_CurrentCullGroup, patch);
978 void CSceneRenderer::Submit(SOverlayLine* overlay)
980 // Overlays are only needed in the default cull group for now,
981 // so just ignore submissions to any other group
982 if (m_CurrentCullGroup == CULL_DEFAULT)
983 m->overlayRenderer.Submit(overlay);
986 void CSceneRenderer::Submit(SOverlayTexturedLine* overlay)
988 if (m_CurrentCullGroup == CULL_DEFAULT)
989 m->overlayRenderer.Submit(overlay);
992 void CSceneRenderer::Submit(SOverlaySprite* overlay)
994 if (m_CurrentCullGroup == CULL_DEFAULT)
995 m->overlayRenderer.Submit(overlay);
998 void CSceneRenderer::Submit(SOverlayQuad* overlay)
1000 if (m_CurrentCullGroup == CULL_DEFAULT)
1001 m->overlayRenderer.Submit(overlay);
1004 void CSceneRenderer::Submit(SOverlaySphere* overlay)
1006 if (m_CurrentCullGroup == CULL_DEFAULT)
1007 m->overlayRenderer.Submit(overlay);
1010 void CSceneRenderer::Submit(CModelDecal* decal)
1012 // Decals can't cast shadows since they're flat on the terrain.
1013 // They can receive shadows, but the terrain under them will have
1014 // already been passed to AddShadowCasterBound, so don't bother
1015 // doing it again here.
1017 m->terrainRenderer.Submit(m_CurrentCullGroup, decal);
1020 void CSceneRenderer::Submit(CParticleEmitter* emitter)
1022 m->particleRenderer.Submit(m_CurrentCullGroup, emitter);
1025 void CSceneRenderer::SubmitNonRecursive(CModel* model)
1027 if (m_CurrentCullGroup == CULL_DEFAULT)
1029 m->shadow.AddShadowReceiverBound(model->GetWorldBounds());
1031 if (model->GetFlags() & ModelFlag::SILHOUETTE_OCCLUDER)
1032 m->silhouetteRenderer.AddOccluder(model);
1033 if (model->GetFlags() & ModelFlag::SILHOUETTE_DISPLAY)
1034 m->silhouetteRenderer.AddCaster(model);
1037 if (CULL_SHADOWS_CASCADE_0 <= m_CurrentCullGroup && m_CurrentCullGroup <= CULL_SHADOWS_CASCADE_3)
1039 if (!(model->GetFlags() & ModelFlag::CAST_SHADOWS))
1040 return;
1042 const int cascade = m_CurrentCullGroup - CULL_SHADOWS_CASCADE_0;
1043 m->shadow.AddShadowCasterBound(cascade, model->GetWorldBounds());
1046 bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0);
1048 if (model->GetMaterial().UsesAlphaBlending())
1050 if (requiresSkinning)
1051 m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model);
1052 else
1053 m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model);
1055 else
1057 if (requiresSkinning)
1058 m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model);
1059 else
1060 m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model);
1064 void CSceneRenderer::PrepareScene(
1065 Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Scene& scene)
1067 m_CurrentScene = &scene;
1069 CFrustum frustum = m_CullCamera.GetFrustum();
1071 m_CurrentCullGroup = CULL_DEFAULT;
1073 scene.EnumerateObjects(frustum, this);
1075 m->particleManager.RenderSubmit(*this, frustum);
1077 if (g_RenderingOptions.GetSilhouettes())
1079 m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera);
1081 m_CurrentCullGroup = CULL_DEFAULT;
1082 m->silhouetteRenderer.RenderSubmitOverlays(*this);
1084 m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER;
1085 m->silhouetteRenderer.RenderSubmitOccluders(*this);
1087 m_CurrentCullGroup = CULL_SILHOUETTE_CASTER;
1088 m->silhouetteRenderer.RenderSubmitCasters(*this);
1091 if (g_RenderingOptions.GetShadows())
1093 for (int cascade = 0; cascade < m->shadow.GetCascadeCount(); ++cascade)
1095 m_CurrentCullGroup = CULL_SHADOWS_CASCADE_0 + cascade;
1096 const CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(cascade);
1097 scene.EnumerateObjects(shadowFrustum, this);
1101 if (m->waterManager.m_RenderWater)
1103 m_WaterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
1105 if (m_WaterScissor.GetVolume() > 0 && m->waterManager.WillRenderFancyWater())
1107 if (g_RenderingOptions.GetWaterReflection())
1109 m_CurrentCullGroup = CULL_REFLECTIONS;
1111 CCamera reflectionCamera;
1112 ComputeReflectionCamera(reflectionCamera, m_WaterScissor);
1114 scene.EnumerateObjects(reflectionCamera.GetFrustum(), this);
1117 if (g_RenderingOptions.GetWaterRefraction())
1119 m_CurrentCullGroup = CULL_REFRACTIONS;
1121 CCamera refractionCamera;
1122 ComputeRefractionCamera(refractionCamera, m_WaterScissor);
1124 scene.EnumerateObjects(refractionCamera.GetFrustum(), this);
1127 // Render the waves to the Fancy effects texture
1128 m->waterManager.RenderWaves(deviceCommandContext, frustum);
1131 else
1132 m_WaterScissor = CBoundingBoxAligned{};
1134 m_CurrentCullGroup = -1;
1136 PrepareSubmissions(deviceCommandContext, m_WaterScissor);
1139 void CSceneRenderer::RenderScene(
1140 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
1142 ENSURE(m_CurrentScene);
1143 RenderSubmissions(deviceCommandContext, m_WaterScissor);
1146 void CSceneRenderer::RenderSceneOverlays(
1147 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
1149 if (g_RenderingOptions.GetSilhouettes())
1151 RenderSilhouettes(deviceCommandContext, m->globalContext);
1154 m->silhouetteRenderer.RenderDebugOverlays(deviceCommandContext);
1156 // Render overlays that should appear on top of all other objects.
1157 m->overlayRenderer.RenderForegroundOverlays(deviceCommandContext, m_ViewCamera);
1159 m_CurrentScene = nullptr;
1162 Scene& CSceneRenderer::GetScene()
1164 ENSURE(m_CurrentScene);
1165 return *m_CurrentScene;
1168 void CSceneRenderer::MakeShadersDirty()
1170 m->waterManager.m_NeedsReloading = true;
1173 WaterManager& CSceneRenderer::GetWaterManager()
1175 return m->waterManager;
1178 SkyManager& CSceneRenderer::GetSkyManager()
1180 return m->skyManager;
1183 CParticleManager& CSceneRenderer::GetParticleManager()
1185 return m->particleManager;
1188 TerrainRenderer& CSceneRenderer::GetTerrainRenderer()
1190 return m->terrainRenderer;
1193 CMaterialManager& CSceneRenderer::GetMaterialManager()
1195 return m->materialManager;
1198 ShadowMap& CSceneRenderer::GetShadowMap()
1200 return m->shadow;
1203 void CSceneRenderer::ResetState()
1205 // Clear all emitters, that were created in previous games
1206 GetParticleManager().ClearUnattachedEmitters();