EEVEE-Next: Add consistent support for thickness output

This add support for the thickness output in a consistent
manner across all BSDF.

If thickness is above zero, for any ray going below the
surface, the object is modeled as a sphere tangent to
the shading point and of diameter equal to the given
thickness. The ray is then intersected with that sphere
and transmitted out.

This model perfectly matches a raytracer behavior
for a sphere model of diameter equal to thickness.

This replaces the old refraction depth model which was
modelling a infinite slab of material.

For simplicity, we do not do 2 microfacet transmission
events. We simply use the main lobe direction for the
first interface. This still matches the raytracer
behavior for smooth surfaces. Weirdly enough the
apparent roughness doesn't need to be amended.

To get shadowing and translucency approximation
to work, this splits the transmission BSDF evaluation
to its own light loop. This simplifies a lot of logic
and assumes only one transmission BSDF is ever
sampled in a nodetree.

This changes the behavior of the thickness output
with regard to the thickness from shadow. We now take
the min instead of the max between both. This
break a lot of file since the default thickness is
set to `0.1`.

This patch doesn't change the default thickness output
behavior but it will be changed in another PR.

Note this might change the shadow sampling pattern
since translucent and non-translucent are now
sampling them separately.

Pull Request: https://projects.blender.org/blender/blender/pulls/120329
This commit is contained in:
Clément Foucault
2024-04-12 22:06:25 +02:00
committed by Clément Foucault
parent b24ac18130
commit 8dfcb121d4
34 changed files with 792 additions and 368 deletions

View File

@@ -613,6 +613,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_surfel_list_lib.glsl
engines/eevee_next/shaders/eevee_surfel_list_sort_comp.glsl
engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl
engines/eevee_next/shaders/eevee_thickness_amend_lib.glsl
engines/eevee_next/shaders/eevee_thickness_lib.glsl
engines/eevee_next/shaders/eevee_transparency_lib.glsl
engines/eevee_next/shaders/eevee_velocity_lib.glsl

View File

@@ -73,7 +73,6 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
float shape_power = shape_radiance_get();
float point_power = point_radiance_get();
this->power[LIGHT_DIFFUSE] = la->diff_fac * shape_power;
this->power[LIGHT_TRANSMIT] = la->diff_fac * point_power;
this->power[LIGHT_SPECULAR] = la->spec_fac * shape_power;
this->power[LIGHT_VOLUME] = la->volume_fac * point_power;

View File

@@ -1169,7 +1169,7 @@ void DeferredProbePipeline::begin_sync()
void DeferredProbePipeline::end_sync()
{
if (opaque_layer_.closure_bits_ & (CLOSURE_DIFFUSE | CLOSURE_REFLECTION)) {
{
PassSimple &pass = eval_light_ps_;
pass.init();
/* Use depth test to reject background pixels. */

View File

@@ -138,7 +138,9 @@ void RayTraceModule::sync()
{
PassSimple &pass = trace_fallback_ps_;
pass.init();
pass.shader_set(inst_.shaders.static_shader_get(RAY_TRACE_FALLBACK));
GPUShader *sh = inst_.shaders.static_shader_get(RAY_TRACE_FALLBACK);
pass.specialize_constant(sh, "closure_index", &data_.closure_index);
pass.shader_set(sh);
pass.bind_ssbo("tiles_coord_buf", &raytrace_tracing_tiles_buf_);
pass.bind_image("ray_data_img", &ray_data_tx_);
pass.bind_image("ray_time_img", &ray_time_tx_);
@@ -148,6 +150,7 @@ void RayTraceModule::sync()
pass.bind_resources(inst_.volume_probes);
pass.bind_resources(inst_.sphere_probes);
pass.bind_resources(inst_.sampling);
pass.bind_resources(inst_.gbuffer);
pass.dispatch(raytrace_tracing_dispatch_buf_);
pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS);
}

View File

@@ -397,10 +397,18 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
}
int32_t closure_data_slots = 0;
int32_t closure_extra_eval = 0;
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_DIFFUSE)) {
info.define("MAT_DIFFUSE");
closure_data_slots |= (1 << 0);
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSLUCENT) &&
!GPU_material_flag_get(gpumat, GPU_MATFLAG_COAT))
{
/* Special case to allow translucent with diffuse without noise.
* Revert back to noise if clear coat is present. */
closure_data_slots |= (1 << 2);
}
else {
closure_data_slots |= (1 << 0);
}
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_SUBSURFACE)) {
info.define("MAT_SUBSURFACE");
@@ -412,23 +420,19 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSLUCENT)) {
info.define("MAT_TRANSLUCENT");
closure_data_slots |= (1 << 1);
closure_data_slots |= (1 << 0);
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_GLOSSY)) {
info.define("MAT_REFLECTION");
closure_data_slots |= (1 << 2);
closure_data_slots |= (1 << 1);
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_COAT)) {
info.define("MAT_CLEARCOAT");
closure_data_slots |= (1 << 3);
closure_data_slots |= (1 << 2);
}
if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSLUCENT | GPU_MATFLAG_SUBSURFACE)) {
info.define("SHADOW_SUBSURFACE");
}
int32_t CLOSURE_BIN_COUNT = count_bits_i(closure_data_slots);
switch (CLOSURE_BIN_COUNT) {
int32_t closure_bin_count = count_bits_i(closure_data_slots);
switch (closure_bin_count) {
/* These need to be separated since the strings need to be static. */
case 0:
case 1:
@@ -446,7 +450,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
}
if (pipeline_type == MAT_PIPE_DEFERRED) {
switch (CLOSURE_BIN_COUNT) {
switch (closure_bin_count) {
/* These need to be separated since the strings need to be static. */
case 0:
case 1:
@@ -467,8 +471,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu
if ((pipeline_type == MAT_PIPE_FORWARD) ||
GPU_material_flag_get(gpumat, GPU_MATFLAG_SHADER_TO_RGBA))
{
int32_t lit_closure_count = CLOSURE_BIN_COUNT + closure_extra_eval;
switch (lit_closure_count) {
switch (closure_bin_count) {
case 0:
/* Define nothing. This will in turn define SKIP_LIGHT_EVAL. */
break;

View File

@@ -717,7 +717,6 @@ enum eLightType : uint32_t {
enum LightingType : uint32_t {
LIGHT_DIFFUSE = 0u,
LIGHT_SPECULAR = 1u,
LIGHT_TRANSMIT = 2u,
LIGHT_VOLUME = 3u,
};

View File

@@ -144,4 +144,34 @@ float refraction_roughness_remapping(float roughness, float ior)
}
}
/**
* `roughness` is expected to be the linear (from UI) roughess from.
*/
vec3 reflection_dominant_dir(vec3 N, vec3 V, float roughness)
{
/* From Frostbite PBR Course
* http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf
* Listing 22.
* Note that the reference labels squared roughness (GGX input) as roughness. */
float m = square(roughness);
vec3 R = -reflect(V, N);
float smoothness = 1.0 - m;
float fac = smoothness * (sqrt(smoothness) + m);
return normalize(mix(N, R, fac));
}
/**
* `roughness` is expected to be the reflection roughess from `refraction_roughness_remapping`.
*/
vec3 refraction_dominant_dir(vec3 N, vec3 V, float ior, float roughness)
{
/* Reusing same thing as reflection_dominant_dir for now with the roughness mapped to
* reflection roughness. */
float m = square(roughness);
vec3 R = refract(-V, N, 1.0 / ior);
float smoothness = 1.0 - m;
float fac = smoothness * (sqrt(smoothness) + m);
return normalize(mix(-N, R, fac));
}
/** \} */

View File

@@ -223,6 +223,30 @@ vec3 sample_cosine_hemisphere(vec3 rand, vec3 N, vec3 T, vec3 B, out float pdf)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cosine Hemisphere
* \{ */
float sample_pdf_uniform_sphere()
{
return 1.0 / (4.0 * M_PI);
}
vec3 sample_uniform_sphere(vec3 rand)
{
float cos_theta = rand.x * 2.0 - 1.0;
float sin_theta = safe_sqrt(1.0 - cos_theta * cos_theta);
return vec3(sin_theta * rand.yz, cos_theta);
}
vec3 sample_uniform_sphere(vec3 rand, vec3 N, vec3 T, vec3 B, out float pdf)
{
pdf = sample_pdf_uniform_sphere();
return mat3(T, B, N) * sample_uniform_sphere(rand);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Uniform Cone sampling
* \{ */

View File

@@ -24,30 +24,6 @@ void main()
return;
}
ClosureLightStack stack;
stack.cl[0].N = gbuf.surface_N;
stack.cl[0].ltc_mat = LTC_LAMBERT_MAT;
stack.cl[0].type = LIGHT_DIFFUSE;
stack.cl[1].N = -gbuf.surface_N;
stack.cl[1].ltc_mat = LTC_LAMBERT_MAT;
stack.cl[1].type = LIGHT_DIFFUSE;
vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth));
vec3 Ng = gbuf.surface_N;
vec3 V = drw_world_incident_vector(P);
float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position());
/* Direct light. */
light_eval(stack, P, Ng, V, vPz, gbuf.thickness);
/* Indirect light. */
/* Can only load irradiance to avoid dependency loop with the reflection probe. */
SphericalHarmonicL1 sh = lightprobe_irradiance_sample(P, V, Ng);
vec3 radiance_front = stack.cl[0].light_shadowed + spherical_harmonics_evaluate_lambert(Ng, sh);
vec3 radiance_back = stack.cl[1].light_shadowed + spherical_harmonics_evaluate_lambert(-Ng, sh);
vec3 albedo_front = vec3(0.0);
vec3 albedo_back = vec3(0.0);
@@ -61,7 +37,7 @@ void main()
break;
case CLOSURE_BSDF_TRANSLUCENT_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
albedo_back += cl.color;
albedo_back += (gbuf.thickness > 0.0) ? square(cl.color) : cl.color;
break;
case CLOSURE_NONE_ID:
/* TODO(fclem): Assert. */
@@ -69,5 +45,39 @@ void main()
}
}
vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth));
vec3 P_transmit = vec3(0.0);
vec3 Ng = gbuf.surface_N;
vec3 V = drw_world_incident_vector(P);
float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position());
ClosureUndetermined cl;
cl.N = gbuf.surface_N;
cl.type = CLOSURE_BSDF_DIFFUSE_ID;
ClosureUndetermined cl_transmit;
cl_transmit.N = gbuf.surface_N;
cl_transmit.type = CLOSURE_BSDF_TRANSLUCENT_ID;
/* Direct light. */
ClosureLightStack stack;
stack.cl[0] = closure_light_new(cl, V);
light_eval_reflection(stack, P, Ng, V, vPz);
vec3 radiance_front = stack.cl[0].light_shadowed;
stack.cl[0] = closure_light_new(cl_transmit, V, P, gbuf.thickness, P_transmit);
light_eval_transmission(stack, P_transmit, Ng, V, vPz);
vec3 radiance_back = stack.cl[0].light_shadowed;
/* Indirect light. */
/* Can only load irradiance to avoid dependency loop with the reflection probe. */
SphericalHarmonicL1 sh = lightprobe_irradiance_sample(P, V, Ng);
radiance_front += spherical_harmonics_evaluate_lambert(Ng, sh);
/* TODO(fclem): Correct transmission eval. */
radiance_back += spherical_harmonics_evaluate_lambert(-Ng, sh);
out_radiance = vec4(radiance_front * albedo_front + radiance_back * albedo_back, 0.0);
}

