1
1
// MIT License, Enes Altun, 2025
2
2
// resources for Skilling's algorithm
3
- // https://github.com/joshspeagle/dynesty
3
+ // https://github.com/joshspeagle/dynesty
4
4
// https://doi.org/10.1063/1.1751381 and https://doi.org/10.1063/1.1751382
5
5
// http://www.inference.org.uk/bayesys/test/hilbert.c
6
6
// https://www.shadertoy.com/view/3tl3zl
7
+ // lighting technique from @fad, (2023): Analytic Direct Lighting: https://www.shadertoy.com/view/dlcXR4
7
8
struct TimeUniform {
8
9
time : f32 ,
9
10
};
10
-
11
11
struct ResolutionUniform {
12
12
dimensions : vec2 <f32 >,
13
13
_padding : vec2 <f32 >,
14
14
};
15
-
16
15
struct Params {
17
16
iterations : u32 ,
18
- num_rays : u32 ,
17
+ num_rays : f32 ,
19
18
_pad1 : vec2 <f32 >,
20
19
scale : f32 ,
21
20
time_scale : f32 ,
22
21
vignette_radius : f32 ,
23
22
vignette_softness : f32 ,
24
23
color_offset : vec3 <f32 >,
25
- _pad2 : f32 ,
24
+ flanc : f32 ,
26
25
};
27
-
28
26
@group (0 ) @binding (0 ) var <uniform > u_time : TimeUniform ;
29
27
@group (1 ) @binding (0 ) var <uniform > u_resolution : ResolutionUniform ;
30
28
@group (2 ) @binding (0 ) var <uniform > params : Params ;
31
29
32
30
const PI : f32 = 3.14159265359 ;
33
- const FLT_MAX: f32 = 33333.0 ;
34
- //adapted from: https://www.shadertoy.com/view/3tl3zl tpfto, 2019.
35
- fn hilbert (k : u32 , s : u32 ) -> vec2 <f32 > {
36
- let bb = 1u << s ;
37
- var b = bb ;
38
- let t = vec2 <u32 >(k ^ (k >> 1 ));
39
- var hp = vec2 <u32 >(0u );
40
-
41
- for (var j : i32 = i32 (s ) - 1 ; j >= 0 ; j -- ) {
42
- b = b >> 1u ;
43
- hp += (t >> vec2 <u32 >(u32 (j + 1 ), u32 (j ))) & vec2 <u32 >(b );
31
+ const MAX_SEGMENTS: u32 = 64u ;
32
+ const MAX_ANGLES: u32 = 128u ;
33
+ fn atan2 (y : f32 , x : f32 ) -> f32 {
34
+ if (x > 0.0 ) {
35
+ return atan (y / x );
36
+ } else if (x < 0.0 ) {
37
+ return atan (y / x ) + select (- PI , PI , y >= 0.0 );
38
+ } else {
39
+ return select (- PI / 2.0 , PI / 2.0 , y >= 0.0 );
44
40
}
45
- for (var p = 2u ; p < bb ; p = p << 1u ) {
46
- let q = p - 1u ;
47
- if ((hp . y & p ) != 0u ) {
48
- hp . x = hp . x ^ q ;
49
- } else {
50
- let temp = (hp . x ^ hp . y) & q ;
51
- hp . x = hp . x ^ temp ;
52
- hp . y = hp . y ^ temp ;
53
- }
54
- if ((hp . x & p ) != 0u ) {
55
- hp . x = hp . x ^ q ;
41
+ }
42
+ fn fmod (x : f32 , y : f32 ) -> f32 {
43
+ return x - y * floor (x / y );
44
+ }
45
+ fn hilbert (idx : u32 ) -> vec2 <u32 > {
46
+ var res = vec2 <u32 >(0u , 0u );
47
+ var i = idx ;
48
+ for (var k = 0u ; k < params . iterations; k ++ ) {
49
+ let r = vec2 <u32 >((i >> 1u ) & 1u , (i ^ (i >> 1u )) & 1u );
50
+ if (r . y == 0u ) {
51
+ if (r . x == 1u ) {
52
+ res = ((1u << k ) - 1u ) - res ;
53
+ }
54
+ let temp = res . x;
55
+ res . x = res . y;
56
+ res . y = temp ;
56
57
}
58
+ res += vec2 <u32 >(r . x << k , r . y << k );
59
+ i >>= 2u ;
57
60
}
58
-
59
- return 2.0 * (vec2 <f32 >(hp ) / f32 (bb - 1u )) - 1.0 ;
61
+ return res ;
60
62
}
61
- fn intersect_line (ro : vec2 <f32 >, rd : vec2 <f32 >, line : vec4 <f32 >, t : ptr <function , f32 >) -> bool {
62
- let A = ro ;
63
- let B = ro + rd ;
64
- let C = line . xy;
65
- let D = line . zw;
63
+ // Generate Hilbert curve point for a given index: chp: calculate hilbert point
64
+ fn CHP (i : u32 ) -> vec2 <f32 > {
65
+ let hpos = hilbert (i );
66
+ let size = 1u << params . iterations;
67
+ let np = vec2 <f32 >(hpos ) / f32 (size );
68
+ let scale = 0.7 * min (u_resolution . dimensions. x, u_resolution . dimensions. y);
69
+ let screen_pos = np * scale + (u_resolution . dimensions - vec2 <f32 >(scale )) * 0.5 ;
66
70
67
- let AmC = A - C ;
68
- let DmC = D - C ;
69
- let BmA = B - A ;
70
-
71
- let denom = (BmA . x * DmC . y) - (BmA . y * DmC . x);
72
-
73
- if (abs (denom ) > 0.0001 ) {
74
- let r = ((AmC . y * DmC . x) - (AmC . x * DmC . y)) / denom ;
75
- let s = ((AmC . y * BmA . x) - (AmC . x * BmA . y)) / denom ;
76
-
77
- if ((r > 0.0 && r < *t ) && (s > 0.0 && s < 1.0 )) {
78
- *t = r ;
79
- return true ;
71
+ return screen_pos ;
72
+ }
73
+ fn get_point (i : u32 ) -> vec2 <f32 > {
74
+ let num_points = 1u << (2u * params . iterations);
75
+ if (i >= num_points ) {
76
+ return vec2 <f32 >(0.0 );
77
+ }
78
+ return CHP (i );
79
+ }
80
+ struct LineSegment {
81
+ p0 : vec2 <f32 >,
82
+ p1 : vec2 <f32 >,
83
+ emissive_color : vec3 <f32 >,
84
+ };
85
+ fn sdf (l : LineSegment , p : vec2 <f32 >) -> f32 {
86
+ let pa = p - l . p0;
87
+ let ba = l . p1 - l . p0;
88
+ let h = clamp (dot (pa , ba ) / dot (ba , ba ), 0.0 , 1.0 );
89
+ return length (pa - ba * h );
90
+ }
91
+ fn blend_over (top : vec4 <f32 >, bottom : vec4 <f32 >) -> vec4 <f32 > {
92
+ let a = top . a + bottom . a * (1.0 - top . a);
93
+ if (a < 0.0001 ) {
94
+ return vec4 <f32 >(0.0 );
95
+ }
96
+ return vec4 <f32 >((top . rgb * top . a + bottom . rgb * bottom . a * (1.0 - top . a)) / a , a );
97
+ }
98
+ fn draw_sdf (dst : vec4 <f32 >, src : vec4 <f32 >, dist : f32 ) -> vec4 <f32 > {
99
+ return blend_over (
100
+ vec4 <f32 >(src . rgb, src . a * clamp (1.5 - abs (dist ), 0.0 , 1.0 )),
101
+ dst
102
+ );
103
+ }
104
+ // Matrix inverse 2x2
105
+ fn inverse_2x2 (m : mat2x2 <f32 >) -> mat2x2 <f32 > {
106
+ let det = m [0 ][0 ] * m [1 ][1 ] - m [0 ][1 ] * m [1 ][0 ];
107
+ if (abs (det ) < 0.0001 ) {
108
+ return mat2x2 <f32 >(1.0 , 0.0 , 0.0 , 1.0 );
109
+ }
110
+ let inv_det = 1.0 / det ;
111
+ return mat2x2 <f32 >(
112
+ m [1 ][1 ] * inv_det , - m [0 ][1 ] * inv_det ,
113
+ - m [1 ][0 ] * inv_det , m [0 ][0 ] * inv_det
114
+ );
115
+ }
116
+
117
+ // insertion sort
118
+ fn sort_angles (segments : ptr <function , array <LineSegment , MAX_SEGMENTS>> , angles : ptr <function , array <f32 , MAX_ANGLES>> , num_segments : u32 ) {
119
+ let num_angles = 2u * num_segments ;
120
+ for (var i = 0u ; i < num_segments ; i ++ ) {
121
+ for (var j = 0u ; j < 2u ; j ++ ) {
122
+ let k = 2u * i + j ;
123
+ let p = select ((*segments )[i ]. p0, (*segments )[i ]. p1, j == 1u );
124
+ let angle = fmod (atan2 (p . y, p . x), 2.0 * PI );
125
+ var l = i32 (k ) - 1 ;
126
+ while (l >= 0 && angle < (*angles )[u32 (l )]) {
127
+ (*angles )[u32 (l ) + 1u ] = (*angles )[u32 (l )];
128
+ l -= 1 ;
129
+ }
130
+ (*angles )[u32 (l ) + 1u ] = angle ;
80
131
}
81
132
}
82
- return false ;
133
+ }
134
+ // add radiance from a line segment
135
+ fn integrate_radiance (segment : LineSegment , angle : vec2 <f32 >) -> vec3 <f32 > {
136
+ return (angle [1 ] - angle [0 ]) * segment . emissive_color;
83
137
}
84
138
85
- fn intersect_scene (ro : vec2 <f32 >, rd : vec2 <f32 >, t : ptr <function , f32 >, colour : ptr <function , vec3 <f32 >> ) -> bool {
86
- var intersect = false ;
87
- var minDist = *t ;
139
+ // add sky radiance for a basic angle range
140
+ fn isb (angle : vec2 <f32 >) -> vec3 <f32 > {
141
+ let sky_color = vec3 <f32 >(0.01 , 0.02 , 0.04 );
142
+ return sky_color * (angle [1 ] - angle [0 ]);
143
+ }
88
144
89
- let s = params . iterations;
90
- let NUM = (1u << (2u * s )) - 1u ;
91
-
92
- var a = hilbert (0u , s );
93
- var b : vec2 <f32 >;
94
-
95
- let scale = params . scale;
96
- let offset = vec2 <f32 >(0.0 );
97
- let scaletime = u_time . time * params . time_scale;
98
- for (var i = 0u ; i < NUM ; i ++ ) {
99
- b = hilbert (i + 1u , s );
100
- let line = vec4 <f32 >((a * scale ) + offset , (b * scale ) + offset );
101
- let lineDir = normalize (line . zw - line . xy);
102
- let normal = vec2 <f32 >(- lineDir. y, lineDir. x);
103
- if (intersect_line (ro , rd , line , & minDist)) {
104
- let hue = f32 (i ) / f32 (NUM ) + scaletime ;
105
- *colour = 0.5 + 0.5 * cos (3.28318 * (hue + params . color_offset));
106
- let lighting = abs (dot (- rd , normal ));
107
- *colour = *colour * lighting ;
108
- intersect = true ;
109
- }
110
-
111
- a = b ;
145
+ // Integrate sky radiance with wrap-around support
146
+ fn isr (angle : vec2 <f32 >) -> vec3 <f32 > {
147
+ if (angle [1 ] < 2.0 * PI ) {
148
+ return isb (angle );
112
149
}
113
-
114
- *t = minDist;
115
- return intersect ;
150
+ return isb (vec2 <f32 >(angle [0 ], 2.0 * PI )) +
151
+ isb (vec2 <f32 >(0.0 , angle [1 ] - 2.0 * PI ));
116
152
}
117
153
154
+ // Find which segment is intersected by a ray at a specific angle
155
+ fn find_index (segments : ptr <function , array <LineSegment , MAX_SEGMENTS>> , angle : f32 , num_segments : u32 ) -> i32 {
156
+ var m = mat2x2 <f32 >(0.0 , 0.0 , 0.0 , 0.0 );
157
+ m [1 ] = vec2 <f32 >(cos (angle ), sin (angle ));
158
+ var best_index = - 1 ;
159
+ var best_u = 1e10 ;
160
+ for (var i = 0u ; i < num_segments ; i ++ ) {
161
+ m [0 ] = (*segments )[i ]. p0 - (*segments )[i ]. p1;
162
+ // Calculate the inverse and multiply manually in case of singular matrices
163
+ let inv_m = inverse_2x2 (m );
164
+ let tu = inv_m * (*segments )[i ]. p0;
165
+ // Check if valid intersection (0 <= t <= 1 and 0 <= u <= best_u)
166
+ if (tu . x >= 0.0 && tu . x <= 1.0 && tu . y >= 0.0 && tu . y <= best_u ) {
167
+ best_u = tu . y;
168
+ best_index = i32 (i );
169
+ }
170
+ }
171
+ return best_index ;
172
+ }
173
+ // total radiance from all directions
174
+ fn calculate_fluence (segments : ptr <function , array <LineSegment , MAX_SEGMENTS>> , angles : ptr <function , array <f32 , MAX_ANGLES>> , num_segments : u32 ) -> vec3 <f32 > {
175
+ var fluence = vec3 <f32 >(0.0 );
176
+ let num_angles = 2u * num_segments ;
177
+ for (var i = 0u ; i < num_angles ; i ++ ) {
178
+ var a = vec2 <f32 >((*angles )[i ], 0.0 );
179
+ if (i + 1u < num_angles ) {
180
+ a [1 ] = (*angles )[i + 1u ];
181
+ } else {
182
+ a [1 ] = (*angles )[0 ] + 2.0 * PI ;
183
+ }
184
+ if (abs (a [0 ] - a [1 ]) < 0.0001 ) {
185
+ continue ;
186
+ }
187
+ let mid_angle = (a [0 ] + a [1 ]) / 2.0 ;
188
+ let j = find_index (segments , mid_angle , num_segments );
189
+ if (j == - 1 ) {
190
+ fluence += isr (a );
191
+ } else {
192
+ fluence += integrate_radiance ((*segments )[u32 (j )], a );
193
+ }
194
+ }
195
+ return fluence ;
196
+ }
118
197
@fragment
119
- fn fs_main (@builtin (position ) FragCoord : vec4 <f32 >) -> @location (0 ) vec4 <f32 > {
120
- let ro = - 1.0 + 2.0 * (vec2 <f32 >(FragCoord . x, u_resolution . dimensions. y - FragCoord . y) / u_resolution . dimensions);
121
-
122
- var total = vec3 <f32 >(0.0 );
123
-
124
- for (var i = 0u ; i < params . num_rays; i ++ ) {
125
- let angle = PI * (f32 (i ) / f32 (params . num_rays));
126
- let rd = vec2 <f32 >(cos (angle ), sin (angle ));
127
-
128
- var t = FLT_MAX;
129
- var colour = vec3 <f32 >(1.0 );
130
-
131
- if (intersect_scene (ro , rd , & t , & colour )) {
132
- total += colour ;
198
+ fn fs_main (@builtin (position ) frag_coord : vec4 <f32 >) -> @location (0 ) vec4 <f32 > {
199
+ let num_points = 1u << (2u * params . iterations);
200
+ let num_segments = min (num_points - 1u , MAX_SEGMENTS);
201
+ var segments : array <LineSegment , MAX_SEGMENTS>;
202
+ var angles : array <f32 , MAX_ANGLES>;
203
+ for (var i = 0u ; i < MAX_ANGLES; i ++ ) {
204
+ angles [i ] = 0.0 ;
205
+ }
206
+ let pixel_pos = vec2 <f32 >(frag_coord . x, u_resolution . dimensions. y - frag_coord . y);
207
+ for (var i = 0u ; i < num_segments ; i ++ ) {
208
+ segments [i ]. p0 = get_point (i ) - pixel_pos ;
209
+ segments [i ]. p1 = get_point (i + 1u ) - pixel_pos ;
210
+ segments [i ]. emissive_color = vec3 <f32 >(0.0 );
211
+ }
212
+ let t = fmod (u_time . time * params . time_scale * 0.1 , 1.0 );
213
+ let exact_pos = t * f32 (num_segments );
214
+ let light_length : f32 = params . num_rays;
215
+ for (var i = 0u ; i < num_segments ; i ++ ) {
216
+ // Calculate distance along the curve from this segment to the light center
217
+ let fi = f32 (i );
218
+ let dcp = min (
219
+ abs (fi - exact_pos ),
220
+ min (
221
+ abs (fi + f32 (num_segments ) - exact_pos ),
222
+ abs (fi - f32 (num_segments ) - exact_pos )
223
+ )
224
+ );
225
+ // Only light up segments within the light's range,
226
+ // dcp: distance to curve position
227
+ if (dcp < light_length ) {
228
+ let intensity = exp (- dcp * dcp / (light_length * 0.5 ));
229
+ let color_pos = dcp / light_length ;
230
+ let base_color1 = vec3 <f32 >(1.0 , 0.3 , 0.05 ) + params . color_offset;
231
+ let base_color2 = vec3 <f32 >(0.1 , 0.7 , 1.0 ) + params . color_offset;
232
+ let light_color = mix (
233
+ base_color1 ,
234
+ base_color2 ,
235
+ smoothstep (0.0 , 1.0 , color_pos )
236
+ );
237
+ segments [i ]. emissive_color = light_color * intensity * 2.0 ;
133
238
}
134
239
}
135
-
136
- total = total / f32 (params . num_rays);
137
-
138
- let dist = length (ro );
139
- let radius = params . vignette_radius;
140
- let softness = params . vignette_softness;
141
- let vignette = smoothstep (radius , radius + softness , dist );
142
- total *= 1.0 - vignette * 0.95 ;
143
-
144
- let exposure = 1.5 ;
145
- return vec4 <f32 >(pow (total * exposure , vec3 <f32 >(1.0 / 1.1 )), 1.0 );
240
+ sort_angles (& segments , & angles , num_segments );
241
+ let fluence = calculate_fluence (& segments , & angles , num_segments );
242
+ var frag_color = vec4 <f32 >(1.0 - params . flanc / pow (1.0 + fluence , vec3 <f32 >(params . vignette_softness)), 1.0 );
243
+ for (var i = 0u ; i < num_segments ; i ++ ) {
244
+ let base_curve_color = vec3 <f32 >(0.15 );
245
+ let has_emission = length (segments [i ]. emissive_color) > 0.01 ;
246
+ let segment_color = select (
247
+ base_curve_color ,
248
+ 2.0 * segments [i ]. emissive_color,
249
+ has_emission
250
+ );
251
+ let thickness = select (1.0 , params . scale, has_emission );
252
+ frag_color = draw_sdf (
253
+ frag_color ,
254
+ vec4 <f32 >(segment_color , 1.0 ),
255
+ sdf (segments [i ], vec2 <f32 >(0.0 )) / thickness
256
+ );
257
+ }
258
+ frag_color = vec4 <f32 >(gamma (frag_color . rgb, params . vignette_radius), frag_color . a);
259
+ return frag_color ;
146
260
}
261
+ fn gamma (color : vec3 <f32 >, gamma : f32 ) -> vec3 <f32 > {
262
+ return pow (max (color , vec3 <f32 >(0.0 )), vec3 <f32 >(1.0 / gamma ));
263
+ }
0 commit comments