Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 53 additions & 23 deletions crates/bevy_pbr/src/render/pbr_lighting.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -348,13 +348,14 @@ fn derive_lighting_input(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> DerivedLig
return input;
}

// Returns L in the `xyz` components and the specular intensity in the `w` component.
// Returns L in the `xyz` components and the modified roughness in the `w` component.
fn compute_specular_layer_values_for_point_light(
input: ptr<function, LightingInput>,
layer: u32,
V: vec3<f32>,
light_to_frag: vec3<f32>,
light_position_radius: f32,
distance: f32,
) -> vec4<f32> {
// Unpack.
let R = (*input).layers[layer].R;
Expand Down Expand Up @@ -382,22 +383,24 @@ fn compute_specular_layer_values_for_point_light(
let closestPoint = light_to_frag + centerToRay * saturate(
light_position_radius * inverseSqrt(dot(centerToRay, centerToRay)));
let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint));
let normalizationFactor = a / saturate(a + (light_position_radius * 0.5 * LspecLengthInverse));
let intensity = normalizationFactor * normalizationFactor;

// a' = saturate( a + sourceRadius / (2 * distance) )
// see Karis 2013
let a_prime = saturate(a + light_position_radius / (2.0 * distance));

let L: vec3<f32> = closestPoint * LspecLengthInverse; // normalize() equivalent?
return vec4(L, intensity);
return vec4(L, a_prime);
}

// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m
// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) }
fn specular(
input: ptr<function, LightingInput>,
derived_input: ptr<function, DerivedLightingInput>,
roughness: f32,
specular_intensity: f32,
) -> vec3<f32> {
// Unpack.
let roughness = (*input).layers[LAYER_BASE].roughness;
let NdotV = (*input).layers[LAYER_BASE].NdotV;
let F0 = (*input).F0_;
let NdotL = (*derived_input).NdotL;
Expand Down Expand Up @@ -425,10 +428,10 @@ fn specular_clearcoat(
input: ptr<function, LightingInput>,
derived_input: ptr<function, DerivedLightingInput>,
clearcoat_strength: f32,
roughness: f32,
specular_intensity: f32,
) -> vec2<f32> {
// Unpack.
let roughness = (*input).layers[LAYER_CLEARCOAT].roughness;
let NdotH = (*derived_input).NdotH;
let LdotH = (*derived_input).LdotH;

Expand All @@ -449,10 +452,10 @@ fn specular_anisotropy(
input: ptr<function, LightingInput>,
derived_input: ptr<function, DerivedLightingInput>,
L: vec3<f32>,
roughness: f32,
specular_intensity: f32,
) -> vec3<f32> {
// Unpack.
let roughness = (*input).layers[LAYER_BASE].roughness;
let NdotV = (*input).layers[LAYER_BASE].NdotV;
let V = (*input).V;
let F0 = (*input).F0_;
Expand Down Expand Up @@ -620,25 +623,37 @@ fn point_light(
let light_to_frag = (*light).position_radius.xyz - P;
let L = normalize(light_to_frag);
let distance_square = dot(light_to_frag, light_to_frag);
let distance = sqrt(distance_square);
let rangeAttenuation = getDistanceAttenuation(distance_square, (*light).color_inverse_square_range.w);

// Base layer

let specular_L_intensity = compute_specular_layer_values_for_point_light(
let a = (*input).layers[LAYER_BASE].roughness;
let specular_L_a_prime = compute_specular_layer_values_for_point_light(
input,
LAYER_BASE,
V,
light_to_frag,
(*light).position_radius.w,
distance,
);
var specular_derived_input = derive_lighting_input(N, V, specular_L_intensity.xyz);
let L_spec = specular_L_a_prime.xyz;
let a_prime = specular_L_a_prime.w;
var specular_derived_input = derive_lighting_input(N, V, L_spec);

let specular_intensity = specular_L_intensity.w;
let normalizationFactor = a / a_prime;
let specular_intensity = normalizationFactor * normalizationFactor;

// This is a modification to Karis 2013 for area lights to fix an issue where the specular reflection on smooth materials
// looks too rough and dim. We lerp between the base roughness and Karis2013 roughness with a lerp factor tuned by looking at reference renders.
// The goal is to preserve sharp specular highlights on smooth materials, without blowing out specular highlights on rough materials.
let lerp = 1.0 - (1.0 - a) * (1.0 - a) * (1.0 - a) * (1.0 - a);
let brdf_roughness = mix(a, a_prime, lerp);

#ifdef STANDARD_MATERIAL_ANISOTROPY
let specular_light = specular_anisotropy(input, &specular_derived_input, L, specular_intensity);
let specular_light = specular_anisotropy(input, &specular_derived_input, L, brdf_roughness, specular_intensity);
#else // STANDARD_MATERIAL_ANISOTROPY
let specular_light = specular(input, &specular_derived_input, specular_intensity);
let specular_light = specular(input, &specular_derived_input, brdf_roughness, specular_intensity);
#endif // STANDARD_MATERIAL_ANISOTROPY

// Clearcoat
Expand All @@ -647,26 +662,40 @@ fn point_light(
// Unpack.
let clearcoat_N = (*input).layers[LAYER_CLEARCOAT].N;
let clearcoat_strength = (*input).clearcoat_strength;
let clearcoat_a = (*input).layers[LAYER_CLEARCOAT].roughness;

// Perform specular input calculations again for the clearcoat layer. We
// can't reuse the above because the clearcoat normal might be different
// from the main layer normal.
let clearcoat_specular_L_intensity = compute_specular_layer_values_for_point_light(
let clearcoat_specular_L_a_prime = compute_specular_layer_values_for_point_light(
input,
LAYER_CLEARCOAT,
V,
light_to_frag,
(*light).position_radius.w,
distance,
);
let L_clearcoat_spec = clearcoat_specular_L_a_prime.xyz;
let clearcoat_a_prime = clearcoat_specular_L_a_prime.w;
var clearcoat_specular_derived_input =
derive_lighting_input(clearcoat_N, V, clearcoat_specular_L_intensity.xyz);
derive_lighting_input(clearcoat_N, V, L_clearcoat_spec);

// Calculate the specular light.
let clearcoat_specular_intensity = clearcoat_specular_L_intensity.w;
let clearcoat_normalizationFactor = clearcoat_a / clearcoat_a_prime;

let clearcoat_specular_intensity = clearcoat_normalizationFactor * clearcoat_normalizationFactor;

// This is a modification to Karis 2013 for area lights to fix an issue where the specular reflection on smooth materials
// looks too rough and dim. We lerp between the base roughness and Karis2013 roughness with a lerp factor tuned by looking at reference renders.
// The goal is to preserve sharp specular highlights on smooth materials, without blowing out specular highlights on rough materials.
let cc_lerp = 1.0 - (1.0 - clearcoat_a) * (1.0 - clearcoat_a) * (1.0 - clearcoat_a) * (1.0 - clearcoat_a);
let clearcoat_brdf_roughness = mix(clearcoat_a, clearcoat_a_prime, cc_lerp);

let Fc_Frc = specular_clearcoat(
input,
&clearcoat_specular_derived_input,
clearcoat_strength,
clearcoat_brdf_roughness,
clearcoat_specular_intensity
);
let inv_Fc = 1.0 - Fc_Frc.r; // Inverse Fresnel term.
Expand Down Expand Up @@ -694,14 +723,14 @@ fn point_light(

// NOTE: (*light).color.rgb is premultiplied with (*light).intensity / 4 π (which would be the luminous intensity) on the CPU

var color: vec3<f32>;
var color_times_NdotL: vec3<f32>;
#ifdef STANDARD_MATERIAL_CLEARCOAT
// Account for the Fresnel term from the clearcoat darkening the main layer.
//
// <https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel/integrationinthesurfaceresponse>
color = (diffuse + specular_light * inv_Fc) * inv_Fc + Frc;
color_times_NdotL = (diffuse * derived_input.NdotL + specular_light * specular_derived_input.NdotL * inv_Fc) * inv_Fc + Frc * clearcoat_specular_derived_input.NdotL;
#else // STANDARD_MATERIAL_CLEARCOAT
color = diffuse + specular_light;
color_times_NdotL = diffuse * derived_input.NdotL + specular_light * specular_derived_input.NdotL;
#endif // STANDARD_MATERIAL_CLEARCOAT

var texture_sample = 1f;
Expand All @@ -722,8 +751,8 @@ fn point_light(
}
#endif

return color * (*light).color_inverse_square_range.rgb *
(rangeAttenuation * derived_input.NdotL) * texture_sample;
return color_times_NdotL * (*light).color_inverse_square_range.rgb *
rangeAttenuation * texture_sample;
}

fn spot_light(
Expand Down Expand Up @@ -797,22 +826,23 @@ fn directional_light(
}

#ifdef STANDARD_MATERIAL_ANISOTROPY
let specular_light = specular_anisotropy(input, &derived_input, L, 1.0);
let specular_light = specular_anisotropy(input, &derived_input, L, roughness, 1.0);
#else // STANDARD_MATERIAL_ANISOTROPY
let specular_light = specular(input, &derived_input, 1.0);
let specular_light = specular(input, &derived_input, roughness, 1.0);
#endif // STANDARD_MATERIAL_ANISOTROPY

#ifdef STANDARD_MATERIAL_CLEARCOAT
let clearcoat_N = (*input).layers[LAYER_CLEARCOAT].N;
let clearcoat_strength = (*input).clearcoat_strength;
let clearcoat_roughness = (*input).layers[LAYER_CLEARCOAT].roughness;

// Perform specular input calculations again for the clearcoat layer. We
// can't reuse the above because the clearcoat normal might be different
// from the main layer normal.
var derived_clearcoat_input = derive_lighting_input(clearcoat_N, V, L);

let Fc_Frc =
specular_clearcoat(input, &derived_clearcoat_input, clearcoat_strength, 1.0);
specular_clearcoat(input, &derived_clearcoat_input, clearcoat_strength, clearcoat_roughness, 1.0);
let inv_Fc = 1.0 - Fc_Frc.r;
let Frc = Fc_Frc.g;
#endif // STANDARD_MATERIAL_CLEARCOAT
Expand Down