struct VertexInput { @location(0) position: vec3, @location(1) color: vec3, @location(2) normal: vec3, }; struct InstanceInput { @location(5) model_row0: vec4, @location(6) model_row1: vec4, @location(7) model_row2: vec4, @location(8) model_row3: vec4, @location(9) color: vec3, @location(10) flags: u32, }; struct VSOutput { @builtin(position) position: vec4, @location(0) frag_color: vec3, @location(1) world_pos: vec3, @location(2) normal: vec3, @location(3) flags: u32, }; struct LightCount { count: u32, }; @group(1) @binding(1) var light_count: LightCount; struct Globals { view_proj: mat4x4, } @group(0) @binding(0) var globals: Globals; struct GpuLight { position: vec3, light_type: u32, color: vec3, intensity: f32, direction: vec3, range: f32, inner_cutoff: f32, outer_cutoff: f32, }; @group(1) @binding(0) var all_lights: array; @vertex fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput { var out: VSOutput; let model = mat4x4( instance.model_row0, instance.model_row1, instance.model_row2, instance.model_row3 ); let world_position = (model * vec4(vertex.position, 1.0)).xyz; let normal_matrix = mat3x3( instance.model_row0.xyz, instance.model_row1.xyz, instance.model_row2.xyz ); out.position = globals.view_proj * vec4(world_position, 1.0); out.frag_color = instance.color * vertex.color; out.world_pos = world_position; out.normal = normalize(normal_matrix * vertex.normal); out.flags = instance.flags; return out; } @fragment fn fs_main(input: VSOutput) -> @location(0) vec4 { var lighting: vec3 = vec3(0.0); let always_lit = (input.flags & 0x1u) != 0u; if (always_lit) { return vec4(input.frag_color * 2.0, 1.0); } for (var i = 0u; i < light_count.count; i = i + 1u) { let light = all_lights[i]; var light_contrib: vec3 = vec3(0.0); let light_dir = normalize(light.position - input.world_pos); let diff = max(dot(input.normal, light_dir), 0.0); switch light.light_type { case 0u: { // Directional light_contrib = light.color * light.intensity * diff; } case 1u: { // Point let dist = distance(light.position, input.world_pos); if (dist < light.range) { let attenuation = 1.0 / (dist * dist); light_contrib = light.color * light.intensity * diff * attenuation; } } case 2u: { // Spot let spot_dir = normalize(-light.direction); let angle = dot(spot_dir, light_dir); if (angle > light.outer_cutoff) { let intensity = clamp((angle - light.outer_cutoff) / (light.inner_cutoff - light.outer_cutoff), 0.0, 1.0); let dist = distance(light.position, input.world_pos); let attenuation = 1.0 / (dist * dist); light_contrib = light.color * light.intensity * diff * attenuation * intensity; } } default: {} } lighting += light_contrib; } return vec4(input.frag_color * lighting, 1.0); }