@@ -18,8 +18,9 @@ struct VertexInput {
1818struct VertexOutput3D {
1919 @builtin (position ) clip_position : vec4 <f32 >,
2020 @location (0 ) local_pos : vec3 <f32 >,
21- @location (1 ) ndc : vec2 <f32 >,
21+ @location (1 ) @interpolate ( linear ) ndc : vec2 <f32 >,
2222 @location (2 ) amp : f32 ,
23+ @location (3 ) depth : f32 ,
2324}
2425
2526@vertex
@@ -53,9 +54,9 @@ fn vs_main_3d(in: VertexInput, @builtin(instance_index) inst_idx: u32) -> Vertex
5354
5455 let y_center = select (- 1 .4 + shift , 4 .4 - shift , is_top );
5556
56- // Scale unit box to match raymarching size b = (0.22, 0.85, 0.22 )
57+ // Scale unit box to match spacing exactly (eliminating gaps )
5758 // UnitBox is [-0.5, 0.5]
58- let scaled = in . position * vec3 <f32 >(0 .44 , 1 .7 , 0 .44 );
59+ let scaled = in . position * vec3 <f32 >(x_spacing , 1 .7 , x_spacing );
5960 let world_pos = scaled + vec3 <f32 >(world_x , y_center , world_z );
6061
6162 // Store local position for face edge detection
@@ -65,12 +66,28 @@ fn vs_main_3d(in: VertexInput, @builtin(instance_index) inst_idx: u32) -> Vertex
6566 // Project position
6667 var clip_pos = camera . proj_matrix * camera . view_matrix * vec4 <f32 >(world_pos , 1 .0 );
6768
68- // Apply barrel distortion in clip space (NDC)
69- let ndc = clip_pos . xy / clip_pos . w ;
70- let r2 = dot (ndc , ndc );
71- let distorted_ndc = ndc * (1 .0 + r2 * 0 .055 );
69+ out . depth = clip_pos . w ;
7270
73- clip_pos = vec4 <f32 >(distorted_ndc * clip_pos . w , clip_pos . z , clip_pos . w );
71+ // Project instance center to evaluate culling
72+ let inst_center = vec3 <f32 >(world_x , y_center , world_z );
73+ let center_clip = camera . proj_matrix * camera . view_matrix * vec4 <f32 >(inst_center , 1 .0 );
74+
75+ var should_cull = center_clip . w < 1 .0 ;
76+ if (! should_cull ) {
77+ let center_ndc = center_clip . xy / center_clip . w ;
78+ should_cull = abs (center_ndc . x ) > 2 .2 || abs (center_ndc . y ) > 2 .2 ;
79+ }
80+
81+ var distorted_ndc = vec2 <f32 >(0 .0 );
82+ if (should_cull ) {
83+ clip_pos = vec4 <f32 >(0 .0 , 0 .0 , 0 .0 , 0 .0 );
84+ } else {
85+ // Apply barrel distortion in clip space (NDC)
86+ let ndc = clamp (clip_pos . xy / clip_pos . w , vec2 <f32 >(- 2 .0 ), vec2 <f32 >(2 .0 ));
87+ let r2 = min (2 .0 , dot (ndc , ndc ));
88+ distorted_ndc = ndc * (1 .0 + r2 * 0 .055 );
89+ clip_pos = vec4 <f32 >(distorted_ndc * clip_pos . w , clip_pos . z , clip_pos . w );
90+ }
7491
7592 out . clip_position = clip_pos ;
7693 out . ndc = distorted_ndc ;
@@ -95,65 +112,64 @@ fn fs_main(in: VertexOutput3D) -> @location(0) vec4<f32> {
95112 let bezel_mask = smoothstep (0 .0 , 0 .03 , border_dist );
96113
97114 // 2. Wireframe / Edge Detection on the Box Face
98- let box_half = vec3 <f32 >(0 .22 , 0 .85 , 0 .22 );
115+ let box_half = vec3 <f32 >(0 .675 , 0 .85 , 0 .675 );
99116 let d = box_half - abs (in . local_pos );
100117
101- // Find the second smallest element of d (distance to nearest edge on this face)
102- let dist_to_edge = min (max (d . x , d . y ), max (min (d . x , d . y ), d . z ));
118+ // Compute distance to nearest edge in pixels using screen-space derivatives
119+ let fw = fwidth (in . local_pos );
120+ var dist_pixels = vec3 <f32 >(1e6 , 1e6 , 1e6 );
121+ if (fw . x > 1e-5 ) { dist_pixels . x = d . x / fw . x ; }
122+ if (fw . y > 1e-5 ) { dist_pixels . y = d . y / fw . y ; }
123+ if (fw . z > 1e-5 ) { dist_pixels . z = d . z / fw . z ; }
124+ let pixel_dist = min (min (dist_pixels . x , dist_pixels . y ), dist_pixels . z );
103125
104- let thick = 0 .009 + clamp (in . amp / 100 .0 , 0 .0 , 1 .0 ) * 0 .007 ;
105- // Wide glow boundary for rich visual aesthetics
106- let glow_width = 0 .09 + clamp (in . amp / 100 .0 , 0 .0 , 1 .0 ) * 0 .04 ;
126+ // 3. Core and bloom/glow calculations (reacting to audio amplitude)
127+ let amp_factor = 1 .0 + clamp (in . amp / 30 .0 , 0 .0 , 3 .0 );
107128
108- if (dist_to_edge > glow_width ) {
109- discard ;
110- }
129+ let line_width = 1 .5 ; // 1.5 pixels wide core
130+ let core_glow = smoothstep (line_width + 1 .0 , line_width - 1 .0 , pixel_dist );
131+
132+ // Exponential bloom/glow falloffs
133+ let bloom_glow = exp (- pixel_dist * 0 .15 ) * amp_factor ; // neon green glow
134+ let wide_bloom = exp (- pixel_dist * 0 .05 ) * 0 .4 * amp_factor ; // wide faint bloom
111135
112- // 3 . Color & Alpha calculation
136+ // 4 . Color calculation: Emissive bright core -> neon green glow -> deep solid green base fill
113137 let bass = clamp (audio . spectrum [0 ]. x + audio . spectrum [1 ]. x + audio . spectrum [2 ]. x , 0 .0 , 1 .0 );
114138
115- let base_green = vec3 <f32 >(0 .02 , 1 .0 , 0 .38 );
116- let neon_green = mix (base_green , vec3 <f32 >(1 .0 , 1 .0 , 1 .0 ), clamp (bass * 0 .45 , 0 .0 , 1 .0 ));
139+ let core_col = vec3 <f32 >(0 .8 , 1 .0 , 0 .9 ); // bright green-white core
140+ let neon_green = vec3 <f32 >(0 .0 , 1 .0 , 0 .4 ); // vibrant neon green
141+ let deep_green = vec3 <f32 >(0 .0 , 0 .08 , 0 .03 ); // deep solid green base fill
117142
118- var final_color = vec3 <f32 >(0 .0 );
119- var alpha = 0 .0 ;
143+ let base_fill = deep_green * (0 .6 + 0 .8 * bass );
120144
121- if (dist_to_edge < thick ) {
122- // Bright phosphor core
123- let core_intensity = 1 .3 + clamp (in . amp / 100 .0 , 0 .0 , 1 .0 ) * 0 .7 ;
124- final_color = vec3 <f32 >(0 .7 , 1 .0 , 0 .88 ) * core_intensity ;
125- alpha = 1 .0 ;
126- } else {
127- // Outer glow
128- let glow_factor = smoothstep (glow_width , thick , dist_to_edge );
129- let glow_intensity = 0 .35 + clamp (bass * 0 .35 , 0 .0 , 0 .6 );
130- final_color = neon_green * glow_factor * glow_intensity ;
131- alpha = 0 .85 * glow_factor ;
132- }
145+ var final_color = base_fill + neon_green * (bloom_glow * 1 .5 + wide_bloom * 0 .8 ) + core_col * (core_glow * 2 .0 );
146+
147+ // 5. Distance fade out (depth fog)
148+ let depth = in . depth ;
149+ // Fade out to black between depth 8.0 and 32.0
150+ let fade = clamp ((32 .0 - depth ) / (32 .0 - 8 .0 ), 0 .0 , 1 .0 );
151+ let fade_smooth = smoothstep (0 .0 , 1 .0 , fade );
152+ final_color = final_color * fade_smooth ;
133153
134- // 4 . CRT Filter: Scanlines (applied to color & alpha so background shows through gaps)
154+ // 6 . CRT Filter: Scanlines
135155 let scanline = 0 .86 + 0 .14 * cos (in . clip_position . y * 3 .14159 );
136156 final_color = final_color * scanline ;
137- alpha = alpha * scanline ;
138157
139- // 5 . CRT Filter: Flicker
158+ // 7 . CRT Filter: Flicker
140159 let flicker = 0 .98 + 0 .02 * sin (audio . time * 115 .0 );
141160 final_color = final_color * flicker ;
142- alpha = alpha * flicker ;
143161
144- // 6 . CRT Filter: Analog static noise
162+ // 8 . CRT Filter: Analog static noise
145163 let noise_val = hash21 (in . clip_position . xy + fract (audio . smooth_time ) * 149 .0 );
146164 let static_noise = noise_val * 0 .022 * bezel_mask ;
147165 final_color = final_color + vec3 <f32 >(static_noise );
148- alpha = clamp (alpha + static_noise * 0 .5 , 0 .0 , 1 .0 );
149166
150167 // Apply bezel mask
151168 final_color = final_color * bezel_mask ;
152- alpha = alpha * bezel_mask ;
153169
154- // 7 . Fitted ACES Tonemap
170+ // 9 . Fitted ACES Tonemap
155171 var final_col = (final_color * (2 .51 * final_color + 0 .03 )) / (final_color * (2 .43 * final_color + 0 .59 ) + 0 .14 );
156172 final_col = max (final_col , vec3 <f32 >(0 .0 ));
157173
158- return vec4 <f32 >(final_col , alpha );
174+ return vec4 <f32 >(final_col , 1 .0 );
159175}
0 commit comments