FORWARD_PLUS_WGSL

Constant FORWARD_PLUS_WGSL 

Source
pub const FORWARD_PLUS_WGSL: &str = "// Forward+ Fragment Shader\n// Tile-based deferred light lookup with Blinn-Phong shading\n//\n// This shader uses the pre-computed light culling data to only\n// iterate over lights that actually affect the current pixel\'s tile.\n\n// --- Vertex Shader (same as lit_forward.wgsl) ---\n\nstruct CameraUniforms {\n    view_projection: mat4x4<f32>,\n    camera_position: vec4<f32>,\n};\n\n@group(0) @binding(0)\nvar<uniform> camera: CameraUniforms;\n\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\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\nstruct ModelUniforms {\n    model_matrix: mat4x4<f32>,\n    normal_matrix: mat4x4<f32>,\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    let world_pos = model.model_matrix * vec4<f32>(input.position, 1.0);\n    out.world_position = world_pos.xyz;\n    out.clip_position = camera.view_projection * world_pos;\n    out.normal = normalize((model.normal_matrix * vec4<f32>(input.normal, 0.0)).xyz);\n    out.uv = input.uv;\n    out.color = input.color;\n    return out;\n}\n\n// --- Fragment Shader ---\n\nstruct MaterialUniforms {\n    base_color: vec4<f32>,\n    emissive: vec3<f32>,\n    specular_power: f32,\n    ambient: vec3<f32>,\n    _padding: f32,\n};\n\n@group(2) @binding(0)\nvar<uniform> material: MaterialUniforms;\n\n// --- Forward+ Light Data ---\n\n// Unified light structure (matches GpuLight in Rust, 64 bytes)\nstruct GpuLight {\n    position: vec3<f32>,\n    range: f32,\n    color: vec3<f32>,\n    intensity: f32,\n    direction: vec3<f32>,\n    light_type: u32,      // 0 = directional, 1 = point, 2 = spot\n    inner_cone_cos: f32,\n    outer_cone_cos: f32,\n    _padding: vec2<f32>,\n};\n\n// Tile info uniform\nstruct TileInfo {\n    tile_count: vec2<u32>,\n    tile_size: u32,\n    max_lights_per_tile: u32,\n};\n\n@group(3) @binding(0)\nvar<storage, read> lights: array<GpuLight>;\n\n@group(3) @binding(1)\nvar<storage, read> light_indices: array<u32>;\n\n@group(3) @binding(2)\nvar<storage, read> light_grid: array<u32>;  // [offset, count] pairs per tile\n\n@group(3) @binding(3)\nvar<uniform> tile_info: TileInfo;\n\n// --- Lighting Functions ---\n\nfn calculate_attenuation(distance: f32, range: f32) -> f32 {\n    let normalized_distance = distance / range;\n    let attenuation = saturate(1.0 - normalized_distance * normalized_distance);\n    return attenuation * attenuation;\n}\n\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\nfn blinn_phong(\n    N: vec3<f32>,\n    V: vec3<f32>,\n    L: vec3<f32>,\n    light_color: vec3<f32>,\n    light_intensity: f32,\n    diffuse_color: vec3<f32>,\n    specular_power: f32\n) -> vec3<f32> {\n    let NdotL = max(dot(N, L), 0.0);\n    let diffuse = diffuse_color * NdotL;\n    \n    let H = normalize(L + V);\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 a single light\nfn calculate_light_contribution(\n    light: GpuLight,\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    // Directional light\n    if (light.light_type == 0u) {\n        let L = -normalize(light.direction);\n        return blinn_phong(N, V, L, light.color, light.intensity, diffuse_color, specular_power);\n    }\n    \n    // Point/Spot light common setup\n    let light_vec = light.position - world_position;\n    let distance = length(light_vec);\n    \n    if (distance > light.range) {\n        return vec3<f32>(0.0);\n    }\n    \n    let L = normalize(light_vec);\n    var attenuation = calculate_attenuation(distance, light.range);\n    \n    // Spot light cone attenuation\n    if (light.light_type == 2u) {\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        attenuation *= spot_attenuation;\n    }\n    \n    if (attenuation <= 0.0) {\n        return vec3<f32>(0.0);\n    }\n    \n    return blinn_phong(N, V, L, light.color, light.intensity * attenuation, diffuse_color, specular_power);\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    let diffuse_color = material.base_color.rgb * input.color;\n    \n    // Calculate tile index from clip position\n    let tile_x = u32(input.clip_position.x) / tile_info.tile_size;\n    let tile_y = u32(input.clip_position.y) / tile_info.tile_size;\n    let tile_index = tile_y * tile_info.tile_count.x + tile_x;\n    \n    // Read light grid for this tile\n    let light_offset = light_grid[tile_index * 2u];\n    let light_count = light_grid[tile_index * 2u + 1u];\n    \n    // Start with ambient lighting\n    var final_color = material.ambient * diffuse_color;\n    \n    // Iterate only over lights that affect this tile\n    for (var i = 0u; i < light_count; i++) {\n        let light_index = light_indices[light_offset + i];\n        let light = lights[light_index];\n        \n        final_color += calculate_light_contribution(\n            light,\n            input.world_position,\n            N, V,\n            diffuse_color,\n            material.specular_power\n        );\n    }\n    \n    // Add emissive\n    final_color += material.emissive;\n    \n    // Reinhard tone mapping\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

Forward+ rendering shader with tile-based light lookup.

Uses pre-computed light culling data to only iterate over lights that actually affect the current pixel’s tile. Implements Blinn-Phong lighting with the same quality as LIT_FORWARD_WGSL but with O(lights_per_tile) complexity instead of O(all_lights).