EEVEE: Add support for Transparent BSDF through Shader To RGB

This PR adds to possibility to get the radiance behind an object using
the Shader To RGB workflow.

The result of this operation depends on the type of material.
- Dithered Material with Raytraced Transmission **off** will sample the nearest probe.
- Dithered Material with Raytraced Transmission **on** will show Dithered Materials
  with transmission **off** or fallback to nearest probe.
- Blended Material will show all Dithered Material materials or fallback to nearest probe.

This covers some workflows that were possible before 4.2 (which
were relying on the refraction BSDF).

This removes the `use_combined_lightprobe_eval` optimization which was defaulting
all Dithered Material to be in the same layer when raytracing was off.

We cannot do that anymore since this transparency feature requires clear separation
between the two material types (Raytraced Transmission on/off).
We could eventually reintroduce it by changing the way the submission is done when
there is no shader to rgb in the scene.

Pull Request: https://projects.blender.org/blender/blender/pulls/149723
This commit is contained in:
Clément Foucault 2025-11-28 17:09:06 +01:00 • committed by Clément Foucault
parent fa2e224625
commit dd12ff0af5
20 changed files with 172 additions and 21 deletions

View file

@ -235,9 +235,11 @@
#define SPHERE_PROBE_TEX_SLOT 7
#define VOLUME_SCATTERING_TEX_SLOT 8
#define VOLUME_TRANSMITTANCE_TEX_SLOT 9
#define HIZ_PREVIOUS_LAYER_TEX_SLOT 10
#define RADIANCE_PREVIOUS_LAYER_TEX_SLOT 11
/* Currently only used by ray-tracing, but might become used by forward too. */
#define PLANAR_PROBE_DEPTH_TEX_SLOT 10
#define PLANAR_PROBE_RADIANCE_TEX_SLOT 11
#define PLANAR_PROBE_DEPTH_TEX_SLOT 12
#define PLANAR_PROBE_RADIANCE_TEX_SLOT 13
/* Images. */
#define RBUFS_COLOR_SLOT 0

View file

