Blender V4.3
workbench_shadow.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
18#include "BKE_object.hh"
19#include "DRW_render.hh"
20#include "GPU_compute.hh"
21
22#include "workbench_private.hh"
23
24namespace blender::workbench {
25
26void ShadowPass::ShadowView::setup(View &view, float3 light_direction, bool force_fail_method)
27{
28 force_fail_method_ = force_fail_method;
29 light_direction_ = light_direction;
30 sync(view.viewmat(), view.winmat());
31
32 /* Prepare frustum extruded in the negative light direction,
33 * so we can test regular bounding boxes against it for culling. */
34
35 /* Frustum Corners indices
36 * Z Y
37 * | /
38 * |/
39 * .-----X
40 * 3----------7
41 * /| /|
42 * / | / |
43 * 0----------4 |
44 * | | | |
45 * | 2-------|--6
46 * | / | /
47 * |/ |/
48 * 1----------5
49 */
50
51 /* Frustum Planes indices */
52 const int x_neg = 0; /* Left */
53 const int x_pos = 5; /* Right */
54 const int y_neg = 1; /* Bottom */
55 const int y_pos = 3; /* Top */
56 const int z_pos = 4; /* Near */
57 const int z_neg = 2; /* Far */
58
59 const int3 corner_faces[8] = {
60 {x_neg, y_neg, z_pos},
61 {x_neg, y_neg, z_neg},
62 {x_neg, y_pos, z_neg},
63 {x_neg, y_pos, z_pos},
64 {x_pos, y_neg, z_pos},
65 {x_pos, y_neg, z_neg},
66 {x_pos, y_pos, z_neg},
67 {x_pos, y_pos, z_pos},
68 };
69
70 const int2 edge_faces[12] = {
71 {x_neg, y_neg},
72 {x_neg, z_neg},
73 {x_neg, y_pos},
74 {x_neg, z_pos},
75 {y_neg, x_pos},
76 {z_neg, x_pos},
77 {y_pos, x_pos},
78 {z_pos, x_pos},
79 {y_neg, z_pos},
80 {z_neg, y_neg},
81 {y_pos, z_neg},
82 {z_pos, y_pos},
83 };
84
85 const int2 edge_corners[12] = {
86 {0, 1},
87 {1, 2},
88 {2, 3},
89 {3, 0},
90 {4, 5},
91 {5, 6},
92 {6, 7},
93 {7, 4},
94 {0, 4},
95 {1, 5},
96 {2, 6},
97 {3, 7},
98 };
99
100 BoundBox frustum_corners;
101 DRW_culling_frustum_corners_get(nullptr, &frustum_corners);
102 float4 frustum_planes[6];
103 DRW_culling_frustum_planes_get(nullptr, (float(*)[4])frustum_planes);
104
105 Vector<float4> faces_result;
106 Vector<float3> corners_result;
107
108 /* "Unlit" frustum faces are left "as-is" */
109
110 bool face_lit[6];
111 for (int i : IndexRange(6)) {
112 /* Make the frustum normals face outwards */
113 frustum_planes[i] *= float4(-1, -1, -1, 1);
114
115 face_lit[i] = math::dot(float3(frustum_planes[i]), light_direction_) < 0;
116 if (!face_lit[i]) {
117 faces_result.append(frustum_planes[i]);
118 }
119 }
120
121 /* Edges between lit and unlit faces are extruded "infinitely" towards the light source */
122
123 for (int i : IndexRange(12)) {
124 int2 f = edge_faces[i];
125 bool a_lit = face_lit[f[0]];
126 bool b_lit = face_lit[f[1]];
127 if (a_lit != b_lit) {
128 /* Extrude Face */
129 float3 corner_a = frustum_corners.vec[edge_corners[i][0]];
130 float3 corner_b = frustum_corners.vec[edge_corners[i][1]];
131 float3 edge_direction = math::normalize(corner_b - corner_a);
132 float3 normal = math::normalize(math::cross(light_direction_, edge_direction));
133
134 float4 extruded_face = float4(UNPACK3(normal), math::dot(normal, corner_a));
135
136 /* Ensure the plane faces outwards */
137 bool flipped = false;
138 for (float3 corner : frustum_corners.vec) {
139 if (math::dot(float3(extruded_face), corner) > (extruded_face.w + 0.1)) {
140 BLI_assert(!flipped);
141 UNUSED_VARS_NDEBUG(flipped);
142 flipped = true;
143 extruded_face *= -1;
144 }
145 }
146
147 faces_result.append(extruded_face);
148 }
149 }
150
151 for (int i_corner : IndexRange(8)) {
152 int lit_faces = 0;
153 for (int i_face : IndexRange(3)) {
154 lit_faces += face_lit[corner_faces[i_corner][i_face]] ? 1 : 0;
155 }
156 if (lit_faces < 3) {
157 /* Add original corner */
158 corners_result.append(frustum_corners.vec[i_corner]);
159
160 if (lit_faces > 0) {
161 /* Add extruded corner */
162 corners_result.append(float3(frustum_corners.vec[i_corner]) - (light_direction_ * 1e4f));
163 }
164 }
165 }
166
167 for (int i : corners_result.index_range()) {
168 extruded_frustum_.corners[i] = float4(corners_result[i], 1);
169 }
170 extruded_frustum_.corners_count = corners_result.size();
171
172 for (int i : faces_result.index_range()) {
173 extruded_frustum_.planes[i] = faces_result[i];
174 }
175 extruded_frustum_.planes_count = faces_result.size();
176
177 extruded_frustum_.push_update();
178}
179
180bool ShadowPass::ShadowView::debug_object_culling(Object *ob)
181{
182 printf("Test %s\n", ob->id.name);
183 const Bounds<float3> bounds = *BKE_object_boundbox_get(ob);
184 BoundBox bb;
185 BKE_boundbox_init_from_minmax(&bb, bounds.min, bounds.max);
186 for (int p : IndexRange(extruded_frustum_.planes_count)) {
187 float4 plane = extruded_frustum_.planes[p];
188 bool separating_axis = true;
189 for (float3 corner : bb.vec) {
190 corner = math::transform_point(ob->object_to_world(), corner);
191 float signed_distance = math::dot(corner, float3(plane)) - plane.w;
192 if (signed_distance <= 0) {
193 separating_axis = false;
194 break;
195 }
196 }
197 if (separating_axis) {
198 printf("Sepatating Axis >>> x: %f, y: %f, z: %f, w: %f \n", UNPACK4(plane));
199 return true;
200 }
201 }
202 return false;
203}
204
205void ShadowPass::ShadowView::set_mode(ShadowPass::PassType type)
206{
207 current_pass_type_ = type;
208}
209
210void ShadowPass::ShadowView::compute_visibility(ObjectBoundsBuf &bounds,
211 ObjectInfosBuf & /*infos*/,
212 uint resource_len,
213 bool /*debug_freeze*/)
214{
215 /* TODO (Miguel Pozo): Add debug_freeze support */
216
217 GPU_debug_group_begin("ShadowView.compute_visibility");
218
219 uint word_per_draw = this->visibility_word_per_draw();
220 /* Switch between tightly packed and set of whole word per instance. */
221 uint words_len = (view_len_ == 1) ? divide_ceil_u(resource_len, 32) :
222 resource_len * word_per_draw;
223 words_len = ceil_to_multiple_u(max_ii(1, words_len), 4);
224 const uint32_t data = 0xFFFFFFFFu;
225
226 if (current_pass_type_ == ShadowPass::PASS) {
227 /* TODO(fclem): Resize to nearest pow2 to reduce fragmentation. */
228 pass_visibility_buf_.resize(words_len);
229 GPU_storagebuf_clear(pass_visibility_buf_, data);
230 fail_visibility_buf_.resize(words_len);
231 GPU_storagebuf_clear(fail_visibility_buf_, data);
232 }
233 else if (current_pass_type_ == ShadowPass::FAIL) {
234 /* Already computed in the ShadowPass::PASS */
236 return;
237 }
238 else {
239 visibility_buf_.resize(words_len);
240 GPU_storagebuf_clear(visibility_buf_, data);
241 }
242
243 if (do_visibility_) {
244 /* TODO(@pragma37): Use regular culling for the caps pass. */
245 GPUShader *shader = current_pass_type_ == ShadowPass::FORCED_FAIL ?
248 GPU_shader_bind(shader);
249 GPU_shader_uniform_1i(shader, "resource_len", resource_len);
250 GPU_shader_uniform_1i(shader, "view_len", view_len_);
251 GPU_shader_uniform_1i(shader, "visibility_word_per_draw", word_per_draw);
252 GPU_shader_uniform_1b(shader, "force_fail_method", force_fail_method_);
253 GPU_shader_uniform_3fv(shader, "shadow_direction", light_direction_);
254 GPU_uniformbuf_bind(extruded_frustum_, GPU_shader_get_ubo_binding(shader, "extruded_frustum"));
255 GPU_storagebuf_bind(bounds, GPU_shader_get_ssbo_binding(shader, "bounds_buf"));
256 if (current_pass_type_ == ShadowPass::FORCED_FAIL) {
257 GPU_storagebuf_bind(visibility_buf_, GPU_shader_get_ssbo_binding(shader, "visibility_buf"));
258 }
259 else {
260 GPU_storagebuf_bind(pass_visibility_buf_,
261 GPU_shader_get_ssbo_binding(shader, "pass_visibility_buf"));
262 GPU_storagebuf_bind(fail_visibility_buf_,
263 GPU_shader_get_ssbo_binding(shader, "fail_visibility_buf"));
264 }
268 }
269
271}
272
273VisibilityBuf &ShadowPass::ShadowView::get_visibility_buffer()
274{
275 switch (current_pass_type_) {
276 case ShadowPass::PASS:
277 return pass_visibility_buf_;
278 case ShadowPass::FAIL:
279 return fail_visibility_buf_;
280 case ShadowPass::FORCED_FAIL:
281 return visibility_buf_;
282 default:
284 }
285 return visibility_buf_;
286}
287
288PassMain::Sub *&ShadowPass::get_pass_ptr(PassType type, bool manifold, bool cap /*=false*/)
289{
290 return passes_[type][manifold][cap];
291}
292
293void ShadowPass::init(const SceneState &scene_state, SceneResources &resources)
294{
295 enabled_ = scene_state.draw_shadows;
296 if (!enabled_) {
297 resources.world_buf.shadow_mul = 0.0f;
298 resources.world_buf.shadow_add = 1.0f;
299 return;
300 }
301 const Scene &scene = *scene_state.scene;
302
303 float3 direction_ws = scene.display.light_direction;
304 /* Turn the light in a way where it's more user friendly to control. */
305 std::swap(direction_ws.y, direction_ws.z);
306 direction_ws *= float3(-1, 1, -1);
307
308 float planes[6][4];
309 DRW_culling_frustum_planes_get(nullptr, planes);
310
311 pass_data_.light_direction_ws = direction_ws;
312 pass_data_.far_plane = planes[2] * float4(-1, -1, -1, 1);
313 pass_data_.push_update();
314
315 /* Shadow direction. */
316 float4x4 view_matrix;
317 DRW_view_viewmat_get(nullptr, view_matrix.ptr(), false);
319 math::transform_direction(view_matrix, direction_ws), 0.0f);
320
321 /* Clamp to avoid overshadowing and shading errors. */
322 float focus = clamp_f(scene.display.shadow_focus, 0.0001f, 0.99999f);
323 resources.world_buf.shadow_shift = scene.display.shadow_shift;
324 resources.world_buf.shadow_focus = 1.0f - focus * (1.0f - resources.world_buf.shadow_shift);
325 resources.world_buf.shadow_mul = scene_state.shading.shadow_intensity;
326 resources.world_buf.shadow_add = 1.0f - resources.world_buf.shadow_mul;
327}
328
330{
331 if (!enabled_) {
332 return;
333 }
334
335#if DEBUG_SHADOW_VOLUME
337 DRWState depth_pass_state = state | DRW_STATE_DEPTH_LESS;
338 DRWState depth_fail_state = state | DRW_STATE_DEPTH_GREATER_EQUAL;
339#else
343#endif
344
345 pass_ps_.init();
346 pass_ps_.state_set(depth_pass_state);
347 pass_ps_.state_stencil(0xFF, 0xFF, 0xFF);
348
349 fail_ps_.init();
350 fail_ps_.state_set(depth_fail_state);
351 fail_ps_.state_stencil(0xFF, 0xFF, 0xFF);
352
353 forced_fail_ps_.init();
354 forced_fail_ps_.state_set(depth_fail_state);
355 forced_fail_ps_.state_stencil(0xFF, 0xFF, 0xFF);
356
357 /* Stencil Shadow passes. */
358 for (bool manifold : {false, true}) {
359 PassMain::Sub *&ps = get_pass_ptr(PASS, manifold);
360 ps = &pass_ps_.sub(manifold ? "manifold" : "non_manifold");
361 ps->shader_set(ShaderCache::get().shadow_get(true, manifold));
362 ps->bind_ubo("pass_data", pass_data_);
363
364 for (PassType fail_type : {FAIL, FORCED_FAIL}) {
365 PassMain &ps_main = fail_type == FAIL ? fail_ps_ : forced_fail_ps_;
366
367 PassMain::Sub *&ps = get_pass_ptr(fail_type, manifold, false);
368 ps = &ps_main.sub(manifold ? "NoCaps.manifold" : "NoCaps.non_manifold");
369 ps->shader_set(ShaderCache::get().shadow_get(false, manifold, false));
370 ps->bind_ubo("pass_data", pass_data_);
371
372 PassMain::Sub *&caps_ps = get_pass_ptr(fail_type, manifold, true);
373 caps_ps = &ps_main.sub(manifold ? "Caps.manifold" : "Caps.non_manifold");
374 caps_ps->shader_set(ShaderCache::get().shadow_get(false, manifold, true));
375 caps_ps->bind_ubo("pass_data", pass_data_);
376 }
377 }
378}
379
381 ObjectRef &ob_ref,
382 ResourceHandle handle,
383 const bool has_transp_mat)
384{
385 if (!enabled_) {
386 return;
387 }
388
389 Object *ob = ob_ref.object;
390 bool is_manifold;
391 blender::gpu::Batch *geom_shadow = DRW_cache_object_edge_detection_get(ob, &is_manifold);
392 if (geom_shadow == nullptr) {
393 return;
394 }
395
396#define DEBUG_CULLING 0
397#if DEBUG_CULLING
398 View view = View("View", DRW_view_default_get());
399 ShadowView shadow_view = ShadowView("ShadowView", view, pass_data_.light_direction_ws);
400 printf(
401 "%s culling : %s\n", ob->id.name, shadow_view.debug_object_culling(ob) ? "true" : "false");
402#endif
403
404 /* Shadow pass technique needs object to be have all its surface opaque. */
405 /* We cannot use the PASS technique on non-manifold object (see #76168). */
406 bool force_fail_pass = has_transp_mat || (!is_manifold && (scene_state.cull_state != 0));
407
408 PassType fail_type = force_fail_pass ? FORCED_FAIL : FAIL;
409
410 /* Unless we force the FAIL Method we add draw commands to both methods,
411 * then the visibility compute shader selects the one needed */
412
414 int tri_len = is_manifold ? 2 : 4;
415
416 if (!force_fail_pass) {
417 PassMain::Sub &ps = *get_pass_ptr(PASS, is_manifold);
418 ps.draw_expand(geom_shadow, prim, tri_len, 1, handle);
419 }
420
421 blender::gpu::Batch *geom_faces = DRW_cache_object_surface_get(ob);
422 /* Caps. */
423 get_pass_ptr(fail_type, is_manifold, true)->draw_expand(geom_faces, prim, 2, 1, handle);
424 /* Sides extrusion. */
425 get_pass_ptr(fail_type, is_manifold, false)->draw_expand(geom_shadow, prim, tri_len, 1, handle);
426}
427
429 View &view,
430 SceneResources &resources,
431 GPUTexture &depth_stencil_tx,
432 bool force_fail_method)
433{
434 if (!enabled_) {
435 return;
436 }
437
438 fb_.ensure(GPU_ATTACHMENT_TEXTURE(&depth_stencil_tx),
440 fb_.bind();
441
442 view_.setup(view, pass_data_.light_direction_ws, force_fail_method);
443
444 view_.set_mode(PASS);
445 manager.submit(pass_ps_, view_);
446 view_.set_mode(FAIL);
447 manager.submit(fail_ps_, view_);
448 view_.set_mode(FORCED_FAIL);
449 manager.submit(forced_fail_ps_, view_);
450}
451
453{
454 return DEBUG_SHADOW_VOLUME;
455}
456
457} // namespace blender::workbench
General operations, lookup, etc. for blender objects.
void BKE_boundbox_init_from_minmax(BoundBox *bb, const float min[3], const float max[3])
std::optional< blender::Bounds< blender::float3 > > BKE_object_boundbox_get(const Object *ob)
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
MINLINE uint ceil_to_multiple_u(uint a, uint b)
MINLINE uint divide_ceil_u(uint a, uint b)
MINLINE float clamp_f(float value, float min, float max)
MINLINE int max_ii(int a, int b)
unsigned int uint
#define UNPACK4(a)
#define UNUSED_VARS_NDEBUG(...)
#define UNPACK3(a)
static void View(GHOST_IWindow *window, bool stereo, int eye=0)
void GPU_compute_dispatch(GPUShader *shader, uint groups_x_len, uint groups_y_len, uint groups_z_len)
void GPU_debug_group_end()
Definition gpu_debug.cc:33
void GPU_debug_group_begin(const char *name)
Definition gpu_debug.cc:22
#define GPU_ATTACHMENT_TEXTURE(_texture)
GPUPrimType
@ GPU_PRIM_TRIS
void GPU_shader_uniform_1i(GPUShader *sh, const char *name, int value)
int GPU_shader_get_ubo_binding(GPUShader *shader, const char *name)
void GPU_shader_uniform_3fv(GPUShader *sh, const char *name, const float data[3])
int GPU_shader_get_ssbo_binding(GPUShader *shader, const char *name)
void GPU_shader_bind(GPUShader *shader)
void GPU_shader_uniform_1b(GPUShader *sh, const char *name, bool value)
void GPU_memory_barrier(eGPUBarrier barrier)
Definition gpu_state.cc:374
@ GPU_BARRIER_SHADER_STORAGE
Definition GPU_state.hh:48
void GPU_storagebuf_bind(GPUStorageBuf *ssbo, int slot)
void GPU_storagebuf_clear(GPUStorageBuf *ssbo, uint32_t clear_value)
void GPU_uniformbuf_bind(GPUUniformBuf *ubo, int slot)
struct GPUShader GPUShader
void ensure(GPUAttachment depth=GPU_ATTACHMENT_NONE, GPUAttachment color1=GPU_ATTACHMENT_NONE, GPUAttachment color2=GPU_ATTACHMENT_NONE, GPUAttachment color3=GPU_ATTACHMENT_NONE, GPUAttachment color4=GPU_ATTACHMENT_NONE, GPUAttachment color5=GPU_ATTACHMENT_NONE, GPUAttachment color6=GPU_ATTACHMENT_NONE, GPUAttachment color7=GPU_ATTACHMENT_NONE, GPUAttachment color8=GPU_ATTACHMENT_NONE)
void submit(PassSimple &pass, View &view)
PassBase< DrawCommandBufType > & sub(const char *name)
Definition draw_pass.hh:616
void state_set(DRWState state, int clip_plane_count=0)
Definition draw_pass.hh:954
void bind_ubo(const char *name, GPUUniformBuf *buffer)
void state_stencil(uint8_t write_mask, uint8_t reference, uint8_t compare_mask)
Definition draw_pass.hh:966
void draw_expand(gpu::Batch *batch, GPUPrimType primitive_type, uint primitive_len, uint instance_len, uint vertex_len=-1, uint vertex_first=-1, ResourceHandle handle={0}, uint custom_id=0)
Definition draw_pass.hh:793
void shader_set(GPUShader *shader)
Definition draw_pass.hh:971
detail::PassBase< command::DrawMultiBuf > Sub
Definition draw_pass.hh:462
GPUShader * shadow_get(bool depth_pass, bool manifold, bool cap=false)
void init(const SceneState &scene_state, SceneResources &resources)
void draw(Manager &manager, View &view, SceneResources &resources, GPUTexture &depth_stencil_tx, bool force_fail_method)
void object_sync(SceneState &scene_state, ObjectRef &ob_ref, ResourceHandle handle, const bool has_transp_mat)
#define printf
blender::gpu::Batch * DRW_cache_object_surface_get(Object *ob)
blender::gpu::Batch * DRW_cache_object_edge_detection_get(Object *ob, bool *r_is_manifold)
#define DRW_VISIBILITY_GROUP_SIZE
#define DRW_VIEW_UBO_SLOT
const DRWView * DRW_view_default_get()
void DRW_view_viewmat_get(const DRWView *view, float mat[4][4], bool inverse)
void DRW_culling_frustum_planes_get(const DRWView *view, float planes[6][4])
void DRW_culling_frustum_corners_get(const DRWView *view, BoundBox *corners)
DRWState
Definition draw_state.hh:25
@ DRW_STATE_STENCIL_ALWAYS
Definition draw_state.hh:46
@ DRW_STATE_DEPTH_LESS
Definition draw_state.hh:37
@ DRW_STATE_DEPTH_GREATER_EQUAL
Definition draw_state.hh:41
@ DRW_STATE_WRITE_STENCIL_SHADOW_FAIL
Definition draw_state.hh:34
@ DRW_STATE_BLEND_ADD_FULL
Definition draw_state.hh:53
@ DRW_STATE_WRITE_COLOR
Definition draw_state.hh:30
@ DRW_STATE_WRITE_STENCIL_SHADOW_PASS
Definition draw_state.hh:33
PassType
static ulong state[N]
StorageArrayBuffer< ObjectBounds, 128 > ObjectBoundsBuf
Definition draw_view.hh:30
StorageArrayBuffer< ObjectInfos, 128 > ObjectInfosBuf
Definition draw_view.hh:31
StorageArrayBuffer< uint, 4, true > VisibilityBuf
Definition draw_view.hh:32
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
AxisSigned cross(const AxisSigned a, const AxisSigned b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
VecBase< T, 3 > transform_direction(const MatBase< T, 3, 3 > &mat, const VecBase< T, 3 > &direction)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
VecBase< float, 4 > float4
VecBase< float, 3 > float3
unsigned int uint32_t
Definition stdint.h:80
float vec[8][3]
char name[66]
Definition DNA_ID.h:425
packed_float3 light_direction_ws
const c_style_mat & ptr() const
UniformBuffer< WorldData > world_buf
#define DEBUG_SHADOW_VOLUME