Skip to content

Commit 7963f69

Browse files
committed
raymarching
1 parent a92ee14 commit 7963f69

3 files changed

Lines changed: 458 additions & 72 deletions

File tree

apps/typegpu-docs/src/examples/tests/uniformity/index.ts

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ import { defineControls } from '../../common/defineControls.ts';
88
type Mode = '2d' | '3d';
99

1010
const modes: Mode[] = ['2d', '3d'];
11-
const initialOpacityPerStep = 0.02;
1211
const gridSizes = [8, 16, 32, 64, 128, 256];
1312
const samplesPerThread = [1, 8, 16, 64, 256, 1024, 131072];
1413
const initialSamplesPerThread = samplesPerThread[0];
1514
const initialTakeAverage = false;
16-
let multiplier = 1;
15+
const initialMultiplier = 1;
1716

18-
let mode = '2d' as Mode;
17+
let mode = modes[1];
1918
let prng = prngKeys[0];
2019
let 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

3735
const 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

133130
const 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

136187
const displayPipelines = {
137188
'2d': displayPipeline2d,
138-
'3d': displayPipeline2d,
189+
'3d': displayPipeline3d,
139190
};
140191

141192
const 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+
156221
export 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

226292
export function onCleanup() {
293+
cleanupCamera();
227294
root.destroy();
228295
}
229296

2.55 MB
Loading

0 commit comments

Comments
 (0)