Blender V5.0
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
17
18#include "BKE_object.hh"
19#include "DNA_scene_types.h"
20#include "DRW_render.hh"
21#include "GPU_compute.hh"
22
23#include "draw_cache.hh"
24
25#include "workbench_private.hh"
26
27namespace blender::workbench {
28
29void ShadowPass::ShadowView::setup(View &view, float3 light_direction, bool force_fail_method)
30{
31 force_fail_method_ = force_fail_method;
32 light_direction_ = light_direction;
33 sync(view.viewmat(), view.winmat());
34
35 /* Prepare frustum extruded in the negative light direction,
36 * so we can test regular bounding boxes against it for culling. */
37
38 /* Frustum Corners indices
39 * Z Y
40 * | /
41 * |/
42 * .-----X
43 * 3----------7
44 * /| /|
45 * / | / |
46 * 0----------4 |
47 * | | | |
48 * | 2-------|--6
49 * | / | /
50 * |/ |/
51 * 1----------5
52 */
53
54 /* Frustum Planes indices */
55 const int x_neg = 0; /* Left */
56 const int x_pos = 5; /* Right */
57 const int y_neg = 1; /* Bottom */
58 const int y_pos = 3; /* Top */
59 const int z_pos = 4; /* Near */
60 const int z_neg = 2; /* Far */
61
62 const int3 corner_faces[8] = {
63 {x_neg, y_neg, z_pos},
64 {x_neg, y_neg, z_neg},
65 {x_neg, y_pos, z_neg},
66 {x_neg, y_pos, z_pos},
67 {x_pos, y_neg, z_pos},
68 {x_pos, y_neg, z_neg},
69 {x_pos, y_pos, z_neg},
70 {x_pos, y_pos, z_pos},
71 };
72
73 const int2 edge_faces[12] = {
74 {x_neg, y_neg},
75 {x_neg, z_neg},
76 {x_neg, y_pos},
77 {x_neg, z_pos},
78 {y_neg, x_pos},
79 {z_neg, x_pos},
80 {y_pos, x_pos},
81 {z_pos, x_pos},
82 {y_neg, z_pos},
83 {z_neg, y_neg},
84 {y_pos, z_neg},
85 {z_pos, y_pos},
86 };
87
88 const int2 edge_corners[12] = {
89 {0, 1},
90 {1, 2},
91 {2, 3},
92 {3, 0},
93 {4, 5},
94 {5, 6},
95 {6, 7},
96 {7, 4},
97 {0, 4},
98 {1, 5},
99 {2, 6},
100 {3, 7},
101 };
102
103 std::array<float3, 8> frustum_corners = this->frustum_corners_get();
104 std::array<float4, 6> frustum_planes = this->frustum_planes_get();
105
106 Vector<float4> faces_result;
107 Vector<float3> corners_result;
108
109 /* "Unlit" frustum faces are left "as-is" */
110
111 bool face_lit[6];
112 for (int i : IndexRange(6)) {
113 /* Make the frustum normals face outwards */
114 frustum_planes[i] *= float4(-1, -1, -1, 1);
115
116 face_lit[i] = math::dot(float3(frustum_planes[i]), light_direction_) < 0;
117 if (!face_lit[i]) {
118 faces_result.append(frustum_planes[i]);
119 }
120 }
121
122 /* Edges between lit and unlit faces are extruded "infinitely" towards the light source */
123
124 for (int i : IndexRange(12)) {
125 int2 f = edge_faces[i];
126 bool a_lit = face_lit[f[0]];
127 bool b_lit = face_lit[f[1]];
128 if (a_lit != b_lit) {
129 /* Extrude Face */
130 float3 corner_a = frustum_corners[edge_corners[i][0]];
131 float3 corner_b = frustum_corners[edge_corners[i][1]];
132 float3 edge_direction = math::normalize(corner_b - corner_a);
133 float3 normal = math::normalize(math::cross(light_direction_, edge_direction));
134
135 float4 extruded_face = float4(UNPACK3(normal), math::dot(normal, corner_a));
136
137 /* Ensure the plane faces outwards */
138 bool flipped = false;
139 for (float3 corner : frustum_corners) {
140 if (math::dot(float3(extruded_face), corner) > (extruded_face.w + 0.1)) {
141 BLI_assert(!flipped);
142 UNUSED_VARS_NDEBUG(flipped);
143 flipped = true;
144 extruded_face *= -1;
145 }
146 }
147
148 faces_result.append(extruded_face);
149 }
150 }
151
152 for (int i_corner : IndexRange(8)) {
153 int lit_faces = 0;
154 for (int i_face : IndexRange(3)) {
155 lit_faces += face_lit[corner_faces[i_corner][i_face]] ? 1 : 0;
156 }
157 if (lit_faces < 3) {
158 /* Add original corner */
159 corners_result.append(frustum_corners[i_corner]);
160
161 if (lit_faces > 0) {
162 /* Add extruded corner */
163 corners_result.append(float3(frustum_corners[i_corner]) - (light_direction_ * 1e4f));
164 }
165 }
166 }
167
168 for (int i : corners_result.index_range()) {
169 extruded_frustum_.corners[i] = float4(corners_result[i], 1);
170 }
171 extruded_frustum_.corners_count = corners_result.size();
172
173 for (int i : faces_result.index_range()) {
174 extruded_frustum_.planes[i] = faces_result[i];
175 }
176 extruded_frustum_.planes_count = faces_result.size();
177
178 extruded_frustum_.push_update();
179}
180
181bool ShadowPass::ShadowView::debug_object_culling(Object *ob)
182{
183 printf("Test %s\n", ob->id.name);
184 const Bounds<float3> bounds = *BKE_object_boundbox_get(ob);
185 const std::array<float3, 8> corners = bounds::corners(bounds);
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 : corners) {
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("Separating 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 /* Ensure compute_visibility runs again after updating the mode. */
209 manager_fingerprint_ = 0;
210}
211
212void ShadowPass::ShadowView::compute_visibility(ObjectBoundsBuf &bounds,
213 ObjectInfosBuf & /*infos*/,
214 uint resource_len,
215 bool /*debug_freeze*/)
216{
217 /* TODO (Miguel Pozo): Add debug_freeze support */
218
219 GPU_debug_group_begin("ShadowView.compute_visibility");
220
221 uint word_per_draw = this->visibility_word_per_draw();
222 /* Switch between tightly packed and set of whole word per instance. */
223 uint words_len = (view_len_ == 1) ? divide_ceil_u(resource_len, 32) :
224 resource_len * word_per_draw;
225 words_len = ceil_to_multiple_u(max_ii(1, words_len), 4);
226 const uint32_t data = 0xFFFFFFFFu;
227
228 if (current_pass_type_ == ShadowPass::PASS) {
229 /* TODO(fclem): Resize to nearest pow2 to reduce fragmentation. */
230 pass_visibility_buf_.resize(words_len);
231 GPU_storagebuf_clear(pass_visibility_buf_, data);
232 fail_visibility_buf_.resize(words_len);
233 GPU_storagebuf_clear(fail_visibility_buf_, data);
234 }
235 else if (current_pass_type_ == ShadowPass::FAIL) {
236 /* Already computed in the ShadowPass::PASS */
238 return;
239 }
240 else {
241 visibility_buf_.resize(words_len);
242 GPU_storagebuf_clear(visibility_buf_, data);
243 }
244
245 if (do_visibility_) {
246 /* TODO(@pragma37): Use regular culling for the caps pass. */
247 gpu::Shader *shader = current_pass_type_ == ShadowPass::FORCED_FAIL ?
250 GPU_shader_bind(shader);
251 GPU_shader_uniform_1i(shader, "resource_len", resource_len);
252 GPU_shader_uniform_1i(shader, "view_len", view_len_);
253 GPU_shader_uniform_1i(shader, "visibility_word_per_draw", word_per_draw);
254 GPU_shader_uniform_1b(shader, "force_fail_method", force_fail_method_);
255 GPU_shader_uniform_3fv(shader, "shadow_direction", light_direction_);
256 GPU_uniformbuf_bind(extruded_frustum_, GPU_shader_get_ubo_binding(shader, "extruded_frustum"));
257 GPU_storagebuf_bind(bounds, GPU_shader_get_ssbo_binding(shader, "bounds_buf"));
258 if (current_pass_type_ == ShadowPass::FORCED_FAIL) {
259 GPU_storagebuf_bind(visibility_buf_, GPU_shader_get_ssbo_binding(shader, "visibility_buf"));
260 }
261 else {
262 GPU_storagebuf_bind(pass_visibility_buf_,
263 GPU_shader_get_ssbo_binding(shader, "pass_visibility_buf"));
264 GPU_storagebuf_bind(fail_visibility_buf_,
265 GPU_shader_get_ssbo_binding(shader, "fail_visibility_buf"));
266 }
270 }
271
273}
274
276{
277 switch (current_pass_type_) {
278 case ShadowPass::PASS:
279 return pass_visibility_buf_;
280 case ShadowPass::FAIL:
281 return fail_visibility_buf_;
282 case ShadowPass::FORCED_FAIL:
283 return visibility_buf_;
284 default:
286 }
287 return visibility_buf_;
288}
289
290PassMain::Sub *&ShadowPass::get_pass_ptr(PassType type, bool manifold, bool cap /*=false*/)
291{
292 return passes_[type][manifold][cap];
293}
294
295void ShadowPass::init(const SceneState &scene_state, SceneResources &resources)
296{
297 enabled_ = scene_state.draw_shadows;
298 if (!enabled_) {
299 resources.world_buf.shadow_mul = 0.0f;
300 resources.world_buf.shadow_add = 1.0f;
301 return;
302 }
303 const Scene &scene = *scene_state.scene;
304
305 float3 direction_ws = scene.display.light_direction;
306 /* Turn the light in a way where it's more user friendly to control. */
307 std::swap(direction_ws.y, direction_ws.z);
308 direction_ws *= float3(-1, 1, -1);
309
310 std::array<float4, 6> planes = View::default_get().frustum_planes_get();
311
312 pass_data_.light_direction_ws = direction_ws;
313 pass_data_.far_plane = planes[2] * float4(-1, -1, -1, 1);
314 pass_data_.push_update();
315
316 /* Shadow direction. */
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 ResourceHandleRange 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 gpu::Texture &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.
std::optional< blender::Bounds< blender::float3 > > BKE_object_boundbox_get(const Object *ob)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
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)
struct Object Object
static AppView * view
static void View(GHOST_IWindow *window, bool stereo, int eye=0)
void GPU_compute_dispatch(blender::gpu::Shader *shader, uint groups_x_len, uint groups_y_len, uint groups_z_len, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
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
int GPU_shader_get_ubo_binding(blender::gpu::Shader *shader, const char *name)
void GPU_shader_uniform_1b(blender::gpu::Shader *sh, const char *name, bool value)
void GPU_shader_uniform_3fv(blender::gpu::Shader *sh, const char *name, const float data[3])
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
int GPU_shader_get_ssbo_binding(blender::gpu::Shader *shader, const char *name)
void GPU_shader_uniform_1i(blender::gpu::Shader *sh, const char *name, int value)
@ GPU_BARRIER_SHADER_STORAGE
Definition GPU_state.hh:48
void GPU_memory_barrier(GPUBarrier barrier)
Definition gpu_state.cc:326
void GPU_storagebuf_clear(blender::gpu::StorageBuf *ssbo, uint32_t clear_value)
void GPU_storagebuf_bind(blender::gpu::StorageBuf *ssbo, int slot)
void GPU_uniformbuf_bind(blender::gpu::UniformBuf *ubo, int slot)
BMesh const char void * data
int64_t size() const
void append(const T &value)
IndexRange index_range() const
void submit(PassSimple &pass, View &view)
const float4x4 & viewmat(int view_id=0) const
Definition draw_view.hh:136
static View & default_get()
Definition draw_view.cc:317
virtual VisibilityBuf & get_visibility_buffer()
Definition draw_view.cc:312
std::array< float3, 8 > frustum_corners_get(int view_id=0)
Definition draw_view.cc:337
std::array< float4, 6 > frustum_planes_get(int view_id=0)
Definition draw_view.cc:327
void shader_set(gpu::Shader *shader)
PassBase< DrawCommandBufType > & sub(const char *name)
Definition draw_pass.hh:690
void draw_expand(gpu::Batch *batch, GPUPrimType primitive_type, uint primitive_len, uint instance_len, uint vertex_len, uint vertex_first, ResourceIndexRange res_index={}, uint custom_id=0)
Definition draw_pass.hh:925
void bind_ubo(const char *name, gpu::UniformBuf *buffer)
detail::PassBase< command::DrawMultiBuf > Sub
Definition draw_pass.hh:499
blender::gpu::Shader * get()
void init(const SceneState &scene_state, SceneResources &resources)
void object_sync(SceneState &scene_state, ObjectRef &ob_ref, ResourceHandleRange handle, const bool has_transp_mat)
void draw(Manager &manager, View &view, SceneResources &resources, gpu::Texture &depth_stencil_tx, bool force_fail_method)
#define DRW_VISIBILITY_GROUP_SIZE
#define DRW_VIEW_UBO_SLOT
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
#define printf(...)
static ulong state[N]
std::array< VecBase< T, 3 >, 8 > corners(const Bounds< VecBase< T, 3 > > &bounds)
detail::Pass< command::DrawMultiBuf > PassMain
gpu::Batch * DRW_cache_object_surface_get(Object *ob)
StorageArrayBuffer< ObjectBounds, 128 > ObjectBoundsBuf
Definition draw_view.hh:33
gpu::Batch * DRW_cache_object_edge_detection_get(Object *ob, bool *r_is_manifold)
StorageArrayBuffer< ObjectInfos, 128 > ObjectInfosBuf
Definition draw_view.hh:34
StorageArrayBuffer< uint, 4, true > VisibilityBuf
Definition draw_view.hh:35
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)
MatBase< float, 4, 4 > float4x4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< int32_t, 3 > int3
VecBase< float, 3 > float3
char name[258]
Definition DNA_ID.h:432
float light_direction[3]
struct SceneDisplay display
UniformBuffer< WorldData > world_buf
float w
Definition sky_math.h:225
i
Definition text_draw.cc:230
#define DEBUG_SHADOW_VOLUME