VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
shadowRendererLocal.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4// Created by Arnis Lektauers on 11.09.2025.
5//
7
8#include <cmath>
9#include <numbers>
10
12#include "scene/graphNode.h"
14#include "spdlog/spdlog.h"
15
16namespace visutwin::canvas
17{
18 Camera* ShadowRendererLocal::prepareLights(std::vector<Light*>& shadowLights, const std::vector<Light*>& lights) {
19 Camera* shadowCamera = nullptr;
20
21 for (auto* light : lights) {
22 if (_shadowRenderer->needsShadowRendering(light) && light->atlasViewportAllocated()) {
23 shadowLights.push_back(light);
24
25 for (int face = 0; face < light->numShadowFaces(); face++) {
26 shadowCamera = _shadowRenderer->prepareFace(light, nullptr, face);
27 }
28 }
29 }
30
31 return shadowCamera;
32 }
33
34 void ShadowRendererLocal::buildNonClusteredRenderPasses(FrameGraph* frameGraph, const std::vector<Light*>& localLights)
35 {
36 for (size_t i = 0; i < localLights.size(); i++) {
37 Light* light = localLights[i];
38 const bool needsRendering = _shadowRenderer->needsShadowRendering(light);
39 if (needsRendering) {
40 // Only spotlights support VSM
41 bool applyVsm = light->type() == LightType::LIGHTTYPE_SPOT;
42
43 // Omni lights render all 6 cubemap faces; spot lights render 1 face.
44 // Omni uses a cubemap depth texture where each face targets a different slice,
45 // so rendering all 6 faces writes to separate storage and does not overwrite.
46 const int faceCount = light->numShadowFaces();
47 for (int face = 0; face < faceCount; face++) {
48 auto renderPass = std::make_shared<RenderPassShadowLocalNonClustered>(_device, _shadowRenderer, light, face, applyVsm);
49 frameGraph->addRenderPass(renderPass);
50 }
51 }
52 }
53 }
54
55 void ShadowRendererLocal::cullLocalLights(const std::vector<Light*>& localLights,
56 const std::shared_ptr<GraphicsDevice>& device,
57 std::vector<std::unique_ptr<ShadowMap>>& ownedShadowMaps)
58 {
59 // Cache the device pointer for use in buildNonClusteredRenderPasses().
60 // The device is needed to create render pass encoders and draw commands.
61 _device = device;
62
63 for (auto* light : localLights) {
64 if (!light || !light->castShadows() || !light->enabled()) {
65 continue;
66 }
67
68 // Allocate shadow map if not yet created.
69 if (!light->shadowMap()) {
70 auto shadowMap = ShadowMap::create(device.get(), light);
71 if (shadowMap) {
72 light->setShadowMap(shadowMap.get());
73 ownedShadowMaps.push_back(std::move(shadowMap));
74 } else {
75 spdlog::warn("[LocalShadow] shadow map allocation FAILED for light");
76 }
77 }
78
79 if (!light->shadowMap()) {
80 continue;
81 }
82
83 const bool isOmni = (light->type() == LightType::LIGHTTYPE_OMNI);
84
85 // Omni lights set up all 6 cubemap faces; spot lights set up 1 face.
86 // For omni, LightCamera::create already sets per-face rotation (±X, ±Y, ±Z)
87 // and 90° FOV for each face index.
88 const int faceCount = light->numShadowFaces();
89 for (int face = 0; face < faceCount; ++face) {
90 LightRenderData* rd = light->getRenderData(nullptr, face);
91 if (!rd || !rd->shadowCamera || !rd->shadowCamera->node()) {
92 continue;
93 }
94
95 Camera* shadowCam = rd->shadowCamera;
96 GraphNode* lightNode = light->node();
97 if (!lightNode) {
98 continue;
99 }
100
101 // Position shadow camera at the light's world position.
102 shadowCam->node()->setPosition(lightNode->position());
103
104 if (light->type() == LightType::LIGHTTYPE_SPOT) {
105 // Spot: orient camera along the light's direction, FOV = outerConeAngle * 2.
106 shadowCam->node()->setRotation(lightNode->rotation());
107 shadowCam->setFov(std::min(light->outerConeAngle() * 2.0f, 179.0f));
108 shadowCam->setNearClip(0.01f);
109 shadowCam->setFarClip(std::max(light->range(), 0.1f));
110 } else {
111 // Point (omni): LightCamera::create already sets per-face rotation and 90° FOV.
112 shadowCam->setNearClip(0.01f);
113 shadowCam->setFarClip(std::max(light->range(), 0.1f));
114 }
115
116 // Assign the render target for this face.
117 int renderTargetIndex = (light->type() == LightType::LIGHTTYPE_DIRECTIONAL) ? 0 : face;
118 const auto& rts = light->shadowMap()->renderTargets();
119 if (renderTargetIndex < static_cast<int>(rts.size())) {
120 shadowCam->setRenderTarget(rts[renderTargetIndex]);
121 }
122 }
123
124 // Compute and store the shadow VP matrix.
125 // Spot lights: VP matrix used for 2D shadow projection in the fragment shader.
126 // Omni lights: VP matrix is not used (cubemap sampling uses direction-based lookup),
127 // but we still compute face 0's VP for backward compat / debug.
128 if (!isOmni) {
129 LightRenderData* rd = light->getRenderData(nullptr, 0);
130 if (rd && rd->shadowCamera && rd->shadowCamera->node()) {
131 const Matrix4 shadowVP = rd->shadowCamera->projectionMatrix()
132 * rd->shadowCamera->node()->worldTransform().inverse();
133
134 // Apply NDC-to-UV viewport bias matrix, matching directional shadow
135 // construction (shadowRendererDirectional.cpp).
136 // Maps NDC x,y [-1,1] → UV [0,1] and z [-1,1] → [0,1] to match
137 // shadow vertex shader's clip.z = 0.5*(clip.z+clip.w) depth mapping.
138 // Metal Y flip: negative Y scale (texture coordinates are top-left origin).
139 Matrix4 viewportBias = Matrix4::identity();
140 viewportBias.setElement(0, 0, 0.5f); // X: scale by 0.5
141 viewportBias.setElement(3, 0, 0.5f); // X: translate by 0.5
142 viewportBias.setElement(1, 1, -0.5f); // Y: scale by -0.5 (Metal Y flip)
143 viewportBias.setElement(3, 1, 0.5f); // Y: translate by 0.5
144 viewportBias.setElement(2, 2, 0.5f); // Z: scale by 0.5
145 viewportBias.setElement(3, 2, 0.5f); // Z: bias by 0.5
146
147 light->setShadowViewProjection(viewportBias * shadowVP);
148 }
149 }
150 }
151 }
152}
Perspective or orthographic camera with projection matrix, jitter (TAA), and render target binding.
Definition camera.h:40
void setRenderTarget(const std::shared_ptr< RenderTarget > &value)
Definition camera.h:87
void setFov(float value)
Definition camera.h:43
const Matrix4 & projectionMatrix()
Definition camera.h:65
const std::unique_ptr< GraphNode > & node() const
Definition camera.h:102
void setFarClip(const float value)
Definition camera.h:58
void setNearClip(const float value)
Definition camera.h:55
void addRenderPass(const std::shared_ptr< RenderPass > &renderPass)
Hierarchical scene graph node with local/world transforms and parent-child relationships.
Definition graphNode.h:28
Directional, point, spot, or area light with shadow mapping and cookie projection.
Definition light.h:54
int numShadowFaces() const
Definition light.cpp:31
LightType type() const
Definition light.h:70
Per-face shadow rendering data: shadow camera, viewport, and scissor.
Definition light.h:25
static std::unique_ptr< ShadowMap > create(GraphicsDevice *device, Light *light)
Definition shadowMap.cpp:15
void buildNonClusteredRenderPasses(FrameGraph *frameGraph, const std::vector< Light * > &localLights)
Camera * prepareLights(std::vector< Light * > &shadowLights, const std::vector< Light * > &lights)
void cullLocalLights(const std::vector< Light * > &localLights, const std::shared_ptr< GraphicsDevice > &device, std::vector< std::unique_ptr< ShadowMap > > &ownedShadowMaps)
std::vector< Light * > & shadowLights()
4x4 column-major transformation matrix with SIMD acceleration.
Definition matrix4.h:31
void setElement(const int col, int row, const float value)
Definition matrix4.h:376
static Matrix4 identity()
Definition matrix4.h:108