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.c

368 lines
13 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright 2020, Blender Foundation.
*/
/** \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 "DRW_render.h"
#include "BKE_object.h"
#include "BLI_math.h"
#include "workbench_engine.h"
#include "workbench_private.h"
static void compute_parallel_lines_nor_and_dist(const float v1[2],
const float v2[2],
const float v3[2],
float r_line[4])
{
sub_v2_v2v2(r_line, v2, v1);
/* Find orthogonal vector. */
SWAP(float, r_line[0], r_line[1]);
r_line[0] = -r_line[0];
/* Edge distances. */
r_line[2] = dot_v2v2(r_line, v1);
r_line[3] = dot_v2v2(r_line, v3);
/* Make sure r_line[2] is the minimum. */
if (r_line[2] > r_line[3]) {
SWAP(float, r_line[2], r_line[3]);
}
}
static void workbench_shadow_update(WORKBENCH_PrivateData *wpd)
{
wpd->shadow_changed = !compare_v3v3(
wpd->shadow_cached_direction, wpd->shadow_direction_ws, 1e-5f);
if (wpd->shadow_changed) {
float up[3] = {0.0f, 0.0f, 1.0f};
unit_m4(wpd->shadow_mat);
/* TODO fix singularity. */
copy_v3_v3(wpd->shadow_mat[2], wpd->shadow_direction_ws);
cross_v3_v3v3(wpd->shadow_mat[0], wpd->shadow_mat[2], up);
normalize_v3(wpd->shadow_mat[0]);
cross_v3_v3v3(wpd->shadow_mat[1], wpd->shadow_mat[2], wpd->shadow_mat[0]);
invert_m4_m4(wpd->shadow_inv, wpd->shadow_mat);
copy_v3_v3(wpd->shadow_cached_direction, wpd->shadow_direction_ws);
}
float planes[6][4];
DRW_culling_frustum_planes_get(NULL, planes);
/* we only need the far plane. */
copy_v4_v4(wpd->shadow_far_plane, planes[2]);
BoundBox frustum_corners;
DRW_culling_frustum_corners_get(NULL, &frustum_corners);
float shadow_near_corners[4][3];
mul_v3_mat3_m4v3(shadow_near_corners[0], wpd->shadow_inv, frustum_corners.vec[0]);
mul_v3_mat3_m4v3(shadow_near_corners[1], wpd->shadow_inv, frustum_corners.vec[3]);
mul_v3_mat3_m4v3(shadow_near_corners[2], wpd->shadow_inv, frustum_corners.vec[7]);
mul_v3_mat3_m4v3(shadow_near_corners[3], wpd->shadow_inv, frustum_corners.vec[4]);
INIT_MINMAX(wpd->shadow_near_min, wpd->shadow_near_max);
for (int i = 0; i < 4; i++) {
minmax_v3v3_v3(wpd->shadow_near_min, wpd->shadow_near_max, shadow_near_corners[i]);
}
compute_parallel_lines_nor_and_dist(shadow_near_corners[0],
shadow_near_corners[1],
shadow_near_corners[2],
wpd->shadow_near_sides[0]);
compute_parallel_lines_nor_and_dist(shadow_near_corners[1],
shadow_near_corners[2],
shadow_near_corners[0],
wpd->shadow_near_sides[1]);
}
void workbench_shadow_data_update(WORKBENCH_PrivateData *wpd, WORKBENCH_UBO_World *wd)
{
const DRWContextState *draw_ctx = DRW_context_state_get();
const Scene *scene = draw_ctx->scene;
float view_matrix[4][4];
DRW_view_viewmat_get(NULL, view_matrix, false);
/* Turn the light in a way where it's more user friendly to control. */
copy_v3_v3(wpd->shadow_direction_ws, scene->display.light_direction);
SWAP(float, wpd->shadow_direction_ws[2], wpd->shadow_direction_ws[1]);
wpd->shadow_direction_ws[2] = -wpd->shadow_direction_ws[2];
wpd->shadow_direction_ws[0] = -wpd->shadow_direction_ws[0];
/* Shadow direction. */
mul_v3_mat3_m4v3(wd->shadow_direction_vs, view_matrix, wpd->shadow_direction_ws);
/* Clamp to avoid overshadowing and shading errors. */
float focus = clamp_f(scene->display.shadow_focus, 0.0001f, 0.99999f);
wd->shadow_shift = scene->display.shadow_shift;
wd->shadow_focus = 1.0f - focus * (1.0f - wd->shadow_shift);
if (SHADOW_ENABLED(wpd)) {
wd->shadow_mul = wpd->shading.shadow_intensity;
wd->shadow_add = 1.0f - wd->shadow_mul;
}
else {
wd->shadow_mul = 0.0f;
wd->shadow_add = 1.0f;
}
}
void workbench_shadow_cache_init(WORKBENCH_Data *data)
{
WORKBENCH_PassList *psl = data->psl;
WORKBENCH_PrivateData *wpd = data->stl->wpd;
struct GPUShader *sh;
DRWShadingGroup *grp;
if (SHADOW_ENABLED(wpd)) {
workbench_shadow_update(wpd);
#if DEBUG_SHADOW_VOLUME
DRWState depth_pass_state = DRW_STATE_DEPTH_LESS;
DRWState depth_fail_state = DRW_STATE_DEPTH_GREATER_EQUAL;
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL;
#else
DRWState depth_pass_state = DRW_STATE_WRITE_STENCIL_SHADOW_PASS;
DRWState depth_fail_state = DRW_STATE_WRITE_STENCIL_SHADOW_FAIL;
DRWState state = DRW_STATE_DEPTH_LESS | DRW_STATE_STENCIL_ALWAYS;
#endif
/* TODO(fclem) Merge into one pass with subpasses. */
DRW_PASS_CREATE(psl->shadow_ps[0], state | depth_pass_state);
DRW_PASS_CREATE(psl->shadow_ps[1], state | depth_fail_state);
/* Stencil Shadow passes. */
for (int manifold = 0; manifold < 2; manifold++) {
sh = workbench_shader_shadow_pass_get(manifold);
wpd->shadow_pass_grp[manifold] = grp = DRW_shgroup_create(sh, psl->shadow_ps[0]);
DRW_shgroup_stencil_mask(grp, 0xFF); /* Needed once to set the stencil state for the pass. */
sh = workbench_shader_shadow_fail_get(manifold, false);
wpd->shadow_fail_grp[manifold] = grp = DRW_shgroup_create(sh, psl->shadow_ps[1]);
DRW_shgroup_stencil_mask(grp, 0xFF); /* Needed once to set the stencil state for the pass. */
sh = workbench_shader_shadow_fail_get(manifold, true);
wpd->shadow_fail_caps_grp[manifold] = grp = DRW_shgroup_create(sh, psl->shadow_ps[1]);
}
}
else {
psl->shadow_ps[0] = NULL;
psl->shadow_ps[1] = NULL;
}
}
static BoundBox *workbench_shadow_object_shadow_bbox_get(WORKBENCH_PrivateData *wpd,
Object *ob,
WORKBENCH_ObjectData *oed)
{
if (oed->shadow_bbox_dirty || wpd->shadow_changed) {
float tmp_mat[4][4];
mul_m4_m4m4(tmp_mat, wpd->shadow_inv, ob->obmat);
/* Get AABB in shadow space. */
INIT_MINMAX(oed->shadow_min, oed->shadow_max);
/* From object space to shadow space */
BoundBox *bbox = BKE_object_boundbox_get(ob);
for (int i = 0; i < 8; i++) {
float corner[3];
mul_v3_m4v3(corner, tmp_mat, bbox->vec[i]);
minmax_v3v3_v3(oed->shadow_min, oed->shadow_max, corner);
}
oed->shadow_depth = oed->shadow_max[2] - oed->shadow_min[2];
/* Extend towards infinity. */
oed->shadow_max[2] += 1e4f;
/* Get extended AABB in world space. */
BKE_boundbox_init_from_minmax(&oed->shadow_bbox, oed->shadow_min, oed->shadow_max);
for (int i = 0; i < 8; i++) {
mul_m4_v3(wpd->shadow_mat, oed->shadow_bbox.vec[i]);
}
oed->shadow_bbox_dirty = false;
}
return &oed->shadow_bbox;
}
static bool workbench_shadow_object_cast_visible_shadow(WORKBENCH_PrivateData *wpd,
Object *ob,
WORKBENCH_ObjectData *oed)
{
BoundBox *shadow_bbox = workbench_shadow_object_shadow_bbox_get(wpd, ob, oed);
const DRWView *default_view = DRW_view_default_get();
return DRW_culling_box_test(default_view, shadow_bbox);
}
static float workbench_shadow_object_shadow_distance(WORKBENCH_PrivateData *wpd,
Object *ob,
WORKBENCH_ObjectData *oed)
{
BoundBox *shadow_bbox = workbench_shadow_object_shadow_bbox_get(wpd, ob, oed);
int corners[4] = {0, 3, 4, 7};
float dist = 1e4f, dist_isect;
for (int i = 0; i < 4; i++) {
if (isect_ray_plane_v3(shadow_bbox->vec[corners[i]],
wpd->shadow_cached_direction,
wpd->shadow_far_plane,
&dist_isect,
true)) {
if (dist_isect < dist) {
dist = dist_isect;
}
}
else {
/* All rays are parallels. If one fails, the other will too. */
break;
}
}
return max_ii(dist - oed->shadow_depth, 0);
}
static bool workbench_shadow_camera_in_object_shadow(WORKBENCH_PrivateData *wpd,
Object *ob,
WORKBENCH_ObjectData *oed)
{
/* Just to be sure the min, max are updated. */
workbench_shadow_object_shadow_bbox_get(wpd, ob, oed);
/* Test if near plane is in front of the shadow. */
if (oed->shadow_min[2] > wpd->shadow_near_max[2]) {
return false;
}
/* Separation Axis Theorem test */
/* Test bbox sides first (faster) */
if ((oed->shadow_min[0] > wpd->shadow_near_max[0]) ||
(oed->shadow_max[0] < wpd->shadow_near_min[0]) ||
(oed->shadow_min[1] > wpd->shadow_near_max[1]) ||
(oed->shadow_max[1] < wpd->shadow_near_min[1])) {
return false;
}
/* Test projected near rectangle sides */
const float pts[4][2] = {
{oed->shadow_min[0], oed->shadow_min[1]},
{oed->shadow_min[0], oed->shadow_max[1]},
{oed->shadow_max[0], oed->shadow_min[1]},
{oed->shadow_max[0], oed->shadow_max[1]},
};
for (int i = 0; i < 2; i++) {
float min_dst = FLT_MAX, max_dst = -FLT_MAX;
for (int j = 0; j < 4; j++) {
float dst = dot_v2v2(wpd->shadow_near_sides[i], pts[j]);
/* Do min max */
if (min_dst > dst) {
min_dst = dst;
}
if (max_dst < dst) {
max_dst = dst;
}
}
if ((wpd->shadow_near_sides[i][2] > max_dst) || (wpd->shadow_near_sides[i][3] < min_dst)) {
return false;
}
}
/* No separation axis found. Both shape intersect. */
return true;
}
static void workbench_init_object_data(DrawData *dd)
{
WORKBENCH_ObjectData *data = (WORKBENCH_ObjectData *)dd;
data->shadow_bbox_dirty = true;
}
void workbench_shadow_cache_populate(WORKBENCH_Data *data, Object *ob, const bool has_transp_mat)
{
WORKBENCH_PrivateData *wpd = data->stl->wpd;
bool is_manifold;
struct GPUBatch *geom_shadow = DRW_cache_object_edge_detection_get(ob, &is_manifold);
if (geom_shadow == NULL) {
return;
}
WORKBENCH_ObjectData *engine_object_data = (WORKBENCH_ObjectData *)DRW_drawdata_ensure(
&ob->id,
&draw_engine_workbench,
sizeof(WORKBENCH_ObjectData),
&workbench_init_object_data,
NULL);
if (workbench_shadow_object_cast_visible_shadow(wpd, ob, engine_object_data)) {
mul_v3_mat3_m4v3(engine_object_data->shadow_dir, ob->imat, wpd->shadow_direction_ws);
DRWShadingGroup *grp;
bool use_shadow_pass_technique = !workbench_shadow_camera_in_object_shadow(
wpd, ob, engine_object_data);
/* Shadow pass technique needs object to be have all its surface opaque. */
if (has_transp_mat) {
use_shadow_pass_technique = false;
}
if (use_shadow_pass_technique) {
grp = DRW_shgroup_create_sub(wpd->shadow_pass_grp[is_manifold]);
DRW_shgroup_uniform_vec3(grp, "lightDirection", engine_object_data->shadow_dir, 1);
DRW_shgroup_uniform_float_copy(grp, "lightDistance", 1e5f);
DRW_shgroup_call_no_cull(grp, geom_shadow, ob);
#if DEBUG_SHADOW_VOLUME
DRW_debug_bbox(&engine_object_data->shadow_bbox, (float[4]){1.0f, 0.0f, 0.0f, 1.0f});
#endif
}
else {
float extrude_distance = workbench_shadow_object_shadow_distance(
wpd, ob, engine_object_data);
/* TODO(fclem): only use caps if they are in the view frustum. */
const bool need_caps = true;
if (need_caps) {
grp = DRW_shgroup_create_sub(wpd->shadow_fail_caps_grp[is_manifold]);
DRW_shgroup_uniform_vec3(grp, "lightDirection", engine_object_data->shadow_dir, 1);
DRW_shgroup_uniform_float_copy(grp, "lightDistance", extrude_distance);
DRW_shgroup_call_no_cull(grp, DRW_cache_object_surface_get(ob), ob);
}
grp = DRW_shgroup_create_sub(wpd->shadow_fail_grp[is_manifold]);
DRW_shgroup_uniform_vec3(grp, "lightDirection", engine_object_data->shadow_dir, 1);
DRW_shgroup_uniform_float_copy(grp, "lightDistance", extrude_distance);
DRW_shgroup_call_no_cull(grp, geom_shadow, ob);
#if DEBUG_SHADOW_VOLUME
DRW_debug_bbox(&engine_object_data->shadow_bbox, (float[4]){0.0f, 1.0f, 0.0f, 1.0f});
#endif
}
}
}