pub const LIT_FORWARD_WGSL: &str = "// Lit Forward Shader\n// Multi-light forward rendering with Blinn-Phong lighting\n// Supports: 4 directional + 16 point + 8 spot lights\n\n// --- Vertex Shader ---\n\n// Camera uniform block containing view and projection matrices\nstruct CameraUniforms {\n view_projection: mat4x4<f32>, // Combined projection * view matrix\n camera_position: vec4<f32>, // Camera position in world space (w is padding)\n};\n\n@group(0) @binding(0)\nvar<uniform> camera: CameraUniforms;\n\n// Vertex input from the vertex buffer\nstruct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) uv: vec2<f32>,\n @location(3) color: vec3<f32>,\n};\n\n// Output from vertex shader to fragment shader\nstruct VertexOutput {\n @builtin(position) clip_position: vec4<f32>,\n @location(0) world_position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) uv: vec2<f32>,\n @location(3) color: vec3<f32>,\n};\n\n// Model transform uniform\nstruct ModelUniforms {\n model_matrix: mat4x4<f32>,\n normal_matrix: mat4x4<f32>, // For transforming normals\n};\n\n@group(1) @binding(0)\nvar<uniform> model: ModelUniforms;\n\n@vertex\nfn vs_main(input: VertexInput) -> VertexOutput {\n var out: VertexOutput;\n \n // Transform vertex position to world space\n let world_pos = model.model_matrix * vec4<f32>(input.position, 1.0);\n out.world_position = world_pos.xyz;\n \n // Transform to clip space\n out.clip_position = camera.view_projection * world_pos;\n \n // Transform normal to world space (using normal matrix to handle non-uniform scales)\n out.normal = normalize((model.normal_matrix * vec4<f32>(input.normal, 0.0)).xyz);\n \n // Pass through UV and color\n out.uv = input.uv;\n out.color = input.color;\n \n return out;\n}\n\n\n// --- Fragment Shader ---\n\n// Material properties uniform\nstruct MaterialUniforms {\n base_color: vec4<f32>, // RGBA base color\n emissive: vec3<f32>, // RGB emissive color\n specular_power: f32, // Specular exponent (shininess)\n ambient: vec3<f32>, // Ambient color\n _padding: f32, // Alignment padding\n};\n\n@group(2) @binding(0)\nvar<uniform> material: MaterialUniforms;\n\n// --- Light Structures ---\n\nstruct DirectionalLight {\n direction: vec3<f32>,\n _padding1: f32,\n color: vec3<f32>,\n intensity: f32,\n};\n\nstruct PointLight {\n position: vec3<f32>,\n range: f32,\n color: vec3<f32>,\n intensity: f32,\n};\n\nstruct SpotLight {\n position: vec3<f32>,\n range: f32,\n direction: vec3<f32>,\n inner_cone_cos: f32,\n color: vec3<f32>,\n outer_cone_cos: f32,\n intensity: f32,\n _padding: vec3<f32>,\n};\n\n// Light arrays with fixed sizes matching LitForwardLane defaults\nconst MAX_DIRECTIONAL_LIGHTS: u32 = 4u;\nconst MAX_POINT_LIGHTS: u32 = 16u;\nconst MAX_SPOT_LIGHTS: u32 = 8u;\n\nstruct LightingUniforms {\n directional_lights: array<DirectionalLight, 4>,\n point_lights: array<PointLight, 16>,\n spot_lights: array<SpotLight, 8>,\n num_directional_lights: u32,\n num_point_lights: u32,\n num_spot_lights: u32,\n _padding: u32,\n};\n\n@group(3) @binding(0)\nvar<uniform> lights: LightingUniforms;\n\n// --- Lighting Functions ---\n\n/// Calculates attenuation for point/spot lights based on distance and range\nfn calculate_attenuation(distance: f32, range: f32) -> f32 {\n // Smooth attenuation that reaches zero at range\n let normalized_distance = distance / range;\n let attenuation = saturate(1.0 - normalized_distance * normalized_distance);\n return attenuation * attenuation;\n}\n\n/// Calculates spotlight cone attenuation\nfn calculate_spot_attenuation(\n light_dir: vec3<f32>,\n spot_direction: vec3<f32>,\n inner_cone_cos: f32,\n outer_cone_cos: f32\n) -> f32 {\n let cos_angle = dot(-light_dir, spot_direction);\n return smoothstep(outer_cone_cos, inner_cone_cos, cos_angle);\n}\n\n/// Blinn-Phong BRDF calculation\nfn blinn_phong(\n N: vec3<f32>, // Surface normal (normalized)\n V: vec3<f32>, // View direction (normalized)\n L: vec3<f32>, // Light direction (normalized, pointing toward light)\n light_color: vec3<f32>,\n light_intensity: f32,\n diffuse_color: vec3<f32>,\n specular_power: f32\n) -> vec3<f32> {\n // Diffuse component (Lambertian)\n let NdotL = max(dot(N, L), 0.0);\n let diffuse = diffuse_color * NdotL;\n \n // Specular component (Blinn-Phong)\n let H = normalize(L + V); // Half vector\n let NdotH = max(dot(N, H), 0.0);\n let specular_strength = pow(NdotH, specular_power);\n let specular = vec3<f32>(specular_strength);\n \n return (diffuse + specular) * light_color * light_intensity;\n}\n\n/// Calculate contribution from all directional lights\nfn calculate_directional_lights(\n N: vec3<f32>,\n V: vec3<f32>,\n diffuse_color: vec3<f32>,\n specular_power: f32\n) -> vec3<f32> {\n var result = vec3<f32>(0.0);\n \n for (var i = 0u; i < lights.num_directional_lights && i < MAX_DIRECTIONAL_LIGHTS; i++) {\n let light = lights.directional_lights[i];\n let L = -normalize(light.direction); // Reverse direction (toward light)\n \n result += blinn_phong(\n N, V, L,\n light.color,\n light.intensity,\n diffuse_color,\n specular_power\n );\n }\n \n return result;\n}\n\n/// Calculate contribution from all point lights\nfn calculate_point_lights(\n world_position: vec3<f32>,\n N: vec3<f32>,\n V: vec3<f32>,\n diffuse_color: vec3<f32>,\n specular_power: f32\n) -> vec3<f32> {\n var result = vec3<f32>(0.0);\n \n for (var i = 0u; i < lights.num_point_lights && i < MAX_POINT_LIGHTS; i++) {\n let light = lights.point_lights[i];\n let light_vec = light.position - world_position;\n let distance = length(light_vec);\n \n // Skip if outside range\n if (distance > light.range) {\n continue;\n }\n \n let L = normalize(light_vec);\n let attenuation = calculate_attenuation(distance, light.range);\n \n result += blinn_phong(\n N, V, L,\n light.color,\n light.intensity * attenuation,\n diffuse_color,\n specular_power\n );\n }\n \n return result;\n}\n\n/// Calculate contribution from all spot lights\nfn calculate_spot_lights(\n world_position: vec3<f32>,\n N: vec3<f32>,\n V: vec3<f32>,\n diffuse_color: vec3<f32>,\n specular_power: f32\n) -> vec3<f32> {\n var result = vec3<f32>(0.0);\n \n for (var i = 0u; i < lights.num_spot_lights && i < MAX_SPOT_LIGHTS; i++) {\n let light = lights.spot_lights[i];\n let light_vec = light.position - world_position;\n let distance = length(light_vec);\n \n // Skip if outside range\n if (distance > light.range) {\n continue;\n }\n \n let L = normalize(light_vec);\n let distance_attenuation = calculate_attenuation(distance, light.range);\n let spot_attenuation = calculate_spot_attenuation(\n L,\n normalize(light.direction),\n light.inner_cone_cos,\n light.outer_cone_cos\n );\n let total_attenuation = distance_attenuation * spot_attenuation;\n \n // Skip if outside cone\n if (total_attenuation <= 0.0) {\n continue;\n }\n \n result += blinn_phong(\n N, V, L,\n light.color,\n light.intensity * total_attenuation,\n diffuse_color,\n specular_power\n );\n }\n \n return result;\n}\n\n@fragment\nfn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {\n // Prepare surface data\n let N = normalize(input.normal);\n let V = normalize(camera.camera_position.xyz - input.world_position);\n \n // Calculate base diffuse color (material * vertex color)\n let diffuse_color = material.base_color.rgb * input.color;\n \n // Start with ambient lighting\n var final_color = material.ambient * diffuse_color;\n \n // Add contributions from all light types\n final_color += calculate_directional_lights(N, V, diffuse_color, material.specular_power);\n final_color += calculate_point_lights(input.world_position, N, V, diffuse_color, material.specular_power);\n final_color += calculate_spot_lights(input.world_position, N, V, diffuse_color, material.specular_power);\n \n // Add emissive\n final_color += material.emissive;\n \n // Simple tone mapping (Reinhard)\n final_color = final_color / (final_color + vec3<f32>(1.0));\n \n // Gamma correction\n final_color = pow(final_color, vec3<f32>(1.0 / 2.2));\n \n return vec4<f32>(final_color, material.base_color.a);\n}\n";Expand description
Lit forward rendering shader with multi-light Blinn-Phong lighting.
Supports:
- Up to 4 directional lights
- Up to 16 point lights
- Up to 8 spot lights
Uses Blinn-Phong BRDF with Reinhard tone mapping.