Skip to content

Commit 156a6be

Browse files
committed
Merge pull request godotengine#77085 from BastiaanOlij/visualise_direction_shadowmap_frustum
Draw frustum splices ontop of direction shadow atlas for debug purposes
2 parents 9fb628c + c782a27 commit 156a6be

9 files changed

Lines changed: 515 additions & 30 deletions

File tree

doc/classes/RenderingServer.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4628,6 +4628,8 @@
46284628
</constant>
46294629
<constant name="VIEWPORT_DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS" value="10" enum="ViewportDebugDraw">
46304630
Draws the shadow atlas that stores shadows from [DirectionalLight3D]s in the upper left quadrant of the [Viewport].
4631+
The slice of the camera frustum related to the shadow map cascade is superimposed to visualize coverage. The color of each slice matches the colors used for [constant VIEWPORT_DEBUG_DRAW_PSSM_SPLITS]. When shadow cascades are blended the overlap is taken into account when drawing the frustum slices.
4632+
The last cascade shows all frustum slices to illustrate the coverage of all slices.
46314633
</constant>
46324634
<constant name="VIEWPORT_DEBUG_DRAW_SCENE_LUMINANCE" value="11" enum="ViewportDebugDraw">
46334635
Draws the estimated scene luminance. This is a 1×1 texture that is generated when autoexposure is enabled to control the scene's exposure.
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
/**************************************************************************/
2+
/* debug_effects.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "debug_effects.h"
32+
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
33+
#include "servers/rendering/renderer_rd/storage_rd/light_storage.h"
34+
#include "servers/rendering/renderer_rd/storage_rd/material_storage.h"
35+
#include "servers/rendering/renderer_rd/uniform_set_cache_rd.h"
36+
37+
using namespace RendererRD;
38+
39+
DebugEffects::DebugEffects() {
40+
{
41+
// Shadow Frustum debug shader
42+
Vector<String> modes;
43+
modes.push_back("");
44+
45+
shadow_frustum.shader.initialize(modes);
46+
shadow_frustum.shader_version = shadow_frustum.shader.version_create();
47+
48+
RD::PipelineRasterizationState raster_state = RD::PipelineRasterizationState();
49+
shadow_frustum.pipelines[SFP_TRANSPARENT].setup(shadow_frustum.shader.version_get_shader(shadow_frustum.shader_version, 0), RD::RENDER_PRIMITIVE_TRIANGLES, raster_state, RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), RD::PipelineColorBlendState::create_blend(), 0);
50+
51+
raster_state.wireframe = true;
52+
shadow_frustum.pipelines[SFP_WIREFRAME].setup(shadow_frustum.shader.version_get_shader(shadow_frustum.shader_version, 0), RD::RENDER_PRIMITIVE_LINES, raster_state, RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), RD::PipelineColorBlendState::create_disabled(), 0);
53+
}
54+
}
55+
56+
void DebugEffects::_create_frustum_arrays() {
57+
if (frustum.vertex_buffer.is_null()) {
58+
// Create vertex buffer, but don't put data in it yet
59+
frustum.vertex_buffer = RD::get_singleton()->vertex_buffer_create(8 * sizeof(float) * 3, Vector<uint8_t>(), false);
60+
61+
Vector<RD::VertexAttribute> attributes;
62+
Vector<RID> buffers;
63+
RD::VertexAttribute vd;
64+
65+
vd.location = 0;
66+
vd.stride = sizeof(float) * 3;
67+
vd.format = RD::DATA_FORMAT_R32G32B32_SFLOAT;
68+
69+
attributes.push_back(vd);
70+
buffers.push_back(frustum.vertex_buffer);
71+
72+
frustum.vertex_format = RD::get_singleton()->vertex_format_create(attributes);
73+
frustum.vertex_array = RD::get_singleton()->vertex_array_create(8, frustum.vertex_format, buffers);
74+
}
75+
76+
if (frustum.index_buffer.is_null()) {
77+
uint32_t indices[6 * 2 * 3] = {
78+
// Far
79+
0, 1, 2, // FLT, FLB, FRT
80+
1, 3, 2, // FLB, FRB, FRT
81+
// Near
82+
4, 6, 5, // NLT, NRT, NLB
83+
6, 7, 5, // NRT, NRB, NLB
84+
// Left
85+
0, 4, 1, // FLT, NLT, FLB
86+
4, 5, 1, // NLT, NLB, FLB
87+
// Right
88+
6, 2, 7, // NRT, FRT, NRB
89+
2, 3, 7, // FRT, FRB, NRB
90+
// Top
91+
0, 2, 4, // FLT, FRT, NLT
92+
2, 6, 4, // FRT, NRT, NLT
93+
// Bottom
94+
5, 7, 1, // NLB, NRB, FLB,
95+
7, 3, 1, // NRB, FRB, FLB
96+
};
97+
98+
// Create our index_array
99+
PackedByteArray data;
100+
data.resize(6 * 2 * 3 * 4);
101+
{
102+
uint8_t *w = data.ptrw();
103+
int *p32 = (int *)w;
104+
for (int i = 0; i < 6 * 2 * 3; i++) {
105+
*p32 = indices[i];
106+
p32++;
107+
}
108+
}
109+
110+
frustum.index_buffer = RD::get_singleton()->index_buffer_create(6 * 2 * 3, RenderingDevice::INDEX_BUFFER_FORMAT_UINT32, data);
111+
frustum.index_array = RD::get_singleton()->index_array_create(frustum.index_buffer, 0, 6 * 2 * 3);
112+
}
113+
114+
if (frustum.lines_buffer.is_null()) {
115+
uint32_t indices[12 * 2] = {
116+
0, 1, // FLT - FLB
117+
1, 3, // FLB - FRB
118+
3, 2, // FRB - FRT
119+
2, 0, // FRT - FLT
120+
121+
4, 6, // NLT - NRT
122+
6, 7, // NRT - NRB
123+
7, 5, // NRB - NLB
124+
5, 4, // NLB - NLT
125+
126+
0, 4, // FLT - NLT
127+
1, 5, // FLB - NLB
128+
2, 6, // FRT - NRT
129+
3, 7, // FRB - NRB
130+
};
131+
132+
// Create our lines_array
133+
PackedByteArray data;
134+
data.resize(12 * 2 * 4);
135+
{
136+
uint8_t *w = data.ptrw();
137+
int *p32 = (int *)w;
138+
for (int i = 0; i < 12 * 2; i++) {
139+
*p32 = indices[i];
140+
p32++;
141+
}
142+
}
143+
144+
frustum.lines_buffer = RD::get_singleton()->index_buffer_create(12 * 2, RenderingDevice::INDEX_BUFFER_FORMAT_UINT32, data);
145+
frustum.lines_array = RD::get_singleton()->index_array_create(frustum.lines_buffer, 0, 12 * 2);
146+
}
147+
}
148+
149+
DebugEffects::~DebugEffects() {
150+
shadow_frustum.shader.version_free(shadow_frustum.shader_version);
151+
152+
// Destroy vertex buffer and array.
153+
if (frustum.vertex_buffer.is_valid()) {
154+
RD::get_singleton()->free(frustum.vertex_buffer); // Array gets freed as dependency.
155+
}
156+
157+
// Destroy index buffer and array,
158+
if (frustum.index_buffer.is_valid()) {
159+
RD::get_singleton()->free(frustum.index_buffer); // Array gets freed as dependency.
160+
}
161+
162+
// Destroy lines buffer and array.
163+
if (frustum.lines_buffer.is_valid()) {
164+
RD::get_singleton()->free(frustum.lines_buffer); // Array gets freed as dependency.
165+
}
166+
}
167+
168+
void DebugEffects::draw_shadow_frustum(RID p_light, const Projection &p_cam_projection, const Transform3D &p_cam_transform, RID p_dest_fb, const Rect2 p_rect) {
169+
RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
170+
171+
RID base = light_storage->light_instance_get_base_light(p_light);
172+
ERR_FAIL_COND(light_storage->light_get_type(base) != RS::LIGHT_DIRECTIONAL);
173+
174+
// Make sure our buffers and arrays exist.
175+
_create_frustum_arrays();
176+
177+
// Setup a points buffer for our view frustum.
178+
PackedByteArray points;
179+
points.resize(8 * sizeof(float) * 3);
180+
181+
// Get info about our splits.
182+
RS::LightDirectionalShadowMode shadow_mode = light_storage->light_directional_get_shadow_mode(base);
183+
bool overlap = light_storage->light_directional_get_blend_splits(base);
184+
int splits = 1;
185+
if (shadow_mode == RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_4_SPLITS) {
186+
splits = 4;
187+
} else if (shadow_mode == RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS) {
188+
splits = 2;
189+
}
190+
191+
// Setup our camera info (this is mostly a duplicate of the logic found in RendererSceneCull::_light_instance_setup_directional_shadow).
192+
bool is_orthogonal = p_cam_projection.is_orthogonal();
193+
real_t aspect = p_cam_projection.get_aspect();
194+
real_t fov = 0.0;
195+
Vector2 vp_he;
196+
if (is_orthogonal) {
197+
vp_he = p_cam_projection.get_viewport_half_extents();
198+
} else {
199+
fov = p_cam_projection.get_fov(); //this is actually yfov, because set aspect tries to keep it
200+
}
201+
real_t min_distance = p_cam_projection.get_z_near();
202+
real_t max_distance = p_cam_projection.get_z_far();
203+
real_t shadow_max = RSG::light_storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_MAX_DISTANCE);
204+
if (shadow_max > 0 && !is_orthogonal) {
205+
max_distance = MIN(shadow_max, max_distance);
206+
}
207+
208+
// Make sure we've not got bad info coming in.
209+
max_distance = MAX(max_distance, min_distance + 0.001);
210+
min_distance = MIN(min_distance, max_distance);
211+
real_t range = max_distance - min_distance;
212+
213+
real_t distances[5];
214+
distances[0] = min_distance;
215+
for (int i = 0; i < splits; i++) {
216+
distances[i + 1] = min_distance + RSG::light_storage->light_get_param(base, RS::LightParam(RS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET + i)) * range;
217+
};
218+
distances[splits] = max_distance;
219+
220+
Color colors[4] = {
221+
Color(1.0, 0.0, 0.0, 0.1),
222+
Color(0.0, 1.0, 0.0, 0.1),
223+
Color(0.0, 0.0, 1.0, 0.1),
224+
Color(1.0, 1.0, 0.0, 0.1),
225+
};
226+
227+
for (int split = 0; split < splits; split++) {
228+
// Load frustum points into vertex buffer.
229+
uint8_t *w = points.ptrw();
230+
Vector3 *vw = (Vector3 *)w;
231+
232+
Projection projection;
233+
234+
if (is_orthogonal) {
235+
projection.set_orthogonal(vp_he.y * 2.0, aspect, distances[(split == 0 || !overlap) ? split : split - 1], distances[split + 1], false);
236+
} else {
237+
projection.set_perspective(fov, aspect, distances[(split == 0 || !overlap) ? split : split - 1], distances[split + 1], true);
238+
}
239+
240+
bool res = projection.get_endpoints(p_cam_transform, vw);
241+
ERR_CONTINUE(!res);
242+
243+
RD::get_singleton()->buffer_update(frustum.vertex_buffer, 0, 8 * sizeof(float) * 3, w);
244+
245+
// Get our light projection info.
246+
Projection light_projection = light_storage->light_instance_get_shadow_camera(p_light, split);
247+
Transform3D light_transform = light_storage->light_instance_get_shadow_transform(p_light, split);
248+
Rect2 atlas_rect_norm = light_storage->light_instance_get_directional_shadow_atlas_rect(p_light, split);
249+
250+
if (!is_orthogonal) {
251+
light_transform.orthogonalize();
252+
}
253+
254+
// Setup our push constant.
255+
ShadowFrustumPushConstant push_constant;
256+
MaterialStorage::store_camera(light_projection * Projection(light_transform.inverse()), push_constant.mvp);
257+
push_constant.color[0] = colors[split].r;
258+
push_constant.color[1] = colors[split].g;
259+
push_constant.color[2] = colors[split].b;
260+
push_constant.color[3] = colors[split].a;
261+
262+
// Adjust our rect to our atlas position.
263+
Rect2 rect = p_rect;
264+
rect.position.x += atlas_rect_norm.position.x * rect.size.x;
265+
rect.position.y += atlas_rect_norm.position.y * rect.size.y;
266+
rect.size.x *= atlas_rect_norm.size.x;
267+
rect.size.y *= atlas_rect_norm.size.y;
268+
269+
// And draw our frustum.
270+
RD::FramebufferFormatID fb_format_id = RD::get_singleton()->framebuffer_get_format(p_dest_fb);
271+
272+
RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dest_fb, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD, Vector<Color>(), 1.0, 0, rect);
273+
274+
RID pipeline = shadow_frustum.pipelines[SFP_TRANSPARENT].get_render_pipeline(frustum.vertex_format, fb_format_id);
275+
RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, pipeline);
276+
RD::get_singleton()->draw_list_bind_vertex_array(draw_list, frustum.vertex_array);
277+
RD::get_singleton()->draw_list_bind_index_array(draw_list, frustum.index_array);
278+
RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, sizeof(ShadowFrustumPushConstant));
279+
RD::get_singleton()->draw_list_draw(draw_list, true);
280+
281+
pipeline = shadow_frustum.pipelines[SFP_WIREFRAME].get_render_pipeline(frustum.vertex_format, fb_format_id);
282+
RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, pipeline);
283+
RD::get_singleton()->draw_list_bind_vertex_array(draw_list, frustum.vertex_array);
284+
RD::get_singleton()->draw_list_bind_index_array(draw_list, frustum.lines_array);
285+
RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, sizeof(ShadowFrustumPushConstant));
286+
RD::get_singleton()->draw_list_draw(draw_list, true);
287+
288+
RD::get_singleton()->draw_list_end();
289+
290+
if (split < (splits - 1) && splits > 1) {
291+
// Also draw it in the last split so we get a proper overview of the whole view frustum...
292+
293+
// Get our light projection info.
294+
light_projection = light_storage->light_instance_get_shadow_camera(p_light, (splits - 1));
295+
light_transform = light_storage->light_instance_get_shadow_transform(p_light, (splits - 1));
296+
atlas_rect_norm = light_storage->light_instance_get_directional_shadow_atlas_rect(p_light, (splits - 1));
297+
298+
if (!is_orthogonal) {
299+
light_transform.orthogonalize();
300+
}
301+
302+
// Update our push constant.
303+
MaterialStorage::store_camera(light_projection * Projection(light_transform.inverse()), push_constant.mvp);
304+
push_constant.color[0] = colors[split].r;
305+
push_constant.color[1] = colors[split].g;
306+
push_constant.color[2] = colors[split].b;
307+
push_constant.color[3] = colors[split].a;
308+
309+
// Adjust our rect to our atlas position.
310+
rect = p_rect;
311+
rect.position.x += atlas_rect_norm.position.x * rect.size.x;
312+
rect.position.y += atlas_rect_norm.position.y * rect.size.y;
313+
rect.size.x *= atlas_rect_norm.size.x;
314+
rect.size.y *= atlas_rect_norm.size.y;
315+
316+
draw_list = RD::get_singleton()->draw_list_begin(p_dest_fb, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD, Vector<Color>(), 1.0, 0, rect);
317+
318+
pipeline = shadow_frustum.pipelines[SFP_TRANSPARENT].get_render_pipeline(frustum.vertex_format, fb_format_id);
319+
RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, pipeline);
320+
RD::get_singleton()->draw_list_bind_vertex_array(draw_list, frustum.vertex_array);
321+
RD::get_singleton()->draw_list_bind_index_array(draw_list, frustum.index_array);
322+
RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, sizeof(ShadowFrustumPushConstant));
323+
RD::get_singleton()->draw_list_draw(draw_list, true);
324+
325+
RD::get_singleton()->draw_list_end();
326+
}
327+
}
328+
}

0 commit comments

Comments
 (0)