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 }