@ -92,6 +92,9 @@ void HiZBuffer::update()
return;
}
/* After this, the front buffer become valid. */
front.ref_tx_ = hiz_tx_.current();
src_tx_ = *src_tx_ptr_;
for (const int i : IndexRange(HIZ_MIP_COUNT)) {
hiz_mip_ref_[i] = hiz_tx_.current().mip_view(i);

View file

@ -14,10 +14,16 @@
#include "DRW_render.hh"
#include "DRW_gpu_wrapper.hh"
#include "draw_pass.hh"
#include "eevee_defines.hh"
#include "eevee_hizbuffer_shared.hh"
namespace blender::eevee {
using namespace draw;
class Instance;
/* -------------------------------------------------------------------- */
@ -33,6 +39,9 @@ class HiZBuffer {
/** References to the mip views of the current (front) HiZ texture. */
std::array<gpu::Texture *, HIZ_MIP_COUNT> hiz_mip_ref_;
/** Dummy texture cleared to max depth to be used before the first hiz update. */
draw::Texture dummy_empty_hiz_tx_ = {"dummy_empty_hiz_tx_"};
/**
* Atomic counter counting the number of tile that have finished down-sampling.
* The last one will process the last few mip level.
@ -57,6 +66,9 @@ class HiZBuffer {
HiZBuffer(Instance &inst, HiZData &data) : inst_(inst), data_(data)
{
atomic_tile_counter_.clear_to_zero();
float value = 1.0f;
dummy_empty_hiz_tx_.ensure_2d(
gpu::TextureFormat::SFLOAT_32, int2(1), GPU_TEXTURE_USAGE_SHADER_READ, &value);
};
void sync();
@ -71,6 +83,9 @@ class HiZBuffer {
src_tx_ptr_ = texture;
layer_id_ = layer;
swap_layer();
/* Both layer becomes undefined. Replace by dummy texture. */
back.ref_tx_ = dummy_empty_hiz_tx_;
front.ref_tx_ = dummy_empty_hiz_tx_;
}
/**
@ -82,7 +97,7 @@ class HiZBuffer {
void swap_layer()
{
hiz_tx_.swap();
back.ref_tx_ = hiz_tx_.previous();
back.ref_tx_ = front.ref_tx_;
front.ref_tx_ = hiz_tx_.current();
set_dirty();
}

View file

@ -320,6 +320,7 @@ void ForwardPipeline::sync()
{
/* Common resources. */
opaque_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
opaque_ps_.bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, &inst_.render_buffers.combined_tx);
opaque_ps_.bind_resources(inst_.uniform_data);
opaque_ps_.bind_resources(inst_.lights);
opaque_ps_.bind_resources(inst_.shadows);
@ -348,6 +349,7 @@ void ForwardPipeline::sync()
/* Textures. */
sub.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
sub.bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, &inst_.render_buffers.combined_tx);
sub.bind_resources(inst_.uniform_data);
sub.bind_resources(inst_.lights);
@ -429,6 +431,13 @@ PassMain::Sub *ForwardPipeline::prepass_transparent_add(const Object *ob,
PassMain::Sub *pass = &transparent_ps_.sub(GPU_material_get_name(gpumat), sorting_value);
pass->state_set(state);
pass->material_set(*inst_.manager, gpumat, true);
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_SHADER_TO_RGBA) &&
GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT))
{
pass->bind_texture(HIZ_PREVIOUS_LAYER_TEX_SLOT, &inst_.hiz_buffer.back.ref_tx_);
pass->bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, &inst_.render_buffers.combined_tx);
}
return pass;
}
@ -449,6 +458,13 @@ PassMain::Sub *ForwardPipeline::material_transparent_add(const Object *ob,
PassMain::Sub *pass = &transparent_ps_.sub(GPU_material_get_name(gpumat), sorting_value);
pass->state_set(state);
pass->material_set(*inst_.manager, gpumat, true);
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_SHADER_TO_RGBA) &&
GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT))
{
pass->bind_texture(HIZ_PREVIOUS_LAYER_TEX_SLOT, &inst_.hiz_buffer.back.ref_tx_);
pass->bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, &inst_.render_buffers.combined_tx);
}
return pass;
}
@ -496,12 +512,15 @@ void ForwardPipeline::render(View &view,
return;
}
inst_.hiz_buffer.swap_layer();
GPU_debug_group_begin("Forward.Opaque");
prepass_fb.bind();
inst_.manager->submit(prepass_ps_, view);
inst_.hiz_buffer.set_dirty();
inst_.hiz_buffer.update();
inst_.shadows.set_view(view, extent);
inst_.volume_probes.set_view(view);
@ -576,32 +595,44 @@ void DeferredLayerBase::gbuffer_pass_sync(Instance &inst)
gbuffer_ps_.bind_resources(inst.uniform_data);
gbuffer_ps_.bind_resources(inst.sampling);
gbuffer_ps_.bind_resources(inst.hiz_buffer.front);
gbuffer_ps_.bind_resources(inst.hiz_buffer.front);
gbuffer_ps_.bind_resources(inst.cryptomatte);
gbuffer_ps_.bind_texture(HIZ_PREVIOUS_LAYER_TEX_SLOT, &inst.hiz_buffer.back.ref_tx_);
gbuffer_ps_.bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, &radiance_behind_tx_);
/* Bind light resources for the NPR materials that gets rendered first.
* Non-NPR shaders will override these resource bindings. */
gbuffer_ps_.bind_resources(inst.lights);
gbuffer_ps_.bind_resources(inst.shadows);
gbuffer_ps_.bind_resources(inst.sphere_probes);
gbuffer_ps_.bind_resources(inst.volume_probes);
gbuffer_ps_.bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, &radiance_behind_tx_);
DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_WRITE_STENCIL |
DRW_STATE_CLIP_CONTROL_UNIT_RANGE | DRW_STATE_STENCIL_ALWAYS;
gbuffer_single_sided_hybrid_ps_ = &gbuffer_ps_.sub("DoubleSided");
gbuffer_single_sided_hybrid_ps_->bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT,
&radiance_behind_tx_);
gbuffer_single_sided_hybrid_ps_->state_set(state | DRW_STATE_CULL_BACK);
gbuffer_double_sided_hybrid_ps_ = &gbuffer_ps_.sub("SingleSided");
gbuffer_double_sided_hybrid_ps_->bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT,
&radiance_behind_tx_);
gbuffer_double_sided_hybrid_ps_->state_set(state);
gbuffer_double_sided_ps_ = &gbuffer_ps_.sub("DoubleSided");
gbuffer_double_sided_ps_->bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, &radiance_behind_tx_);
gbuffer_double_sided_ps_->state_set(state);
gbuffer_single_sided_ps_ = &gbuffer_ps_.sub("SingleSided");
gbuffer_single_sided_ps_->bind_texture(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, &radiance_behind_tx_);
gbuffer_single_sided_ps_->state_set(state | DRW_STATE_CULL_BACK);
closure_bits_ = CLOSURE_NONE;
closure_count_ = 0;
radiance_behind_tx_ = nullptr;
}
void DeferredLayer::begin_sync()
@ -666,6 +697,8 @@ void DeferredLayer::end_sync(bool is_first_pass,
const bool is_layer_refracted = (next_layer_has_transmission && has_any_closure);
const bool has_transmit_closure = (closure_bits_ & (CLOSURE_REFRACTION | CLOSURE_TRANSLUCENT));
const bool has_reflect_closure = (closure_bits_ & (CLOSURE_REFLECTION | CLOSURE_DIFFUSE));
const bool has_transparent_shader_to_rgba = (closure_bits_ & CLOSURE_TRANSPARENCY) &&
(closure_bits_ & CLOSURE_SHADER_TO_RGBA);
use_raytracing_ = (has_transmit_closure || has_reflect_closure) &&
inst_.raytracing.use_raytracing();
use_clamp_direct_ = inst_.sampling.use_clamp_direct();
@ -675,7 +708,9 @@ void DeferredLayer::end_sync(bool is_first_pass,
/* The first pass will never have any surfaces behind it. Nothing is refracted except the
* environment. So in this case, disable tracing and fallback to probe. */
use_screen_transmission_ = use_raytracing_ && has_transmit_closure && !is_first_pass;
use_screen_transmission_ = use_raytracing_ &&
(has_transmit_closure || has_transparent_shader_to_rgba) &&
!is_first_pass;
use_screen_reflection_ = use_raytracing_ && has_reflect_closure;
use_feedback_output_ = (use_raytracing_ || is_layer_refracted) &&
@ -925,9 +960,11 @@ gpu::Texture *DeferredLayer::render(View &main_view,
gpu::Texture *radiance_behind_tx)
{
if (closure_count_ == 0) {
return nullptr;
return radiance_behind_tx;
}
radiance_behind_tx_ = radiance_behind_tx ? radiance_behind_tx : dummy_black;
RenderBuffers &rb = inst_.render_buffers;
constexpr eGPUTextureUsage usage_read = GPU_TEXTURE_USAGE_SHADER_READ;
@ -1013,11 +1050,6 @@ gpu::Texture *DeferredLayer::render(View &main_view,
void DeferredPipeline::begin_sync()
{
Instance &inst = opaque_layer_.inst_;
const bool use_raytracing = (inst.scene->eevee.flag & SCE_EEVEE_SSR_ENABLED) != 0;
use_combined_lightprobe_eval = !use_raytracing;
opaque_layer_.begin_sync();
refraction_layer_.begin_sync();
}
@ -1083,7 +1115,7 @@ PassMain::Sub *DeferredPipeline::prepass_add(::Material *blender_mat,
GPUMaterial *gpumat,
bool has_motion)
{
if (!use_combined_lightprobe_eval && (blender_mat->blend_flag & MA_BL_SS_REFRACTION)) {
if (blender_mat->blend_flag & MA_BL_SS_REFRACTION) {
return refraction_layer_.prepass_add(blender_mat, gpumat, has_motion);
}
return opaque_layer_.prepass_add(blender_mat, gpumat, has_motion);
@ -1091,7 +1123,7 @@ PassMain::Sub *DeferredPipeline::prepass_add(::Material *blender_mat,
PassMain::Sub *DeferredPipeline::material_add(::Material *blender_mat, GPUMaterial *gpumat)
{
if (!use_combined_lightprobe_eval && (blender_mat->blend_flag & MA_BL_SS_REFRACTION)) {
if (blender_mat->blend_flag & MA_BL_SS_REFRACTION) {
return refraction_layer_.material_add(blender_mat, gpumat);
}
return opaque_layer_.material_add(blender_mat, gpumat);
@ -1445,6 +1477,8 @@ void DeferredProbePipeline::render(View &view,
{
GPU_debug_group_begin("Probe.Render");
opaque_layer_.radiance_behind_tx_ = dummy_black;
GPU_framebuffer_bind(prepass_fb);
inst_.manager->submit(opaque_layer_.prepass_ps_, view);

View file

@ -16,6 +16,7 @@
#include "DRW_render.hh"
#include "eevee_defines.hh"
#include "eevee_lut.hh"
#include "eevee_material.hh"
#include "eevee_raytrace.hh"
@ -181,7 +182,7 @@ class ForwardPipeline {
} transp_buffer_;
public:
ForwardPipeline(Instance &inst) : inst_(inst) {};
ForwardPipeline(Instance &inst) : inst_(inst) {}
void sync();
void end_sync();
@ -227,6 +228,8 @@ struct DeferredLayerBase {
PassMain::Sub *gbuffer_single_sided_ps_ = nullptr;
PassMain::Sub *gbuffer_double_sided_ps_ = nullptr;
gpu::Texture *radiance_behind_tx_ = nullptr;
/* Closures bits from the materials in this pass. */
eClosureBits closure_bits_ = CLOSURE_NONE;
/* Maximum closure count considering all material in this pass. */
@ -362,7 +365,8 @@ class DeferredLayer : DeferredLayerBase {
bool has_transmission() const
{
return closure_bits_ & CLOSURE_TRANSMISSION;
return (closure_bits_ & CLOSURE_TRANSMISSION) ||
((closure_bits_ & CLOSURE_TRANSPARENCY) && (closure_bits_ & CLOSURE_SHADER_TO_RGBA));
}
/* Do we compute indirect lighting inside the light eval pass. */
@ -393,8 +397,6 @@ class DeferredPipeline {
PassSimple debug_draw_ps_ = {"debug_gbuffer"};
bool use_combined_lightprobe_eval;
public:
DeferredPipeline(Instance &inst)
: opaque_layer_(inst), refraction_layer_(inst), volumetric_layer_(inst) {};
@ -572,8 +574,16 @@ class DeferredProbePipeline {
PassSimple eval_light_ps_ = {"EvalLights"};
/* Used when there is no feedback radiance buffer. */
Texture dummy_black = {"dummy_black"};
public:
DeferredProbePipeline(Instance &inst) : inst_(inst) {};
DeferredProbePipeline(Instance &inst) : inst_(inst)
{
float4 data(0.0f);
dummy_black.ensure_2d(
gpu::TextureFormat::SFLOAT_16_16_16_16, int2(1), GPU_TEXTURE_USAGE_SHADER_READ, data);
}
void begin_sync();
void end_sync();

View file

@ -857,6 +857,17 @@ void ShaderModule::material_create_info_amend(GPUMaterial *gpumat, GPUCodegenOut
info.additional_info("eevee_cryptomatte_out");
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_SHADER_TO_RGBA)) {
info.define("MAT_SHADER_TO_RGBA");
}
if (ELEM(pipeline_type, MAT_PIPE_DEFERRED, MAT_PIPE_FORWARD) &&
GPU_material_flag_get(gpumat, GPU_MATFLAG_SHADER_TO_RGBA))
{
info.additional_info("eevee_hiz_prev_data");
info.additional_info("eevee_previous_layer_radiance");
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_DIFFUSE)) {
info.define("MAT_DIFFUSE");
}

View file

@ -38,6 +38,22 @@ float4 closure_to_rgba(Closure cl_unused)
float closure_rand = fract(noise + sampling_rng_1D_get(SAMPLING_CLOSURE));
closure_weights_reset(closure_rand);
#if defined(MAT_TRANSPARENT) && defined(MAT_SHADER_TO_RGBA)
float3 V = -drw_world_incident_vector(g_data.P);
LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V);
float3 radiance_behind = lightprobe_spherical_sample_normalized_with_parallax(
samp, g_data.P, V, 0.0);
# ifndef MAT_FIRST_LAYER
int2 texel = int2(gl_FragCoord.xy);
if (texelFetchExtend(hiz_prev_tx, texel, 0).x != 1.0f) {
radiance_behind = texelFetch(previous_layer_radiance_tx, texel, 0).xyz;
}
# endif
radiance += radiance_behind * saturate(transmittance);
#endif
return float4(radiance, saturate(1.0f - average(transmittance)));
}

View file

@ -40,6 +40,22 @@ float4 closure_to_rgba(Closure cl_unused)
float closure_rand = fract(noise + sampling_rng_1D_get(SAMPLING_CLOSURE));
closure_weights_reset(closure_rand);
#if defined(MAT_TRANSPARENT) && defined(MAT_SHADER_TO_RGBA)
float3 V = -drw_world_incident_vector(g_data.P);
LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V);
float3 radiance_behind = lightprobe_spherical_sample_normalized_with_parallax(
samp, g_data.P, V, 0.0);
# ifndef MAT_FIRST_LAYER
int2 texel = int2(gl_FragCoord.xy);
if (texelFetchExtend(hiz_prev_tx, texel, 0).x != 1.0f) {
radiance_behind = texelFetch(previous_layer_radiance_tx, texel, 0).xyz;
}
# endif
radiance += radiance_behind * saturate(transmittance);
#endif
return float4(radiance, saturate(1.0f - average(transmittance)));
}

View file

@ -36,6 +36,15 @@ SAMPLER(HIZ_TEX_SLOT, sampler2D, hiz_tx)
ADDITIONAL_INFO(eevee_global_ubo)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(eevee_hiz_prev_data)
SAMPLER(HIZ_PREVIOUS_LAYER_TEX_SLOT, sampler2D, hiz_prev_tx)
ADDITIONAL_INFO(eevee_global_ubo)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(eevee_previous_layer_radiance)
SAMPLER(RADIANCE_PREVIOUS_LAYER_TEX_SLOT, sampler2D, previous_layer_radiance_tx)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(eevee_utility_texture)
SAMPLER(RBUFS_UTILITY_TEX_SLOT, sampler2DArray, utility_tx)
GPU_SHADER_CREATE_END()
@ -51,9 +60,9 @@ GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(eevee_gbuffer_data)
DEFINE("GBUFFER_LOAD")
SAMPLER(12, usampler2DArray, gbuf_header_tx)
SAMPLER(13, sampler2DArray, gbuf_closure_tx)
SAMPLER(14, sampler2DArray, gbuf_normal_tx)
SAMPLER(11, usampler2DArray, gbuf_header_tx)
SAMPLER(14, sampler2DArray, gbuf_closure_tx)
SAMPLER(15, sampler2DArray, gbuf_normal_tx)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(eevee_render_pass_out)

View file

@ -49,6 +49,8 @@ FRAGMENT_SOURCE("eevee_surf_forward_frag.glsl")
/* Optionally added depending on the material. */
// ADDITIONAL_INFO(eevee_render_pass_out)
// ADDITIONAL_INFO(eevee_cryptomatte_out)
// ADDITIONAL_INFO(eevee_hiz_prev_data)
// ADDITIONAL_INFO(eevee_previous_layer_radiance)
ADDITIONAL_INFO(eevee_global_ubo)
ADDITIONAL_INFO(eevee_light_data)
ADDITIONAL_INFO(eevee_lightprobe_data)

View file

@ -33,4 +33,7 @@ ADDITIONAL_INFO(eevee_surf_deferred_base)
ADDITIONAL_INFO(eevee_light_data)
ADDITIONAL_INFO(eevee_lightprobe_data)
ADDITIONAL_INFO(eevee_shadow_data)
/* Optionally added depending on the material. */
// ADDITIONAL_INFO(eevee_hiz_prev_data)
// ADDITIONAL_INFO(eevee_previous_layer_radiance)
GPU_SHADER_CREATE_END()

View file

@ -184,6 +184,12 @@ RESHAPE(float3x3, mat3x3, mat3x4)
#define thread
#define threadgroup
float4 texelFetchExtend(sampler2D samp, int2 texel, int lvl)
{
texel = clamp(texel, int2(0), textureSize(samp, lvl).xy - 1);
return texelFetch(samp, texel, lvl);
}
/* Stage agnostic builtin function.
* GLSL doesn't allow mixing shader stages inside the same source file.
* Make sure builtin functions are stubbed when used in an invalid stage. */

View file

@ -103,6 +103,12 @@
#define _shared_sta (&
#define _shared_end )
float4 texelFetchExtend(sampler2D samp, int2 texel, int lvl)
{
texel = clamp(texel, int2(0), textureSize(samp, lvl).xy - 1);
return texelFetch(samp, texel, lvl);
}
/* Resource accessor. */
#define specialization_constant_get(create_info, _res) _res
#define shared_variable_get(create_info, _res) _res

View file

@ -107,6 +107,8 @@ float4 texelFetch(samplerBuffer, int) RET;
int4 texelFetch(isamplerBuffer, int) RET;
uint4 texelFetch(usamplerBuffer, int) RET;
float4 texelFetchExtend(sampler2D samp, int2 texel, int lvl) RET;
/** \} */
#undef RET

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:44e80108906894adc7928c5e5682746faaad776faea0110864ec44dfea2266a3
size 131665

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8dad9271062cb7a9e27e0d204c1a0f72e55bf326675cd7a639594a9a9fc59776
size 161383

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b459673d7d8d793285240b78eba12d4a989e5acd0829e9cb9a8ff9088908acd0
size 95771

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b0cb91715ca216cf3b9a5d4589ad21fbe7b37bd98e26b16ef6dc30852ee0bc7
size 28201

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c35ba2d18c8d16fe405ef55fbed7b269b4164676a9b76c1179dabdd013dd9eb5
size 108184

View file

@ -26,7 +26,8 @@ BLOCKLIST_ALL = [
"camera_depth_of_field_jittered.blend",
"shadow_resolution.blend",
"shadow_min_pool_size.blend",
"shadow_resolution_scale.blend"
"shadow_resolution_scale.blend",
"shader_to_rgb_transparent.blend",
]
# Blocklist for device + build configuration that does not support OSL at all.