diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index e32cb4c5536..97f5f701a68 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -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 diff --git a/source/blender/draw/engines/eevee_next/eevee_light.cc b/source/blender/draw/engines/eevee_next/eevee_light.cc index 08096bc2452..b91ee41dd9b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_light.cc +++ b/source/blender/draw/engines/eevee_next/eevee_light.cc @@ -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; diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 18e316ee605..f969a06123a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -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. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_raytrace.cc b/source/blender/draw/engines/eevee_next/eevee_raytrace.cc index 1a34b6a1b48..857850494a8 100644 --- a/source/blender/draw/engines/eevee_next/eevee_raytrace.cc +++ b/source/blender/draw/engines/eevee_next/eevee_raytrace.cc @@ -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); } diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index 77db8caf54f..50285d64b82 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -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; diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index edbde9d3ea6..4926c0416d3 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -717,7 +717,6 @@ enum eLightType : uint32_t { enum LightingType : uint32_t { LIGHT_DIFFUSE = 0u, LIGHT_SPECULAR = 1u, - LIGHT_TRANSMIT = 2u, LIGHT_VOLUME = 3u, }; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl index 4bf783768aa..a7f5edbbb98 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl @@ -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)); +} + /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl index 12244869c7a..9e5c6ac34ee 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl @@ -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 * \{ */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl index 0109600d790..e294ef4e361 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl @@ -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); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl index b108d14eca9..9d0362f35eb 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl @@ -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; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl index a1c0ec0f651..21c3b69d979 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl @@ -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); } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl index c128dd74f3a..4681503ab7a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl @@ -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); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_forward_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_forward_lib.glsl index 3de8852752a..2b2b5de2f0a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_forward_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_forward_lib.glsl @@ -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; } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_gbuffer_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_gbuffer_lib.glsl index 7ad69019d68..17132359403 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_gbuffer_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_gbuffer_lib.glsl @@ -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); +} /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_horizon_resolve_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_horizon_resolve_comp.glsl index 8a6b99ce345..779d33089c8 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_horizon_resolve_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_horizon_resolve_comp.glsl @@ -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. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl index d2c790b28da..8ea7dd14e7e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl @@ -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; } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl index b65fff65d1a..7f2459e6b83 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl @@ -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 +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl index 1d197825c53..5751f811651 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl @@ -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( diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl index a2b1d0b2307..4073d65aea0 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl @@ -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. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl index 239123550bb..85e6a43b160 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl @@ -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); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl index 549ac4c8873..a8790e3480f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl @@ -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; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_comp.glsl index 83fdc1ac5ef..dab144793e7 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_comp.glsl @@ -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. diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl index 97f4986dcd3..6fb5cf79717 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl @@ -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], diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl index c01ca6264d7..dfbff7975f4 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl @@ -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; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl index c9143d7e3ef..2fedf4b13e4 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl @@ -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); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl index cf84c85d605..6d8d08a1678 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl @@ -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; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_lib.glsl index 1d0ec4f4412..e922af460cb 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_lib.glsl @@ -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; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl index 5c2ed6ff201..a9b7dfa393d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl @@ -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; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl index c2adf0529cf..a3e53922096 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl @@ -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; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_amend_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_amend_lib.glsl new file mode 100644 index 00000000000..a3771e04215 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_amend_lib.glsl @@ -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; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_lib.glsl index fc6811c5db1..604a9e7001b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_lib.glsl @@ -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; } diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh index 3de5fb2c36b..37da5b12d4f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh @@ -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", diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh index 54dc7b974e4..6380d407151 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh @@ -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", diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh index eef2b0f7e25..bf9dc0e4ed3 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh @@ -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)