View File

@@ -85,6 +85,14 @@ void main()
break;
}
if ((cl.type == CLOSURE_BSDF_TRANSLUCENT_ID ||
cl.type == CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID) &&
(gbuf.thickness > 0.0))
{
/* We model two transmission event, so the surface color need to be applied twice. */
cl.color *= cl.color;
}
closure_light *= cl.color;
out_combined.rgb += closure_light;
}

View File

@@ -11,9 +11,10 @@
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_renderpass_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_subsurface_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_subsurface_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_thickness_amend_lib.glsl)
void main()
{
@@ -32,36 +33,47 @@ void main()
stack.cl[i] = closure_light_new(gbuffer_closure_get(gbuf, i), V);
}
/* TODO(fclem): Split thickness computation. */
float thickness = gbuf.thickness;
#ifdef MAT_SUBSURFACE
/* NOTE: BSSRDF is supposed to always be the first closure. */
bool has_sss = gbuffer_closure_get(gbuf, 0).type == CLOSURE_BSSRDF_BURLEY_ID;
if (has_sss) {
/* TODO(fclem): If transmission (no SSS) is present, we could reduce LIGHT_CLOSURE_EVAL_COUNT
* by 1 for this evaluaiton and skip evaluating the transmission closure twice. */
light_eval_reflection(stack, P, Ng, V, vPz);
#if 1 /* TODO Limit to transmission. Can bypass the check if stencil is tagged properly and use \
specialization constant. */
ClosureUndetermined cl_transmit = gbuffer_closure_get(gbuf, 0);
if ((cl_transmit.type == CLOSURE_BSDF_TRANSLUCENT_ID) ||
(cl_transmit.type == CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID) ||
(cl_transmit.type == CLOSURE_BSSRDF_BURLEY_ID))
{
float shadow_thickness = thickness_from_shadow(P, Ng, vPz);
thickness = (shadow_thickness != THICKNESS_NO_VALUE) ? max(shadow_thickness, gbuf.thickness) :
gbuf.thickness;
gbuf.thickness = (shadow_thickness != THICKNESS_NO_VALUE) ?
min(shadow_thickness, gbuf.thickness) :
gbuf.thickness;
/* Add one translucent closure for all SSS closure. Reuse the same lighting. */
ClosureLight cl_light;
cl_light.N = -Ng;
cl_light.ltc_mat = LTC_LAMBERT_MAT;
cl_light.type = LIGHT_DIFFUSE;
cl_light.subsurface = true;
stack.cl[gbuf.closure_count] = cl_light;
}
#endif
# if 1 /* TODO Limit to SSS. */
vec3 sss_reflect_shadowed, sss_reflect_unshadowed;
if (cl_transmit.type == CLOSURE_BSSRDF_BURLEY_ID) {
sss_reflect_shadowed = stack.cl[0].light_shadowed;
sss_reflect_unshadowed = stack.cl[0].light_unshadowed;
}
# endif
light_eval(stack, P, Ng, V, vPz, thickness);
vec3 P_transmit = vec3(0.0);
stack.cl[0] = closure_light_new(cl_transmit, V, P, gbuf.thickness, P_transmit);
#ifdef MAT_SUBSURFACE
if (has_sss) {
/* Add to diffuse light for processing inside the Screen Space SSS pass.
* The translucent light is not outputted as a separate quantity because
* it is over the closure_count. */
vec3 sss_profile = subsurface_transmission(gbuffer_closure_get(gbuf, 0).data.rgb, thickness);
stack.cl[0].light_shadowed += stack.cl[gbuf.closure_count].light_shadowed * sss_profile;
stack.cl[0].light_unshadowed += stack.cl[gbuf.closure_count].light_unshadowed * sss_profile;
/* Note: Only evaluates `stack.cl[0]`. */
light_eval_transmission(stack, P_transmit, Ng, V, vPz);
# if 1 /* TODO Limit to SSS. */
if (cl_transmit.type == CLOSURE_BSSRDF_BURLEY_ID) {
/* Apply transmission profile onto transmitted light and sum with reflected light. */
vec3 sss_profile = subsurface_transmission(to_closure_subsurface(cl_transmit).sss_radius,
gbuf.thickness);
stack.cl[0].light_shadowed *= sss_profile;
stack.cl[0].light_unshadowed *= sss_profile;
stack.cl[0].light_shadowed += sss_reflect_shadowed;
stack.cl[0].light_unshadowed += sss_reflect_unshadowed;
}
# endif
}
#endif
@@ -82,7 +94,7 @@ void main()
for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT && i < gbuf.closure_count; i++) {
ClosureUndetermined cl = gbuffer_closure_get(gbuf, i);
lightprobe_eval(samp, cl, g_data.P, V, stack.cl[i].light_shadowed);
lightprobe_eval(samp, cl, g_data.P, V, gbuf.thickness, stack.cl[i].light_shadowed);
}
}

View File

@@ -19,28 +19,6 @@ void main()
GBufferReader gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel);
vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth));
vec3 Ng = gbuf.surface_N;
vec3 V = drw_world_incident_vector(P);
float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position());
ClosureLightStack stack;
stack.cl[0].N = gbuf.surface_N;
stack.cl[0].ltc_mat = LTC_LAMBERT_MAT;
stack.cl[0].type = LIGHT_DIFFUSE;
stack.cl[1].N = -gbuf.surface_N;
stack.cl[1].ltc_mat = LTC_LAMBERT_MAT;
stack.cl[1].type = LIGHT_DIFFUSE;
/* Direct light. */
light_eval(stack, P, Ng, V, vPz, gbuf.thickness);
/* Indirect light. */
SphericalHarmonicL1 sh = lightprobe_irradiance_sample(P, V, Ng);
vec3 radiance_front = stack.cl[0].light_shadowed + spherical_harmonics_evaluate_lambert(Ng, sh);
vec3 radiance_back = stack.cl[1].light_shadowed + spherical_harmonics_evaluate_lambert(-Ng, sh);
vec3 albedo_front = vec3(0.0);
vec3 albedo_back = vec3(0.0);
@@ -54,7 +32,7 @@ void main()
break;
case CLOSURE_BSDF_TRANSLUCENT_ID:
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
albedo_back += cl.color;
albedo_back += (gbuf.thickness > 0.0) ? square(cl.color) : cl.color;
break;
case CLOSURE_NONE_ID:
/* TODO(fclem): Assert. */
@@ -62,5 +40,37 @@ void main()
}
}
vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth));
vec3 P_transmit = vec3(0.0);
vec3 Ng = gbuf.surface_N;
vec3 V = drw_world_incident_vector(P);
float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position());
ClosureUndetermined cl;
cl.N = gbuf.surface_N;
cl.type = CLOSURE_BSDF_DIFFUSE_ID;
ClosureUndetermined cl_transmit;
cl_transmit.N = gbuf.surface_N;
cl_transmit.type = CLOSURE_BSDF_TRANSLUCENT_ID;
/* Direct light. */
ClosureLightStack stack;
stack.cl[0] = closure_light_new(cl, V);
light_eval_reflection(stack, P, Ng, V, vPz);
vec3 radiance_front = stack.cl[0].light_shadowed;
stack.cl[0] = closure_light_new(cl_transmit, V, P, gbuf.thickness, P_transmit);
light_eval_transmission(stack, P_transmit, Ng, V, vPz);
vec3 radiance_back = stack.cl[0].light_shadowed;
/* Indirect light. */
SphericalHarmonicL1 sh = lightprobe_irradiance_sample(P, V, Ng);
radiance_front += spherical_harmonics_evaluate_lambert(Ng, sh);
radiance_back += spherical_harmonics_evaluate_lambert(-Ng, sh);
out_radiance = vec4(radiance_front * albedo_front + radiance_back * albedo_back, 0.0);
}

View File

