notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }