Changeset View
Changeset View
Standalone View
Standalone View
source/blender/draw/engines/workbench/workbench_shadow.cc
- This file was added.
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | |||||
| /** \file | |||||
| * \ingroup draw_engine | |||||
| * | |||||
| * Shadow: | |||||
| * | |||||
| * Use stencil shadow buffer to cast a sharp shadow over opaque surfaces. | |||||
| * | |||||
| * After the main pre-pass we render shadow volumes using custom depth & stencil states to | |||||
| * set the stencil of shadowed area to anything but 0. | |||||
| * | |||||
| * Then the shading pass will shade the areas with stencil not equal 0 differently. | |||||
| */ | |||||
| #include "BKE_object.h" | |||||
| #include "BLI_math.h" | |||||
| #include "DRW_render.h" | |||||
| #include "GPU_compute.h" | |||||
| #include "workbench_private.hh" | |||||
| #define DEBUG_SHADOW_VOLUME 0 | |||||
| namespace blender::workbench { | |||||
| ShadowPass::ShadowView::ShadowView() : View("ShadowPass.View"){}; | |||||
| void ShadowPass::ShadowView::setup(View &view, float3 light_direction, bool force_fail_method) | |||||
| { | |||||
| force_fail_method_ = force_fail_method; | |||||
| light_direction_ = light_direction; | |||||
| sync(view.viewmat(), view.winmat()); | |||||
| /* Prepare frustum extruded in the negative light direction, | |||||
| * so we can test regular bounding boxes against it for culling. */ | |||||
| /* Frustum Corners indices | |||||
| * Z Y | |||||
| * | / | |||||
| * |/ | |||||
| * .-----X | |||||
| * 3----------7 | |||||
| * /| /| | |||||
| * / | / | | |||||
| * 0----------4 | | |||||
| * | | | | | |||||
| * | 2-------|--6 | |||||
| * | / | / | |||||
| * |/ |/ | |||||
| * 1----------5 | |||||
| */ | |||||
| /* Frustum Planes indices */ | |||||
| const int x_neg = 0; /* Left */ | |||||
| const int x_pos = 5; /* Right */ | |||||
| const int y_neg = 1; /* Bottom */ | |||||
| const int y_pos = 3; /* Top */ | |||||
| const int z_pos = 4; /* Near */ | |||||
| const int z_neg = 2; /* Far */ | |||||
| int3 corner_faces[8] = {{x_neg, y_neg, z_pos}, | |||||
| {x_neg, y_neg, z_neg}, | |||||
| {x_neg, y_pos, z_neg}, | |||||
| {x_neg, y_pos, z_pos}, | |||||
| {x_pos, y_neg, z_pos}, | |||||
| {x_pos, y_neg, z_neg}, | |||||
| {x_pos, y_pos, z_neg}, | |||||
| {x_pos, y_pos, z_pos}}; | |||||
| int2 edge_faces[12] = {{x_neg, y_neg}, | |||||
| {x_neg, z_neg}, | |||||
| {x_neg, y_pos}, | |||||
| {x_neg, z_pos}, | |||||
| {y_neg, x_pos}, | |||||
| {z_neg, x_pos}, | |||||
| {y_pos, x_pos}, | |||||
| {z_pos, x_pos}, | |||||
| {y_neg, z_pos}, | |||||
| {z_neg, y_neg}, | |||||
| {y_pos, z_neg}, | |||||
| {z_pos, y_pos}}; | |||||
| int2 edge_corners[12] = {{0, 1}, | |||||
| {1, 2}, | |||||
| {2, 3}, | |||||
| {3, 0}, | |||||
| {4, 5}, | |||||
| {5, 6}, | |||||
| {6, 7}, | |||||
| {7, 4}, | |||||
| {0, 4}, | |||||
| {1, 5}, | |||||
| {2, 6}, | |||||
| {3, 7}}; | |||||
| BoundBox frustum_corners; | |||||
| DRW_culling_frustum_corners_get(nullptr, &frustum_corners); | |||||
| float4 frustum_planes[6]; | |||||
| DRW_culling_frustum_planes_get(nullptr, (float(*)[4])frustum_planes); | |||||
| Vector<float4> faces_result = {}; | |||||
| Vector<float3> corners_result = {}; | |||||
fclem: You are already in `blender` namespace. | |||||
| /* "Unlit" frustum faces are left "as-is" */ | |||||
| bool face_lit[6]; | |||||
| for (int i : IndexRange(6)) { | |||||
| /* Make the frustum normals face outwards */ | |||||
| frustum_planes[i] *= float4(-1, -1, -1, 1); | |||||
| face_lit[i] = math::dot(float3(frustum_planes[i]), light_direction_) < 0; | |||||
| if (!face_lit[i]) { | |||||
| faces_result.append(frustum_planes[i]); | |||||
| } | |||||
| } | |||||
| /* Edges between lit and unlit faces are extruded "infinitely" towards the light source */ | |||||
| for (int i : IndexRange(12)) { | |||||
| int2 f = edge_faces[i]; | |||||
| bool a_lit = face_lit[f[0]]; | |||||
| bool b_lit = face_lit[f[1]]; | |||||
| if (a_lit != b_lit) { | |||||
| /* Extrude Face */ | |||||
| float3 corner_a = frustum_corners.vec[edge_corners[i][0]]; | |||||
| float3 corner_b = frustum_corners.vec[edge_corners[i][1]]; | |||||
| float3 edge_direction = math::normalize(corner_b - corner_a); | |||||
| float3 normal = math::normalize(math::cross(light_direction_, edge_direction)); | |||||
| float4 extruded_face = float4(UNPACK3(normal), math::dot(normal, corner_a)); | |||||
| /* Ensure the plane faces outwards */ | |||||
| bool flipped = false; | |||||
| for (float3 corner : frustum_corners.vec) { | |||||
| if (math::dot(float3(extruded_face), corner) > (extruded_face.w + 0.1)) { | |||||
| BLI_assert(!flipped); | |||||
| flipped = true; | |||||
| extruded_face *= -1; | |||||
| } | |||||
| } | |||||
| faces_result.append(extruded_face); | |||||
| } | |||||
| } | |||||
| for (int i_corner : IndexRange(8)) { | |||||
| int lit_faces = 0; | |||||
| for (int i_face : IndexRange(3)) { | |||||
| lit_faces += face_lit[corner_faces[i_corner][i_face]] ? 1 : 0; | |||||
| } | |||||
| if (lit_faces < 3) { | |||||
| /* Add original corner */ | |||||
| corners_result.append(frustum_corners.vec[i_corner]); | |||||
| if (lit_faces > 0) { | |||||
| /* Add extruded corner */ | |||||
| corners_result.append(float3(frustum_corners.vec[i_corner]) - (light_direction_ * 1e4f)); | |||||
| } | |||||
| } | |||||
| } | |||||
| for (int i : corners_result.index_range()) { | |||||
| extruded_frustum_.corners[i] = float4(corners_result[i], 1); | |||||
| } | |||||
| extruded_frustum_.corners_count = corners_result.size(); | |||||
| for (int i : faces_result.index_range()) { | |||||
| extruded_frustum_.planes[i] = faces_result[i]; | |||||
| } | |||||
| extruded_frustum_.planes_count = faces_result.size(); | |||||
| extruded_frustum_.push_update(); | |||||
| } | |||||
| bool ShadowPass::ShadowView::debug_object_culling(Object *ob) | |||||
| { | |||||
| printf("Test %s\n", ob->id.name); | |||||
| const BoundBox *_bbox = BKE_object_boundbox_get(ob); | |||||
| for (int p : IndexRange(extruded_frustum_.planes_count)) { | |||||
| float4 plane = extruded_frustum_.planes[p]; | |||||
| bool separating_axis = true; | |||||
| for (float3 corner : _bbox->vec) { | |||||
| corner = float4x4(ob->object_to_world) * corner; | |||||
| float signed_distance = math::dot(corner, float3(plane)) - plane.w; | |||||
| if (signed_distance <= 0) { | |||||
| separating_axis = false; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (separating_axis) { | |||||
| printf("Sepatating Axis >>> x: %f, y: %f, z: %f, w: %f \n", UNPACK4(plane)); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| void ShadowPass::ShadowView::set_mode(ShadowPass::PassType type) | |||||
| { | |||||
| current_pass_type_ = type; | |||||
| } | |||||
| void ShadowPass::ShadowView::compute_visibility(ObjectBoundsBuf &bounds, | |||||
| uint resource_len, | |||||
| bool debug_freeze) | |||||
| { | |||||
| GPU_debug_group_begin("ShadowView.compute_visibility"); | |||||
| uint word_per_draw = this->visibility_word_per_draw(); | |||||
| /* Switch between tightly packed and set of whole word per instance. */ | |||||
| uint words_len = (view_len_ == 1) ? divide_ceil_u(resource_len, 32) : | |||||
| resource_len * word_per_draw; | |||||
| words_len = ceil_to_multiple_u(max_ii(1, words_len), 4); | |||||
| uint32_t data = 0xFFFFFFFFu; | |||||
| if (current_pass_type_ == ShadowPass::PASS) { | |||||
| /* TODO(fclem): Resize to nearest pow2 to reduce fragmentation. */ | |||||
| pass_visibility_buf_.resize(words_len); | |||||
| GPU_storagebuf_clear(pass_visibility_buf_, GPU_R32UI, GPU_DATA_UINT, &data); | |||||
| fail_visibility_buf_.resize(words_len); | |||||
| GPU_storagebuf_clear(fail_visibility_buf_, GPU_R32UI, GPU_DATA_UINT, &data); | |||||
| } | |||||
| else if (current_pass_type_ == ShadowPass::FAIL) { | |||||
| /* Already computed in the ShadowPass::PASS */ | |||||
| GPU_debug_group_end(); | |||||
| return; | |||||
| } | |||||
| else { | |||||
| visibility_buf_.resize(words_len); | |||||
| GPU_storagebuf_clear(visibility_buf_, GPU_R32UI, GPU_DATA_UINT, &data); | |||||
| } | |||||
| if (do_visibility_) { | |||||
| /* TODO(Miguel Pozo): Use regular culling for the caps pass */ | |||||
| static GPUShader *dynamic_pass_type_shader = GPU_shader_create_from_info_name( | |||||
| "workbench_next_shadow_visibility_compute_dynamic_pass_type"); | |||||
| static GPUShader *static_pass_type_shader = GPU_shader_create_from_info_name( | |||||
| "workbench_next_shadow_visibility_compute_static_pass_type"); | |||||
| GPUShader *shader = current_pass_type_ == ShadowPass::FORCED_FAIL ? static_pass_type_shader : | |||||
| dynamic_pass_type_shader; | |||||
| GPU_shader_bind(shader); | |||||
| GPU_shader_uniform_1i(shader, "resource_len", resource_len); | |||||
| GPU_shader_uniform_1i(shader, "view_len", view_len_); | |||||
| GPU_shader_uniform_1i(shader, "visibility_word_per_draw", word_per_draw); | |||||
| GPU_shader_uniform_1b(shader, "force_fail_method", force_fail_method_); | |||||
| GPU_shader_uniform_3fv(shader, "shadow_direction", light_direction_); | |||||
| GPU_uniformbuf_bind(extruded_frustum_, | |||||
| GPU_shader_get_uniform_block(shader, "extruded_frustum")); | |||||
| GPU_storagebuf_bind(bounds, GPU_shader_get_ssbo(shader, "bounds_buf")); | |||||
| if (current_pass_type_ == ShadowPass::FORCED_FAIL) { | |||||
| GPU_storagebuf_bind(visibility_buf_, GPU_shader_get_ssbo(shader, "visibility_buf")); | |||||
| } | |||||
| else { | |||||
| GPU_storagebuf_bind(pass_visibility_buf_, | |||||
| GPU_shader_get_ssbo(shader, "pass_visibility_buf")); | |||||
| GPU_storagebuf_bind(fail_visibility_buf_, | |||||
| GPU_shader_get_ssbo(shader, "fail_visibility_buf")); | |||||
| } | |||||
| GPU_uniformbuf_bind(data_, DRW_VIEW_UBO_SLOT); | |||||
| GPU_compute_dispatch(shader, divide_ceil_u(resource_len, DRW_VISIBILITY_GROUP_SIZE), 1, 1); | |||||
| GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE); | |||||
| } | |||||
| GPU_debug_group_end(); | |||||
| } | |||||
| VisibilityBuf &ShadowPass::ShadowView::get_visibility_buffer() | |||||
| { | |||||
| switch (current_pass_type_) { | |||||
| case ShadowPass::PASS: | |||||
| return pass_visibility_buf_; | |||||
| case ShadowPass::FAIL: | |||||
| return fail_visibility_buf_; | |||||
| case ShadowPass::FORCED_FAIL: | |||||
| return visibility_buf_; | |||||
| default: | |||||
| BLI_assert_unreachable(); | |||||
| } | |||||
| return visibility_buf_; | |||||
| } | |||||
| PassMain::Sub *&ShadowPass::get_pass_ptr(PassType type, bool manifold, bool cap /*= false*/) | |||||
| { | |||||
| return passes_[type][manifold][cap]; | |||||
| } | |||||
| GPUShader *ShadowPass::get_shader(bool depth_pass, bool manifold, bool cap /*= false*/) | |||||
| { | |||||
| GPUShader *&shader = shaders_[depth_pass][manifold][cap]; | |||||
| if (shader == nullptr) { | |||||
| std::string create_info_name = "workbench_next_shadow"; | |||||
| create_info_name += (depth_pass) ? "_pass" : "_fail"; | |||||
| create_info_name += (manifold) ? "_manifold" : "_no_manifold"; | |||||
| create_info_name += (cap) ? "_caps" : "_no_caps"; | |||||
| #if DEBUG_SHADOW_VOLUME | |||||
| create_info_name += "_debug"; | |||||
| #endif | |||||
| shader = GPU_shader_create_from_info_name(create_info_name.c_str()); | |||||
| } | |||||
| return shader; | |||||
| } | |||||
| void ShadowPass::init(const SceneState &scene_state, SceneResources &resources) | |||||
| { | |||||
| enabled_ = scene_state.draw_shadows; | |||||
| if (!enabled_) { | |||||
| resources.world_buf.shadow_mul = 0.0f; | |||||
| resources.world_buf.shadow_add = 1.0f; | |||||
| return; | |||||
| } | |||||
| const Scene &scene = *scene_state.scene; | |||||
| float3 direction_ws = scene.display.light_direction; | |||||
| /* Turn the light in a way where it's more user friendly to control. */ | |||||
| SWAP(float, direction_ws.y, direction_ws.z); | |||||
| direction_ws *= float3(-1, 1, -1); | |||||
Done Inline Actionsuse std::swap instead. fclem: use `std::swap` instead. | |||||
| float planes[6][4]; | |||||
| DRW_culling_frustum_planes_get(nullptr, planes); | |||||
| pass_data_.light_direction_ws = direction_ws; | |||||
| pass_data_.far_plane = planes[2] * float4(-1, -1, -1, 1); | |||||
| pass_data_.push_update(); | |||||
| /* Shadow direction. */ | |||||
| float4x4 view_matrix; | |||||
| DRW_view_viewmat_get(NULL, view_matrix.ptr(), false); | |||||
| resources.world_buf.shadow_direction_vs = float4(view_matrix.ref_3x3() * direction_ws); | |||||
| /* Clamp to avoid overshadowing and shading errors. */ | |||||
| float focus = clamp_f(scene.display.shadow_focus, 0.0001f, 0.99999f); | |||||
| resources.world_buf.shadow_shift = scene.display.shadow_shift; | |||||
| resources.world_buf.shadow_focus = 1.0f - focus * (1.0f - resources.world_buf.shadow_shift); | |||||
| resources.world_buf.shadow_mul = scene_state.shading.shadow_intensity; | |||||
| resources.world_buf.shadow_add = 1.0f - resources.world_buf.shadow_mul; | |||||
| } | |||||
| void ShadowPass::sync() | |||||
| { | |||||
| if (!enabled_) { | |||||
| return; | |||||
| } | |||||
| #if DEBUG_SHADOW_VOLUME | |||||
| DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL; | |||||
| DRWState depth_pass_state = state | DRW_STATE_DEPTH_LESS; | |||||
| DRWState depth_fail_state = state | DRW_STATE_DEPTH_GREATER_EQUAL; | |||||
| #else | |||||
| DRWState state = DRW_STATE_DEPTH_LESS | DRW_STATE_STENCIL_ALWAYS; | |||||
| DRWState depth_pass_state = state | DRW_STATE_WRITE_STENCIL_SHADOW_PASS; | |||||
| DRWState depth_fail_state = state | DRW_STATE_WRITE_STENCIL_SHADOW_FAIL; | |||||
| #endif | |||||
| pass_ps_.init(); | |||||
| pass_ps_.state_set(depth_pass_state); | |||||
| pass_ps_.state_stencil(0xFF, 0xFF, 0xFF); | |||||
| fail_ps_.init(); | |||||
| fail_ps_.state_set(depth_fail_state); | |||||
| fail_ps_.state_stencil(0xFF, 0xFF, 0xFF); | |||||
| forced_fail_ps_.init(); | |||||
| forced_fail_ps_.state_set(depth_fail_state); | |||||
| forced_fail_ps_.state_stencil(0xFF, 0xFF, 0xFF); | |||||
| /* Stencil Shadow passes. */ | |||||
| for (bool manifold : {false, true}) { | |||||
| PassMain::Sub *&ps = get_pass_ptr(PASS, manifold); | |||||
| ps = &pass_ps_.sub(manifold ? "manifold" : "non_manifold"); | |||||
| ps->shader_set(get_shader(true, manifold)); | |||||
| ps->bind_ubo("pass_data", pass_data_); | |||||
| for (PassType fail_type : {FAIL, FORCED_FAIL}) { | |||||
| PassMain &ps_main = fail_type == FAIL ? fail_ps_ : forced_fail_ps_; | |||||
| PassMain::Sub *&ps = get_pass_ptr(fail_type, manifold, false); | |||||
| ps = &ps_main.sub(manifold ? "NoCaps.manifold" : "NoCaps.non_manifold"); | |||||
| ps->shader_set(get_shader(false, manifold, false)); | |||||
| ps->bind_ubo("pass_data", pass_data_); | |||||
| PassMain::Sub *&caps_ps = get_pass_ptr(fail_type, manifold, true); | |||||
| caps_ps = &ps_main.sub(manifold ? "Caps.manifold" : "Caps.non_manifold"); | |||||
| caps_ps->shader_set(get_shader(false, manifold, true)); | |||||
| caps_ps->bind_ubo("pass_data", pass_data_); | |||||
| } | |||||
| } | |||||
| } | |||||
| void ShadowPass::object_sync(Manager &manager, | |||||
| SceneState &scene_state, | |||||
| ObjectRef &ob_ref, | |||||
| ResourceHandle handle, | |||||
| const bool has_transp_mat) | |||||
| { | |||||
| if (!enabled_) { | |||||
| return; | |||||
| } | |||||
| Object *ob = ob_ref.object; | |||||
| bool is_manifold; | |||||
| GPUBatch *geom_shadow = DRW_cache_object_edge_detection_get(ob, &is_manifold); | |||||
| if (geom_shadow == nullptr) { | |||||
| return; | |||||
| } | |||||
| #define DEBUG_CULLING 0 | |||||
| #if DEBUG_CULLING | |||||
| View view = View("View", DRW_view_default_get()); | |||||
| ShadowView shadow_view = ShadowView("ShadowView", view, pass_data_.light_direction_ws); | |||||
| printf( | |||||
| "%s culling : %s\n", ob->id.name, shadow_view.debug_object_culling(ob) ? "true" : "false"); | |||||
| #endif | |||||
| /* Shadow pass technique needs object to be have all its surface opaque. */ | |||||
| /* We cannot use the PASS technique on non-manifold object (see T76168). */ | |||||
| bool force_fail_pass = has_transp_mat || (!is_manifold && (scene_state.cull_state != 0)); | |||||
| PassType fail_type = force_fail_pass ? FORCED_FAIL : FAIL; | |||||
| /* Unless we force the FAIL Method we add draw commands to both methods, | |||||
| * then the visibility compute shader selects the one needed */ | |||||
| if (!force_fail_pass) { | |||||
| PassMain::Sub &ps = *get_pass_ptr(PASS, is_manifold); | |||||
| ps.draw(geom_shadow, handle); | |||||
| } | |||||
| get_pass_ptr(fail_type, is_manifold, true)->draw(DRW_cache_object_surface_get(ob), handle); | |||||
| get_pass_ptr(fail_type, is_manifold, false)->draw(geom_shadow, handle); | |||||
| } | |||||
| void ShadowPass::draw(Manager &manager, | |||||
| View &view, | |||||
| SceneResources &resources, | |||||
| int2 resolution, | |||||
| GPUTexture &depth_stencil_tx, | |||||
| bool force_fail_method) | |||||
| { | |||||
| if (!enabled_) { | |||||
| return; | |||||
| } | |||||
| fb_.ensure(GPU_ATTACHMENT_TEXTURE(&depth_stencil_tx), | |||||
| GPU_ATTACHMENT_TEXTURE(resources.color_tx)); | |||||
| fb_.bind(); | |||||
| view_.setup(view, pass_data_.light_direction_ws, force_fail_method); | |||||
| view_.set_mode(PASS); | |||||
| manager.submit(pass_ps_, view_); | |||||
| view_.set_mode(FAIL); | |||||
| manager.submit(fail_ps_, view_); | |||||
| view_.set_mode(FORCED_FAIL); | |||||
| manager.submit(forced_fail_ps_, view_); | |||||
| } | |||||
| } // namespace blender::workbench | |||||
You are already in blender namespace.