grid.wgsl (3986B)
1 struct Globals { 2 time: f32, 3 _pad0: f32, 4 resolution: vec2<f32>, 5 6 cam_pos: vec3<f32>, 7 _pad3: f32, 8 9 light_dir: vec3<f32>, 10 _pad1: f32, 11 12 light_color: vec3<f32>, 13 _pad2: f32, 14 15 fill_light_dir: vec3<f32>, 16 _pad4: f32, 17 18 fill_light_color: vec3<f32>, 19 _pad5: f32, 20 21 view_proj: mat4x4<f32>, 22 inv_view_proj: mat4x4<f32>, 23 light_view_proj: mat4x4<f32>, 24 }; 25 26 @group(0) @binding(0) var<uniform> globals: Globals; 27 @group(0) @binding(1) var shadow_map: texture_depth_2d; 28 @group(0) @binding(2) var shadow_sampler: sampler_comparison; 29 30 struct VSOut { 31 @builtin(position) clip: vec4<f32>, 32 @location(0) near_point: vec3<f32>, 33 @location(1) far_point: vec3<f32>, 34 }; 35 36 fn unproject(clip: vec2<f32>, z: f32) -> vec3<f32> { 37 let p = globals.inv_view_proj * vec4<f32>(clip, z, 1.0); 38 return p.xyz / p.w; 39 } 40 41 @vertex 42 fn vs_main(@builtin(vertex_index) vi: u32) -> VSOut { 43 var out: VSOut; 44 45 // Fullscreen triangle: vertices at (-1,-1), (3,-1), (-1,3) 46 let x = f32((vi << 1u) & 2u) * 2.0 - 1.0; 47 let y = f32(vi & 2u) * 2.0 - 1.0; 48 out.clip = vec4<f32>(x, y, 0.0, 1.0); 49 50 // Unproject near and far planes to world space 51 out.near_point = unproject(vec2<f32>(x, y), 0.0); 52 out.far_point = unproject(vec2<f32>(x, y), 1.0); 53 54 return out; 55 } 56 57 // Compute grid intensity for a given world-space xz coordinate and grid spacing 58 fn grid_line(coord: vec2<f32>, spacing: f32, line_width: f32) -> f32 { 59 let grid = abs(fract(coord / spacing - 0.5) - 0.5) * spacing; 60 let dxz = fwidth(coord); 61 let width = dxz * line_width; 62 let line = smoothstep(width, vec2<f32>(0.0), grid); 63 return max(line.x, line.y); 64 } 65 66 fn calc_shadow(world_pos: vec3<f32>) -> f32 { 67 let light_clip = globals.light_view_proj * vec4<f32>(world_pos, 1.0); 68 let ndc = light_clip.xyz / light_clip.w; 69 let shadow_uv = vec2<f32>(ndc.x * 0.5 + 0.5, -ndc.y * 0.5 + 0.5); 70 71 if shadow_uv.x < 0.0 || shadow_uv.x > 1.0 || shadow_uv.y < 0.0 || shadow_uv.y > 1.0 { 72 return 1.0; 73 } 74 75 let ref_depth = ndc.z; 76 let texel_size = 1.0 / 2048.0; 77 var shadow = 0.0; 78 for (var y = -1i; y <= 1i; y++) { 79 for (var x = -1i; x <= 1i; x++) { 80 let offset = vec2<f32>(f32(x), f32(y)) * texel_size; 81 shadow += textureSampleCompareLevel( 82 shadow_map, shadow_sampler, shadow_uv + offset, ref_depth, 83 ); 84 } 85 } 86 return shadow / 9.0; 87 } 88 89 struct FragOut { 90 @location(0) color: vec4<f32>, 91 @builtin(frag_depth) depth: f32, 92 }; 93 94 @fragment 95 fn fs_main(in: VSOut) -> FragOut { 96 var out: FragOut; 97 98 // Ray from near to far point 99 let ray_dir = in.far_point - in.near_point; 100 101 // Intersect y=0 plane: near.y + t * dir.y = 0 102 let t = -in.near_point.y / ray_dir.y; 103 104 // Discard if no intersection (ray parallel or pointing away) 105 if t < 0.0 { 106 discard; 107 } 108 109 // World position on the grid plane 110 let world_pos = in.near_point + t * ray_dir; 111 let xz = vec2<f32>(world_pos.x, world_pos.z); 112 113 // Distance from camera for fading 114 let dist = length(world_pos - globals.cam_pos); 115 116 // Grid lines 117 let minor = grid_line(xz, 0.25, 0.5); // 0.25m subdivisions 118 let major = grid_line(xz, 1.0, 1.0); // 1.0m major lines 119 120 // Combine: major lines are brighter 121 let grid_val = max(minor * 0.3, major * 0.6); 122 123 // Fade with distance (start fading at 10m, fully gone at 80m) 124 let fade = 1.0 - smoothstep(10.0, 80.0, dist); 125 126 let alpha = grid_val * fade; 127 128 // Discard fully transparent fragments 129 if alpha < 0.001 { 130 discard; 131 } 132 133 // Shadow: darken the grid where objects cast shadows 134 let shadow = calc_shadow(world_pos); 135 let brightness = mix(0.15, 0.5, shadow); 136 137 out.color = vec4<f32>(brightness, brightness, brightness, alpha); 138 139 // Compute proper depth from world position 140 let clip = globals.view_proj * vec4<f32>(world_pos, 1.0); 141 out.depth = clip.z / clip.w; 142 143 return out; 144 }