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

workbench_shadow.c « workbench « engines « draw « blender « source - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3386d9b639066247ffdc488c944ea664a9ae01af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/*
 * 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) {
    const 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 sub-passes. */
    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);

  const 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;
    }

    /* We cannot use Shadow Pass technique on non-manifold object (see T76168). */
    if (use_shadow_pass_technique && !is_manifold && (wpd->cull_state != 0)) {
      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
    }
  }
}