368 lines
13 KiB
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
|
|
}
|
|
}
|
|
}
|