@@ -27,8 +27,40 @@ void forward_lighting_eval(float thickness, out vec3 radiance, out vec3 transmit
stack.cl[i] = closure_light_new(g_closure_get(i), V);
}
#ifndef SKIP_LIGHT_EVAL
light_eval(stack, g_data.P, g_data.Ng, V, vPz, thickness);
/* TODO(fclem): If transmission (no SSS) is present, we could reduce LIGHT_CLOSURE_EVAL_COUNT
* by 1 for this evaluaiton and skip evaluating the transmission closure twice. */
light_eval_reflection(stack, g_data.P, g_data.Ng, V, vPz);
#if defined(MAT_SUBSURFACE) || defined(MAT_REFRACTION) || defined(MAT_TRANSLUCENT)
ClosureUndetermined cl_transmit = g_closure_get(0);
if (cl_transmit.type != CLOSURE_NONE) {
# if defined(MAT_SUBSURFACE)
vec3 sss_reflect_shadowed, sss_reflect_unshadowed;
if (cl_transmit.type == CLOSURE_BSSRDF_BURLEY_ID) {
sss_reflect_shadowed = stack.cl[0].light_shadowed;
sss_reflect_unshadowed = stack.cl[0].light_unshadowed;
}
# endif
vec3 P_transmit = vec3(0.0);
stack.cl[0] = closure_light_new(cl_transmit, V, g_data.P, thickness, P_transmit);
/* Note: Only evaluates `stack.cl[0]`. */
light_eval_transmission(stack, P_transmit, g_data.Ng, V, vPz);
# if defined(MAT_SUBSURFACE)
if (cl_transmit.type == CLOSURE_BSSRDF_BURLEY_ID) {
/* Apply transmission profile onto transmitted light and sum with reflected light. */
vec3 sss_profile = subsurface_transmission(to_closure_subsurface(cl_transmit).sss_radius,
gbuf.thickness);
stack.cl[0].light_shadowed *= sss_profile;
stack.cl[0].light_unshadowed *= sss_profile;
stack.cl[0].light_shadowed += sss_reflect_shadowed;
stack.cl[0].light_unshadowed += sss_reflect_unshadowed;
}
# endif
}
#endif
LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V);
@@ -37,8 +69,15 @@ void forward_lighting_eval(float thickness, out vec3 radiance, out vec3 transmit
radiance = g_emission;
for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT; i++) {
ClosureUndetermined cl = g_closure_get(i);
lightprobe_eval(samp, cl, g_data.P, V, stack.cl[i].light_shadowed);
lightprobe_eval(samp, cl, g_data.P, V, thickness, stack.cl[i].light_shadowed);
if (cl.weight > 1e-5) {
if ((cl.type == CLOSURE_BSDF_TRANSLUCENT_ID ||
cl.type == CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID) &&
(thickness > 0.0))
{
/* We model two transmission event, so the surface color need to be applied twice. */
stack.cl[i].light_shadowed *= cl.color;
}
radiance += stack.cl[i].light_shadowed * cl.color * cl.weight;
}
}

View File

