Skip to content

Commit

Permalink
Spherical Area Lights (#1901)
Browse files Browse the repository at this point in the history
I still need to simplify and optimize the code, but here's a preliminary working version of Spherical Area Lights. See the example image below from a modified version of my [cubism-demo-rs](https://github.com/Josh015/cubism-demo-rs) app, which you can also clone and run to see them in action.

![Spherical Area Lights v1](https://user-images.githubusercontent.com/8846132/114491862-60df6000-9be5-11eb-8950-f039b74e1e96.jpg)
  • Loading branch information
Josh015 committed Apr 22, 2021
1 parent b964024 commit 19f467e
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 11 deletions.
7 changes: 5 additions & 2 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct PointLight {
pub color: Color,
pub intensity: f32,
pub range: f32,
pub radius: f32,
}

impl Default for PointLight {
Expand All @@ -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,
}
}
}
Expand All @@ -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 {}
Expand All @@ -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],
}
}
}
Expand Down
38 changes: 29 additions & 9 deletions crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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⟩
Expand Down

0 comments on commit 19f467e

Please sign in to comment.