diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 24b0a687516a4..7e5b9766a4dd9 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -11,6 +11,7 @@ pub struct PointLight { pub color: Color, pub intensity: f32, pub range: f32, + pub radius: f32, } impl Default for PointLight { @@ -19,6 +20,7 @@ impl Default for PointLight { color: Color::rgb(1.0, 1.0, 1.0), intensity: 200.0, range: 20.0, + radius: 0.0, } } } @@ -29,7 +31,7 @@ pub(crate) struct PointLightUniform { pub pos: [f32; 4], pub color: [f32; 4], // storing as a `[f32; 4]` for memory alignement - pub inverse_range_squared: [f32; 4], + pub light_params: [f32; 4], } unsafe impl Byteable for PointLightUniform {} @@ -41,10 +43,11 @@ impl PointLightUniform { // premultiply color by intensity // we don't use the alpha at all, so no reason to multiply only [0..3] let color: [f32; 4] = (light.color * light.intensity).into(); + PointLightUniform { pos: [x, y, z, 1.0], color, - inverse_range_squared: [1.0 / (light.range * light.range), 0., 0., 0.], + light_params: [1.0 / (light.range * light.range), light.radius, 0.0, 0.0], } } } diff --git a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag index 5b217cee20f04..3891e90e0ba5d 100644 --- a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag +++ b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag @@ -39,7 +39,7 @@ const int MAX_LIGHTS = 10; struct PointLight { vec4 pos; vec4 color; - float inverseRangeSquared; + vec4 lightParams; }; layout(location = 0) in vec3 v_WorldPosition; @@ -193,12 +193,12 @@ vec3 fresnel(vec3 f0, float LoH) { // 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) } vec3 specular(vec3 f0, float roughness, const vec3 h, float NoV, float NoL, - float NoH, float LoH) { + float NoH, float LoH, float specularIntensity) { float D = D_GGX(roughness, NoH, h); float V = V_SmithGGXCorrelated(roughness, NoV, NoL); vec3 F = fresnel(f0, LoH); - return (D * V) * F; + return (specularIntensity * D * V) * F; } // Diffuse BRDF @@ -339,24 +339,44 @@ void main() { // Diffuse strength inversely related to metallicity vec3 diffuseColor = output_color.rgb * (1.0 - metallic); + vec3 R = reflect(-V, N); + // accumulate color vec3 light_accum = vec3(0.0); for (int i = 0; i < int(NumLights.x) && i < MAX_LIGHTS; ++i) { PointLight light = PointLights[i]; - vec3 light_to_frag = light.pos.xyz - v_WorldPosition.xyz; - vec3 L = normalize(light_to_frag); float distance_square = dot(light_to_frag, light_to_frag); - float rangeAttenuation = - getDistanceAttenuation(distance_square, light.inverseRangeSquared); - + getDistanceAttenuation(distance_square, light.lightParams.r); + + // Specular. + // Representative Point Area Lights. + // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 + float a = roughness; + float radius = light.lightParams.g; + vec3 centerToRay = dot(light_to_frag, R) * R - light_to_frag; + vec3 closestPoint = light_to_frag + centerToRay * saturate(radius * inversesqrt(dot(centerToRay, centerToRay))); + float LspecLengthInverse = inversesqrt(dot(closestPoint, closestPoint)); + float normalizationFactor = a / saturate(a + (radius * 0.5 * LspecLengthInverse)); + float specularIntensity = normalizationFactor * normalizationFactor; + + vec3 L = closestPoint * LspecLengthInverse; // normalize() equivalent? vec3 H = normalize(L + V); float NoL = saturate(dot(N, L)); float NoH = saturate(dot(N, H)); float LoH = saturate(dot(L, H)); - vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH); + vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); + + // Diffuse. + // Comes after specular since its NoL is used in the lighting equation. + L = normalize(light_to_frag); + H = normalize(L + V); + NoL = saturate(dot(N, L)); + NoH = saturate(dot(N, H)); + LoH = saturate(dot(L, H)); + vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