Approximate indirect specular occlusion (#11152)
# Objective - The current PBR renderer over-brightens indirect specular reflections, which tends to cause objects to appear to glow, because specular occlusion is not accounted for. ## Solution - Attenuate indirect specular term with an approximation for specular occlusion, using [[Lagarde et al., 2014] (pg. 76)](https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf). | Before | After | Animation | | --- | --- | --- | | <img width="1840" alt="before bike" src="https://github.com/bevyengine/bevy/assets/2632925/b6e10d15-a998-4a94-875a-1c2b1e98348a"> | <img width="1840" alt="after bike" src="https://github.com/bevyengine/bevy/assets/2632925/53b1479c-b1e4-427f-b140-53df26ca7193"> |  | | <img width="1840" alt="classroom before" src="https://github.com/bevyengine/bevy/assets/2632925/b16c0e74-741e-4f40-a7df-8863eaa62596"> | <img width="1840" alt="classroom after" src="https://github.com/bevyengine/bevy/assets/2632925/26f9e971-0c63-4ee9-9544-964e5703d65e"> |  | --- ## Changelog - Ambient occlusion now applies to indirect specular reflections to approximate how objects occlude specular light. ## Migration Guide - Renamed `PbrInput::occlusion` to `diffuse_occlusion`, and added `specular_occlusion`.
This commit is contained in:
		
							parent
							
								
									4695b82f6b
								
							
						
					
					
						commit
						839d2f8353
					
				@ -4,6 +4,7 @@
 | 
				
			|||||||
    pbr_functions,
 | 
					    pbr_functions,
 | 
				
			||||||
    pbr_deferred_functions::pbr_input_from_deferred_gbuffer,
 | 
					    pbr_deferred_functions::pbr_input_from_deferred_gbuffer,
 | 
				
			||||||
    pbr_deferred_types::unpack_unorm3x4_plus_unorm_20_,
 | 
					    pbr_deferred_types::unpack_unorm3x4_plus_unorm_20_,
 | 
				
			||||||
 | 
					    lighting,
 | 
				
			||||||
    mesh_view_bindings::deferred_prepass_texture,
 | 
					    mesh_view_bindings::deferred_prepass_texture,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,7 +65,15 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
 | 
				
			|||||||
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
 | 
					#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
 | 
				
			||||||
        let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2<i32>(in.position.xy), 0i).r;
 | 
					        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);
 | 
					        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 and Rousiers 2014, "Moving Frostbite to Physically Based Rendering"
 | 
				
			||||||
 | 
					        pbr_input.specular_occlusion =  saturate(pow(NdotV + ssao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ssao);
 | 
				
			||||||
#endif // SCREEN_SPACE_AMBIENT_OCCLUSION
 | 
					#endif // SCREEN_SPACE_AMBIENT_OCCLUSION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        output_color = pbr_functions::apply_pbr_lighting(pbr_input);
 | 
					        output_color = pbr_functions::apply_pbr_lighting(pbr_input);
 | 
				
			||||||
 | 
				
			|||||||
@ -22,18 +22,18 @@ fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> {
 | 
				
			|||||||
     // Real time occlusion is applied in the deferred lighting pass.
 | 
					     // Real time occlusion is applied in the deferred lighting pass.
 | 
				
			||||||
     // Deriving luminance via Rec. 709. coefficients
 | 
					     // Deriving luminance via Rec. 709. coefficients
 | 
				
			||||||
     // https://en.wikipedia.org/wiki/Rec._709
 | 
					     // 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.
 | 
					#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
 | 
				
			||||||
    var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4(
 | 
					    var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4(
 | 
				
			||||||
        in.material.reflectance,
 | 
					        in.material.reflectance,
 | 
				
			||||||
        in.material.metallic,
 | 
					        in.material.metallic,
 | 
				
			||||||
        occlusion, 
 | 
					        diffuse_occlusion, 
 | 
				
			||||||
        in.frag_coord.z));
 | 
					        in.frag_coord.z));
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
    var props = deferred_types::pack_unorm4x8_(vec4(
 | 
					    var props = deferred_types::pack_unorm4x8_(vec4(
 | 
				
			||||||
        in.material.reflectance, // could be fewer bits
 | 
					        in.material.reflectance, // could be fewer bits
 | 
				
			||||||
        in.material.metallic, // 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
 | 
					        0.0)); // spare
 | 
				
			||||||
#endif // WEBGL2
 | 
					#endif // WEBGL2
 | 
				
			||||||
    let flags = deferred_types::deferred_flags_from_mesh_material_flags(in.flags, in.material.flags);
 | 
					    let flags = deferred_types::deferred_flags_from_mesh_material_flags(in.flags, in.material.flags);
 | 
				
			||||||
@ -85,7 +85,7 @@ fn pbr_input_from_deferred_gbuffer(frag_coord: vec4<f32>, gbuffer: vec4<u32>) ->
 | 
				
			|||||||
    pbr.material.reflectance = props.r;
 | 
					    pbr.material.reflectance = props.r;
 | 
				
			||||||
#endif // WEBGL2
 | 
					#endif // WEBGL2
 | 
				
			||||||
    pbr.material.metallic = props.g;
 | 
					    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 octahedral_normal = deferred_types::unpack_24bit_normal(gbuffer.a);
 | 
				
			||||||
    let N = octahedral_decode(octahedral_normal);
 | 
					    let N = octahedral_decode(octahedral_normal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@
 | 
				
			|||||||
    pbr_bindings,
 | 
					    pbr_bindings,
 | 
				
			||||||
    pbr_types,
 | 
					    pbr_types,
 | 
				
			||||||
    prepass_utils,
 | 
					    prepass_utils,
 | 
				
			||||||
 | 
					    lighting,
 | 
				
			||||||
    mesh_bindings::mesh,
 | 
					    mesh_bindings::mesh,
 | 
				
			||||||
    mesh_view_bindings::view,
 | 
					    mesh_view_bindings::view,
 | 
				
			||||||
    parallax_mapping::parallaxed_uv,
 | 
					    parallax_mapping::parallaxed_uv,
 | 
				
			||||||
@ -68,6 +69,9 @@ fn pbr_input_from_standard_material(
 | 
				
			|||||||
    pbr_input.material.base_color *= pbr_bindings::material.base_color;
 | 
					    pbr_input.material.base_color *= pbr_bindings::material.base_color;
 | 
				
			||||||
    pbr_input.material.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id;
 | 
					    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
 | 
					#ifdef VERTEX_UVS
 | 
				
			||||||
    var uv = in.uv;
 | 
					    var uv = in.uv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -120,6 +124,7 @@ fn pbr_input_from_standard_material(
 | 
				
			|||||||
        // metallic and perceptual roughness
 | 
					        // metallic and perceptual roughness
 | 
				
			||||||
        var metallic: f32 = pbr_bindings::material.metallic;
 | 
					        var metallic: f32 = pbr_bindings::material.metallic;
 | 
				
			||||||
        var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness;
 | 
					        var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness;
 | 
				
			||||||
 | 
					        let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness);
 | 
				
			||||||
#ifdef VERTEX_UVS
 | 
					#ifdef VERTEX_UVS
 | 
				
			||||||
        if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) {
 | 
					        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);
 | 
					            let metallic_roughness = textureSampleBias(pbr_bindings::metallic_roughness_texture, pbr_bindings::metallic_roughness_sampler, uv, view.mip_bias);
 | 
				
			||||||
@ -159,20 +164,23 @@ fn pbr_input_from_standard_material(
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
        pbr_input.material.diffuse_transmission = diffuse_transmission;
 | 
					        pbr_input.material.diffuse_transmission = diffuse_transmission;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // occlusion
 | 
					        var diffuse_occlusion: vec3<f32> = vec3(1.0);
 | 
				
			||||||
        // TODO: Split into diffuse/specular occlusion?
 | 
					        var specular_occlusion: f32 = 1.0;
 | 
				
			||||||
        var occlusion: vec3<f32> = vec3(1.0);
 | 
					 | 
				
			||||||
#ifdef VERTEX_UVS
 | 
					#ifdef VERTEX_UVS
 | 
				
			||||||
        if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) {
 | 
					        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
 | 
					#endif
 | 
				
			||||||
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
 | 
					#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
 | 
				
			||||||
        let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2<i32>(in.position.xy), 0i).r;
 | 
					        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);
 | 
					        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 and Rousiers 2014, "Moving Frostbite to Physically Based Rendering"
 | 
				
			||||||
 | 
					        specular_occlusion =  saturate(pow(NdotV + ssao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ssao);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
        pbr_input.occlusion = occlusion;
 | 
					        pbr_input.diffuse_occlusion = diffuse_occlusion;
 | 
				
			||||||
 | 
					        pbr_input.specular_occlusion = specular_occlusion;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // N (normal vector)
 | 
					        // N (normal vector)
 | 
				
			||||||
#ifndef LOAD_PREPASS_NORMALS
 | 
					#ifndef LOAD_PREPASS_NORMALS
 | 
				
			||||||
 | 
				
			|||||||
@ -164,7 +164,8 @@ fn apply_pbr_lighting(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let specular_transmissive_color = specular_transmission * in.material.base_color.rgb;
 | 
					    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"
 | 
					    // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
 | 
				
			||||||
    let NdotV = max(dot(in.N, in.V), 0.0001);
 | 
					    let NdotV = max(dot(in.N, in.V), 0.0001);
 | 
				
			||||||
@ -306,7 +307,7 @@ fn apply_pbr_lighting(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Ambient light (indirect)
 | 
					    // 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 {
 | 
					    if diffuse_transmission > 0.0 {
 | 
				
			||||||
        // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
 | 
					        // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
 | 
				
			||||||
@ -316,14 +317,14 @@ fn apply_pbr_lighting(
 | 
				
			|||||||
        // perceptual_roughness = 1.0;
 | 
					        // perceptual_roughness = 1.0;
 | 
				
			||||||
        // NdotV = 1.0;
 | 
					        // NdotV = 1.0;
 | 
				
			||||||
        // F0 = vec3<f32>(0.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));
 | 
					        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)
 | 
					    // Environment map light (indirect)
 | 
				
			||||||
#ifdef ENVIRONMENT_MAP
 | 
					#ifdef ENVIRONMENT_MAP
 | 
				
			||||||
    let environment_light = environment_map::environment_map_light(perceptual_roughness, roughness, diffuse_color, NdotV, f_ab, in.N, R, F0);
 | 
					    let environment_light = environment_map::environment_map_light(perceptual_roughness, roughness, diffuse_color, NdotV, f_ab, in.N, R, F0);
 | 
				
			||||||
    indirect_light += (environment_light.diffuse * occlusion) + environment_light.specular;
 | 
					    indirect_light += (environment_light.diffuse * diffuse_occlusion) + (environment_light.specular * specular_occlusion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // we'll use the specular component of the transmitted environment
 | 
					    // we'll use the specular component of the transmitted environment
 | 
				
			||||||
    // light in the call to `specular_transmissive_light()` below
 | 
					    // light in the call to `specular_transmissive_light()` below
 | 
				
			||||||
@ -338,7 +339,7 @@ fn apply_pbr_lighting(
 | 
				
			|||||||
        // NdotV = 1.0;
 | 
					        // NdotV = 1.0;
 | 
				
			||||||
        // R = T // see definition below
 | 
					        // R = T // see definition below
 | 
				
			||||||
        // F0 = vec3<f32>(1.0)
 | 
					        // 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
 | 
					        // (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)
 | 
					        // map light returns both diffuse and specular components separately, and we want to use both)
 | 
				
			||||||
 | 
				
			|||||||
@ -80,7 +80,8 @@ fn standard_material_new() -> StandardMaterial {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct PbrInput {
 | 
					struct PbrInput {
 | 
				
			||||||
    material: StandardMaterial,
 | 
					    material: StandardMaterial,
 | 
				
			||||||
    occlusion: vec3<f32>,
 | 
					    diffuse_occlusion: vec3<f32>,
 | 
				
			||||||
 | 
					    specular_occlusion: f32,
 | 
				
			||||||
    frag_coord: vec4<f32>,
 | 
					    frag_coord: vec4<f32>,
 | 
				
			||||||
    world_position: vec4<f32>,
 | 
					    world_position: vec4<f32>,
 | 
				
			||||||
    // Normalized world normal used for shadow mapping as normal-mapping is not used for shadow
 | 
					    // Normalized world normal used for shadow mapping as normal-mapping is not used for shadow
 | 
				
			||||||
@ -101,7 +102,8 @@ fn pbr_input_new() -> PbrInput {
 | 
				
			|||||||
    var pbr_input: PbrInput;
 | 
					    var pbr_input: PbrInput;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pbr_input.material = standard_material_new();
 | 
					    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.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);
 | 
					    pbr_input.world_position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user