Blender V4.3
eevee_pipeline.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
12
13#pragma once
14
15#include "BLI_math_bits.h"
16
17#include "DRW_render.hh"
18#include "draw_shader_shared.hh"
19
20#include "eevee_lut.hh"
21#include "eevee_raytrace.hh"
22#include "eevee_subsurface.hh"
23
24namespace blender::eevee {
25
26class Instance;
27struct RayTraceBuffer;
28
29/* -------------------------------------------------------------------- */
34
36 private:
37 Instance &inst_;
38
39 PassSimple clear_ps_ = {"World.Background.Clear"};
40 PassSimple world_ps_ = {"World.Background"};
41
42 public:
43 BackgroundPipeline(Instance &inst) : inst_(inst){};
44
45 void sync(GPUMaterial *gpumat, float background_opacity, float background_blur);
46 void clear(View &view);
47 void render(View &view);
48};
49
51
52/* -------------------------------------------------------------------- */
57
59 private:
60 Instance &inst_;
61
62 /* Dummy textures: required to reuse background shader and avoid another shader variation. */
63 Texture dummy_renderpass_tx_;
64 Texture dummy_cryptomatte_tx_;
65 Texture dummy_aov_color_tx_;
66 Texture dummy_aov_value_tx_;
67
68 PassSimple cubemap_face_ps_ = {"World.Probe"};
69
70 public:
71 WorldPipeline(Instance &inst) : inst_(inst){};
72
73 void sync(GPUMaterial *gpumat);
74 void render(View &view);
75
76}; // namespace blender::eevee
77
79
80/* -------------------------------------------------------------------- */
84
86 private:
87 Instance &inst_;
88 bool is_valid_;
89
90 PassSimple world_ps_ = {"World.Volume"};
91
92 public:
93 WorldVolumePipeline(Instance &inst) : inst_(inst){};
94
95 void sync(GPUMaterial *gpumat);
96 void render(View &view);
97};
98
100
101/* -------------------------------------------------------------------- */
105
107 private:
108 Instance &inst_;
109
110 /* Shadow update pass. */
111 PassMain render_ps_ = {"Shadow.Surface"};
112 /* Shadow surface render sub-passes. */
113 PassMain::Sub *surface_double_sided_ps_ = nullptr;
114 PassMain::Sub *surface_single_sided_ps_ = nullptr;
115
116 public:
117 ShadowPipeline(Instance &inst) : inst_(inst){};
118
120
121 void sync();
122
123 void render(View &view);
124};
125
127
128/* -------------------------------------------------------------------- */
133
135 private:
136 Instance &inst_;
137
138 PassMain prepass_ps_ = {"Prepass"};
139 PassMain::Sub *prepass_single_sided_static_ps_ = nullptr;
140 PassMain::Sub *prepass_single_sided_moving_ps_ = nullptr;
141 PassMain::Sub *prepass_double_sided_static_ps_ = nullptr;
142 PassMain::Sub *prepass_double_sided_moving_ps_ = nullptr;
143
144 PassMain opaque_ps_ = {"Shading"};
145 PassMain::Sub *opaque_single_sided_ps_ = nullptr;
146 PassMain::Sub *opaque_double_sided_ps_ = nullptr;
147
148 PassSortable transparent_ps_ = {"Forward.Transparent"};
149 float3 camera_forward_;
150
151 bool has_opaque_ = false;
152 bool has_transparent_ = false;
153
154 public:
155 ForwardPipeline(Instance &inst) : inst_(inst){};
156
157 void sync();
158
159 PassMain::Sub *prepass_opaque_add(::Material *blender_mat, GPUMaterial *gpumat, bool has_motion);
161
163 ::Material *blender_mat,
164 GPUMaterial *gpumat);
166 ::Material *blender_mat,
167 GPUMaterial *gpumat);
168
169 void render(View &view, Framebuffer &prepass_fb, Framebuffer &combined_fb, int2 extent);
170};
171
173
174/* -------------------------------------------------------------------- */
177
179 PassMain prepass_ps_ = {"Prepass"};
184
185 PassMain gbuffer_ps_ = {"Shading"};
186 /* Shaders that use the ClosureToRGBA node needs to be rendered first.
187 * Consider they hybrid forward and deferred. */
192
193 /* Closures bits from the materials in this pass. */
195 /* Maximum closure count considering all material in this pass. */
197
198 /* Stencil values used during the deferred pipeline. */
199 enum class StencilBits : uint8_t {
200 /* Bits 0 to 1 are reserved for closure count [0..3]. */
201 CLOSURE_COUNT_0 = (1u << 0u),
202 CLOSURE_COUNT_1 = (1u << 1u),
203 /* Set for pixels have a transmission closure. */
204 TRANSMISSION = (1u << 2u),
207
208 /* Set for materials that uses the shadow amend pass. */
212 };
213
214 /* Return the amount of gbuffer layer needed. */
216 {
217 /* Diffuse and translucent require only one layer. */
219 /* SSS require an additional layer compared to diffuse. */
221 /* Reflection and refraction can have at most two layers. */
224 return count;
225 }
226
227 /* Return the amount of gbuffer layer needed. */
229 {
230 /* TODO(fclem): We could count the number of different tangent frame in the shader and use
231 * min(tangent_frame_count, closure_count) once we have the normal reuse optimization.
232 * For now, allocate a split normal layer for each Closure. */
236 /* Count the additional infos layer needed by some closures. */
238 return count;
239 }
240
241 void gbuffer_pass_sync(Instance &inst);
242};
243
244class DeferredPipeline;
245
247 friend DeferredPipeline;
248
249 private:
250 Instance &inst_;
251
252 static constexpr int max_lighting_tile_count_ = 128 * 128;
253
254 /* Evaluate all light objects contribution. */
255 PassSimple eval_light_ps_ = {"EvalLights"};
256 /* Combine direct and indirect light contributions and apply BSDF color. */
257 PassSimple combine_ps_ = {"Combine"};
258
268 TextureFromPool direct_radiance_txs_[3] = {
269 {"direct_radiance_1"}, {"direct_radiance_2"}, {"direct_radiance_3"}};
270 /* NOTE: Only used when `use_split_radiance` is true. */
271 TextureFromPool indirect_radiance_txs_[3] = {
272 {"indirect_radiance_1"}, {"indirect_radiance_2"}, {"indirect_radiance_3"}};
273 /* Used when there is no indirect radiance buffer. */
274 Texture dummy_black = {"dummy_black"};
275 /* Reference to ray-tracing results. */
276 GPUTexture *radiance_feedback_tx_ = nullptr;
277
282 Texture tile_mask_tx_ = {"tile_mask_tx_"};
283
284 RayTraceResult indirect_result_;
285
286 bool use_split_radiance_ = true;
287 /* Output radiance from the combine shader instead of copy. Allow passing unclamped result. */
288 bool use_feedback_output_ = false;
289 bool use_raytracing_ = false;
290 bool use_screen_transmission_ = false;
291 bool use_screen_reflection_ = false;
292 bool use_clamp_direct_ = false;
293 bool use_clamp_indirect_ = false;
294
295 public:
296 DeferredLayer(Instance &inst) : inst_(inst)
297 {
298 float4 data(0.0f);
299 dummy_black.ensure_2d(RAYTRACE_RADIANCE_FORMAT,
300 int2(1),
302 data);
303 }
304
305 void begin_sync();
306 void end_sync(bool is_first_pass, bool is_last_pass, bool next_layer_has_transmission);
307
308 PassMain::Sub *prepass_add(::Material *blender_mat, GPUMaterial *gpumat, bool has_motion);
309 PassMain::Sub *material_add(::Material *blender_mat, GPUMaterial *gpumat);
310
311 bool is_empty() const
312 {
313 return closure_count_ == 0;
314 }
315
316 bool has_transmission() const
317 {
319 }
320
321 /* Returns the radiance buffer to feed the next layer. */
322 GPUTexture *render(View &main_view,
323 View &render_view,
324 Framebuffer &prepass_fb,
325 Framebuffer &combined_fb,
326 Framebuffer &gbuffer_fb,
327 int2 extent,
328 RayTraceBuffer &rt_buffer,
329 GPUTexture *radiance_behind_tx);
330};
331
333 friend DeferredLayer;
334
335 private:
336 /* Gbuffer filling passes. We could have an arbitrary number of them but for now we just have
337 * a hardcoded number of them. */
338 DeferredLayer opaque_layer_;
339 DeferredLayer refraction_layer_;
340 DeferredLayer volumetric_layer_;
341
342 PassSimple debug_draw_ps_ = {"debug_gbuffer"};
343
344 bool use_combined_lightprobe_eval;
345
346 public:
348 : opaque_layer_(inst), refraction_layer_(inst), volumetric_layer_(inst){};
349
350 void begin_sync();
351 void end_sync();
352
353 PassMain::Sub *prepass_add(::Material *material, GPUMaterial *gpumat, bool has_motion);
355
356 void render(View &main_view,
357 View &render_view,
358 Framebuffer &prepass_fb,
359 Framebuffer &combined_fb,
360 Framebuffer &gbuffer_fb,
361 int2 extent,
362 RayTraceBuffer &rt_buffer_opaque_layer,
363 RayTraceBuffer &rt_buffer_refract_layer);
364
365 /* Return the maximum amount of gbuffer layer needed. */
367 {
368 return max_ii(opaque_layer_.closure_layer_count(), refraction_layer_.closure_layer_count());
369 }
370
371 /* Return the maximum amount of gbuffer layer needed. */
373 {
374 return max_ii(opaque_layer_.normal_layer_count(), refraction_layer_.normal_layer_count());
375 }
376
377 void debug_draw(draw::View &view, GPUFrameBuffer *combined_fb);
378
379 bool is_empty() const
380 {
381 return opaque_layer_.is_empty() && refraction_layer_.is_empty();
382 }
383
384 private:
385 void debug_pass_sync();
386};
387
389
390/* -------------------------------------------------------------------- */
394
396 /* Screen 2D bounds for layer intersection check. */
397 std::optional<Bounds<float2>> screen_bounds;
398 /* Combined bounds in Z. Allow tighter integration bounds. */
399 std::optional<Bounds<float>> z_range;
400
402};
403
408 public:
409 bool use_hit_list = false;
410 bool is_empty = true;
411 bool finalized = false;
412 bool has_scatter = false;
413 bool has_absorption = false;
414
415 private:
416 Instance &inst_;
417
418 PassMain volume_layer_ps_ = {"Volume.Layer"};
419 /* Sub-passes of volume_layer_ps. */
420 PassMain::Sub *occupancy_ps_;
421 PassMain::Sub *material_ps_;
422 /* List of bounds from all objects contained inside this pass. */
424 /* Combined bounds from object_bounds_. */
425 std::optional<Bounds<float2>> combined_screen_bounds_;
426
427 public:
428 VolumeLayer(Instance &inst) : inst_(inst)
429 {
430 this->sync();
431 }
432
434 const ::Material *blender_mat,
435 GPUMaterial *gpumat);
437 const ::Material *blender_mat,
438 GPUMaterial *gpumat);
439
440 /* Return true if the given bounds overlaps any of the contained object in this layer. */
441 bool bounds_overlaps(const VolumeObjectBounds &object_aabb) const;
442
443 void add_object_bound(const VolumeObjectBounds &object_aabb);
444
445 void sync();
446 void render(View &view, Texture &occupancy_tx);
447};
448
450 private:
451 Instance &inst_;
452
454
455 /* Combined bounds in Z. Allow tighter integration bounds. */
456 std::optional<Bounds<float>> object_integration_range_;
457 /* Aggregated properties of all volume objects. */
458 bool has_scatter_ = false;
459 bool has_absorption_ = false;
460
461 public:
462 VolumePipeline(Instance &inst) : inst_(inst){};
463
464 void sync();
465 void render(View &view, Texture &occupancy_tx);
466
472
473 std::optional<Bounds<float>> object_integration_range() const;
474
475 bool has_scatter() const
476 {
477 for (auto &layer : layers_) {
478 if (layer->has_scatter) {
479 return true;
480 }
481 }
482 return false;
483 }
484 bool has_absorption() const
485 {
486 for (auto &layer : layers_) {
487 if (layer->has_absorption) {
488 return true;
489 }
490 }
491 return false;
492 }
493
494 /* Returns true if any volume layer uses the hist list. */
495 bool use_hit_list() const;
496};
497
499
500/* -------------------------------------------------------------------- */
503
505 private:
506 Instance &inst_;
507
508 DeferredLayerBase opaque_layer_;
509
510 PassSimple eval_light_ps_ = {"EvalLights"};
511
512 public:
513 DeferredProbePipeline(Instance &inst) : inst_(inst){};
514
515 void begin_sync();
516 void end_sync();
517
518 PassMain::Sub *prepass_add(::Material *material, GPUMaterial *gpumat);
520
521 void render(View &view,
522 Framebuffer &prepass_fb,
523 Framebuffer &combined_fb,
524 Framebuffer &gbuffer_fb,
525 int2 extent);
526
527 /* Return the maximum amount of gbuffer layer needed. */
529 {
530 return opaque_layer_.closure_layer_count();
531 }
532
533 /* Return the maximum amount of gbuffer layer needed. */
535 {
536 return opaque_layer_.normal_layer_count();
537 }
538};
539
541
542/* -------------------------------------------------------------------- */
545
547 private:
548 Instance &inst_;
549
550 PassSimple eval_light_ps_ = {"EvalLights"};
551
552 public:
553 PlanarProbePipeline(Instance &inst) : inst_(inst){};
554
555 void begin_sync();
556 void end_sync();
557
558 PassMain::Sub *prepass_add(::Material *material, GPUMaterial *gpumat);
560
561 void render(View &view,
562 GPUTexture *depth_layer_tx,
563 Framebuffer &gbuffer,
564 Framebuffer &combined_fb,
565 int2 extent);
566};
567
569
570/* -------------------------------------------------------------------- */
574
576 private:
577 Instance &inst_;
578
579 PassMain surface_ps_ = {"Capture.Surface"};
580
581 public:
582 CapturePipeline(Instance &inst) : inst_(inst){};
583
585
586 void sync();
587 void render(View &view);
588};
589
591
592/* -------------------------------------------------------------------- */
597
598class UtilityTexture : public Texture {
599 struct Layer {
601 };
602
603 static constexpr int lut_size = UTIL_TEX_SIZE;
604 static constexpr int lut_size_sqr = lut_size * lut_size;
605 static constexpr int layer_count = UTIL_BTDF_LAYER + UTIL_BTDF_LAYER_COUNT;
606
607 public:
609 : Texture("UtilityTx",
612 int2(lut_size),
613 layer_count,
614 nullptr)
615 {
616 Vector<Layer> data(layer_count);
617 {
618 Layer &layer = data[UTIL_BLUE_NOISE_LAYER];
619 memcpy(layer.data, lut::blue_noise, sizeof(layer));
620 }
621 {
623 for (auto y : IndexRange(lut_size)) {
624 for (auto x : IndexRange(lut_size)) {
625 /* Repeatedly stored on every row for correct interpolation. */
626 layer.data[y][x][0] = lut::burley_sss_profile[x][0];
627 layer.data[y][x][1] = lut::random_walk_sss_profile[x][0];
628 layer.data[y][x][2] = 0.0f;
630 }
631 }
633 }
634 {
635 Layer &layer = data[UTIL_LTC_MAT_LAYER];
636 memcpy(layer.data, lut::ltc_mat_ggx, sizeof(layer));
637 }
638 {
639 Layer &layer = data[UTIL_BSDF_LAYER];
640 for (auto x : IndexRange(lut_size)) {
641 for (auto y : IndexRange(lut_size)) {
642 layer.data[y][x][0] = lut::brdf_ggx[y][x][0];
643 layer.data[y][x][1] = lut::brdf_ggx[y][x][1];
644 layer.data[y][x][2] = lut::brdf_ggx[y][x][2];
645 layer.data[y][x][3] = 0.0f;
646 }
647 }
648 }
649 {
650 for (auto layer_id : IndexRange(16)) {
651 Layer &layer = data[UTIL_BTDF_LAYER + layer_id];
652 for (auto x : IndexRange(lut_size)) {
653 for (auto y : IndexRange(lut_size)) {
654 layer.data[y][x][0] = lut::bsdf_ggx[layer_id][y][x][0];
655 layer.data[y][x][1] = lut::bsdf_ggx[layer_id][y][x][1];
656 layer.data[y][x][2] = lut::bsdf_ggx[layer_id][y][x][2];
657 layer.data[y][x][3] = lut::btdf_ggx[layer_id][y][x][0];
658 }
659 }
660 }
661 }
663 }
664
666};
667
669
670/* -------------------------------------------------------------------- */
675
677 public:
688
691
692 public:
694 : background(inst),
695 world(inst),
696 world_volume(inst),
697 probe(inst),
698 planar(inst),
699 deferred(inst),
700 forward(inst),
701 shadow(inst),
702 volume(inst),
703 capture(inst),
704 data(data){};
705
707 {
708 data.is_sphere_probe = false;
709 probe.begin_sync();
710 planar.begin_sync();
711 deferred.begin_sync();
712 forward.sync();
713 shadow.sync();
714 volume.sync();
715 capture.sync();
716 }
717
718 void end_sync()
719 {
720 probe.end_sync();
721 planar.end_sync();
722 deferred.end_sync();
723 }
724
725 PassMain::Sub *material_add(Object * /*ob*/ /* TODO remove. */,
726 ::Material *blender_mat,
727 GPUMaterial *gpumat,
728 eMaterialPipeline pipeline_type,
729 eMaterialProbe probe_capture)
730 {
731 if (probe_capture == MAT_PROBE_REFLECTION) {
732 switch (pipeline_type) {
734 return probe.prepass_add(blender_mat, gpumat);
736 return probe.material_add(blender_mat, gpumat);
737 default:
739 break;
740 }
741 }
742 if (probe_capture == MAT_PROBE_PLANAR) {
743 switch (pipeline_type) {
745 return planar.prepass_add(blender_mat, gpumat);
747 return planar.material_add(blender_mat, gpumat);
748 default:
750 break;
751 }
752 }
753
754 switch (pipeline_type) {
756 return deferred.prepass_add(blender_mat, gpumat, false);
758 return forward.prepass_opaque_add(blender_mat, gpumat, false);
760 BLI_assert_msg(0, "Overlap prepass should register to the forward pipeline directly.");
761 return nullptr;
762
764 return deferred.prepass_add(blender_mat, gpumat, true);
766 return forward.prepass_opaque_add(blender_mat, gpumat, true);
767
769 return deferred.material_add(blender_mat, gpumat);
770 case MAT_PIPE_FORWARD:
771 return forward.material_opaque_add(blender_mat, gpumat);
772 case MAT_PIPE_SHADOW:
773 return shadow.surface_material_add(blender_mat, gpumat);
774 case MAT_PIPE_CAPTURE:
775 return capture.surface_material_add(blender_mat, gpumat);
776
779 BLI_assert_msg(0, "Volume shaders must register to the volume pipeline directly.");
780 return nullptr;
781
783 /* Should be handled by the `probe_capture == MAT_PROBE_PLANAR` case. */
785 return nullptr;
786 }
787 return nullptr;
788 }
789};
790
792
793} // namespace blender::eevee
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
MINLINE int max_ii(int a, int b)
MINLINE int count_bits_i(unsigned int n)
static AppView * view
@ GPU_DATA_FLOAT
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_TEXTURE_USAGE_SHADER_WRITE
void GPU_texture_update_mipmap(GPUTexture *texture, int mip_level, eGPUDataFormat data_format, const void *pixels)
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a or normal between camera
Texture(const char *name="gpu::Texture")
detail::PassBase< command::DrawMultiBuf > Sub
Definition draw_pass.hh:462
void sync(GPUMaterial *gpumat, float background_opacity, float background_blur)
PassMain::Sub * surface_material_add(::Material *blender_mat, GPUMaterial *gpumat)
void end_sync(bool is_first_pass, bool is_last_pass, bool next_layer_has_transmission)
PassMain::Sub * prepass_add(::Material *blender_mat, GPUMaterial *gpumat, bool has_motion)
PassMain::Sub * material_add(::Material *blender_mat, GPUMaterial *gpumat)
PassMain::Sub * material_add(::Material *material, GPUMaterial *gpumat)
void debug_draw(draw::View &view, GPUFrameBuffer *combined_fb)
PassMain::Sub * prepass_add(::Material *material, GPUMaterial *gpumat, bool has_motion)
PassMain::Sub * prepass_add(::Material *material, GPUMaterial *gpumat)
PassMain::Sub * material_add(::Material *material, GPUMaterial *gpumat)
PassMain::Sub * prepass_opaque_add(::Material *blender_mat, GPUMaterial *gpumat, bool has_motion)
PassMain::Sub * material_opaque_add(::Material *blender_mat, GPUMaterial *gpumat)
PassMain::Sub * material_transparent_add(const Object *ob, ::Material *blender_mat, GPUMaterial *gpumat)
PassMain::Sub * prepass_transparent_add(const Object *ob, ::Material *blender_mat, GPUMaterial *gpumat)
A running instance of the engine.
PipelineModule(Instance &inst, PipelineInfoData &data)
PassMain::Sub * material_add(Object *, ::Material *blender_mat, GPUMaterial *gpumat, eMaterialPipeline pipeline_type, eMaterialProbe probe_capture)
PassMain::Sub * prepass_add(::Material *material, GPUMaterial *gpumat)
PassMain::Sub * material_add(::Material *material, GPUMaterial *gpumat)
PassMain::Sub * surface_material_add(::Material *material, GPUMaterial *gpumat)
bool bounds_overlaps(const VolumeObjectBounds &object_aabb) const
PassMain::Sub * occupancy_add(const Object *ob, const ::Material *blender_mat, GPUMaterial *gpumat)
void add_object_bound(const VolumeObjectBounds &object_aabb)
PassMain::Sub * material_add(const Object *ob, const ::Material *blender_mat, GPUMaterial *gpumat)
std::optional< Bounds< float > > object_integration_range() const
VolumeLayer * register_and_get_layer(Object *ob)
void sync(GPUMaterial *gpumat)
#define RAYTRACE_RADIANCE_FORMAT
#define UTIL_DISK_INTEGRAL_COMP
#define UTIL_BSDF_LAYER
#define UTIL_BTDF_LAYER
#define UTIL_DISK_INTEGRAL_LAYER
#define UTIL_BTDF_LAYER_COUNT
#define UTIL_LTC_MAT_LAYER
#define UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER
#define UTIL_TEX_SIZE
#define UTIL_BLUE_NOISE_LAYER
int count
detail::Pass< command::DrawCommandBuf > PassSimple
detail::Pass< command::DrawMultiBuf > PassMain
const float ltc_mat_ggx[64][64][4]
Definition eevee_lut.cc:13
const float burley_sss_profile[64][1]
const float brdf_ggx[64][64][3]
const float random_walk_sss_profile[64][1]
const float ltc_disk_integral[64][64][1]
const float btdf_ggx[16][64][64][1]
const float bsdf_ggx[16][64][64][3]
const float blue_noise[64][64][4]
@ MAT_PIPE_PREPASS_FORWARD_VELOCITY
@ MAT_PIPE_PREPASS_DEFERRED_VELOCITY
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 3 > float3
unsigned char uint8_t
Definition stdint.h:78
PassMain::Sub * gbuffer_double_sided_hybrid_ps_
PassMain::Sub * prepass_double_sided_static_ps_
PassMain::Sub * prepass_single_sided_static_ps_
void gbuffer_pass_sync(Instance &inst)
PassMain::Sub * prepass_double_sided_moving_ps_
PassMain::Sub * gbuffer_single_sided_hybrid_ps_
PassMain::Sub * prepass_single_sided_moving_ps_
std::optional< Bounds< float2 > > screen_bounds
VolumeObjectBounds(const Camera &camera, Object *ob)
std::optional< Bounds< float > > z_range