@@ -858,6 +858,19 @@ int gbuffer_closure_count(uint header)
return reduce_add(ivec3(not(equal(closure_types, uvec3(0u)))));
}
/* Return the number of normal layer as encoded in the given header value. */
int gbuffer_normal_count(uint header)
{
if (header == 0u) {
return 0;
}
/* Count implicit first layer. */
uint count = 1u;
count += uint(((header >> 12u) & 3u) != 0);
count += uint(((header >> 14u) & 3u) != 0);
return int(count);
}
/* Return the type of a closure using its bin index. */
ClosureType gbuffer_closure_type_get_by_bin(uint header, int bin_index)
{
@@ -994,7 +1007,7 @@ GBufferReader gbuffer_read(samplerGBufferHeader header_tx,
}
/* Read only one bin from the GBuffer. */
ClosureUndetermined gbuffer_read_bin(samplerGBufferHeader header_tx,
ClosureUndetermined gbuffer_read_bin(uint header,
samplerGBufferClosure closure_tx,
samplerGBufferNormal normal_tx,
ivec2 texel,
@@ -1005,7 +1018,7 @@ ClosureUndetermined gbuffer_read_bin(samplerGBufferHeader header_tx,
gbuf.closure_count = 0;
gbuf.data_len = 0;
gbuf.normal_len = 0;
gbuf.header = fetchGBuffer(header_tx, texel);
gbuf.header = header;
if (gbuf.header == 0u) {
return closure_new(CLOSURE_NONE_ID);
@@ -1047,7 +1060,6 @@ ClosureUndetermined gbuffer_read_bin(samplerGBufferHeader header_tx,
}
}
bool has_additional_data = false;
switch (mode) {
default:
case GBUF_NONE:
@@ -1078,5 +1090,37 @@ ClosureUndetermined gbuffer_read_bin(samplerGBufferHeader header_tx,
return gbuffer_closure_get(gbuf, gbuf.closure_count);
}
ClosureUndetermined gbuffer_read_bin(samplerGBufferHeader header_tx,
samplerGBufferClosure closure_tx,
samplerGBufferNormal normal_tx,
ivec2 texel,
int bin_index)
{
return gbuffer_read_bin(fetchGBuffer(header_tx, texel), closure_tx, normal_tx, texel, bin_index);
}
/* Load thickness data only if available. Return 0 otherwise. */
float gbuffer_read_thickness(uint header, samplerGBufferNormal normal_tx, ivec2 texel)
{
/* WATCH: Assumes all closures needing additional data are in first bin. */
switch (gbuffer_closure_type_get_by_bin(header, 0)) {
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
case CLOSURE_BSDF_TRANSLUCENT_ID:
case CLOSURE_BSSRDF_BURLEY_ID: {
int normal_len = gbuffer_normal_count(header);
vec2 data_packed = fetchGBuffer(normal_tx, texel, normal_len).rg;
return gbuffer_thickness_unpack(data_packed.x);
}
default:
return 0.0;
}
}
/* Returns the first world normal stored in the gbuffer. Assume gbuffer header is non-null. */
vec3 gbuffer_read_normal(samplerGBufferNormal normal_tx, ivec2 texel)
{
vec2 normal_packed = fetchGBuffer(normal_tx, texel, 0).rg;
return gbuffer_normal_unpack(normal_packed);
}
/** \} */

View File

@@ -141,13 +141,23 @@ void main()
vec3 L;
switch (cl.type) {
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID:
L = lightprobe_reflection_dominant_dir(cl.N, V, roughness);
L = reflection_dominant_dir(cl.N, V, roughness);
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
L = lightprobe_refraction_dominant_dir(cl.N, V, to_closure_refraction(cl).ior, roughness);
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
float ior = to_closure_refraction(cl).ior;
if (gbuf.thickness > 0.0) {
vec3 L = refraction_dominant_dir(cl.N, V, ior, roughness);
cl.N = -thickness_sphere_intersect(gbuf.thickness, cl.N, L).hit_N;
ior = 1.0 / ior;
V = -L;
}
L = refraction_dominant_dir(cl.N, V, ior, roughness);
break;
}
case CLOSURE_BSDF_TRANSLUCENT_ID:
L = -N;
/* Translucent BSDF with thickness is modelled as uniform sphere distribution which drops
* all the directional terms. */
L = (gbuf.thickness > 0.0) ? vec3(0.0) : -N;
break;
default:
L = N;
@@ -156,7 +166,6 @@ void main()
vec3 vL = drw_normal_world_to_view(L);
/* Evaluate lighting from horizon scan. */
/* TODO(fclem): Evaluate depending on BSDF. */
vec3 radiance = spherical_harmonics_evaluate_lambert(vL, accum_sh);
/* Evaluate visibility from horizon scan. */

View File

@@ -37,7 +37,9 @@ void main()
LightData light = light_buf[l_idx];
LightVector lv = light_vector_get(light, false, P);
/* Use light vector as Ng to never cull based on angle to light. */
if (light_attenuation_surface(light, false, lv.L, lv).x > LIGHT_ATTENUATION_THRESHOLD) {
if (light_attenuation_surface(light, false, false, false, lv.L, lv) >
LIGHT_ATTENUATION_THRESHOLD)
{
light_nocull |= 1u << l_idx;
}
}

View File

@@ -17,6 +17,7 @@
#pragma BLENDER_REQUIRE(eevee_bxdf_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
/* If using compute, the shader should define its own pixel. */
@@ -41,9 +42,9 @@ float shadow_unpack(uint shadow_bits, uint bit_depth, uint shift)
void light_shadow_single(uint l_idx,
const bool is_directional,
const bool is_transmission,
vec3 P,
vec3 Ng,
float thickness,
inout uint shadow_bits,
inout uint shift)
{
@@ -54,8 +55,9 @@ void light_shadow_single(uint l_idx,
}
LightVector lv = light_vector_get(light, is_directional, P);
vec2 attenuation = light_attenuation_surface(light, is_directional, Ng, lv);
if (reduce_max(attenuation) < LIGHT_ATTENUATION_THRESHOLD) {
float attenuation = light_attenuation_surface(
light, is_directional, is_transmission, false, Ng, lv);
if (attenuation < LIGHT_ATTENUATION_THRESHOLD) {
return;
}
@@ -68,13 +70,13 @@ void light_shadow_single(uint l_idx,
#endif
ShadowEvalResult result = shadow_eval(
light, is_directional, P, Ng, thickness, ray_count, ray_step_count);
light, is_directional, is_transmission, P, Ng, ray_count, ray_step_count);
shadow_bits |= shadow_pack(result.light_visibilty, ray_count, shift);
shift += ray_count;
}
void light_shadow_mask(vec3 P, vec3 Ng, float vPz, float thickness, out uint shadow_bits)
void light_shadow_mask(vec3 P, vec3 Ng, float vPz, out uint shadow_bits)
{
int ray_count = uniform_buf.shadow.ray_count;
int ray_step_count = uniform_buf.shadow.step_count;
@@ -82,44 +84,85 @@ void light_shadow_mask(vec3 P, vec3 Ng, float vPz, float thickness, out uint sha
uint shift = 0u;
shadow_bits = 0u;
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
light_shadow_single(l_idx, true, P, Ng, thickness, shadow_bits, shift);
light_shadow_single(l_idx, true, false, P, Ng, shadow_bits, shift);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, PIXEL, vPz, l_idx) {
light_shadow_single(l_idx, false, P, Ng, thickness, shadow_bits, shift);
light_shadow_single(l_idx, false, false, P, Ng, shadow_bits, shift);
}
LIGHT_FOREACH_END
}
struct ClosureLight {
/* Shading normal. */
vec3 N;
/* LTC matrix. */
vec4 ltc_mat;
/* Enum (used as index) telling how to treat the lighting. */
/* Shading normal. */
vec3 N;
/* Enum used as index to fetch which light intensity to use [0..3]. */
LightingType type;
/* True if closure is receiving light from below the surface. */
bool subsurface;
/* Is a translucent BSDF with thickness greater than 0. */
bool is_translucent_with_thickness;
/* Output both shadowed and unshadowed for shadow denoising. */
vec3 light_shadowed;
vec3 light_unshadowed;
};
ClosureLight closure_light_new(ClosureUndetermined cl, vec3 V)
struct ClosureLightStack {
/* NOTE: This is wrapped into a struct to avoid array shenanigans on MSL. */
ClosureLight cl[LIGHT_CLOSURE_EVAL_COUNT];
};
ClosureLight closure_light_new_ex(ClosureUndetermined cl,
vec3 V,
vec3 P,
float thickness,
const bool is_transmission,
out vec3 out_P)
{
ClosureLight cl_light;
cl_light.N = cl.N;
cl_light.ltc_mat = LTC_LAMBERT_MAT;
cl_light.type = LIGHT_DIFFUSE;
cl_light.light_shadowed = vec3(0.0);
cl_light.subsurface = false;
cl_light.light_unshadowed = vec3(0.0);
cl_light.is_translucent_with_thickness = false;
switch (cl.type) {
case CLOSURE_BSDF_TRANSLUCENT_ID:
cl_light.N = -cl.N;
cl_light.subsurface = true;
if (is_transmission) {
cl_light.N = -cl.N;
cl_light.type = LIGHT_DIFFUSE;
out_P = P;
if (thickness > 0.0) {
vec2 random_2d = pcg3d(P).xy;
#ifdef EEVEE_SAMPLING_DATA
random_2d = fract(random_2d + sampling_rng_2D_get(SAMPLING_SHADOW_X));
#endif
float radius = thickness * 0.5;
/* Store random shadow position inside the normal since it has no effect. */
cl_light.N = vec3(sample_disk(random_2d) * radius, radius);
/* Strangely, a translucent sphere lit by a light outside the sphere transmits the light
* uniformly over the sphere. To mimic this phenomenon, we shift the shading position to
* a unique position on the sphere and use the light vector as normal. */
out_P -= cl.N * radius;
cl_light.is_translucent_with_thickness = true;
}
}
break;
case CLOSURE_BSSRDF_BURLEY_ID:
if (is_transmission) {
/* If the `thickness / sss_radius` ratio is near 0, this transmission term should converge
* to a uniform term like the translucent BSDF. But we need to find what to do in other
* cases. For now, approximate the transmission term as just backfacing. */
cl_light.N = -cl.N;
cl_light.type = LIGHT_DIFFUSE;
/* Lit and shadow as outside of the object. */
out_P = P - cl.N * thickness;
break;
}
/* Reflection term uses the lambertian diffuse. */
break;
case CLOSURE_BSDF_DIFFUSE_ID:
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID:
@@ -127,13 +170,27 @@ ClosureLight closure_light_new(ClosureUndetermined cl, vec3 V)
cl_light.type = LIGHT_SPECULAR;
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
ClosureRefraction cl_refract = to_closure_refraction(cl);
vec3 R = refract(-V, cl.N, 1.0 / cl_refract.ior);
float roughness = refraction_roughness_remapping(cl_refract.roughness, cl_refract.ior);
cl_light.ltc_mat = LTC_GGX_MAT(dot(-cl.N, R), roughness);
cl_light.N = -cl.N;
cl_light.type = LIGHT_SPECULAR;
cl_light.subsurface = true;
if (is_transmission) {
out_P = P;
ClosureRefraction cl_refract = to_closure_refraction(cl);
cl_refract.roughness = refraction_roughness_remapping(cl_refract.roughness,
cl_refract.ior);
if (thickness > 0.0) {
vec3 L = refraction_dominant_dir(cl.N, V, cl_refract.ior, cl_refract.roughness);
ThicknessIsect isect = thickness_sphere_intersect(thickness, cl.N, L);
cl.N = -isect.hit_N;
out_P += isect.hit_P;
cl_refract.ior = 1.0 / cl_refract.ior;
V = -L;
}
vec3 R = refract(-V, cl.N, 1.0 / cl_refract.ior);
cl_light.ltc_mat = LTC_GGX_MAT(dot(-cl.N, R), cl_refract.roughness);
cl_light.N = -cl.N;
cl_light.type = LIGHT_SPECULAR;
}
break;
}
case CLOSURE_NONE_ID:
@@ -143,37 +200,42 @@ ClosureLight closure_light_new(ClosureUndetermined cl, vec3 V)
return cl_light;
}
struct ClosureLightStack {
/* NOTE: This is wrapped into a struct to avoid array shenanigans on MSL. */
ClosureLight cl[LIGHT_CLOSURE_EVAL_COUNT];
};
ClosureLight closure_light_new(
ClosureUndetermined cl, vec3 V, vec3 P, float thickness, out vec3 out_P)
{
return closure_light_new_ex(cl, V, P, thickness, true, out_P);
}
ClosureLight closure_light_new(ClosureUndetermined cl, vec3 V)
{
vec3 unused_P = vec3(0.0), unused_out_P = vec3(0.0);
return closure_light_new_ex(cl, V, unused_P, 0.0, false, unused_out_P);
}
void light_eval_single_closure(LightData light,
LightVector lv,
inout ClosureLight cl,
vec3 P,
vec3 V,
float thickness,
vec2 attenuation,
float shadow)
float attenuation,
float shadow,
const bool is_transmission)
{
if (light.power[cl.type] > 0.0) {
float ltc_result = light_ltc(utility_tx, light, cl.N, V, lv, cl.ltc_mat);
vec3 out_radiance = light.color * light.power[cl.type] * ltc_result;
float attenuation_sided = (cl.subsurface) ? attenuation.y : attenuation.x;
float visibility = shadow * attenuation_sided;
float visibility = shadow * attenuation;
cl.light_shadowed += visibility * out_radiance;
cl.light_unshadowed += attenuation_sided * out_radiance;
cl.light_unshadowed += attenuation * out_radiance;
}
}
void light_eval_single(uint l_idx,
const bool is_directional,
const bool is_transmission,
inout ClosureLightStack stack,
vec3 P,
vec3 Ng,
vec3 V,
float thickness,
uint packed_shadows,
inout uint shift)
{
@@ -187,69 +249,86 @@ void light_eval_single(uint l_idx,
int ray_step_count = uniform_buf.shadow.step_count;
#endif
bool use_subsurface = thickness > 0.0;
LightVector lv = light_vector_get(light, is_directional, P);
vec2 attenuation = light_attenuation_surface(light, is_directional, Ng, lv);
if (reduce_max(attenuation) < LIGHT_ATTENUATION_THRESHOLD) {
bool is_translucent_with_thickness = is_transmission &&
stack.cl[0].is_translucent_with_thickness;
float attenuation = light_attenuation_surface(
light, is_directional, is_transmission, is_translucent_with_thickness, Ng, lv);
if (attenuation < LIGHT_ATTENUATION_THRESHOLD) {
return;
}
float shadow = 1.0;
if (light.tilemap_index != LIGHT_NO_SHADOW) {
#ifdef SHADOW_DEFERRED
shadow = shadow_unpack(packed_shadows, ray_count, shift);
shift += ray_count;
#else
if (is_translucent_with_thickness) {
/* Translucent doesn't use the normal for shading.
* Instead it stores the random shadow position offsets. */
P += from_up_axis(lv.L) * stack.cl[0].N;
}
ShadowEvalResult result = shadow_eval(
light, is_directional, P, Ng, thickness, ray_count, ray_step_count);
light, is_directional, is_transmission, P, Ng, ray_count, ray_step_count);
shadow = result.light_visibilty;
#endif
}
if (is_translucent_with_thickness) {
/* This makes the LTC compute the solid angle of the light (still with the cosine term applied
* but that still works great enough in practice). */
stack.cl[0].N = lv.L;
/* Adjust power because of the second lambertian distribution. */
attenuation *= M_1_PI;
}
/* WATCH(@fclem): Might have to manually unroll for best performance. */
for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT; i++) {
light_eval_single_closure(light, lv, stack.cl[i], P, V, thickness, attenuation, shadow);
for (int i = 0; i < (is_transmission ? 1 : LIGHT_CLOSURE_EVAL_COUNT); i++) {
light_eval_single_closure(light, lv, stack.cl[i], V, attenuation, shadow, is_transmission);
}
}
void light_eval(inout ClosureLightStack stack,
vec3 P,
vec3 Ng,
vec3 V,
float vPz,
float thickness,
uint packed_shadows)
void light_eval_transmission(inout ClosureLightStack stack, vec3 P, vec3 Ng, vec3 V, float vPz)
{
for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT; i++) {
stack.cl[i].light_shadowed = vec3(0.0);
stack.cl[i].light_unshadowed = vec3(0.0);
}
#ifdef SKIP_LIGHT_EVAL
return;
#endif
/* Packed / Decoupled shadow evaluation. Not yet implemented. */
uint packed_shadows = 0u;
uint shift = 0u;
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
light_eval_single(l_idx, true, stack, P, Ng, V, thickness, packed_shadows, shift);
light_eval_single(l_idx, true, true, stack, P, Ng, V, packed_shadows, shift);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, PIXEL, vPz, l_idx) {
light_eval_single(l_idx, false, stack, P, Ng, V, thickness, packed_shadows, shift);
light_eval_single(l_idx, false, true, stack, P, Ng, V, packed_shadows, shift);
}
LIGHT_FOREACH_END
}
/* Variations that have less arguments. */
#if !defined(SHADOW_DEFERRED)
void light_eval(inout ClosureLightStack stack, vec3 P, vec3 Ng, vec3 V, float vPz, float thickness)
void light_eval_reflection(inout ClosureLightStack stack, vec3 P, vec3 Ng, vec3 V, float vPz)
{
light_eval(stack, P, Ng, V, vPz, thickness, 0u);
}
# if !defined(SHADOW_SUBSURFACE) && defined(LIGHT_ITER_FORCE_NO_CULLING)
void light_eval(inout ClosureLightStack stack, vec3 P, vec3 Ng, vec3 V)
{
light_eval(stack, P, Ng, V, 0.0, 0.0, 0u);
}
# endif
#ifdef SKIP_LIGHT_EVAL
return;
#endif
/* Packed / Decoupled shadow evaluation. Not yet implemented. */
uint packed_shadows = 0u;
uint shift = 0u;
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
light_eval_single(l_idx, true, false, stack, P, Ng, V, packed_shadows, shift);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, PIXEL, vPz, l_idx) {
light_eval_single(l_idx, false, false, stack, P, Ng, V, packed_shadows, shift);
}
LIGHT_FOREACH_END
}

View File

@@ -125,8 +125,18 @@ float light_attenuation_common(LightData light, const bool is_directional, vec3
* L is normalized vector to light shape center.
* Ng is ideally the geometric normal.
*/
vec2 light_attenuation_facing(LightData light, vec3 L, float distance_to_light, vec3 Ng)
float light_attenuation_facing(LightData light,
vec3 L,
float distance_to_light,
vec3 Ng,
const bool is_transmission,
bool is_translucent_with_thickness)
{
if (is_translucent_with_thickness) {
/* No attenuation in this case since we integrate the whole sphere. */
return 1.0;
}
float radius;
if (is_sun_light(light.type)) {
radius = light_sun_data_get(light).radius;
@@ -143,13 +153,19 @@ vec2 light_attenuation_facing(LightData light, vec3 L, float distance_to_light,
float sin_light_angle = dot(L, Ng);
/* Do attenuation after the horizon line to avoid harsh cut
* or biasing of surfaces without light bleeding. */
/* Compute for both front facing and back-facing. */
return saturate((vec2(sin_light_angle, -sin_light_angle) + sin_solid_angle + 0.1) * 10.0);
float dist = sin_solid_angle + (is_transmission ? -sin_light_angle : sin_light_angle);
return saturate((dist + 0.1) * 10.0);
}
vec2 light_attenuation_surface(LightData light, const bool is_directional, vec3 Ng, LightVector lv)
float light_attenuation_surface(LightData light,
const bool is_directional,
const bool is_transmission,
bool is_translucency_with_thickness,
vec3 Ng,
LightVector lv)
{
vec2 result = light_attenuation_facing(light, lv.L, lv.dist, Ng);
float result = light_attenuation_facing(
light, lv.L, lv.dist, Ng, is_transmission, is_translucency_with_thickness);
result *= light_attenuation_common(light, is_directional, lv.L);
if (!is_directional) {
result *= light_influence_attenuation(

View File

@@ -12,6 +12,7 @@
#pragma BLENDER_REQUIRE(eevee_lightprobe_volume_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl)
#ifdef SPHERE_PROBE
@@ -92,28 +93,18 @@ vec3 lightprobe_eval(LightProbeSample samp, ClosureDiffuse cl, vec3 P, vec3 V)
return radiance_sh;
}
vec3 lightprobe_eval(LightProbeSample samp, ClosureTranslucent cl, vec3 P, vec3 V)
vec3 lightprobe_eval(LightProbeSample samp, ClosureTranslucent cl, vec3 P, vec3 V, float thickness)
{
if (thickness > 0.0) {
return spherical_harmonics_L0_evaluate(-cl.N, samp.volume_irradiance.L0).rgb;
}
vec3 radiance_sh = spherical_harmonics_evaluate_lambert(-cl.N, samp.volume_irradiance);
return radiance_sh;
}
vec3 lightprobe_reflection_dominant_dir(vec3 N, vec3 V, float roughness)
{
/* From Frostbite PBR Course
* http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf
* Listing 22.
* Note that the reference labels squared roughness (GGX input) as roughness. */
float m = square(roughness);
vec3 R = -reflect(V, N);
float smoothness = 1.0 - m;
float fac = smoothness * (sqrt(smoothness) + m);
return normalize(mix(N, R, fac));
}
vec3 lightprobe_eval(LightProbeSample samp, ClosureReflection reflection, vec3 P, vec3 V)
{
vec3 L = lightprobe_reflection_dominant_dir(reflection.N, V, reflection.roughness);
vec3 L = reflection_dominant_dir(reflection.N, V, reflection.roughness);
float lod = sphere_probe_roughness_to_lod(reflection.roughness);
vec3 radiance_cube = lightprobe_spherical_sample_normalized_with_parallax(
@@ -124,39 +115,40 @@ vec3 lightprobe_eval(LightProbeSample samp, ClosureReflection reflection, vec3 P
return mix(radiance_cube, radiance_sh, fac);
}
vec3 lightprobe_refraction_dominant_dir(vec3 N, vec3 V, float ior, float roughness)
vec3 lightprobe_eval(LightProbeSample samp, ClosureRefraction cl, vec3 P, vec3 V, float thickness)
{
/* Reusing same thing as lightprobe_reflection_dominant_dir for now with the roughness mapped to
* reflection roughness. */
float m = square(roughness);
vec3 R = refract(-V, N, 1.0 / ior);
float smoothness = 1.0 - m;
float fac = smoothness * (sqrt(smoothness) + m);
return normalize(mix(-N, R, fac));
}
cl.roughness = refraction_roughness_remapping(cl.roughness, cl.ior);
vec3 lightprobe_eval(LightProbeSample samp, ClosureRefraction cl, vec3 P, vec3 V)
{
float effective_roughness = refraction_roughness_remapping(cl.roughness, cl.ior);
if (thickness > 0.0) {
vec3 L = refraction_dominant_dir(cl.N, V, cl.ior, cl.roughness);
ThicknessIsect isect = thickness_sphere_intersect(thickness, cl.N, L);
P += isect.hit_P;
cl.N = -isect.hit_N;
cl.ior = 1.0 / cl.ior;
V = -L;
}
vec3 L = lightprobe_refraction_dominant_dir(cl.N, V, cl.ior, effective_roughness);
vec3 L = refraction_dominant_dir(cl.N, V, cl.ior, cl.roughness);
float lod = sphere_probe_roughness_to_lod(effective_roughness);
float lod = sphere_probe_roughness_to_lod(cl.roughness);
vec3 radiance_cube = lightprobe_spherical_sample_normalized_with_parallax(
samp.spherical_id, P, L, lod, samp.volume_irradiance);
float fac = sphere_probe_roughness_to_mix_fac(effective_roughness);
float fac = sphere_probe_roughness_to_mix_fac(cl.roughness);
vec3 radiance_sh = spherical_harmonics_evaluate_lambert(L, samp.volume_irradiance);
return mix(radiance_cube, radiance_sh, fac);
}
void lightprobe_eval(
LightProbeSample samp, ClosureUndetermined cl, vec3 P, vec3 V, inout vec3 radiance)
void lightprobe_eval(LightProbeSample samp,
ClosureUndetermined cl,
vec3 P,
vec3 V,
float thickness,
inout vec3 radiance)
{
switch (cl.type) {
case CLOSURE_BSDF_TRANSLUCENT_ID:
/* TODO: Support in ray tracing first. Otherwise we have a discrepancy. */
radiance += lightprobe_eval(samp, to_closure_translucent(cl), P, V);
radiance += lightprobe_eval(samp, to_closure_translucent(cl), P, V, thickness);
break;
case CLOSURE_BSSRDF_BURLEY_ID:
/* TODO: Support translucency in ray tracing first. Otherwise we have a discrepancy. */
@@ -167,7 +159,7 @@ void lightprobe_eval(
radiance += lightprobe_eval(samp, to_closure_reflection(cl), P, V);
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
radiance += lightprobe_eval(samp, to_closure_refraction(cl), P, V);
radiance += lightprobe_eval(samp, to_closure_refraction(cl), P, V, thickness);
break;
case CLOSURE_NONE_ID:
/* TODO(fclem): Assert. */

View File

@@ -146,8 +146,13 @@ Closure closure_eval(ClosureDiffuse diffuse)
{
ClosureUndetermined cl;
closure_base_copy(cl, diffuse);
/* Diffuse & SSS always use the first closure. */
#if (CLOSURE_BIN_COUNT > 1) && defined(MAT_TRANSLUCENT) && !defined(MAT_CLEARCOAT)
/* Use second slot so we can have diffuse + translucent without noise. */
closure_select(g_closure_bins[1], g_closure_rand[1], cl);
#else
/* Either is single closure or use same bin as transmission bin. */
closure_select(g_closure_bins[0], g_closure_rand[0], cl);
#endif
return Closure(0);
}
@@ -156,7 +161,7 @@ Closure closure_eval(ClosureSubsurface diffuse)
ClosureUndetermined cl;
closure_base_copy(cl, diffuse);
cl.data.rgb = diffuse.sss_radius;
/* Diffuse & SSS always use the first closure. */
/* Transmission Closures are always in first bin. */
closure_select(g_closure_bins[0], g_closure_rand[0], cl);
return Closure(0);
}
@@ -165,13 +170,8 @@ Closure closure_eval(ClosureTranslucent translucent)
{
ClosureUndetermined cl;
closure_base_copy(cl, translucent);
#if CLOSURE_BIN_COUNT == 1
/* Only one closure type is present in the whole tree. */
/* Transmission Closures are always in first bin. */
closure_select(g_closure_bins[0], g_closure_rand[0], cl);
#else
/* Use second slot so we can have diffuse + translucent without noise. */
closure_select(g_closure_bins[1], g_closure_rand[1], cl);
#endif
return Closure(0);
}
@@ -198,16 +198,27 @@ Closure closure_eval(ClosureReflection reflection)
closure_base_copy(cl, reflection);
cl.data.r = reflection.roughness;
#if CLOSURE_BIN_COUNT == 1
#ifdef MAT_CLEARCOAT
# if CLOSURE_BIN_COUNT == 2
/* Multiple reflection closures. */
CHOOSE_MIN_WEIGHT_CLOSURE_BIN(0, 1);
# elif CLOSURE_BIN_COUNT == 3
/* Multiple reflection closures and one other closure. */
CHOOSE_MIN_WEIGHT_CLOSURE_BIN(1, 2);
# else
# error Clearcoat should always have at least 2 bins
# endif
#else
# if CLOSURE_BIN_COUNT == 1
/* Only one reflection closure is present in the whole tree. */
closure_select(g_closure_bins[0], g_closure_rand[0], cl);
#elif CLOSURE_BIN_COUNT == 2
/* Case with either only one reflection and one other closure
* or only multiple reflection closures. */
CHOOSE_MIN_WEIGHT_CLOSURE_BIN(0, 1);
#elif CLOSURE_BIN_COUNT == 3
/* Case with multiple reflection closures and one other closure. */
CHOOSE_MIN_WEIGHT_CLOSURE_BIN(1, 2);
# elif CLOSURE_BIN_COUNT == 2
/* Only one reflection and one other closure. */
closure_select(g_closure_bins[1], g_closure_rand[1], cl);
# elif CLOSURE_BIN_COUNT == 3
/* Only one reflection and two other closures. */
closure_select(g_closure_bins[2], g_closure_rand[2], cl);
# endif
#endif
#undef CHOOSE_MIN_WEIGHT_CLOSURE_BIN
@@ -221,8 +232,7 @@ Closure closure_eval(ClosureRefraction refraction)
closure_base_copy(cl, refraction);
cl.data.r = refraction.roughness;
cl.data.g = refraction.ior;
/* Use same slot as diffuse as mixed diffuse/refraction are not common.
* Allow glass material with clearcoat without noise. */
/* Transmission Closures are always in first bin. */
closure_select(g_closure_bins[0], g_closure_rand[0], cl);
return Closure(0);
}

View File

@@ -19,14 +19,20 @@
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_bxdf_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_closure_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl)
float bxdf_eval(ClosureUndetermined cl, vec3 L, vec3 V)
float bxdf_eval(ClosureUndetermined cl, vec3 L, vec3 V, float thickness)
{
switch (cl.type) {
case CLOSURE_BSDF_TRANSLUCENT_ID:
if (thickness > 0.0) {
/* Uniform sphere weighting. */
return 1.0;
}
return bsdf_lambert(-cl.N, L);
case CLOSURE_BSSRDF_BURLEY_ID:
case CLOSURE_BSDF_DIFFUSE_ID:
@@ -47,6 +53,25 @@ float bxdf_eval(ClosureUndetermined cl, vec3 L, vec3 V)
}
}
void transmission_thickness_amend_closure(inout ClosureUndetermined cl,
inout vec3 V,
float thickness)
{
switch (cl.type) {
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
float ior = to_closure_refraction(cl).ior;
float roughness = to_closure_refraction(cl).roughness;
roughness = refraction_roughness_remapping(roughness, ior);
vec3 L = refraction_dominant_dir(cl.N, V, ior, roughness);
cl.N = -thickness_sphere_intersect(thickness, cl.N, L).hit_N;
cl.data.y = 1.0 / ior;
V = -L;
} break;
default:
break;
}
}
/* Tag pixel radiance as invalid. */
void invalid_pixel_write(ivec2 texel)
{
@@ -109,8 +134,10 @@ void main()
return;
}
uint gbuf_header = texelFetch(gbuf_header_tx, texel_fullres, 0).r;
ClosureUndetermined closure = gbuffer_read_bin(
gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel_fullres, closure_index);
gbuf_header, gbuf_closure_tx, gbuf_normal_tx, texel_fullres, closure_index);
if (closure.type == CLOSURE_NONE_ID) {
invalid_pixel_write(texel_fullres);
@@ -121,6 +148,11 @@ void main()
vec3 P = drw_point_screen_to_world(vec3(uv, 0.5));
vec3 V = drw_world_incident_vector(P);
float thickness = gbuffer_read_thickness(gbuf_header, gbuf_normal_tx, texel_fullres);
if (thickness > 0.0) {
transmission_thickness_amend_closure(closure, V, thickness);
}
/* Compute filter size and needed sample count */
float apparent_roughness = closure_apparent_roughness_get(closure);
float filter_size_factor = saturate(apparent_roughness * 8.0);
@@ -162,7 +194,7 @@ void main()
/* Slide 54. */
/* TODO(fclem): Apparently, ratio estimator should be pdf_bsdf / pdf_ray. */
float weight = bxdf_eval(closure, ray_direction, V) * ray_pdf_inv;
float weight = bxdf_eval(closure, ray_direction, V, thickness) * ray_pdf_inv;
radiance_accum += ray_radiance.rgb * weight;
weight_accum += weight;

View File

@@ -21,8 +21,9 @@ void main()
ivec2 texel_fullres = texel * uniform_buf.raytrace.resolution_scale +
uniform_buf.raytrace.resolution_bias;
uint gbuf_header = texelFetch(gbuf_header_tx, texel_fullres, 0).r;
ClosureUndetermined closure = gbuffer_read_bin(
gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel_fullres, closure_index);
gbuf_header, gbuf_closure_tx, gbuf_normal_tx, texel_fullres, closure_index);
if (closure.type == CLOSURE_NONE_ID) {
imageStore(out_ray_data_img, texel, vec4(0.0));
@@ -35,7 +36,9 @@ void main()
vec2 noise = utility_tx_fetch(utility_tx, vec2(texel), UTIL_BLUE_NOISE_LAYER).rg;
noise = fract(noise + sampling_rng_2D_get(SAMPLING_RAYTRACE_U));
BsdfSample samp = ray_generate_direction(noise.xy, closure, V);
float thickness = gbuffer_read_thickness(gbuf_header, gbuf_normal_tx, texel_fullres);
BsdfSample samp = ray_generate_direction(noise.xy, closure, V, thickness);
/* Store inverse pdf to speedup denoising.
* Limit to the smallest non-0 value that the format can encode.

View File

@@ -13,6 +13,7 @@
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl)
struct BsdfSample {
vec3 direction;
@@ -28,7 +29,7 @@ bool is_singular_ray(float roughness)
}
/* Returns view-space ray. */
BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V)
BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V, float thickness)
{
vec3 random_point_on_cylinder = sample_cylinder(noise);
/* Bias the rays so we never get really high energy rays almost parallel to the surface. */
@@ -39,11 +40,19 @@ BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V)
BsdfSample samp;
switch (cl.type) {
case CLOSURE_BSDF_TRANSLUCENT_ID:
samp.direction = sample_cosine_hemisphere(random_point_on_cylinder,
-world_to_tangent[2],
world_to_tangent[1],
world_to_tangent[0],
samp.pdf);
if (thickness > 0.0) {
/* When modeling object thickness as a sphere, the outgoing rays are distributed uniformly
* over the sphere. We don't need the RAY_BIAS in this case. */
samp.direction = sample_sphere(noise);
samp.pdf = sample_pdf_uniform_sphere();
}
else {
samp.direction = sample_cosine_hemisphere(random_point_on_cylinder,
-world_to_tangent[2],
world_to_tangent[1],
world_to_tangent[0],
samp.pdf);
}
break;
case CLOSURE_BSSRDF_BURLEY_ID:
case CLOSURE_BSDF_DIFFUSE_ID:
@@ -70,14 +79,26 @@ BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V)
break;
}
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
if (is_singular_ray(to_closure_refraction(cl).roughness)) {
samp.direction = refract(-V, cl.N, 1.0 / to_closure_refraction(cl).ior);
float ior = to_closure_refraction(cl).ior;
float roughness = to_closure_refraction(cl).roughness;
if (thickness > 0.0) {
float apparent_roughness = refraction_roughness_remapping(roughness, ior);
vec3 L = refraction_dominant_dir(cl.N, V, ior, apparent_roughness);
/* NOTE(fclem): Tracing origin is modified in the trace shader. */
cl.N = -thickness_sphere_intersect(thickness, cl.N, L).hit_N;
ior = 1.0 / ior;
V = -L;
world_to_tangent = from_up_axis(cl.N);
}
if (is_singular_ray(roughness)) {
samp.direction = refract(-V, cl.N, 1.0 / ior);
samp.pdf = 1.0;
}
else {
samp.direction = sample_ggx_refract(random_point_on_cylinder,
square(to_closure_refraction(cl).roughness),
to_closure_refraction(cl).ior,
square(roughness),
ior,
V,
world_to_tangent[2],
world_to_tangent[1],

View File

@@ -10,6 +10,7 @@
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_trace_screen_lib.glsl)
@@ -42,12 +43,24 @@ void main()
ray.origin = P;
ray.direction = ray_data.xyz;
/* Only closure 0 can be a transmission closure. */
if (closure_index == 0) {
uint gbuf_header = texelFetch(gbuf_header_tx, texel_fullres, 0).r;
float thickness = gbuffer_read_thickness(gbuf_header, gbuf_normal_tx, texel_fullres);
if (thickness > 0.0) {
vec3 surface_N = gbuffer_read_normal(gbuf_normal_tx, texel_fullres);
ClosureType cl_type = gbuffer_closure_type_get_by_bin(gbuf_header, closure_index);
ray = raytrace_thickness_ray_ammend(ray, cl_type, surface_N, thickness);
}
}
/* Using ray direction as geometric normal to bias the sampling position.
* This is faster than loading the gbuffer again and averages between reflected and normal
* direction over many rays. */
vec3 Ng = ray.direction;
LightProbeSample samp = lightprobe_load(P, Ng, V);
vec3 radiance = lightprobe_eval_direction(samp, P, ray.direction, safe_rcp(ray_pdf_inv));
LightProbeSample samp = lightprobe_load(ray.origin, Ng, V);
vec3 radiance = lightprobe_eval_direction(
samp, ray.origin, ray.direction, safe_rcp(ray_pdf_inv));
/* Set point really far for correct reprojection of background. */
float hit_time = 1000.0;

View File

@@ -57,6 +57,17 @@ void main()
ray.origin = P;
ray.direction = ray_data.xyz;
/* Only closure 0 can be a transmission closure. */
if (closure_index == 0) {
uint gbuf_header = texelFetch(gbuf_header_tx, texel_fullres, 0).r;
float thickness = gbuffer_read_thickness(gbuf_header, gbuf_normal_tx, texel_fullres);
if (thickness > 0.0) {
vec3 surface_N = gbuffer_read_normal(gbuf_normal_tx, texel_fullres);
ClosureType cl_type = gbuffer_closure_type_get_by_bin(gbuf_header, closure_index);
ray = raytrace_thickness_ray_ammend(ray, cl_type, surface_N, thickness);
}
}
vec3 radiance = vec3(0.0);
float noise_offset = sampling_rng_1D_get(SAMPLING_RAYTRACE_W);
float rand_trace = interlieved_gradient_noise(vec2(texel), 5.0, noise_offset);

View File

@@ -16,6 +16,7 @@
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_fast_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl)
/* Inputs expected to be in view-space. */
void raytrace_clip_ray_to_near_plane(inout Ray ray)
@@ -221,3 +222,26 @@ ScreenTraceHitData raytrace_planar(RayTraceData rt_data,
}
#endif
/* Modify the ray origin before tracing it. We must do this because ray origin is implicitly
* reconstructed from from gbuffer depth which we cannot modify. */
Ray raytrace_thickness_ray_ammend(Ray ray,
ClosureType closure_type,
vec3 surface_N,
float thickness)
{
switch (closure_type) {
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
/* The ray direction was generated using the same 2 transmission events assumption.
* Only change its origin. Skip the volume inside the object. */
ray.origin += thickness_sphere_intersect(thickness, surface_N, ray.direction).hit_P;
case CLOSURE_BSDF_TRANSLUCENT_ID:
/* Ray direction is distributed on the whole sphere.
* Move the ray origin to the sphere surface (with bias to avoid self-intersection). */
ray.origin += (ray.direction - surface_N) * thickness * 0.505;
break;
default:
break;
}
return ray;
}

View File

@@ -117,8 +117,11 @@ float shadow_linear_occluder_distance(LightData light,
float occluder_z = (is_directional) ? (occluder * (far - near) + near) :
((near * far) / (occluder * (near - far) + far));
float receiver_z = (is_directional) ? -lP.z : max(abs(lP.x), max(abs(lP.y), abs(lP.z)));
float receiver_z = (is_directional) ? -lP.z : reduce_max(abs(lP));
if (!is_directional) {
float lP_len = length(lP);
return lP_len - lP_len * (occluder_z / receiver_z);
}
return receiver_z - occluder_z;
}

View File

@@ -196,12 +196,8 @@ struct ShadowRayDirectional {
LightData light;
};
ShadowRayDirectional shadow_ray_generate_directional(LightData light,
vec2 random_2d,
vec3 lP,
vec3 lNg,
float thickness,
out bool r_is_above_surface)
ShadowRayDirectional shadow_ray_generate_directional(
LightData light, vec2 random_2d, vec3 lP, vec3 lNg, out bool r_is_above_surface)
{
float clip_near = orderedIntBitsToFloat(light.clip_near);
float clip_far = orderedIntBitsToFloat(light.clip_far);
@@ -219,10 +215,10 @@ ShadowRayDirectional shadow_ray_generate_directional(LightData light,
r_is_above_surface = dot(lNg, direction.xyz) > 0.0;
if (!r_is_above_surface) {
/* Skip the object volume. */
origin += direction * thickness;
}
/* TODO(fclem): Bias sample direction above the horizon (or below if transmission).
* We don't really care about the shadow shape at such light elevation angles but more about
* noise. */
/* It only make sense to trace where there can be occluder. Clamp by distance to near plane. */
direction *= min(light_sun_data_get(light).shadow_trace_distance,
dist_to_near_plane / disk_direction.z);
@@ -289,12 +285,8 @@ struct ShadowRayPunctual {
};
/* Return ray in UV clip space [0..1]. */
ShadowRayPunctual shadow_ray_generate_punctual(LightData light,
vec2 random_2d,
vec3 lP,
vec3 lNg,
float thickness,
out bool r_is_above_surface)
ShadowRayPunctual shadow_ray_generate_punctual(
LightData light, vec2 random_2d, vec3 lP, vec3 lNg, out bool r_is_above_surface)
{
if (light.type == LIGHT_RECT) {
random_2d = random_2d * 2.0 - 1.0;
@@ -321,14 +313,9 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light,
direction = point_on_light_shape - lP;
r_is_above_surface = dot(direction, lNg) > 0.0;
#ifdef SHADOW_SUBSURFACE
if (!r_is_above_surface) {
/* Skip the object volume. Do not push behind the light. */
float offset_len = saturate(thickness / length(direction));
lP += direction * offset_len;
direction *= 1.0 - offset_len;
}
#endif
/* TODO(fclem): Bias sample direction above the horizon (or below if transmission).
* We don't really care about the shadow shape at such light elevation angles but more about
* noise. */
/* Clip the ray to not cross the near plane.
* Scale it so that it encompass the whole cube (with a safety margin). */
@@ -356,14 +343,9 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light,
direction = point_on_light_shape - lP;
r_is_above_surface = dot(direction, lNg) > 0.0;
#ifdef SHADOW_SUBSURFACE
if (!r_is_above_surface) {
/* Skip the object volume. Do not push behind the light. */
float offset_len = saturate(thickness / length(direction));
lP += direction * offset_len;
direction *= 1.0 - offset_len;
}
#endif
/* TODO(fclem): Bias sample direction above the horizon (or below if transmission).
* We don't really care about the shadow shape at such light elevation angles but more about
* noise. */
/* Clip the ray to not cross the light shape. */
float clip_distance = light_spot_data_get(light).radius;
@@ -525,9 +507,9 @@ vec3 shadow_pcf_offset(LightData light, const bool is_directional, vec3 P, vec3
*/
ShadowEvalResult shadow_eval(LightData light,
const bool is_directional,
const bool is_transmission,
vec3 P,
vec3 Ng,
float thickness,
int ray_count,
int ray_step_count)
{
@@ -549,6 +531,9 @@ ShadowEvalResult shadow_eval(LightData light,
float normal_offset = 0.02;
#endif
/* We want to bias inside the object for transmission to go through the object itself. */
normal_offset = is_transmission ? -normal_offset : normal_offset;
P += shadow_pcf_offset(light, is_directional, P, Ng, random_pcf_2d);
/* Avoid self intersection. */
@@ -563,8 +548,6 @@ ShadowEvalResult shadow_eval(LightData light,
float surface_hit = 0.0;
float surface_ray_count = 0.0;
float subsurface_hit = 0.0;
float subsurface_ray_count = 0.0;
for (int ray_index = 0; ray_index < ray_count && ray_index < SHADOW_MAX_RAY; ray_index++) {
vec2 random_ray_2d = fract(hammersley_2d(ray_index, ray_count) + random_shadow_3d.xy);
@@ -575,29 +558,23 @@ ShadowEvalResult shadow_eval(LightData light,
ShadowMapTraceResult trace;
if (is_directional) {
ShadowRayDirectional clip_ray = shadow_ray_generate_directional(
light, random_ray_2d, lP, lNg, thickness, is_above_surface);
light, random_ray_2d, lP, lNg, is_above_surface);
trace = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z);
}
else {
ShadowRayPunctual clip_ray = shadow_ray_generate_punctual(
light, random_ray_2d, lP, lNg, thickness, is_above_surface);
light, random_ray_2d, lP, lNg, is_above_surface);
trace = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z);
}
if (is_above_surface) {
if (is_above_surface != is_transmission) {
surface_hit += float(trace.has_hit);
surface_ray_count += 1.0;
}
else {
subsurface_hit += float(trace.has_hit);
subsurface_ray_count += 1.0;
}
}
/* Average samples. */
ShadowEvalResult result;
result.light_visibilty = saturate(1.0 - surface_hit * safe_rcp(surface_ray_count));
result.light_visibilty = min(result.light_visibilty,
saturate(1.0 - subsurface_hit * safe_rcp(subsurface_ray_count)));
result.occluder_distance = 0.0; /* Unused. Could reintroduced if needed. */
return result;
}

View File

@@ -8,6 +8,10 @@
#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl)
#ifndef LIGHT_ITER_FORCE_NO_CULLING
# error light_eval_reflection argument assumes this is defined
#endif
void main()
{
int index = int(gl_GlobalInvocationID.x);
@@ -17,26 +21,29 @@ void main()
Surfel surfel = surfel_buf[index];
ClosureLightStack stack;
stack.cl[0].N = surfel.normal;
stack.cl[0].ltc_mat = LTC_LAMBERT_MAT;
stack.cl[0].type = LIGHT_DIFFUSE;
/* There is no view dependent effect as we evaluate everything using diffuse. */
vec3 V = surfel.normal;
vec3 Ng = surfel.normal;
vec3 P = surfel.position;
light_eval(stack, P, Ng, V);
ClosureLightStack stack;
ClosureUndetermined cl_reflect;
cl_reflect.N = surfel.normal;
cl_reflect.type = CLOSURE_BSDF_DIFFUSE_ID;
stack.cl[0] = closure_light_new(cl_reflect, V);
light_eval_reflection(stack, P, Ng, V, 0.0);
if (capture_info_buf.capture_indirect) {
surfel_buf[index].radiance_direct.front.rgb += stack.cl[0].light_shadowed *
surfel.albedo_front;
}
V = -surfel.normal;
Ng = -surfel.normal;
stack.cl[0].N = -surfel.normal;
light_eval(stack, P, Ng, V);
ClosureUndetermined cl_transmit;
cl_transmit.N = -surfel.normal;
cl_transmit.type = CLOSURE_BSDF_DIFFUSE_ID;
stack.cl[0] = closure_light_new(cl_transmit, -V);
light_eval_reflection(stack, P, -Ng, -V, 0.0);
if (capture_info_buf.capture_indirect) {
surfel_buf[index].radiance_direct.back.rgb += stack.cl[0].light_shadowed * surfel.albedo_back;

View File

@@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Amend thickness after object rasterization using shadowmaps.
*
* Required resources:
* - atlas_tx
* - tilemaps_tx
*/
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
/* If using compute, the shader should define its own pixel. */
#if !defined(PIXEL) && defined(GPU_FRAGMENT_SHADER)
# define PIXEL gl_FragCoord.xy
#endif
void thickness_from_shadow_single(uint l_idx,
const bool is_directional,
vec3 P,
vec3 Ng,
inout float thickness_accum,
inout float weight_accum)
{
LightData light = light_buf[l_idx];
if (light.tilemap_index == LIGHT_NO_SHADOW) {
return;
}
LightVector lv = light_vector_get(light, is_directional, P);
float attenuation = light_attenuation_surface(light, is_directional, true, false, Ng, lv);
if ((attenuation < LIGHT_ATTENUATION_THRESHOLD)) {
return;
}
/* Weight result by facing ratio to avoid harsh transitions. */
float weight = saturate(dot(lv.L, -Ng));
ShadowEvalResult result = shadow_sample(
is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, P);
if (result.light_visibilty == 0.0) {
/* Flatten the accumulation to avoid weighting the outliers too much. */
thickness_accum += safe_sqrt(result.occluder_distance) * weight;
weight_accum += weight;
}
}
#define THICKNESS_NO_VALUE -1.0
/**
* Return the apparent thickness of an object behind surface considering all shadow maps
* available. If no shadow-map has a record of the other side of the surface, this function
* returns THICKNESS_NO_VALUE.
*/
float thickness_from_shadow(vec3 P, vec3 Ng, float vPz)
{
float thickness_accum = 0.0;
float weight_accum = 0.0;
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
thickness_from_shadow_single(l_idx, true, P, Ng, thickness_accum, weight_accum);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, PIXEL, vPz, l_idx) {
thickness_from_shadow_single(l_idx, false, P, Ng, thickness_accum, weight_accum);
}
LIGHT_FOREACH_END
if (weight_accum == 0.0) {
return THICKNESS_NO_VALUE;
}
float thickness = thickness_accum / weight_accum;
/* Flatten the accumulation to avoid weighting the outliers too much. */
thickness = square(thickness);
/* Add a bias because it is usually too small to prevent self shadowing. */
return thickness;
}

View File

@@ -2,62 +2,23 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
struct ThicknessIsect {
/* Normal at the intersection point on the sphere. */
vec3 hit_N;
/* Position of the intersection point on the sphere. */
vec3 hit_P;
};
/**
* Various utilities related to object subsurface thickness approximation.
*
* Required resources:
* - atlas_tx
* - tilemaps_tx
* Model sub-surface ray interaction with a sphere of the given diameter tangent to the shading
* point. This allows to model 2 refraction events quite cheaply.
* Everything is relative to the entrance shading point.
*/
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
void thickness_from_shadow_single(
uint l_idx, const bool is_directional, vec3 P, vec3 Ng, inout vec2 thickness)
ThicknessIsect thickness_sphere_intersect(float diameter, vec3 N, vec3 L)
{
LightData light = light_buf[l_idx];
if (light.tilemap_index == LIGHT_NO_SHADOW) {
return;
}
LightVector lv = light_vector_get(light, is_directional, P);
/* Note that we reverse the surface normal to reject surfaces facing the light. */
float attenuation = light_attenuation_surface(light, is_directional, Ng, lv).y;
if ((attenuation < LIGHT_ATTENUATION_THRESHOLD)) {
return;
}
/* Weight result by facing ratio to avoid harsh transitions. */
float weight = saturate(dot(lv.L, -Ng));
ShadowEvalResult result = shadow_sample(
is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, P);
/* Flatten the accumulation to avoid weighting the outliers too much. */
thickness += vec2(safe_sqrt(result.occluder_distance), 1.0) * weight;
}
#define THICKNESS_NO_VALUE 1.0e6
/**
* Return the apparent thickness of an object behind surface considering all shadow maps
* available. If no shadow-map has a record of the other side of the surface, this function
* returns THICKNESS_NO_VALUE.
*/
float thickness_from_shadow(vec3 P, vec3 Ng, float vPz)
{
/* Bias surface inward to avoid shadow map aliasing. */
float normal_offset = uniform_buf.shadow.normal_bias;
P += -Ng * normal_offset;
vec2 thickness = vec2(0.0);
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
thickness_from_shadow_single(l_idx, true, P, Ng, thickness);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, PIXEL, vPz, l_idx) {
thickness_from_shadow_single(l_idx, false, P, Ng, thickness);
}
LIGHT_FOREACH_END
return (thickness.y > 0.0) ? square(thickness.x / thickness.y) : THICKNESS_NO_VALUE;
ThicknessIsect isect;
float cos_alpha = dot(L, -N);
isect.hit_N = normalize(N + L * (cos_alpha * 2.0));
isect.hit_P = L * (cos_alpha * diameter);
return isect;
}

View File

@@ -90,7 +90,6 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light_double)
GPU_SHADER_CREATE_INFO(eevee_deferred_light_triple)
.additional_info("eevee_deferred_light")
.define("SHADOW_SUBSURFACE")
.define("MAT_SUBSURFACE")
.define("LIGHT_CLOSURE_EVAL_COUNT", "3")
.do_static_compilation(true);
@@ -124,8 +123,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_capture_eval)
.early_fragment_test(true)
/* Inputs. */
.fragment_out(0, Type::VEC4, "out_radiance")
.define("SHADOW_SUBSURFACE")
.define("LIGHT_CLOSURE_EVAL_COUNT", "2")
.define("LIGHT_CLOSURE_EVAL_COUNT", "1")
.additional_info("eevee_shared",
"eevee_gbuffer_data",
"eevee_utility_texture",
@@ -145,8 +143,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_planar_eval)
/* Inputs. */
.fragment_out(0, Type::VEC4, "out_radiance")
.define("SPHERE_PROBE")
.define("SHADOW_SUBSURFACE")
.define("LIGHT_CLOSURE_EVAL_COUNT", "2")
.define("LIGHT_CLOSURE_EVAL_COUNT", "1")
.additional_info("eevee_shared",
"eevee_gbuffer_data",
"eevee_utility_texture",

View File

@@ -72,8 +72,8 @@ GPU_SHADER_CREATE_INFO(eevee_surfel_common)
.storage_buf(CAPTURE_BUF_SLOT, Qualifier::READ, "CaptureInfoData", "capture_info_buf");
GPU_SHADER_CREATE_INFO(eevee_surfel_light)
.define("SURFEL_LIGHT")
.define("LIGHT_ITER_FORCE_NO_CULLING")
.define("LIGHT_CLOSURE_EVAL_COUNT", "1")
.local_group_size(SURFEL_GROUP_SIZE)
.additional_info("eevee_shared",
"draw_view",

View File

@@ -58,6 +58,7 @@ GPU_SHADER_CREATE_INFO(eevee_ray_trace_fallback)
.do_static_compilation(true)
.local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE)
.additional_info("eevee_shared",
"eevee_gbuffer_data",
"eevee_global_ubo",
"draw_view",
"eevee_sampling_data",
@@ -67,6 +68,7 @@ GPU_SHADER_CREATE_INFO(eevee_ray_trace_fallback)
.image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_radiance_img")
.sampler(1, ImageType::DEPTH_2D, "depth_tx")
.storage_buf(5, Qualifier::READ, "uint", "tiles_coord_buf[]")
.specialization_constant(Type::INT, "closure_index", 0)
.compute_source("eevee_ray_trace_fallback_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_ray_trace_planar)