mirror of
https://github.com/bevyengine/bevy
synced 2024-12-24 12:03:14 +00:00
ad6872275f
We want to use the clustering infrastructure for light probes and decals as well, not just point lights. This patch builds on top of #13640 and performs the rename. To make this series easier to review, this patch makes no code changes. Only identifiers and comments are modified. ## Migration Guide * In the PBR shaders, `point_lights` is now known as `clusterable_objects`, `PointLight` is now known as `ClusterableObject`, and `cluster_light_index_lists` is now known as `clusterable_object_index_lists`.
611 lines
22 KiB
WebGPU Shading Language
611 lines
22 KiB
WebGPU Shading Language
#define_import_path bevy_pbr::lighting
|
||
|
||
#import bevy_pbr::{
|
||
mesh_view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE,
|
||
mesh_view_bindings as view_bindings,
|
||
}
|
||
#import bevy_render::maths::PI
|
||
|
||
const LAYER_BASE: u32 = 0;
|
||
const LAYER_CLEARCOAT: u32 = 1;
|
||
|
||
// From the Filament design doc
|
||
// https://google.github.io/filament/Filament.html#table_symbols
|
||
// Symbol Definition
|
||
// v View unit vector
|
||
// l Incident light unit vector
|
||
// n Surface normal unit vector
|
||
// h Half unit vector between l and v
|
||
// f BRDF
|
||
// f_d Diffuse component of a BRDF
|
||
// f_r Specular component of a BRDF
|
||
// α Roughness, remapped from using input perceptualRoughness
|
||
// σ Diffuse reflectance
|
||
// Ω Spherical domain
|
||
// f0 Reflectance at normal incidence
|
||
// f90 Reflectance at grazing angle
|
||
// χ+(a) Heaviside function (1 if a>0 and 0 otherwise)
|
||
// nior Index of refraction (IOR) of an interface
|
||
// ⟨n⋅l⟩ Dot product clamped to [0..1]
|
||
// ⟨a⟩ Saturated value (clamped to [0..1])
|
||
|
||
// The Bidirectional Reflectance Distribution Function (BRDF) describes the surface response of a standard material
|
||
// and consists of two components, the diffuse component (f_d) and the specular component (f_r):
|
||
// f(v,l) = f_d(v,l) + f_r(v,l)
|
||
//
|
||
// The form of the microfacet model is the same for diffuse and specular
|
||
// f_r(v,l) = f_d(v,l) = 1 / { |n⋅v||n⋅l| } ∫_Ω D(m,α) G(v,l,m) f_m(v,l,m) (v⋅m) (l⋅m) dm
|
||
//
|
||
// In which:
|
||
// D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets
|
||
// G models the visibility (or occlusion or shadow-masking) of the microfacets
|
||
// f_m is the microfacet BRDF and differs between specular and diffuse components
|
||
//
|
||
// The above integration needs to be approximated.
|
||
|
||
// Input to a lighting function for a single layer (either the base layer or the
|
||
// clearcoat layer).
|
||
struct LayerLightingInput {
|
||
// The normal vector.
|
||
N: vec3<f32>,
|
||
// The reflected vector.
|
||
R: vec3<f32>,
|
||
// The normal vector ⋅ the view vector.
|
||
NdotV: f32,
|
||
|
||
// The perceptual roughness of the layer.
|
||
perceptual_roughness: f32,
|
||
// The roughness of the layer.
|
||
roughness: f32,
|
||
}
|
||
|
||
// Input to a lighting function (`point_light`, `spot_light`,
|
||
// `directional_light`).
|
||
struct LightingInput {
|
||
#ifdef STANDARD_MATERIAL_CLEARCOAT
|
||
layers: array<LayerLightingInput, 2>,
|
||
#else // STANDARD_MATERIAL_CLEARCOAT
|
||
layers: array<LayerLightingInput, 1>,
|
||
#endif // STANDARD_MATERIAL_CLEARCOAT
|
||
|
||
// The world-space position.
|
||
P: vec3<f32>,
|
||
// The vector to the view.
|
||
V: vec3<f32>,
|
||
|
||
// The diffuse color of the material.
|
||
diffuse_color: vec3<f32>,
|
||
|
||
// Specular reflectance at the normal incidence angle.
|
||
//
|
||
// This should be read F₀, but due to Naga limitations we can't name it that.
|
||
F0_: vec3<f32>,
|
||
// Constants for the BRDF approximation.
|
||
//
|
||
// See `EnvBRDFApprox` in
|
||
// <https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile>.
|
||
// What we call `F_ab` they call `AB`.
|
||
F_ab: vec2<f32>,
|
||
|
||
#ifdef STANDARD_MATERIAL_CLEARCOAT
|
||
// The strength of the clearcoat layer.
|
||
clearcoat_strength: f32,
|
||
#endif // STANDARD_MATERIAL_CLEARCOAT
|
||
|
||
#ifdef STANDARD_MATERIAL_ANISOTROPY
|
||
// The anisotropy strength, reflecting the amount of increased roughness in
|
||
// the tangent direction.
|
||
anisotropy: f32,
|
||
// The tangent direction for anisotropy: i.e. the direction in which
|
||
// roughness increases.
|
||
Ta: vec3<f32>,
|
||
// The bitangent direction, which is the cross product of the normal with
|
||
// the tangent direction.
|
||
Ba: vec3<f32>,
|
||
#endif // STANDARD_MATERIAL_ANISOTROPY
|
||
}
|
||
|
||
// Values derived from the `LightingInput` for both diffuse and specular lights.
|
||
struct DerivedLightingInput {
|
||
// The half-vector between L, the incident light vector, and V, the view
|
||
// vector.
|
||
H: vec3<f32>,
|
||
// The normal vector ⋅ the incident light vector.
|
||
NdotL: f32,
|
||
// The normal vector ⋅ the half-vector.
|
||
NdotH: f32,
|
||
// The incident light vector ⋅ the half-vector.
|
||
LdotH: f32,
|
||
}
|
||
|
||
// distanceAttenuation is simply the square falloff of light intensity
|
||
// combined with a smooth attenuation at the edge of the light radius
|
||
//
|
||
// light radius is a non-physical construct for efficiency purposes,
|
||
// because otherwise every light affects every fragment in the scene
|
||
fn getDistanceAttenuation(distanceSquare: f32, inverseRangeSquared: f32) -> f32 {
|
||
let factor = distanceSquare * inverseRangeSquared;
|
||
let smoothFactor = saturate(1.0 - factor * factor);
|
||
let attenuation = smoothFactor * smoothFactor;
|
||
return attenuation * 1.0 / max(distanceSquare, 0.0001);
|
||
}
|
||
|
||
// Normal distribution function (specular D)
|
||
// Based on https://google.github.io/filament/Filament.html#citation-walter07
|
||
|
||
// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 }
|
||
|
||
// Simple implementation, has precision problems when using fp16 instead of fp32
|
||
// see https://google.github.io/filament/Filament.html#listing_speculardfp16
|
||
fn D_GGX(roughness: f32, NdotH: f32, h: vec3<f32>) -> f32 {
|
||
let oneMinusNdotHSquared = 1.0 - NdotH * NdotH;
|
||
let a = NdotH * roughness;
|
||
let k = roughness / (oneMinusNdotHSquared + a * a);
|
||
let d = k * k * (1.0 / PI);
|
||
return d;
|
||
}
|
||
|
||
// An approximation of the anisotropic GGX distribution function.
|
||
//
|
||
// 1
|
||
// D(𝐡) = ───────────────────────────────────────────────────
|
||
// παₜα_b((𝐡 ⋅ 𝐭)² / αₜ²) + (𝐡 ⋅ 𝐛)² / α_b² + (𝐡 ⋅ 𝐧)²)²
|
||
//
|
||
// * `T` = 𝐭 = the tangent direction = the direction of increased roughness.
|
||
//
|
||
// * `B` = 𝐛 = the bitangent direction = the direction of decreased roughness.
|
||
//
|
||
// * `at` = αₜ = the alpha-roughness in the tangent direction.
|
||
//
|
||
// * `ab` = α_b = the alpha-roughness in the bitangent direction.
|
||
//
|
||
// This is from the `KHR_materials_anisotropy` spec:
|
||
// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md#individual-lights>
|
||
fn D_GGX_anisotropic(at: f32, ab: f32, NdotH: f32, TdotH: f32, BdotH: f32) -> f32 {
|
||
let a2 = at * ab;
|
||
let f = vec3(ab * TdotH, at * BdotH, a2 * NdotH);
|
||
let w2 = a2 / dot(f, f);
|
||
let d = a2 * w2 * w2 * (1.0 / PI);
|
||
return d;
|
||
}
|
||
|
||
// Visibility function (Specular G)
|
||
// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) }
|
||
// such that f_r becomes
|
||
// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0)
|
||
// where
|
||
// V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) }
|
||
// Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv
|
||
fn V_SmithGGXCorrelated(roughness: f32, NdotV: f32, NdotL: f32) -> f32 {
|
||
let a2 = roughness * roughness;
|
||
let lambdaV = NdotL * sqrt((NdotV - a2 * NdotV) * NdotV + a2);
|
||
let lambdaL = NdotV * sqrt((NdotL - a2 * NdotL) * NdotL + a2);
|
||
let v = 0.5 / (lambdaV + lambdaL);
|
||
return v;
|
||
}
|
||
|
||
// The visibility function, anisotropic variant.
|
||
fn V_GGX_anisotropic(
|
||
at: f32,
|
||
ab: f32,
|
||
NdotL: f32,
|
||
NdotV: f32,
|
||
BdotV: f32,
|
||
TdotV: f32,
|
||
TdotL: f32,
|
||
BdotL: f32,
|
||
) -> f32 {
|
||
let GGX_V = NdotL * length(vec3(at * TdotV, ab * BdotV, NdotV));
|
||
let GGX_L = NdotV * length(vec3(at * TdotL, ab * BdotL, NdotL));
|
||
let v = 0.5 / (GGX_V + GGX_L);
|
||
return saturate(v);
|
||
}
|
||
|
||
// A simpler, but nonphysical, alternative to Smith-GGX. We use this for
|
||
// clearcoat, per the Filament spec.
|
||
//
|
||
// https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel#toc4.9.1
|
||
fn V_Kelemen(LdotH: f32) -> f32 {
|
||
return 0.25 / (LdotH * LdotH);
|
||
}
|
||
|
||
// Fresnel function
|
||
// see https://google.github.io/filament/Filament.html#citation-schlick94
|
||
// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5
|
||
fn F_Schlick_vec(f0: vec3<f32>, f90: f32, VdotH: f32) -> vec3<f32> {
|
||
// not using mix to keep the vec3 and float versions identical
|
||
return f0 + (f90 - f0) * pow(1.0 - VdotH, 5.0);
|
||
}
|
||
|
||
fn F_Schlick(f0: f32, f90: f32, VdotH: f32) -> f32 {
|
||
// not using mix to keep the vec3 and float versions identical
|
||
return f0 + (f90 - f0) * pow(1.0 - VdotH, 5.0);
|
||
}
|
||
|
||
fn fresnel(f0: vec3<f32>, LdotH: f32) -> vec3<f32> {
|
||
// f_90 suitable for ambient occlusion
|
||
// see https://google.github.io/filament/Filament.html#lighting/occlusion
|
||
let f90 = saturate(dot(f0, vec3<f32>(50.0 * 0.33)));
|
||
return F_Schlick_vec(f0, f90, LdotH);
|
||
}
|
||
|
||
// Given distribution, visibility, and Fresnel term, calculates the final
|
||
// specular light.
|
||
//
|
||
// Multiscattering approximation:
|
||
// <https://google.github.io/filament/Filament.html#listing_energycompensationimpl>
|
||
fn specular_multiscatter(
|
||
input: ptr<function, LightingInput>,
|
||
D: f32,
|
||
V: f32,
|
||
F: vec3<f32>,
|
||
specular_intensity: f32,
|
||
) -> vec3<f32> {
|
||
// Unpack.
|
||
let F0 = (*input).F0_;
|
||
let F_ab = (*input).F_ab;
|
||
|
||
var Fr = (specular_intensity * D * V) * F;
|
||
Fr *= 1.0 + F0 * (1.0 / F_ab.x - 1.0);
|
||
return Fr;
|
||
}
|
||
|
||
// Specular BRDF
|
||
// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf
|
||
|
||
// N, V, and L must all be normalized.
|
||
fn derive_lighting_input(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> DerivedLightingInput {
|
||
var input: DerivedLightingInput;
|
||
var H: vec3<f32> = normalize(L + V);
|
||
input.H = H;
|
||
input.NdotL = saturate(dot(N, L));
|
||
input.NdotH = saturate(dot(N, H));
|
||
input.LdotH = saturate(dot(L, H));
|
||
return input;
|
||
}
|
||
|
||
// Returns L in the `xyz` components and the specular intensity 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,
|
||
) -> vec4<f32> {
|
||
// Unpack.
|
||
let R = (*input).layers[layer].R;
|
||
let a = (*input).layers[layer].roughness;
|
||
|
||
// Representative Point Area Lights.
|
||
// see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16
|
||
let centerToRay = dot(light_to_frag, R) * R - light_to_frag;
|
||
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;
|
||
|
||
let L: vec3<f32> = closestPoint * LspecLengthInverse; // normalize() equivalent?
|
||
return vec4(L, intensity);
|
||
}
|
||
|
||
// 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>,
|
||
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 H = (*derived_input).H;
|
||
let NdotL = (*derived_input).NdotL;
|
||
let NdotH = (*derived_input).NdotH;
|
||
let LdotH = (*derived_input).LdotH;
|
||
|
||
// Calculate distribution.
|
||
let D = D_GGX(roughness, NdotH, H);
|
||
// Calculate visibility.
|
||
let V = V_SmithGGXCorrelated(roughness, NdotV, NdotL);
|
||
// Calculate the Fresnel term.
|
||
let F = fresnel(F0, LdotH);
|
||
|
||
// Calculate the specular light.
|
||
let Fr = specular_multiscatter(input, D, V, F, specular_intensity);
|
||
return Fr;
|
||
}
|
||
|
||
// Calculates the specular light for the clearcoat layer. Returns Fc, the
|
||
// Fresnel term, in the first channel, and Frc, the specular clearcoat light, in
|
||
// the second channel.
|
||
//
|
||
// <https://google.github.io/filament/Filament.html#listing_clearcoatbrdf>
|
||
fn specular_clearcoat(
|
||
input: ptr<function, LightingInput>,
|
||
derived_input: ptr<function, DerivedLightingInput>,
|
||
clearcoat_strength: f32,
|
||
specular_intensity: f32,
|
||
) -> vec2<f32> {
|
||
// Unpack.
|
||
let roughness = (*input).layers[LAYER_CLEARCOAT].roughness;
|
||
let H = (*derived_input).H;
|
||
let NdotH = (*derived_input).NdotH;
|
||
let LdotH = (*derived_input).LdotH;
|
||
|
||
// Calculate distribution.
|
||
let Dc = D_GGX(roughness, NdotH, H);
|
||
// Calculate visibility.
|
||
let Vc = V_Kelemen(LdotH);
|
||
// Calculate the Fresnel term.
|
||
let Fc = F_Schlick(0.04, 1.0, LdotH) * clearcoat_strength;
|
||
// Calculate the specular light.
|
||
let Frc = (specular_intensity * Dc * Vc) * Fc;
|
||
return vec2(Fc, Frc);
|
||
}
|
||
|
||
#ifdef STANDARD_MATERIAL_ANISOTROPY
|
||
|
||
fn specular_anisotropy(
|
||
input: ptr<function, LightingInput>,
|
||
derived_input: ptr<function, DerivedLightingInput>,
|
||
L: vec3<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_;
|
||
let anisotropy = (*input).anisotropy;
|
||
let Ta = (*input).Ta;
|
||
let Ba = (*input).Ba;
|
||
let H = (*derived_input).H;
|
||
let NdotL = (*derived_input).NdotL;
|
||
let NdotH = (*derived_input).NdotH;
|
||
let LdotH = (*derived_input).LdotH;
|
||
|
||
let TdotL = dot(Ta, L);
|
||
let BdotL = dot(Ba, L);
|
||
let TdotH = dot(Ta, H);
|
||
let BdotH = dot(Ba, H);
|
||
let TdotV = dot(Ta, V);
|
||
let BdotV = dot(Ba, V);
|
||
|
||
let ab = roughness * roughness;
|
||
let at = mix(ab, 1.0, anisotropy * anisotropy);
|
||
|
||
let Da = D_GGX_anisotropic(at, ab, NdotH, TdotH, BdotH);
|
||
let Va = V_GGX_anisotropic(at, ab, NdotL, NdotV, BdotV, TdotV, TdotL, BdotL);
|
||
let Fa = fresnel(F0, LdotH);
|
||
|
||
// Calculate the specular light.
|
||
let Fr = specular_multiscatter(input, Da, Va, Fa, specular_intensity);
|
||
return Fr;
|
||
}
|
||
|
||
#endif // STANDARD_MATERIAL_ANISOTROPY
|
||
|
||
// Diffuse BRDF
|
||
// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf
|
||
// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm
|
||
//
|
||
// simplest approximation
|
||
// float Fd_Lambert() {
|
||
// return 1.0 / PI;
|
||
// }
|
||
//
|
||
// vec3 Fd = diffuseColor * Fd_Lambert();
|
||
//
|
||
// Disney approximation
|
||
// See https://google.github.io/filament/Filament.html#citation-burley12
|
||
// minimal quality difference
|
||
fn Fd_Burley(
|
||
input: ptr<function, LightingInput>,
|
||
derived_input: ptr<function, DerivedLightingInput>,
|
||
) -> f32 {
|
||
// Unpack.
|
||
let roughness = (*input).layers[LAYER_BASE].roughness;
|
||
let NdotV = (*input).layers[LAYER_BASE].NdotV;
|
||
let NdotL = (*derived_input).NdotL;
|
||
let LdotH = (*derived_input).LdotH;
|
||
|
||
let f90 = 0.5 + 2.0 * roughness * LdotH * LdotH;
|
||
let lightScatter = F_Schlick(1.0, f90, NdotL);
|
||
let viewScatter = F_Schlick(1.0, f90, NdotV);
|
||
return lightScatter * viewScatter * (1.0 / PI);
|
||
}
|
||
|
||
// Scale/bias approximation
|
||
// https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
|
||
// TODO: Use a LUT (more accurate)
|
||
fn F_AB(perceptual_roughness: f32, NdotV: f32) -> vec2<f32> {
|
||
let c0 = vec4<f32>(-1.0, -0.0275, -0.572, 0.022);
|
||
let c1 = vec4<f32>(1.0, 0.0425, 1.04, -0.04);
|
||
let r = perceptual_roughness * c0 + c1;
|
||
let a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
|
||
return vec2<f32>(-1.04, 1.04) * a004 + r.zw;
|
||
}
|
||
|
||
fn EnvBRDFApprox(F0: vec3<f32>, F_ab: vec2<f32>) -> vec3<f32> {
|
||
return F0 * F_ab.x + F_ab.y;
|
||
}
|
||
|
||
fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 {
|
||
// clamp perceptual roughness to prevent precision problems
|
||
// According to Filament design 0.089 is recommended for mobile
|
||
// Filament uses 0.045 for non-mobile
|
||
let clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0);
|
||
return clampedPerceptualRoughness * clampedPerceptualRoughness;
|
||
}
|
||
|
||
fn point_light(light_id: u32, input: ptr<function, LightingInput>) -> vec3<f32> {
|
||
// Unpack.
|
||
let diffuse_color = (*input).diffuse_color;
|
||
let P = (*input).P;
|
||
let N = (*input).layers[LAYER_BASE].N;
|
||
let V = (*input).V;
|
||
|
||
let light = &view_bindings::clusterable_objects.data[light_id];
|
||
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 rangeAttenuation = getDistanceAttenuation(distance_square, (*light).color_inverse_square_range.w);
|
||
|
||
// Base layer
|
||
|
||
let specular_L_intensity = compute_specular_layer_values_for_point_light(
|
||
input,
|
||
LAYER_BASE,
|
||
V,
|
||
light_to_frag,
|
||
(*light).position_radius.w,
|
||
);
|
||
var specular_derived_input = derive_lighting_input(N, V, specular_L_intensity.xyz);
|
||
|
||
let specular_intensity = specular_L_intensity.w;
|
||
|
||
#ifdef STANDARD_MATERIAL_ANISOTROPY
|
||
let specular_light = specular_anisotropy(input, &specular_derived_input, L, specular_intensity);
|
||
#else // STANDARD_MATERIAL_ANISOTROPY
|
||
let specular_light = specular(input, &specular_derived_input, specular_intensity);
|
||
#endif // STANDARD_MATERIAL_ANISOTROPY
|
||
|
||
// Clearcoat
|
||
|
||
#ifdef STANDARD_MATERIAL_CLEARCOAT
|
||
// Unpack.
|
||
let clearcoat_N = (*input).layers[LAYER_CLEARCOAT].N;
|
||
let clearcoat_strength = (*input).clearcoat_strength;
|
||
|
||
// 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(
|
||
input,
|
||
LAYER_CLEARCOAT,
|
||
V,
|
||
light_to_frag,
|
||
(*light).position_radius.w,
|
||
);
|
||
var clearcoat_specular_derived_input =
|
||
derive_lighting_input(clearcoat_N, V, clearcoat_specular_L_intensity.xyz);
|
||
|
||
// Calculate the specular light.
|
||
let clearcoat_specular_intensity = clearcoat_specular_L_intensity.w;
|
||
let Fc_Frc = specular_clearcoat(
|
||
input,
|
||
&clearcoat_specular_derived_input,
|
||
clearcoat_strength,
|
||
clearcoat_specular_intensity
|
||
);
|
||
let inv_Fc = 1.0 - Fc_Frc.r; // Inverse Fresnel term.
|
||
let Frc = Fc_Frc.g; // Clearcoat light.
|
||
#endif // STANDARD_MATERIAL_CLEARCOAT
|
||
|
||
// Diffuse.
|
||
// Comes after specular since its N⋅L is used in the lighting equation.
|
||
var derived_input = derive_lighting_input(N, V, L);
|
||
let diffuse = diffuse_color * Fd_Burley(input, &derived_input);
|
||
|
||
// See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation
|
||
// Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩
|
||
// where
|
||
// f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color
|
||
// Φ is luminous power in lumens
|
||
// our rangeAttenuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius
|
||
|
||
// For a point light, luminous intensity, I, in lumens per steradian is given by:
|
||
// I = Φ / 4 π
|
||
// The derivation of this can be seen here: https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower
|
||
|
||
// NOTE: (*light).color.rgb is premultiplied with (*light).intensity / 4 π (which would be the luminous intensity) on the CPU
|
||
|
||
var color: 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;
|
||
#else // STANDARD_MATERIAL_CLEARCOAT
|
||
color = diffuse + specular_light;
|
||
#endif // STANDARD_MATERIAL_CLEARCOAT
|
||
|
||
return color * (*light).color_inverse_square_range.rgb *
|
||
(rangeAttenuation * derived_input.NdotL);
|
||
}
|
||
|
||
fn spot_light(light_id: u32, input: ptr<function, LightingInput>) -> vec3<f32> {
|
||
// reuse the point light calculations
|
||
let point_light = point_light(light_id, input);
|
||
|
||
let light = &view_bindings::clusterable_objects.data[light_id];
|
||
|
||
// reconstruct spot dir from x/z and y-direction flag
|
||
var spot_dir = vec3<f32>((*light).light_custom_data.x, 0.0, (*light).light_custom_data.y);
|
||
spot_dir.y = sqrt(max(0.0, 1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z));
|
||
if ((*light).flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u {
|
||
spot_dir.y = -spot_dir.y;
|
||
}
|
||
let light_to_frag = (*light).position_radius.xyz - (*input).P.xyz;
|
||
|
||
// calculate attenuation based on filament formula https://google.github.io/filament/Filament.html#listing_glslpunctuallight
|
||
// spot_scale and spot_offset have been precomputed
|
||
// note we normalize here to get "l" from the filament listing. spot_dir is already normalized
|
||
let cd = dot(-spot_dir, normalize(light_to_frag));
|
||
let attenuation = saturate(cd * (*light).light_custom_data.z + (*light).light_custom_data.w);
|
||
let spot_attenuation = attenuation * attenuation;
|
||
|
||
return point_light * spot_attenuation;
|
||
}
|
||
|
||
fn directional_light(light_id: u32, input: ptr<function, LightingInput>) -> vec3<f32> {
|
||
// Unpack.
|
||
let diffuse_color = (*input).diffuse_color;
|
||
let NdotV = (*input).layers[LAYER_BASE].NdotV;
|
||
let N = (*input).layers[LAYER_BASE].N;
|
||
let V = (*input).V;
|
||
let roughness = (*input).layers[LAYER_BASE].roughness;
|
||
|
||
let light = &view_bindings::lights.directional_lights[light_id];
|
||
|
||
let L = (*light).direction_to_light.xyz;
|
||
var derived_input = derive_lighting_input(N, V, L);
|
||
|
||
let diffuse = diffuse_color * Fd_Burley(input, &derived_input);
|
||
|
||
#ifdef STANDARD_MATERIAL_ANISOTROPY
|
||
let specular_light = specular_anisotropy(input, &derived_input, L, 1.0);
|
||
#else // STANDARD_MATERIAL_ANISOTROPY
|
||
let specular_light = specular(input, &derived_input, 1.0);
|
||
#endif // STANDARD_MATERIAL_ANISOTROPY
|
||
|
||
#ifdef STANDARD_MATERIAL_CLEARCOAT
|
||
let clearcoat_N = (*input).layers[LAYER_CLEARCOAT].N;
|
||
let clearcoat_strength = (*input).clearcoat_strength;
|
||
|
||
// 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);
|
||
let inv_Fc = 1.0 - Fc_Frc.r;
|
||
let Frc = Fc_Frc.g;
|
||
#endif // STANDARD_MATERIAL_CLEARCOAT
|
||
|
||
var color: 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 * derived_input.NdotL +
|
||
Frc * derived_clearcoat_input.NdotL;
|
||
#else // STANDARD_MATERIAL_CLEARCOAT
|
||
color = (diffuse + specular_light) * derived_input.NdotL;
|
||
#endif // STANDARD_MATERIAL_CLEARCOAT
|
||
|
||
return color * (*light).color.rgb;
|
||
}
|