EEVEE-Next: Planar Probe Pipeline #113203

Merged
Clément Foucault merged 13 commits from Jeroen-Bakker/blender:eevee/planar-reflection-probes into main 2023-10-08 19:50:08 +02:00
22 changed files with 533 additions and 74 deletions

View File

@ -152,7 +152,9 @@ class DATA_PT_lightprobe_eevee_next(DataButtonsPanel, Panel):
sub.prop(probe, "clip_end", text="End")
elif probe.type == 'PLANAR':
# Currently unsupported
col = layout.column()
row = col.row()
col.prop(probe, "clip_start", text="Clipping Offset")
pass
else:
# Currently unsupported

View File

@ -395,6 +395,7 @@ class OBJECT_PT_visibility(ObjectButtonsPanel, Panel):
col = layout.column(heading="Light Probes")
col.prop(ob, "hide_probe_volume", text="Volume", toggle=False, invert_checkbox=True)
col.prop(ob, "hide_probe_cubemap", text="Cubemap", toggle=False, invert_checkbox=True)
col.prop(ob, "hide_probe_planar", text="Planar", toggle=False, invert_checkbox=True)
if ob.type == 'GPENCIL':
col = layout.column(heading="Grease Pencil")

View File

@ -153,6 +153,7 @@ set(SRC
engines/eevee_next/eevee_material.cc
engines/eevee_next/eevee_motion_blur.cc
engines/eevee_next/eevee_pipeline.cc
engines/eevee_next/eevee_planar_probes.cc
engines/eevee_next/eevee_precompute.cc
engines/eevee_next/eevee_raytrace.cc
engines/eevee_next/eevee_reflection_probes.cc
@ -289,6 +290,7 @@ set(SRC
engines/eevee_next/eevee_material.hh
engines/eevee_next/eevee_motion_blur.hh
engines/eevee_next/eevee_pipeline.hh
engines/eevee_next/eevee_planar_probes.hh
engines/eevee_next/eevee_precompute.hh
engines/eevee_next/eevee_raytrace.hh
engines/eevee_next/eevee_reflection_probes.hh

View File

@ -76,6 +76,7 @@ void Instance::init(const int2 &output_res,
shadows.init();
motion_blur.init();
main_view.init();
planar_probes.init();
/* Irradiance Cache needs reflection probes to be initialized. */
reflection_probes.init();
irradiance_cache.init();
@ -108,6 +109,7 @@ void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager)
depth_of_field.init();
shadows.init();
main_view.init();
planar_probes.init();
/* Irradiance Cache needs reflection probes to be initialized. */
reflection_probes.init();
irradiance_cache.init();
@ -150,6 +152,7 @@ void Instance::begin_sync()
pipelines.begin_sync();
cryptomatte.begin_sync();
reflection_probes.begin_sync();
planar_probes.begin_sync();
light_probes.begin_sync();
gpencil_engine_enabled = false;
@ -279,6 +282,7 @@ void Instance::end_sync()
pipelines.end_sync();
light_probes.end_sync();
reflection_probes.end_sync();
planar_probes.end_sync();
volume.end_sync();
global_ubo_.push_update();
@ -315,12 +319,23 @@ void Instance::render_sync()
DRW_curves_update();
}
bool Instance::do_probe_sync() const
bool Instance::do_reflection_probe_sync() const
{
if (!reflection_probes.update_probes_this_sample_) {
return false;
}
if (materials.queued_shaders_count > 0) {
return false;
}
if (!reflection_probes.update_probes_this_sample_) {
return true;
}
bool Instance::do_planar_probe_sync() const
{
if (!planar_probes.update_probes_) {
return false;
}
if (materials.queued_shaders_count > 0) {
return false;
}
return true;
@ -439,6 +454,7 @@ void Instance::render_frame(RenderLayer *render_layer, const char *view_name)
* are other light probes in the scene. */
if (DEG_id_type_any_exists(this->depsgraph, ID_LP)) {
reflection_probes.update_probes_next_sample_ = true;
planar_probes.update_probes_ = true;
}
while (!sampling.finished()) {

View File

@ -29,6 +29,7 @@
#include "eevee_material.hh"
#include "eevee_motion_blur.hh"
#include "eevee_pipeline.hh"
#include "eevee_planar_probes.hh"
#include "eevee_raytrace.hh"
#include "eevee_reflection_probes.hh"
#include "eevee_renderbuffers.hh"
@ -64,6 +65,7 @@ class Instance {
AmbientOcclusion ambient_occlusion;
RayTraceModule raytracing;
ReflectionProbeModule reflection_probes;
PlanarProbeModule planar_probes;
VelocityModule velocity;
MotionBlurModule motion_blur;
DepthOfField depth_of_field;
@ -121,6 +123,7 @@ class Instance {
ambient_occlusion(*this, global_ubo_.ao),
raytracing(*this, global_ubo_.raytrace),
reflection_probes(*this),
planar_probes(*this),
velocity(*this),
motion_blur(*this),
depth_of_field(*this),
@ -158,7 +161,8 @@ class Instance {
/**
* Return true when probe pipeline is used during this sample.
*/
bool do_probe_sync() const;
bool do_reflection_probe_sync() const;
bool do_planar_probe_sync() const;
/* Render. */

View File

@ -160,7 +160,7 @@ MaterialPass MaterialModule::material_pass_get(Object *ob,
::Material *blender_mat,
eMaterialPipeline pipeline_type,
eMaterialGeometry geometry_type,
bool probe_capture)
eMaterialProbe probe_capture)
{
bNodeTree *ntree = (blender_mat->use_nodes && blender_mat->nodetree != nullptr) ?
blender_mat->nodetree :
@ -267,8 +267,10 @@ Material &MaterialModule::material_sync(Object *ob,
* to avoid this shader compilation in another context. */
mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type);
mat.capture = material_pass_get(ob, blender_mat, MAT_PIPE_CAPTURE, geometry_type);
mat.probe_prepass = MaterialPass();
mat.probe_shading = MaterialPass();
mat.reflection_probe_prepass = MaterialPass();
mat.reflection_probe_shading = MaterialPass();
mat.planar_probe_prepass = MaterialPass();
mat.planar_probe_shading = MaterialPass();
mat.volume = MaterialPass();
}
else {
@ -276,14 +278,22 @@ Material &MaterialModule::material_sync(Object *ob,
mat.prepass = material_pass_get(ob, blender_mat, prepass_pipe, geometry_type);
mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type);
mat.capture = MaterialPass();
mat.probe_prepass = MaterialPass();
mat.probe_shading = MaterialPass();
mat.reflection_probe_prepass = MaterialPass();
mat.reflection_probe_shading = MaterialPass();
mat.planar_probe_prepass = MaterialPass();
mat.planar_probe_shading = MaterialPass();
if (inst_.do_probe_sync()) {
mat.probe_prepass = material_pass_get(
ob, blender_mat, MAT_PIPE_DEFERRED_PREPASS, geometry_type, true);
mat.probe_shading = material_pass_get(
ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, true);
if (inst_.do_reflection_probe_sync()) {
mat.reflection_probe_prepass = material_pass_get(
ob, blender_mat, MAT_PIPE_DEFERRED_PREPASS, geometry_type, MAT_PROBE_REFLECTION);
mat.reflection_probe_shading = material_pass_get(
ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_REFLECTION);
}
if (inst_.do_planar_probe_sync()) {
mat.planar_probe_prepass = material_pass_get(
ob, blender_mat, MAT_PIPE_PLANAR_PREPASS, geometry_type, MAT_PROBE_PLANAR);
mat.planar_probe_shading = material_pass_get(
ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_PLANAR);
}
if (GPU_material_has_volume_output(mat.shading.gpumat)) {

View File

@ -35,6 +35,7 @@ enum eMaterialPipeline {
MAT_PIPE_VOLUME,
MAT_PIPE_SHADOW,
MAT_PIPE_CAPTURE,
MAT_PIPE_PLANAR_PREPASS,
};
enum eMaterialGeometry {
@ -47,6 +48,12 @@ enum eMaterialGeometry {
MAT_GEOM_WORLD,
};
enum eMaterialProbe {
MAT_PROBE_NONE = 0,
MAT_PROBE_REFLECTION,
MAT_PROBE_PLANAR,
};
static inline void material_type_from_shader_uuid(uint64_t shader_uuid,
eMaterialPipeline &pipeline_type,
eMaterialGeometry &geometry_type)
@ -154,13 +161,13 @@ struct ShaderKey {
eMaterialGeometry geometry,
eMaterialPipeline pipeline,
char blend_flags,
bool probe_capture)
eMaterialProbe probe_capture)
{
shader = GPU_material_get_shader(gpumat);
options = blend_flags;
options = (options << 6u) | shader_uuid_from_material_type(pipeline, geometry);
options = (options << 16u) | shader_closure_bits_from_flag(gpumat);
options = (options << 1u) | uint64_t(probe_capture);
options = (options << 2u) | uint64_t(probe_capture);
}
uint64_t hash() const
@ -220,7 +227,8 @@ struct MaterialPass {
struct Material {
bool is_alpha_blend_transparent;
MaterialPass shadow, shading, prepass, capture, probe_prepass, probe_shading, volume;
MaterialPass shadow, shading, prepass, capture, reflection_probe_prepass,
reflection_probe_shading, planar_probe_prepass, planar_probe_shading, volume;
};
struct MaterialArray {
@ -276,7 +284,7 @@ class MaterialModule {
::Material *blender_mat,
eMaterialPipeline pipeline_type,
eMaterialGeometry geometry_type,
bool probe_capture = false);
eMaterialProbe probe_capture = MAT_PROBE_NONE);
};
/** \} */

View File

@ -797,12 +797,11 @@ void DeferredProbeLayer::begin_sync()
}
DRWState state_depth_only = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS;
prepass_double_sided_ps_ = &prepass_ps_.sub("DoubleSided");
prepass_double_sided_ps_->state_set(state_depth_only);
prepass_single_sided_ps_ = &prepass_ps_.sub("SingleSided");
prepass_single_sided_ps_->state_set(state_depth_only | DRW_STATE_CULL_BACK);
/* Only setting up static pass because we don't use motion vectors for light-probes. */
prepass_double_sided_static_ps_ = &prepass_ps_.sub("DoubleSided");
prepass_double_sided_static_ps_->state_set(state_depth_only);
prepass_single_sided_static_ps_ = &prepass_ps_.sub("SingleSided");
prepass_single_sided_static_ps_->state_set(state_depth_only | DRW_STATE_CULL_BACK);
}
{
gbuffer_ps_.init();
@ -869,8 +868,8 @@ void DeferredProbeLayer::end_sync()
PassMain::Sub *DeferredProbeLayer::prepass_add(::Material *blender_mat, GPUMaterial *gpumat)
{
PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ?
prepass_single_sided_ps_ :
prepass_double_sided_ps_;
prepass_single_sided_static_ps_ :
prepass_double_sided_static_ps_;
return &pass->sub(GPU_material_get_name(gpumat));
}
@ -951,6 +950,115 @@ void DeferredProbePipeline::render(View &view,
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deferred Planar Probe Pipeline
*
* \{ */
void PlanarProbePipeline::begin_sync()
{
{
prepass_ps_.init();
prepass_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
prepass_ps_.bind_ubo(CLIP_PLANE_BUF, inst_.planar_probes.world_clip_buf_);
inst_.bind_uniform_data(&prepass_ps_);
inst_.sampling.bind_resources(&prepass_ps_);
DRWState state_depth_only = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS;
prepass_double_sided_static_ps_ = &prepass_ps_.sub("DoubleSided.Static");
prepass_double_sided_static_ps_->state_set(state_depth_only);
prepass_single_sided_static_ps_ = &prepass_ps_.sub("SingleSided.Static");
prepass_single_sided_static_ps_->state_set(state_depth_only | DRW_STATE_CULL_BACK);
}
{
gbuffer_ps_.init();
gbuffer_ps_.bind_image(GBUF_CLOSURE_SLOT, &inst_.gbuffer.closure_tx);
gbuffer_ps_.bind_image(GBUF_COLOR_SLOT, &inst_.gbuffer.color_tx);
gbuffer_ps_.bind_image(GBUF_HEADER_SLOT, &inst_.gbuffer.header_tx);
gbuffer_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
inst_.bind_uniform_data(&gbuffer_ps_);
inst_.sampling.bind_resources(&gbuffer_ps_);
inst_.hiz_buffer.bind_resources(&gbuffer_ps_);
inst_.cryptomatte.bind_resources(&gbuffer_ps_);
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM | DRW_STATE_DEPTH_EQUAL;
gbuffer_double_sided_ps_ = &gbuffer_ps_.sub("DoubleSided");
gbuffer_double_sided_ps_->state_set(state);
gbuffer_single_sided_ps_ = &gbuffer_ps_.sub("SingleSided");
gbuffer_single_sided_ps_->state_set(state | DRW_STATE_CULL_BACK);
}
{
PassSimple &pass = eval_light_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(DEFERRED_CAPTURE_EVAL));
inst_.bind_uniform_data(&pass);
inst_.gbuffer.bind_resources(&pass);
inst_.lights.bind_resources(&pass);
inst_.shadows.bind_resources(&pass);
inst_.sampling.bind_resources(&pass);
inst_.hiz_buffer.bind_resources(&pass);
inst_.irradiance_cache.bind_resources(&pass);
pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
pass.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
closure_bits_ = CLOSURE_NONE;
}
void PlanarProbePipeline::end_sync()
{
/* No-op for now. */
}
PassMain::Sub *PlanarProbePipeline::prepass_add(::Material *blender_mat, GPUMaterial *gpumat)
{
PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ?
prepass_single_sided_static_ps_ :
prepass_double_sided_static_ps_;
return &pass->sub(GPU_material_get_name(gpumat));
}
PassMain::Sub *PlanarProbePipeline::material_add(::Material *blender_mat, GPUMaterial *gpumat)
{
eClosureBits closure_bits = shader_closure_bits_from_flag(gpumat);
closure_bits_ |= closure_bits;
PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ?
gbuffer_single_sided_ps_ :
gbuffer_double_sided_ps_;
return &pass->sub(GPU_material_get_name(gpumat));
}
void PlanarProbePipeline::render(View &view, Framebuffer &combined_fb, int2 extent)
{
GPU_debug_group_begin("Planar.Capture");
GPU_framebuffer_bind(combined_fb);
GPU_framebuffer_clear_depth(combined_fb, 1.0f);
inst_.manager->submit(prepass_ps_, view);
inst_.lights.set_view(view, extent);
inst_.shadows.set_view(view);
inst_.irradiance_cache.set_view(view);
inst_.gbuffer.acquire(extent, closure_bits_);
GPU_framebuffer_bind(combined_fb);
GPU_framebuffer_clear_color(combined_fb, float4(0.0f, 0.0f, 0.0f, 1.0f));
inst_.manager->submit(gbuffer_ps_, view);
inst_.manager->submit(eval_light_ps_, view);
inst_.gbuffer.release();
GPU_debug_group_end();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture Pipeline
*

View File

@ -176,10 +176,7 @@ class ForwardPipeline {
/** \name Deferred lighting.
* \{ */
class DeferredLayer {
private:
Instance &inst_;
struct DeferredLayerBase {
PassMain prepass_ps_ = {"Prepass"};
PassMain::Sub *prepass_single_sided_static_ps_ = nullptr;
PassMain::Sub *prepass_single_sided_moving_ps_ = nullptr;
@ -190,14 +187,19 @@ class DeferredLayer {
PassMain::Sub *gbuffer_single_sided_ps_ = nullptr;
PassMain::Sub *gbuffer_double_sided_ps_ = nullptr;
/* Closures bits from the materials in this pass. */
eClosureBits closure_bits_ = CLOSURE_NONE;
};
class DeferredLayer : private DeferredLayerBase {
private:
Instance &inst_;
/* Evaluate all light objects contribution. */
PassSimple eval_light_ps_ = {"EvalLights"};
/* Combine direct and indirect light contributions and apply BSDF color. */
PassSimple combine_ps_ = {"Combine"};
/* Closures bits from the materials in this pass. */
eClosureBits closure_bits_ = CLOSURE_NONE;
/**
* Accumulation textures for all stages of lighting evaluation (Light, SSR, SSSS, SSGI ...).
* These are split and separate from the main radiance buffer in order to accumulate light for
@ -214,7 +216,9 @@ class DeferredLayer {
GPUTexture *indirect_reflect_tx_ = nullptr;
GPUTexture *indirect_refract_tx_ = nullptr;
/* TODO(fclem): This should be a TextureFromPool. */
Texture radiance_behind_tx_ = {"radiance_behind_tx"};
/* TODO(fclem): This shouldn't be part of the pipeline but of the view. */
Texture radiance_feedback_tx_ = {"radiance_feedback_tx"};
float4x4 radiance_feedback_persmat_;
@ -290,23 +294,12 @@ class VolumePipeline {
/* -------------------------------------------------------------------- */
/** \name Deferred Probe Capture.
* \{ */
class DeferredProbeLayer {
class DeferredProbeLayer : DeferredLayerBase {
private:
Instance &inst_;
PassMain prepass_ps_ = {"Prepass"};
PassMain::Sub *prepass_single_sided_ps_ = nullptr;
PassMain::Sub *prepass_double_sided_ps_ = nullptr;
PassMain gbuffer_ps_ = {"Shading"};
PassMain::Sub *gbuffer_single_sided_ps_ = nullptr;
PassMain::Sub *gbuffer_double_sided_ps_ = nullptr;
PassSimple eval_light_ps_ = {"EvalLights"};
/* Closures bits from the materials in this pass. */
eClosureBits closure_bits_;
public:
DeferredProbeLayer(Instance &inst) : inst_(inst){};
@ -337,6 +330,33 @@ class DeferredProbePipeline {
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deferred Planar Probe Capture.
* \{ */
class PlanarProbePipeline : DeferredLayerBase {
private:
Instance &inst_;
PassSimple eval_light_ps_ = {"EvalLights"};
/* Closures bits from the materials in this pass. */
eClosureBits closure_bits_ = CLOSURE_NONE;
public:
PlanarProbePipeline(Instance &inst) : inst_(inst){};
void begin_sync();
void end_sync();
PassMain::Sub *prepass_add(::Material *material, GPUMaterial *gpumat);
PassMain::Sub *material_add(::Material *material, GPUMaterial *gpumat);
void render(View &view, Framebuffer &combined_fb, int2 extent);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture Pipeline
*
@ -449,6 +469,7 @@ class PipelineModule {
WorldPipeline world;
WorldVolumePipeline world_volume;
DeferredProbePipeline probe;
PlanarProbePipeline planar;
DeferredPipeline deferred;
ForwardPipeline forward;
ShadowPipeline shadow;
@ -463,6 +484,7 @@ class PipelineModule {
world(inst),
world_volume(inst),
probe(inst),
planar(inst),
deferred(inst),
forward(inst),
shadow(inst),
@ -472,6 +494,7 @@ class PipelineModule {
void begin_sync()
{
probe.begin_sync();
planar.begin_sync();
deferred.begin_sync();
forward.sync();
shadow.sync();
@ -482,6 +505,7 @@ class PipelineModule {
void end_sync()
{
probe.end_sync();
planar.end_sync();
deferred.end_sync();
}
@ -489,15 +513,27 @@ class PipelineModule {
::Material *blender_mat,
GPUMaterial *gpumat,
eMaterialPipeline pipeline_type,
bool probe_capture)
eMaterialProbe probe_capture)
{
if (probe_capture) {
if (probe_capture == MAT_PROBE_REFLECTION) {
switch (pipeline_type) {
case MAT_PIPE_DEFERRED_PREPASS:
return probe.prepass_add(blender_mat, gpumat);
case MAT_PIPE_DEFERRED:
return probe.material_add(blender_mat, gpumat);
default:
BLI_assert_unreachable();
break;
}
}
if (probe_capture == MAT_PROBE_PLANAR) {
switch (pipeline_type) {
case MAT_PIPE_PLANAR_PREPASS:
return planar.prepass_add(blender_mat, gpumat);
case MAT_PIPE_DEFERRED:
return planar.material_add(blender_mat, gpumat);
default:
BLI_assert_unreachable();
break;
}
}
@ -532,6 +568,9 @@ class PipelineModule {
return shadow.surface_material_add(gpumat);
case MAT_PIPE_CAPTURE:
return capture.surface_material_add(gpumat);
case MAT_PIPE_PLANAR_PREPASS:
BLI_assert_unreachable();
return nullptr;
}
return nullptr;
}

View File

@ -0,0 +1,117 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "eevee_planar_probes.hh"
#include "eevee_instance.hh"
namespace blender::eevee {
/* -------------------------------------------------------------------- */
/** \name Planar Probe Module
* \{ */
void PlanarProbeModule::init()
{
update_probes_ = !probes_.is_empty();
}
void PlanarProbeModule::begin_sync()
{
for (PlanarProbe &planar_probe : probes_.values()) {
planar_probe.is_probe_used = false;
}
}
void PlanarProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle)
{
const ::LightProbe *light_probe = (::LightProbe *)ob->data;
if (light_probe->type != LIGHTPROBE_TYPE_PLANAR) {
return;
}
/* TODO Cull out of view planars. */
PlanarProbe &probe = find_or_insert(ob_handle);
probe.plane_to_world = float4x4(ob->object_to_world);
probe.world_to_plane = float4x4(ob->world_to_object);
probe.clipping_offset = light_probe->clipsta;
probe.is_probe_used = true;
}
Jeroen-Bakker marked this conversation as resolved Outdated

This shouldn't be a power of 2. But a screen percentage. Should be fine to change the meaning of it in the RNA for planar.

This shouldn't be a power of 2. But a screen percentage. Should be fine to change the meaning of it in the RNA for planar.

I added its own attribute, otherwise bpy API will get confusing.

I added its own attribute, otherwise bpy API will get confusing.
void PlanarProbeModule::end_sync()
{
remove_unused_probes();
// if (probes_.is_empty()) {
// update_probes_ = true;
// instance_.sampling.reset();
// }
}
Jeroen-Bakker marked this conversation as resolved Outdated

use float clip_distance (only start)

use float clip_distance (only start)
float4x4 PlanarProbeModule::reflection_matrix_get(const float4x4 &plane_to_world,
const float4x4 &world_to_plane)
{
return math::normalize(plane_to_world) * math::from_scale<float4x4>(float3(1, 1, -1)) *
Jeroen-Bakker marked this conversation as resolved Outdated

What's this?

What's this?
math::normalize(world_to_plane);
}
float4 PlanarProbeModule::reflection_clip_plane_get(const float4x4 &plane_to_world,
float clip_offset)
{
/* Compute clip plane equation / normal. */
float4 plane_equation = float4(-math::normalize(plane_to_world.z_axis()));
Jeroen-Bakker marked this conversation as resolved Outdated

Use R11G11B10 for radiance and a separate DEPTH32F for depth.
I think you should add a Framebuffer too (unconfigured here).

Use R11G11B10 for radiance and a separate DEPTH32F for depth. I think you should add a `Framebuffer` too (unconfigured here).
plane_equation.w = -math::dot(plane_equation.xyz(), plane_to_world.location());
plane_equation.w -= clip_offset;
return plane_equation;
}
void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_extent)
{
const int64_t num_probes = probes_.size();
Jeroen-Bakker marked this conversation as resolved Outdated

capturing this?

capturing `this`?
if (resources_.size() != num_probes) {
Jeroen-Bakker marked this conversation as resolved Outdated

Use lookup_or_add_default instead

Use `lookup_or_add_default` instead
resources_.reinitialize(num_probes);
}
/* TODO resolution percentage. */
int2 extent = main_view_extent;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ;
color_tx_.ensure_2d_array(GPU_R11F_G11F_B10F, extent, num_probes, usage);
depth_tx_.ensure_2d_array(GPU_DEPTH_COMPONENT32F, extent, num_probes, usage);
int resource_index = 0;
for (PlanarProbe &probe : probes_.values()) {
PlanarProbeResources &res = resources_[resource_index];
float4x4 winmat = main_view.winmat();
float4x4 viewmat = main_view.viewmat();
viewmat = viewmat * reflection_matrix_get(probe.plane_to_world, probe.world_to_plane);
res.view.sync(viewmat, winmat);
res.view.visibility_test(false);
world_clip_buf_.plane = reflection_clip_plane_get(probe.plane_to_world, probe.clipping_offset);
world_clip_buf_.push_update();
res.combined_fb.ensure(GPU_ATTACHMENT_TEXTURE_LAYER(depth_tx_, resource_index),
GPU_ATTACHMENT_TEXTURE_LAYER(color_tx_, resource_index));
instance_.pipelines.planar.render(res.view, res.combined_fb, main_view_extent);
}
}
PlanarProbe &PlanarProbeModule::find_or_insert(ObjectHandle &ob_handle)
{
PlanarProbe &planar_probe = probes_.lookup_or_add_default(ob_handle.object_key.hash());
return planar_probe;
}
void PlanarProbeModule::remove_unused_probes()
{
probes_.remove_if(
[](const PlanarProbes::MutableItem &item) { return !item.value.is_probe_used; });
}
/** \} */
} // namespace blender::eevee

View File

@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eevee
*/
#pragma once
#include "eevee_shader_shared.hh"
#include "BKE_cryptomatte.hh"
extern "C" {
struct Material;
}
namespace blender::eevee {
class Instance;
struct ObjectHandle;
/* -------------------------------------------------------------------- */
/** \name Planar Probe
* \{ */
struct PlanarProbe {
/* Copy of object matrices. */
float4x4 plane_to_world;
float4x4 world_to_plane;
/* Offset to the clipping plane in the normal direction. */
float clipping_offset;
/* Index in the resource array. */
int resource_index;
/* Pruning flag. */
bool is_probe_used = false;
};
struct PlanarProbeResources : NonCopyable {
Framebuffer combined_fb = {"planar.combined_fb"};
draw::View view = {"planar.view"};
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Planar Probe Module
* \{ */
class PlanarProbeModule {
using PlanarProbes = Map<uint64_t, PlanarProbe>;
using Resources = Array<PlanarProbeResources>;
private:
Instance &instance_;
PlanarProbes probes_;
Resources resources_;
Texture color_tx_ = {"planar.color_tx"};
Texture depth_tx_ = {"planar.depth_tx"};
ClipPlaneBuf world_clip_buf_ = {"world_clip_buf"};
bool update_probes_ = false;
public:
PlanarProbeModule(Instance &instance) : instance_(instance) {}
void init();
void begin_sync();
void sync_object(Object *ob, ObjectHandle &ob_handle);
void end_sync();
void set_view(const draw::View &main_view, int2 main_view_extent);
template<typename T> void bind_resources(draw::detail::PassBase<T> * /*pass*/) {}
private:
PlanarProbe &find_or_insert(ObjectHandle &ob_handle);
void remove_unused_probes();
/**
* Create the reflection matrix that reflect along the XY plane of the given transform.
* The transform does not need to be normalized but is expected to be orthogonal.
*/
float4x4 reflection_matrix_get(const float4x4 &plane_to_world, const float4x4 &world_to_plane);
/**
* Create the reflection clip plane equation that clips along the XY plane of the given
* transform. The `clip_offset` will push the clip plane a bit further to avoid missing pixels in
* reflections. The transform does not need to be normalized but is expected to be orthogonal.
*/
float4 reflection_clip_plane_get(const float4x4 &plane_to_world, float clip_offset);
friend class Instance;
friend class PlanarProbePipeline;
};
/** \} */
} // namespace blender::eevee

View File

@ -154,7 +154,7 @@ void ReflectionProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle)
probe.do_render |= is_dirty;
probe.is_probe_used = true;
const bool probe_sync_active = instance_.do_probe_sync();
const bool probe_sync_active = instance_.do_reflection_probe_sync();
if (!probe_sync_active && probe.do_render) {
update_probes_next_sample_ = true;
}
@ -342,7 +342,7 @@ void ReflectionProbeModule::end_sync()
}
}
const bool do_update = instance_.do_probe_sync() || (only_world && world_updated);
const bool do_update = instance_.do_reflection_probe_sync() || (only_world && world_updated);
if (!do_update) {
if (update_probes_next_sample_ && !update_probes_this_sample_) {
DRW_viewport_request_redraw();
@ -548,7 +548,7 @@ std::ostream &operator<<(std::ostream &os, const ReflectionProbe &probe)
std::optional<ReflectionProbeUpdateInfo> ReflectionProbeModule::update_info_pop(
const ReflectionProbe::Type probe_type)
{
const bool do_probe_sync = instance_.do_probe_sync();
const bool do_probe_sync = instance_.do_reflection_probe_sync();
const bool only_world = has_only_world_probe();
const int max_shift = int(log2(max_resolution_));
for (const Map<uint64_t, ReflectionProbe>::Item &item : probes_.items()) {

View File

@ -336,11 +336,6 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
}
}
/* WORKAROUND: Needed because node_tree isn't present in test shaders. */
if (pipeline_type == MAT_PIPE_DEFERRED) {
info.additional_info("eevee_render_pass_out");
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_AO) &&
ELEM(pipeline_type, MAT_PIPE_FORWARD, MAT_PIPE_DEFERRED) &&
ELEM(geometry_type, MAT_GEOM_MESH, MAT_GEOM_CURVES))
@ -356,10 +351,14 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
}
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT) == false &&
pipeline_type == MAT_PIPE_FORWARD)
{
/* Opaque forward do support AOVs and render pass if not using transparency. */
bool supports_render_passes = (pipeline_type == MAT_PIPE_DEFERRED);
/* Opaque forward do support AOVs and render pass if not using transparency. */
if (!GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT) &&
(pipeline_type == MAT_PIPE_FORWARD)) {
supports_render_passes = true;
}
if (supports_render_passes) {
info.additional_info("eevee_render_pass_out");
info.additional_info("eevee_cryptomatte_out");
}
@ -561,6 +560,9 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
case MAT_PIPE_DEFERRED_PREPASS:
info.additional_info("eevee_surf_depth");
break;
case MAT_PIPE_PLANAR_PREPASS:
info.additional_info("eevee_surf_depth", "eevee_clip_plane");
break;
case MAT_PIPE_SHADOW:
/* Determine surface shadow shader depending on used update technique. */
switch (ShadowModule::shadow_technique) {

View File

@ -139,7 +139,10 @@ void SyncModule::sync_mesh(Object *ob,
bool is_shadow_caster = false;
bool is_alpha_blend = false;
bool do_probe_sync = inst_.do_probe_sync() && !(ob->visibility_flag & OB_HIDE_PROBE_CUBEMAP);
bool do_reflection_probe_sync = inst_.do_reflection_probe_sync() &&
!(ob->visibility_flag & OB_HIDE_PROBE_CUBEMAP);
bool do_planar_probe_sync = inst_.do_planar_probe_sync() &&
!(ob->visibility_flag & OB_HIDE_PROBE_PLANAR);
for (auto i : material_array.gpu_materials.index_range()) {
GPUBatch *geom = mat_geom[i];
if (geom == nullptr) {
@ -163,9 +166,15 @@ void SyncModule::sync_mesh(Object *ob,
geometry_call(material.prepass.sub_pass, geom, res_handle);
Jeroen-Bakker marked this conversation as resolved Outdated

Add a TODO that we should not compile the shader and create a subpass if the object has no visibiility for these passes.

Add a TODO that we should not compile the shader and create a subpass if the object has no visibiility for these passes.
geometry_call(material.shadow.sub_pass, geom, res_handle);
geometry_call(material.capture.sub_pass, geom, res_handle);
if (do_probe_sync) {
geometry_call(material.probe_prepass.sub_pass, geom, res_handle);
geometry_call(material.probe_shading.sub_pass, geom, res_handle);
/* TODO: We should not compile the shader and create a subpass if the object has no visibiility
* for these passes. */
if (do_reflection_probe_sync) {
geometry_call(material.reflection_probe_prepass.sub_pass, geom, res_handle);
geometry_call(material.reflection_probe_shading.sub_pass, geom, res_handle);
}
if (do_planar_probe_sync) {
geometry_call(material.planar_probe_prepass.sub_pass, geom, res_handle);
geometry_call(material.planar_probe_shading.sub_pass, geom, res_handle);
}
is_shadow_caster = is_shadow_caster || material.shadow.sub_pass != nullptr;
@ -212,7 +221,10 @@ bool SyncModule::sync_sculpt(Object *ob,
bool is_shadow_caster = false;
bool is_alpha_blend = false;
bool do_probe_sync = inst_.do_probe_sync() && !(ob->visibility_flag & OB_HIDE_PROBE_CUBEMAP);
bool do_reflection_probe_sync = inst_.do_reflection_probe_sync() &&
!(ob->visibility_flag & OB_HIDE_PROBE_CUBEMAP);
bool do_planar_probe_sync = inst_.do_reflection_probe_sync() &&
!(ob->visibility_flag & OB_HIDE_PROBE_PLANAR);
for (SculptBatch &batch :
sculpt_batches_per_material_get(ob_ref.object, material_array.gpu_materials))
{
@ -229,9 +241,12 @@ bool SyncModule::sync_sculpt(Object *ob,
/* TODO(Miguel Pozo): Is this needed ? */
geometry_call(material.capture.sub_pass, geom, res_handle);
if (do_probe_sync) {
geometry_call(material.probe_prepass.sub_pass, geom, res_handle);
geometry_call(material.probe_shading.sub_pass, geom, res_handle);
if (do_reflection_probe_sync) {
geometry_call(material.reflection_probe_prepass.sub_pass, geom, res_handle);
geometry_call(material.reflection_probe_shading.sub_pass, geom, res_handle);
}
if (do_planar_probe_sync) {
geometry_call(material.planar_probe_prepass.sub_pass, geom, res_handle);
}
is_shadow_caster = is_shadow_caster || material.shadow.sub_pass != nullptr;
@ -496,6 +511,7 @@ void SyncModule::sync_light_probe(Object *ob, ObjectHandle &ob_handle)
{
inst_.light_probes.sync_probe(ob, ob_handle);
inst_.reflection_probes.sync_object(ob, ob_handle);
inst_.planar_probes.sync_object(ob, ob_handle);
}
/** \} */

View File

@ -107,6 +107,8 @@ void ShadingView::render()
DRW_stats_group_start(name_);
DRW_view_set_active(render_view_);
inst_.planar_probes.set_view(render_view_new_, extent_);
/* If camera has any motion, compute motion vector in the film pass. Otherwise, we avoid float
* precision issue by setting the motion of all static geometry to 0. */
float4 clear_velocity = float4(inst_.velocity.camera_has_motion() ? VELOCITY_INVALID : 0.0f);

View File

@ -161,4 +161,22 @@ class CaptureView {
/** \} */
/* -------------------------------------------------------------------- */
/** \name Capture Planar View
*
* View for capturing planar probes outside a ShadingView.
* \{ */
class CapturePlanarView {
private:
Instance &inst_;
Framebuffer capture_fb_ = {"Planar.Capture"};
public:
CapturePlanarView(Instance &inst) : inst_(inst) {}
void render_probes();
};
/** \} */
} // namespace blender::eevee

View File

@ -37,6 +37,7 @@ void main()
/* World opacity. */
out_background = mix(vec4(0.0, 0.0, 0.0, 1.0), out_background, world_opacity_fade);
#ifdef MAT_RENDER_PASS_SUPPORT
/* Clear Render Buffers. */
ivec2 texel = ivec2(gl_FragCoord.xy);
@ -56,4 +57,5 @@ void main()
output_renderpass_value(uniform_buf.render_pass.shadow_id, 1.0);
/** NOTE: AO is done on its own pass. */
imageStore(rp_cryptomatte_img, texel, vec4(0.0));
#endif
}

View File

@ -151,11 +151,11 @@ GPU_SHADER_CREATE_INFO(eevee_surf_deferred)
.fragment_source("eevee_surf_deferred_frag.glsl")
.additional_info("eevee_global_ubo",
"eevee_utility_texture",
"eevee_sampling_data",
"eevee_hiz_data",
/* Added at runtime because of test shaders not having `node_tree`. */
// "eevee_render_pass_out",
"eevee_cryptomatte_out");
// "eevee_render_pass_out",
// "eevee_cryptomatte_out",
"eevee_sampling_data",
"eevee_hiz_data");
GPU_SHADER_CREATE_INFO(eevee_surf_forward)
/* Early fragment test is needed for render passes support for forward surfaces. */
@ -195,8 +195,9 @@ GPU_SHADER_CREATE_INFO(eevee_surf_world)
.fragment_out(0, Type::VEC4, "out_background")
.fragment_source("eevee_surf_world_frag.glsl")
.additional_info("eevee_global_ubo",
"eevee_render_pass_out",
"eevee_cryptomatte_out",
/* Optionally added depending on the material. */
// "eevee_render_pass_out",
// "eevee_cryptomatte_out",
"eevee_utility_texture");
GPU_SHADER_CREATE_INFO(eevee_surf_shadow)

View File

@ -562,6 +562,7 @@ class Texture : NonCopyable {
tx_ = create(UNPACK3(extent), mip_len, format, usage, data, false, false);
}
Texture(Texture &&other) = default;
~Texture()
{
free();

View File

@ -76,7 +76,8 @@ typedef struct LightProbe {
float surfel_density;
/**
* Resolution of the light probe when baked to a texture. Contains `eLightProbeResolution`.
* Resolution of the cube light probe when baked to a texture.
* Contains `eLightProbeResolution`.
*/
int resolution;

View File

@ -803,6 +803,7 @@ enum {
OB_SHADOW_CATCHER = 1 << 10,
OB_HIDE_PROBE_VOLUME = 1 << 11,
OB_HIDE_PROBE_CUBEMAP = 1 << 12,
OB_HIDE_PROBE_PLANAR = 1 << 13,
};
/** #Object.shapeflag */

View File

@ -2921,6 +2921,11 @@ static void rna_def_object_visibility(StructRNA *srna)
prop, "Disable in Cubemap Probes", "Globally disable in cubemap probes");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Object_internal_update_draw");
prop = RNA_def_property(srna, "hide_probe_planar", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "visibility_flag", OB_HIDE_PROBE_PLANAR);
RNA_def_property_ui_text(prop, "Disable in Planar Probes", "Globally disable in planar probes");
RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Object_internal_update_draw");
/* Instancer options. */
prop = RNA_def_property(srna, "show_instancer_for_render", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "duplicator_visibility_flag", OB_DUPLI_FLAG_RENDER);