@@ -8,14 +8,13 @@ import { defineControls } from '../../common/defineControls.ts';
88type Mode = '2d' | '3d' ;
99
1010const modes : Mode [ ] = [ '2d' , '3d' ] ;
11- const initialOpacityPerStep = 0.02 ;
1211const gridSizes = [ 8 , 16 , 32 , 64 , 128 , 256 ] ;
1312const samplesPerThread = [ 1 , 8 , 16 , 64 , 256 , 1024 , 131072 ] ;
1413const initialSamplesPerThread = samplesPerThread [ 0 ] ;
1514const initialTakeAverage = false ;
16- let multiplier = 1 ;
15+ const initialMultiplier = 1 ;
1716
18- let mode = '2d' as Mode ;
17+ let mode = modes [ 1 ] ;
1918let prng = prngKeys [ 0 ] ;
2019let gridSize = gridSizes [ 2 ] ;
2120
@@ -30,16 +29,14 @@ const Config = d.struct({
3029 samplesPerThread : d . i32 ,
3130 takeAverage : d . i32 ,
3231 multiplier : d . f32 ,
33- opacityPerStep : d . f32 ,
3432 canvasRatio : d . f32 ,
3533} ) ;
3634
3735const configUniform = root . createUniform ( Config , {
3836 gridSize,
3937 samplesPerThread : initialSamplesPerThread ,
4038 takeAverage : d . i32 ( initialTakeAverage ) ,
41- opacityPerStep : initialOpacityPerStep ,
42- multiplier,
39+ multiplier : initialMultiplier ,
4340 canvasRatio : canvas . width / canvas . height ,
4441} ) ;
4542
@@ -131,11 +128,65 @@ const displayPipeline2d = root.createRenderPipeline({
131128} ) ;
132129
133130const cameraUniform = root . createUniform ( Camera ) ;
134- // HERE
131+ const BoxIntersection = d . struct ( { tNear : d . f32 , tFar : d . f32 , hit : d . bool } ) ;
132+
133+ // based on: https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection.html
134+ const getBoxIntersection = ( rayOrigin : d . v3f , rayDir : d . v3f , boxMin : d . v3f , boxMax : d . v3f ) => {
135+ 'use gpu' ;
136+ const invDir = 1 / rayDir ;
137+ const t0 = ( boxMin - rayOrigin ) * invDir ;
138+ const t1 = ( boxMax - rayOrigin ) * invDir ;
139+ const tmin = std . min ( t0 , t1 ) ;
140+ const tmax = std . max ( t0 , t1 ) ;
141+ const tNear = std . max ( tmin . x , tmin . y , tmin . z ) ;
142+ const tFar = std . min ( tmax . x , tmax . y , tmax . z ) ;
143+ return BoxIntersection ( { tNear, tFar, hit : tFar >= tNear } ) ;
144+ } ;
145+
146+ const STEPS = 64 ;
147+ const displayPipeline3d = root . createRenderPipeline ( {
148+ vertex : common . fullScreenTriangle ,
149+ fragment : ( { uv } ) => {
150+ 'use gpu' ;
151+ const ndc = d . vec2f ( uv . x * 2 - 1 , 1 - uv . y * 2 ) ;
152+ const invViewProj = cameraUniform . $ . viewInverse * cameraUniform . $ . projectionInverse ;
153+ const worldNear = invViewProj * d . vec4f ( ndc , 0 , 1 ) ;
154+ const worldFar = invViewProj * d . vec4f ( ndc , 1 , 1 ) ;
155+ const rayOrigin = worldNear . xyz / worldNear . w ;
156+ const rayDir = std . normalize ( worldFar . xyz / worldFar . w - rayOrigin ) ;
157+
158+ const gridSize = configUniform . $ . gridSize ;
159+ const boxMax = d . vec3f ( gridSize ) ;
160+ const isect = getBoxIntersection ( rayOrigin , rayDir , d . vec3f ( 0 ) , boxMax ) ;
161+ if ( ! isect . hit ) {
162+ return d . vec4f ( ) ;
163+ }
164+
165+ const stepSize = ( isect . tFar - isect . tNear ) / STEPS ;
166+ const opacity = ( stepSize / gridSize ) * 3 ;
167+ let transmittance = d . f32 ( 1 ) ;
168+ let accum = d . f32 ( ) ;
169+ let i = 0 ;
170+ while ( i < STEPS && transmittance > 1e-3 ) {
171+ const t = isect . tNear + ( d . f32 ( i ) + 0.5 ) * stepSize ;
172+ const pos = rayOrigin + rayDir * t ;
173+ const value = std . textureLoad (
174+ layouts . display . $ . texture ,
175+ d . vec3u ( std . clamp ( pos , d . vec3f ( 0 ) , boxMax - 1 ) ) ,
176+ ) . r ;
177+ accum += value * opacity * transmittance ;
178+ transmittance *= 1 - opacity ;
179+ i += 1 ;
180+ }
181+
182+ return d . vec4f ( d . vec3f ( accum ) , 1 - transmittance ) ;
183+ } ,
184+ targets : { format : presentationFormat } ,
185+ } ) ;
135186
136187const displayPipelines = {
137188 '2d' : displayPipeline2d ,
138- '3d' : displayPipeline2d ,
189+ '3d' : displayPipeline3d ,
139190} ;
140191
141192const resample = ( ) => {
@@ -153,6 +204,20 @@ const redraw = () => {
153204 . draw ( 3 ) ;
154205} ;
155206
207+ const { cleanupCamera, targetCamera } = setupOrbitCamera (
208+ canvas ,
209+ {
210+ initPos : d . vec4f ( d . vec3f ( 2 * gridSize ) , 1 ) ,
211+ target : d . vec4f ( d . vec3f ( 0.5 * gridSize ) , 1 ) ,
212+ minZoom : 10 ,
213+ maxZoom : 1000 ,
214+ } ,
215+ ( updates ) => {
216+ cameraUniform . patch ( updates ) ;
217+ redraw ( ) ;
218+ } ,
219+ ) ;
220+
156221export const controls = defineControls ( {
157222 Mode : {
158223 initial : mode ,
@@ -177,6 +242,7 @@ export const controls = defineControls({
177242 options : gridSizes ,
178243 onSelectChange : ( value ) => {
179244 gridSize = value ;
245+ targetCamera ( d . vec4f ( d . vec3f ( 2 * gridSize ) , 1 ) , d . vec4f ( d . vec3f ( 0.5 * gridSize ) , 1 ) ) ;
180246 resample ( ) ;
181247 redraw ( ) ;
182248 } ,
@@ -199,9 +265,9 @@ export const controls = defineControls({
199265 } ,
200266 } ,
201267 'Seed Multiplier' : {
202- initial : multiplier ,
203- min : 0.0001 ,
204- max : 1000 ,
268+ initial : initialMultiplier ,
269+ min : 0.00001 ,
270+ max : 2000 ,
205271 step : 1 ,
206272 onSliderChange : ( value ) => {
207273 configUniform . patch ( { multiplier : value } ) ;
@@ -224,6 +290,7 @@ export const controls = defineControls({
224290} ) ;
225291
226292export function onCleanup ( ) {
293+ cleanupCamera ( ) ;
227294 root . destroy ( ) ;
228295}
229296
0 commit comments