notedeck

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

dave.wgsl (5071B)


      1 struct Uniforms {
      2     view_proj: mat4x4<f32>,
      3     model: mat4x4<f32>,
      4     camera_pos: vec3<f32>,
      5     time: f32,
      6     is_light: vec4<f32>,
      7 };
      8 
      9 @group(0) @binding(0)
     10 var<uniform> uniforms: Uniforms;
     11 
     12 struct VSOut {
     13     @builtin(position) position: vec4<f32>,
     14     @location(0) normal: vec3<f32>,
     15     @location(1) world_pos: vec3<f32>,
     16     @location(2) color: vec3<f32>,
     17 };
     18 
     19 // Vertex inputs
     20 @vertex
     21 fn vs_main(
     22     @location(0) in_pos: vec3<f32>,
     23     @location(1) in_normal: vec3<f32>,
     24     @location(2) base_pos: vec3<f32>,
     25     @location(3) scale: f32,
     26     @location(4) seed: f32,
     27     @location(5) color: vec3<f32>,
     28 ) -> VSOut {
     29     var out: VSOut;
     30 
     31     let t = uniforms.time;
     32 
     33     // --- Coherent spherical layout ---
     34     let dir = normalize(base_pos + vec3<f32>(1e-6, 0.0, 0.0)); // avoid NaN if zero
     35     let radius = 0.4;
     36 
     37     // Gentle, coherent drift so it breathes
     38     let drift = vec3<f32>(
     39         0.06 * sin(0.9 * t + seed * 1.3),
     40         0.05 * sin(1.1 * t + seed * 2.1),
     41         0.06 * cos(0.7 * t + seed * 0.7)
     42     );
     43 
     44     // Final instance position on/near the sphere
     45     //let loose = 0.2 * base_pos + drift;
     46     let tight = dir * radius + drift;
     47     //let tight = dir * radius;
     48     //let coherence = 0.8; // [0..1], or pass as a uniform
     49     //let pos_ws = mix(loose, tight, coherence);
     50     let pos_ws = tight;
     51 
     52     // --- Orient cube so its local +Z points outward (along dir) ---
     53     // Build a stable tangent basis
     54     var up = vec3<f32>(0.0, 1.0, 0.0);
     55     if (abs(dot(dir, up)) > 0.92) {
     56         up = vec3<f32>(1.0, 0.0, 0.0);
     57     }
     58     let tangent   = normalize(cross(up, dir));
     59     let bitangent = cross(dir, tangent);
     60 
     61     // Optional tiny spin around outward axis for sparkle
     62     let spin = 0.9 * t + seed * 0.9;
     63     let cs = cos(spin);
     64     let sn = sin(spin);
     65     let rot_tangent   =  cs * tangent + sn * bitangent;
     66     let rot_bitangent = -sn * tangent + cs * bitangent;
     67 
     68     // Rotation matrix whose columns are the local basis
     69     let R = mat3x3<f32>(rot_tangent, rot_bitangent, dir);
     70 
     71     // Scale + orient local vertex + place at spherical position
     72     let local = R * (in_pos * scale);
     73     let world_vec4 = uniforms.model * vec4<f32>(local, 1.0);
     74     let world = world_vec4 + vec4<f32>(pos_ws, 0.0);
     75 
     76     out.position = uniforms.view_proj * world;
     77 
     78     // Normal from model rotation only (ignoring per-instance rotation for now)
     79     let nmat = mat3x3<f32>(
     80         uniforms.model[0].xyz,
     81         uniforms.model[1].xyz,
     82         uniforms.model[2].xyz
     83     );
     84     out.normal = normalize(nmat * in_normal);
     85     out.world_pos = world.xyz;
     86     out.color = color;
     87     return out;
     88 }
     89 
     90 @fragment
     91 fn fs_main(in: VSOut) -> @location(0) vec4<f32> {
     92     // Same lighting as you had, but tint by per-instance color
     93     let material_color = in.color;
     94 
     95     let ambient_strength = 0.2;
     96     let diffuse_strength = 0.7;
     97     let specular_strength = 0.2;
     98     let shininess = 20.0;
     99 
    100     let light_pos = vec3<f32>(2.0, 2.0, 2.0);
    101     let light_color = vec3<f32>(1.0, 1.0, 1.0);
    102     let view_pos = uniforms.camera_pos;
    103 
    104     let n = normalize(in.normal);
    105     let l = normalize(light_pos - in.world_pos);
    106     let v = normalize(view_pos - in.world_pos);
    107     let r = reflect(-l, n);
    108 
    109     let ambient = ambient_strength * light_color;
    110     let diffuse = diffuse_strength * max(dot(n, l), 0.0) * light_color;
    111     let specular = specular_strength * pow(max(dot(v, r), 0.0), shininess) * light_color;
    112 
    113     let exposure = exp2(1.5);
    114     var color = (ambient + diffuse + specular) * material_color;
    115 
    116     // --- Distance-based factor (camera-space distance) ---
    117     let dist       = length(view_pos - in.world_pos);
    118     let FADE_NEAR  = 1.0;  // start ramping here
    119     let FADE_FAR   = 2.2;  // fully applied by here
    120     let fade       = smoothstep(FADE_NEAR, FADE_FAR, dist); // 0..1
    121 
    122     // --- Exposure drift with distance (sign flips by mode) ---
    123     // Dark mode target exposure at far: lower; Light mode target at far: higher.
    124     let min_exp    = 1.80; // far-end exposure multiplier in dark mode
    125     let max_exp    = 1.35; // far-end exposure multiplier in light mode
    126     let darker     = mix(1.0, min_exp, fade);   // darkens with distance
    127     let brighter   = mix(1.0, max_exp, fade);   // brightens with distance
    128     let exp_factor = select(darker, brighter, uniforms.is_light.x > 0.0);
    129 
    130     // Apply exposure + tonemap
    131     let base_exposure = exp2(1.5);
    132     color = aces_fitted(color * base_exposure * exp_factor);
    133 
    134     // --- Optional: fade to background so distant points dissolve away ---
    135     // Background: black in dark mode, white in light mode.
    136     let bg = select(vec3<f32>(0.0), vec3<f32>(1.0), uniforms.is_light.x > 0.0);
    137     // If you want white for BOTH modes instead, use:
    138     // let bg = vec3<f32>(1.0);
    139 
    140     color = mix(color, bg, fade);
    141 
    142     return vec4<f32>(color, 1.0);
    143 }
    144 
    145 
    146 
    147 // ACES-fit tonemap (keeps highlights nicer than Reinhard)
    148 fn aces_fitted(x: vec3<f32>) -> vec3<f32> {
    149     let a = 2.51;
    150     let b = 0.03;
    151     let c = 2.43;
    152     let d = 0.59;
    153     let e = 0.14;
    154     return clamp((x * (a * x + b)) / (x * (c * x + d) + e), vec3(0.0), vec3(1.0));
    155 }