Skip to content

Commit

Permalink
Implement Lagarde specular occlusion
Browse files Browse the repository at this point in the history
  • Loading branch information
aevyrie committed Jan 3, 2024
1 parent 64ecbb4 commit bd6188c
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 24 deletions.
11 changes: 10 additions & 1 deletion crates/bevy_pbr/src/deferred/deferred_lighting.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pbr_functions,
pbr_deferred_functions::pbr_input_from_deferred_gbuffer,
pbr_deferred_types::unpack_unorm3x4_plus_unorm_20_,
lighting,
mesh_view_bindings::deferred_prepass_texture,
}

Expand Down Expand Up @@ -64,7 +65,15 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2<i32>(in.position.xy), 0i).r;
let ssao_multibounce = gtao_multibounce(ssao, pbr_input.material.base_color.rgb);
pbr_input.occlusion = min(pbr_input.occlusion, ssao_multibounce);
pbr_input.diffuse_occlusion = min(pbr_input.diffuse_occlusion, ssao_multibounce);

// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
let NdotV = max(dot(pbr_input.N, pbr_input.V), 0.0001);
var perceptual_roughness: f32 = pbr_input.material.perceptual_roughness;
let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness);

// Use ssao to estimate the specular occlusion. [Lagarde et al., 2014]
pbr_input.specular_occlusion = clamp(pow(NdotV + ssao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ssao, 0.0, 1.0);
#endif // SCREEN_SPACE_AMBIENT_OCCLUSION

output_color = pbr_functions::apply_pbr_lighting(pbr_input);
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> {
// Real time occlusion is applied in the deferred lighting pass.
// Deriving luminance via Rec. 709. coefficients
// https://en.wikipedia.org/wiki/Rec._709
let occlusion = dot(in.occlusion, vec3<f32>(0.2126, 0.7152, 0.0722));
let diffuse_occlusion = dot(in.diffuse_occlusion, vec3<f32>(0.2126, 0.7152, 0.0722));
#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4(
in.material.reflectance,
in.material.metallic,
occlusion,
diffuse_occlusion,
in.frag_coord.z));
#else
var props = deferred_types::pack_unorm4x8_(vec4(
in.material.reflectance, // could be fewer bits
in.material.metallic, // could be fewer bits
occlusion, // is this worth including?
diffuse_occlusion, // is this worth including?
0.0)); // spare
#endif // WEBGL2
let flags = deferred_types::deferred_flags_from_mesh_material_flags(in.flags, in.material.flags);
Expand Down Expand Up @@ -85,7 +85,7 @@ fn pbr_input_from_deferred_gbuffer(frag_coord: vec4<f32>, gbuffer: vec4<u32>) ->
pbr.material.reflectance = props.r;
#endif // WEBGL2
pbr.material.metallic = props.g;
pbr.occlusion = vec3(props.b);
pbr.diffuse_occlusion = vec3(props.b);
let octahedral_normal = deferred_types::unpack_24bit_normal(gbuffer.a);
let N = octahedral_decode(octahedral_normal);

Expand Down
19 changes: 13 additions & 6 deletions crates/bevy_pbr/src/render/pbr_fragment.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
pbr_bindings,
pbr_types,
prepass_utils,
lighting,
mesh_bindings::mesh,
mesh_view_bindings::view,
parallax_mapping::parallaxed_uv,
Expand Down Expand Up @@ -67,6 +68,9 @@ fn pbr_input_from_standard_material(
pbr_input.material.base_color *= pbr_bindings::material.base_color;
pbr_input.material.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id;

// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
let NdotV = max(dot(pbr_input.N, pbr_input.V), 0.0001);

#ifdef VERTEX_UVS
var uv = in.uv;

Expand Down Expand Up @@ -119,6 +123,7 @@ fn pbr_input_from_standard_material(
// metallic and perceptual roughness
var metallic: f32 = pbr_bindings::material.metallic;
var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness;
let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness);
#ifdef VERTEX_UVS
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) {
let metallic_roughness = textureSampleBias(pbr_bindings::metallic_roughness_texture, pbr_bindings::metallic_roughness_sampler, uv, view.mip_bias);
Expand Down Expand Up @@ -158,20 +163,22 @@ fn pbr_input_from_standard_material(
#endif
pbr_input.material.diffuse_transmission = diffuse_transmission;

// occlusion
// TODO: Split into diffuse/specular occlusion?
var occlusion: vec3<f32> = vec3(1.0);
var diffuse_occlusion: vec3<f32> = vec3(1.0);
var specular_occlusion: f32 = 1.0;
#ifdef VERTEX_UVS
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) {
occlusion = vec3(textureSampleBias(pbr_bindings::occlusion_texture, pbr_bindings::occlusion_sampler, uv, view.mip_bias).r);
diffuse_occlusion = vec3(textureSampleBias(pbr_bindings::occlusion_texture, pbr_bindings::occlusion_sampler, uv, view.mip_bias).r);
}
#endif
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2<i32>(in.position.xy), 0i).r;
let ssao_multibounce = gtao_multibounce(ssao, pbr_input.material.base_color.rgb);
occlusion = min(occlusion, ssao_multibounce);
diffuse_occlusion = min(diffuse_occlusion, ssao_multibounce);
// Use ssao to estimate the specular occlusion. [Lagarde et al., 2014]
specular_occlusion = clamp(pow(NdotV + ssao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ssao, 0.0, 1.0);
#endif
pbr_input.occlusion = occlusion;
pbr_input.diffuse_occlusion = diffuse_occlusion;
pbr_input.specular_occlusion = specular_occlusion;

// N (normal vector)
#ifndef LOAD_PREPASS_NORMALS
Expand Down
17 changes: 6 additions & 11 deletions crates/bevy_pbr/src/render/pbr_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ fn apply_pbr_lighting(

let specular_transmissive_color = specular_transmission * in.material.base_color.rgb;

let occlusion = in.occlusion;
let diffuse_occlusion = in.diffuse_occlusion;
let specular_occlusion = in.specular_occlusion;

// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
let NdotV = max(dot(in.N, in.V), 0.0001);
Expand Down Expand Up @@ -306,7 +307,7 @@ fn apply_pbr_lighting(
}

// Ambient light (indirect)
var indirect_light = ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, occlusion);
var indirect_light = ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, diffuse_occlusion);

if diffuse_transmission > 0.0 {
// NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
Expand All @@ -316,20 +317,14 @@ fn apply_pbr_lighting(
// perceptual_roughness = 1.0;
// NdotV = 1.0;
// F0 = vec3<f32>(0.0)
// occlusion = vec3<f32>(1.0)
// diffuse_occlusion = vec3<f32>(1.0)
transmitted_light += ambient::ambient_light(diffuse_transmissive_lobe_world_position, -in.N, -in.V, 1.0, diffuse_transmissive_color, vec3<f32>(0.0), 1.0, vec3<f32>(1.0));
}

// Environment map light (indirect)
#ifdef ENVIRONMENT_MAP
let environment_light = environment_map::environment_map_light(perceptual_roughness, roughness, diffuse_color, NdotV, f_ab, in.N, R, F0);
// This is technically not physically corrent. Ambient occlusion should only attenuate the
// diffuse term, not the specular term. However, without ray traced or screen space reflections,
// the physically correct math will tend to over-brighten specular,as it completely ignores
// occlusion. This results in, for example, a wheel in a vehicle's wheel well appearing to glow
// because it is not accounting for the fact that light cannot reach the wheel. Thus, ambient
// occlusion serves as an imperfect but decent *approximation* of specular occlusion.
indirect_light += (environment_light.diffuse + environment_light.specular) * occlusion;
indirect_light += (environment_light.diffuse * diffuse_occlusion) + (environment_light.specular * specular_occlusion);

// we'll use the specular component of the transmitted environment
// light in the call to `specular_transmissive_light()` below
Expand All @@ -344,7 +339,7 @@ fn apply_pbr_lighting(
// NdotV = 1.0;
// R = T // see definition below
// F0 = vec3<f32>(1.0)
// occlusion = 1.0
// diffuse_occlusion = 1.0
//
// (This one is slightly different from the other light types above, because the environment
// map light returns both diffuse and specular components separately, and we want to use both)
Expand Down
6 changes: 4 additions & 2 deletions crates/bevy_pbr/src/render/pbr_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ fn standard_material_new() -> StandardMaterial {

struct PbrInput {
material: StandardMaterial,
occlusion: vec3<f32>,
diffuse_occlusion: vec3<f32>,
specular_occlusion: f32,
frag_coord: vec4<f32>,
world_position: vec4<f32>,
// Normalized world normal used for shadow mapping as normal-mapping is not used for shadow
Expand All @@ -99,7 +100,8 @@ fn pbr_input_new() -> PbrInput {
var pbr_input: PbrInput;

pbr_input.material = standard_material_new();
pbr_input.occlusion = vec3<f32>(1.0);
pbr_input.diffuse_occlusion = vec3<f32>(1.0);
pbr_input.specular_occlusion = 1.0;

pbr_input.frag_coord = vec4<f32>(0.0, 0.0, 0.0, 1.0);
pbr_input.world_position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
Expand Down

0 comments on commit bd6188c

Please sign in to comment.