11import * as sdf from '@typegpu/sdf' ;
2- import tgpu , {
3- type SampledFlag ,
4- type StorageFlag ,
5- type TgpuTexture ,
6- } from 'typegpu' ;
2+ import tgpu from 'typegpu' ;
73import { fullScreenTriangle } from 'typegpu/common' ;
84import * as d from 'typegpu/data' ;
95import * as std from 'typegpu/std' ;
@@ -50,24 +46,13 @@ const cascadeAmount = Math.ceil(
5046 Math . log2 ( ( maxIntervalStart * 3 ) / interval0 + 1 ) / 2 ,
5147) ;
5248
53- type CascadeTexture =
54- & TgpuTexture < {
55- size : [ number , number , number ] ;
56- format : 'rgba16float' ;
57- } >
58- & StorageFlag
59- & SampledFlag ;
60-
61- const cascadeTextures = Array . from (
62- { length : 2 } ,
63- ( ) =>
64- root [ '~unstable' ]
65- . createTexture ( {
66- size : [ cascadeDimX , cascadeDimY , cascadeAmount ] ,
67- format : 'rgba16float' ,
68- } )
69- . $usage ( 'storage' , 'sampled' ) ,
70- ) as [ CascadeTexture , CascadeTexture ] ;
49+ const cascadeTextures = Array . from ( { length : 2 } , ( ) =>
50+ root [ '~unstable' ]
51+ . createTexture ( {
52+ size : [ cascadeDimX , cascadeDimY , cascadeAmount ] ,
53+ format : 'rgba16float' ,
54+ } )
55+ . $usage ( 'storage' , 'sampled' ) ) ;
7156
7257const radianceFieldTex = root [ '~unstable' ]
7358 . createTexture ( {
@@ -114,11 +99,6 @@ const cascadeProbesUniform = root.createUniform(
11499const overlayEnabledUniform = root . createUniform ( d . u32 , 0 ) ;
115100const overlayDebugCascadeUniform = root . createUniform ( d . u32 , 0 ) ;
116101
117- const atlasPos = ( dir : d . v2u , probe : d . v2u , probes : d . v2u ) => {
118- 'use gpu' ;
119- return dir . mul ( probes ) . add ( probe ) ;
120- } ;
121-
122102const cascadePassBGL = tgpu . bindGroupLayout ( {
123103 upper : { texture : d . texture2d ( d . f32 ) } ,
124104 upperSampler : { sampler : 'filtering' } ,
@@ -145,25 +125,18 @@ const cascadePassCompute = tgpu['~unstable'].computeFn({
145125 const probes = probesUniform . $ ;
146126 const cascadeProbes = cascadeProbesUniform . $ ;
147127
148- // Decode atlas position to (direction, probe) using direction-first layout
149128 const dirStored = gid . xy . div ( probes ) ;
150129 const probe = std . mod ( gid . xy , probes ) ;
151-
152- // Each stored texel = 2x2 actual rays; raysDimStored doubles per layer
153130 const raysDimStored = d . u32 ( 2 ) << layer ;
154131 const raysDimActual = raysDimStored * d . u32 ( 2 ) ;
155132 const rayCountActual = raysDimActual * raysDimActual ;
156133
157- // Skip texels outside valid atlas region
158134 if ( dirStored . x >= raysDimStored || dirStored . y >= raysDimStored ) {
159135 std . textureStore ( cascadePassBGL . $ . dst , gid . xy , d . vec4f ( 0 , 0 , 0 , 1 ) ) ;
160136 return ;
161137 }
162138
163139 const probePos = d . vec2f ( probe ) . add ( 0.5 ) . div ( d . vec2f ( probes ) ) ;
164-
165- // Interval: each layer covers 4× the distance of the previous
166- // Use min probe dimension for uniform spacing in both directions
167140 const cascadeProbesMinVal = d . f32 ( std . min ( cascadeProbes . x , cascadeProbes . y ) ) ;
168141 const interval0 = 1.0 / cascadeProbesMinVal ;
169142 const pow4 = d . f32 ( d . u32 ( 1 ) << ( layer * d . u32 ( 2 ) ) ) ;
@@ -174,7 +147,6 @@ const cascadePassCompute = tgpu['~unstable'].computeFn({
174147
175148 let accum = d . vec4f ( ) ;
176149
177- // Cast 4 rays per stored texel (2x2 block) and average
178150 for ( let i = 0 ; i < 4 ; i ++ ) {
179151 const dirActual = dirStored
180152 . mul ( d . u32 ( 2 ) )
@@ -187,8 +159,10 @@ const cascadePassCompute = tgpu['~unstable'].computeFn({
187159 let T = d . f32 ( 1 ) ;
188160 let t = startUv ;
189161
190- for ( let step = 0 ; step < 32 ; step ++ ) {
191- if ( t > endUv ) break ;
162+ for ( let step = 0 ; step < 64 ; step ++ ) {
163+ if ( t > endUv ) {
164+ break ;
165+ }
192166 const hit = sceneSDF ( probePos . add ( rayDir . mul ( t ) ) ) ;
193167 if ( hit . dist <= eps ) {
194168 rgb = d . vec3f ( hit . color ) ;
@@ -198,7 +172,6 @@ const cascadePassCompute = tgpu['~unstable'].computeFn({
198172 t += std . max ( hit . dist , minStep ) ;
199173 }
200174
201- // Merge with upper cascade if ray didn't hit anything
202175 if ( layer < d . u32 ( cascadeAmount - 1 ) && T > 0.01 ) {
203176 const probesU = d . vec2u (
204177 std . max ( probes . x >> d . u32 ( 1 ) , d . u32 ( 1 ) ) ,
@@ -219,7 +192,7 @@ const cascadePassCompute = tgpu['~unstable'].computeFn({
219192 0 ,
220193 ) ;
221194 rgb = rgb . add ( upper . xyz . mul ( T ) ) ;
222- T = T * upper . w ;
195+ T *= upper . w ;
223196 }
224197
225198 accum = accum . add ( d . vec4f ( rgb , T ) ) ;
@@ -233,7 +206,9 @@ const buildRadianceFieldCompute = tgpu['~unstable'].computeFn({
233206 in : { gid : d . builtin . globalInvocationId } ,
234207} ) ( ( { gid } ) => {
235208 const outputProbes = outputProbesUniform . $ ;
236- if ( gid . x >= outputProbes . x || gid . y >= outputProbes . y ) return ;
209+ if ( gid . x >= outputProbes . x || gid . y >= outputProbes . y ) {
210+ return ;
211+ }
237212
238213 const cascadeProbes = cascadeProbesUniform . $ ;
239214 const cascadeDim = cascadeDimUniform . $ ;
@@ -250,43 +225,18 @@ const buildRadianceFieldCompute = tgpu['~unstable'].computeFn({
250225 const uvStride = d . vec2f ( cascadeProbes ) . mul ( invCascadeDim ) ;
251226 const baseSampleUV = probePixel . mul ( invCascadeDim ) ;
252227
253- // Sample Tile (0, 0)
254- let sum = std . textureSampleLevel (
255- buildRadianceFieldBGL . $ . src ,
256- buildRadianceFieldBGL . $ . srcSampler ,
257- baseSampleUV ,
258- 0 ,
259- ) . xyz ;
260-
261- // Sample Tile (1, 0)
262- sum = sum . add (
263- std . textureSampleLevel (
264- buildRadianceFieldBGL . $ . src ,
265- buildRadianceFieldBGL . $ . srcSampler ,
266- baseSampleUV . add ( d . vec2f ( uvStride . x , 0.0 ) ) ,
267- 0 ,
268- ) . xyz ,
269- ) ;
270-
271- // Sample Tile (0, 1)
272- sum = sum . add (
273- std . textureSampleLevel (
274- buildRadianceFieldBGL . $ . src ,
275- buildRadianceFieldBGL . $ . srcSampler ,
276- baseSampleUV . add ( d . vec2f ( 0.0 , uvStride . y ) ) ,
277- 0 ,
278- ) . xyz ,
279- ) ;
280-
281- // Sample Tile (1, 1)
282- sum = sum . add (
283- std . textureSampleLevel (
284- buildRadianceFieldBGL . $ . src ,
285- buildRadianceFieldBGL . $ . srcSampler ,
286- baseSampleUV . add ( uvStride ) ,
287- 0 ,
288- ) . xyz ,
289- ) ;
228+ let sum = d . vec3f ( ) ;
229+ for ( let i = d . u32 ( 0 ) ; i < 4 ; i ++ ) {
230+ const offset = d . vec2f ( d . f32 ( i & 1 ) , d . f32 ( i >> 1 ) ) . mul ( uvStride ) ;
231+ sum = sum . add (
232+ std . textureSampleLevel (
233+ buildRadianceFieldBGL . $ . src ,
234+ buildRadianceFieldBGL . $ . srcSampler ,
235+ baseSampleUV . add ( offset ) ,
236+ 0 ,
237+ ) . xyz ,
238+ ) ;
239+ }
290240
291241 std . textureStore (
292242 buildRadianceFieldBGL . $ . dst ,
@@ -308,19 +258,6 @@ const ACESFilm = tgpu.fn(
308258 return std . saturate ( res ) ;
309259} ) ;
310260
311- const finalRadianceFieldFrag = tgpu [ '~unstable' ] . fragmentFn ( {
312- in : { uv : d . vec2f } ,
313- out : d . vec4f ,
314- } ) ( ( { uv } ) => {
315- const field = std . textureSample (
316- radianceFieldView . $ ,
317- radianceSampler . $ ,
318- uv ,
319- ) . xyz ;
320- const outRgb = std . saturate ( field ) ;
321- return d . vec4f ( ACESFilm ( outRgb ) , 1.0 ) ;
322- } ) ;
323-
324261const overlayDebugBGL = tgpu . bindGroupLayout ( {
325262 cascadeTex : { texture : d . texture2dArray ( d . f32 ) } ,
326263 cascadeSampler : { sampler : 'filtering' } ,
@@ -344,19 +281,13 @@ const overlayFrag = tgpu['~unstable'].fragmentFn({
344281 std . max ( cascadeProbes . x >> debugLayer , d . u32 ( 1 ) ) ,
345282 std . max ( cascadeProbes . y >> debugLayer , d . u32 ( 1 ) ) ,
346283 ) ;
347-
348- // Ray dimensions: 2x2 stored at layer 0, doubling each layer
349284 const raysDimStored = d . u32 ( 2 ) << debugLayer ;
350285 const raysDimActual = raysDimStored * d . u32 ( 2 ) ;
351286 const rayCountActual = raysDimActual * raysDimActual ;
352-
353- // Interval for ray visualization
354287 const cascadeProbesMinVal = d . f32 ( std . min ( cascadeProbes . x , cascadeProbes . y ) ) ;
355288 const interval0 = 1 / cascadeProbesMinVal ;
356289 const pow4 = d . f32 ( d . u32 ( 1 ) << ( debugLayer * d . u32 ( 2 ) ) ) ;
357290 const endUv = ( interval0 * ( pow4 - 1 ) ) / 3 + interval0 * pow4 ;
358-
359- // Visual parameters
360291 const probeSpacing = std . min ( 1 / d . f32 ( probes . x ) , 1 / d . f32 ( probes . y ) ) ;
361292 const probeRadius = std . max ( probeSpacing * 0.08 , 0.002 ) ;
362293 const rayThickness = std . max ( probeSpacing * 0.03 , 0.001 ) ;
@@ -367,7 +298,6 @@ const overlayFrag = tgpu['~unstable'].fragmentFn({
367298
368299 const centerProbe = d . vec2i ( std . floor ( uv . mul ( d . vec2f ( probes ) ) ) ) ;
369300
370- // Check nearby probes (3x3 grid)
371301 for ( let py = - 1 ; py <= 1 ; py ++ ) {
372302 for ( let px = - 1 ; px <= 1 ; px ++ ) {
373303 const probeXY = centerProbe . add ( d . vec2i ( px , py ) ) ;
@@ -411,7 +341,7 @@ const overlayFrag = tgpu['~unstable'].fragmentFn({
411341 ) ;
412342 const sample = std . textureLoad (
413343 overlayDebugBGL . $ . cascadeTex ,
414- d . vec2i ( atlasPos ( dirStored , probe , probes ) ) ,
344+ d . vec2i ( dirStored . mul ( probes ) . add ( probe ) ) ,
415345 debugLayer ,
416346 0 ,
417347 ) ;
@@ -423,7 +353,6 @@ const overlayFrag = tgpu['~unstable'].fragmentFn({
423353 }
424354 }
425355
426- // Visualize rays
427356 let overlayColor = d . vec3f ( ) ;
428357 let overlayAlpha = d . f32 ( 0 ) ;
429358 if ( minRayDist < rayThickness ) {
@@ -432,7 +361,6 @@ const overlayFrag = tgpu['~unstable'].fragmentFn({
432361 std . smoothstep ( rayThickness * 1.5 , rayThickness * 0.3 , minRayDist ) * 0.8 ;
433362 }
434363
435- // Probe outline
436364 if ( std . abs ( minProbeDist ) < probeRadius * 0.2 ) {
437365 const edgeAlpha = std . smoothstep (
438366 probeRadius * 0.3 ,
@@ -564,23 +492,13 @@ async function frame() {
564492}
565493frameId = requestAnimationFrame ( frame ) ;
566494
567- function updateUniforms ( ) {
495+ const onDrag = ( id : string , position : d . v2f ) => {
496+ updateElementPosition ( id , position ) ;
568497 sceneDataUniform . write ( sceneData ) ;
569- }
498+ updateLighting ( ) ;
499+ } ;
570500
571- const dragController = new DragController (
572- canvas ,
573- ( id , position ) => {
574- updateElementPosition ( id , position ) ;
575- updateUniforms ( ) ;
576- updateLighting ( ) ;
577- } ,
578- ( id , position ) => {
579- updateElementPosition ( id , position ) ;
580- updateUniforms ( ) ;
581- updateLighting ( ) ;
582- } ,
583- ) ;
501+ const dragController = new DragController ( canvas , onDrag , onDrag ) ;
584502
585503export function onCleanup ( ) {
586504 dragController . destroy ( ) ;
0 commit comments