This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/draw/engines/workbench/workbench_shadow.cc

483 lines
16 KiB
C++

/* 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"){};
ShadowPass::ShadowView::~ShadowView()
{
DRW_SHADER_FREE_SAFE(dynamic_pass_type_shader_);
DRW_SHADER_FREE_SAFE(static_pass_type_shader_);
}
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 = {};
/* "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);
UNUSED_VARS_NDEBUG(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 = math::transform_point(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*/)
{
/* TODO (Miguel Pozo): Add debug_freeze support */
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(@pragma37): Use regular culling for the caps pass. */
if (dynamic_pass_type_shader_ == nullptr) {
dynamic_pass_type_shader_ = GPU_shader_create_from_info_name(
"workbench_next_shadow_visibility_compute_dynamic_pass_type");
}
if (static_pass_type_shader_ == nullptr) {
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_ubo_binding(shader, "extruded_frustum"));
GPU_storagebuf_bind(bounds, GPU_shader_get_ssbo_binding(shader, "bounds_buf"));
if (current_pass_type_ == ShadowPass::FORCED_FAIL) {
GPU_storagebuf_bind(visibility_buf_, GPU_shader_get_ssbo_binding(shader, "visibility_buf"));
}
else {
GPU_storagebuf_bind(pass_visibility_buf_,
GPU_shader_get_ssbo_binding(shader, "pass_visibility_buf"));
GPU_storagebuf_bind(fail_visibility_buf_,
GPU_shader_get_ssbo_binding(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_;
}
ShadowPass::~ShadowPass()
{
for (int depth_pass : IndexRange(2)) {
for (int manifold : IndexRange(2)) {
for (int cap : IndexRange(2)) {
DRW_SHADER_FREE_SAFE(shaders_[depth_pass][manifold][cap]);
}
}
}
}
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);
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(nullptr, view_matrix.ptr(), false);
resources.world_buf.shadow_direction_vs = float4(
math::transform_direction(view_matrix, 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(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 #76168). */
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,
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