Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClément Foucault <foucault.clem@gmail.com>2020-03-11 19:07:43 +0300
committerClément Foucault <foucault.clem@gmail.com>2020-03-11 19:12:16 +0300
commitc476c36e400883d929a7149def8dcb6ad6157a86 (patch)
treec19c43ad1ed82f333c08bee7d2096024fed812dd /source/blender/draw/engines/workbench/workbench_shadow.c
parentf01bc597a8e6bf5df19f1af0c422918c96b25e41 (diff)
Workbench Simplification Refactor
This patch is (almost) a complete rewrite of workbench engine. The features remain unchanged but the code quality is greatly improved. Hair shading is brighter but also more correct. This also introduce the concept of `DRWShaderLibrary` to make a simple include system inside the GLSL files. Differential Revision: https://developer.blender.org/D7060
Diffstat (limited to 'source/blender/draw/engines/workbench/workbench_shadow.c')
-rw-r--r--source/blender/draw/engines/workbench/workbench_shadow.c367
1 files changed, 367 insertions, 0 deletions
diff --git a/source/blender/draw/engines/workbench/workbench_shadow.c b/source/blender/draw/engines/workbench/workbench_shadow.c
new file mode 100644
index 00000000000..8da75942944
--- /dev/null
+++ b/source/blender/draw/engines/workbench/workbench_shadow.c
@@ -0,0 +1,367 @@
+/*
+ * 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 prepass 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
+ }
+ }
+} \ No newline at end of file