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"
44 #include "ps/Profile.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"
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
);
78 Internals(Renderer::Backend::IDevice
* device
)
79 : waterManager(device
), shadow(device
)
83 ~Internals() = default;
86 WaterManager waterManager
;
89 SkyManager skyManager
;
92 TerrainRenderer terrainRenderer
;
95 OverlayRenderer overlayRenderer
;
98 CParticleManager particleManager
;
100 /// Particle renderer
101 ParticleRenderer particleRenderer
;
104 CMaterialManager materialManager
;
109 SilhouetteRenderer silhouetteRenderer
;
111 /// Various model renderers
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
;
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).
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
]);
235 m
->globalContext
.Add(str_USE_SHADOW_SAMPLER
, str_1
);
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
));
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");
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");
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
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
458 matrix
[10] = c
.Z
- matrix
[11];
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());
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
));
496 vp
.m_Height
= wm
.m_RefTextureSize
;
497 vp
.m_Width
= wm
.m_RefTextureSize
;
500 camera
.SetViewPort(vp
);
501 camera
.SetProjection(projection
);
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());
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.
536 vp
.m_Height
= wm
.m_RefTextureSize
;
537 vp
.m_Width
= wm
.m_RefTextureSize
;
540 camera
.SetViewPort(vp
);
541 camera
.SetProjection(projection
);
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
;
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
)
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();
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
;
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
)
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();
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
722 // TODO: do we need clear here?
723 deviceCommandContext
->ClearFramebuffer(false, true, true);
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
,
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
);
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
);
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
);
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())
911 if (g_RenderingOptions
.GetDisplayShadowsFrustum())
912 m
->shadow
.RenderDebugBounds();
914 m
->silhouetteRenderer
.RenderDebugBounds(deviceCommandContext
);
917 void CSceneRenderer::EndFrame()
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
))
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
);
1053 m
->Model
.TranspUnskinned
->Submit(m_CurrentCullGroup
, model
);
1057 if (requiresSkinning
)
1058 m
->Model
.NormalSkinned
->Submit(m_CurrentCullGroup
, model
);
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
);
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()
1203 void CSceneRenderer::ResetState()
1205 // Clear all emitters, that were created in previous games
1206 GetParticleManager().ClearUnattachedEmitters();