feat(renderer): add multi-light system with LightsUniform and updated PBR shader
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,22 @@ struct CameraUniform {
|
||||
camera_pos: vec3<f32>,
|
||||
};
|
||||
|
||||
struct LightUniform {
|
||||
struct LightData {
|
||||
position: vec3<f32>,
|
||||
light_type: u32,
|
||||
direction: vec3<f32>,
|
||||
range: f32,
|
||||
color: vec3<f32>,
|
||||
ambient_strength: f32,
|
||||
intensity: f32,
|
||||
inner_cone: f32,
|
||||
outer_cone: f32,
|
||||
_padding: vec2<f32>,
|
||||
};
|
||||
|
||||
struct LightsUniform {
|
||||
lights: array<LightData, 16>,
|
||||
count: u32,
|
||||
ambient_color: vec3<f32>,
|
||||
};
|
||||
|
||||
struct MaterialUniform {
|
||||
@@ -18,7 +30,7 @@ struct MaterialUniform {
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> camera: CameraUniform;
|
||||
@group(0) @binding(1) var<uniform> light: LightUniform;
|
||||
@group(0) @binding(1) var<uniform> lights_uniform: LightsUniform;
|
||||
|
||||
@group(1) @binding(0) var t_diffuse: texture_2d<f32>;
|
||||
@group(1) @binding(1) var s_diffuse: sampler;
|
||||
@@ -81,6 +93,78 @@ fn fresnel_schlick(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> {
|
||||
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||
}
|
||||
|
||||
// Point light distance attenuation: inverse-square with smooth falloff at range boundary
|
||||
fn attenuation_point(distance: f32, range: f32) -> f32 {
|
||||
let d_over_r = distance / range;
|
||||
let d_over_r4 = d_over_r * d_over_r * d_over_r * d_over_r;
|
||||
let falloff = clamp(1.0 - d_over_r4, 0.0, 1.0);
|
||||
return (falloff * falloff) / (distance * distance + 0.0001);
|
||||
}
|
||||
|
||||
// Spot light angular attenuation
|
||||
fn attenuation_spot(light: LightData, L: vec3<f32>) -> f32 {
|
||||
let spot_dir = normalize(light.direction);
|
||||
let theta = dot(spot_dir, -L);
|
||||
return clamp(
|
||||
(theta - light.outer_cone) / (light.inner_cone - light.outer_cone + 0.0001),
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
|
||||
// Cook-Torrance BRDF contribution for one light
|
||||
fn compute_light_contribution(
|
||||
light: LightData,
|
||||
N: vec3<f32>,
|
||||
V: vec3<f32>,
|
||||
world_pos: vec3<f32>,
|
||||
F0: vec3<f32>,
|
||||
albedo: vec3<f32>,
|
||||
metallic: f32,
|
||||
roughness: f32,
|
||||
) -> vec3<f32> {
|
||||
var L: vec3<f32>;
|
||||
var radiance: vec3<f32>;
|
||||
|
||||
if light.light_type == 0u {
|
||||
// Directional
|
||||
L = normalize(-light.direction);
|
||||
radiance = light.color * light.intensity;
|
||||
} else if light.light_type == 1u {
|
||||
// Point
|
||||
let to_light = light.position - world_pos;
|
||||
let dist = length(to_light);
|
||||
L = normalize(to_light);
|
||||
let att = attenuation_point(dist, light.range);
|
||||
radiance = light.color * light.intensity * att;
|
||||
} else {
|
||||
// Spot
|
||||
let to_light = light.position - world_pos;
|
||||
let dist = length(to_light);
|
||||
L = normalize(to_light);
|
||||
let att_dist = attenuation_point(dist, light.range);
|
||||
let att_ang = attenuation_spot(light, L);
|
||||
radiance = light.color * light.intensity * att_dist * att_ang;
|
||||
}
|
||||
|
||||
let H = normalize(V + L);
|
||||
|
||||
let NDF = distribution_ggx(N, H, roughness);
|
||||
let G = geometry_smith(N, V, L, roughness);
|
||||
let F = fresnel_schlick(max(dot(H, V), 0.0), F0);
|
||||
|
||||
let ks = F;
|
||||
let kd = (vec3<f32>(1.0) - ks) * (1.0 - metallic);
|
||||
|
||||
let numerator = NDF * G * F;
|
||||
let NdotL = max(dot(N, L), 0.0);
|
||||
let NdotV = max(dot(N, V), 0.0);
|
||||
let denominator = 4.0 * NdotV * NdotL + 0.0001;
|
||||
let specular = numerator / denominator;
|
||||
|
||||
return (kd * albedo / 3.14159265358979 + specular) * radiance * NdotL;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let tex_color = textureSample(t_diffuse, s_diffuse, in.uv);
|
||||
@@ -95,29 +179,18 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// F0: base reflectivity; 0.04 for dielectrics, albedo for metals
|
||||
let F0 = mix(vec3<f32>(0.04, 0.04, 0.04), albedo, metallic);
|
||||
|
||||
// Single directional light
|
||||
let L = normalize(-light.direction);
|
||||
let H = normalize(V + L);
|
||||
let radiance = light.color;
|
||||
|
||||
// Cook-Torrance BRDF components
|
||||
let NDF = distribution_ggx(N, H, roughness);
|
||||
let G = geometry_smith(N, V, L, roughness);
|
||||
let F = fresnel_schlick(max(dot(H, V), 0.0), F0);
|
||||
|
||||
let ks = F;
|
||||
let kd = (vec3<f32>(1.0) - ks) * (1.0 - metallic);
|
||||
|
||||
let numerator = NDF * G * F;
|
||||
let NdotL = max(dot(N, L), 0.0);
|
||||
let NdotV = max(dot(N, V), 0.0);
|
||||
let denominator = 4.0 * NdotV * NdotL + 0.0001;
|
||||
let specular = numerator / denominator;
|
||||
|
||||
let Lo = (kd * albedo / 3.14159265358979 + specular) * radiance * NdotL;
|
||||
// Accumulate contribution from all active lights
|
||||
var Lo = vec3<f32>(0.0);
|
||||
let light_count = min(lights_uniform.count, 16u);
|
||||
for (var i = 0u; i < light_count; i++) {
|
||||
Lo += compute_light_contribution(
|
||||
lights_uniform.lights[i],
|
||||
N, V, in.world_pos, F0, albedo, metallic, roughness,
|
||||
);
|
||||
}
|
||||
|
||||
// Ambient term
|
||||
let ambient = light.ambient_strength * light.color * albedo * ao;
|
||||
let ambient = lights_uniform.ambient_color * albedo * ao;
|
||||
|
||||
var color = ambient + Lo;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user