Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/typegpu-docs/src/components/ExampleShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const ADVANCED_EXAMPLE_KEYS = [
'rendering--clouds',
'rendering--cubemap-reflection',
'rendering--function-visualizer',
'rendering--jelly-knob',
'rendering--jelly-slider',
'rendering--jelly-switch',
'rendering--os-awards',
Expand Down
118 changes: 118 additions & 0 deletions apps/typegpu-docs/src/examples/rendering/jelly-knob/camera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { d, type TgpuRoot, type TgpuUniform } from 'typegpu';
import * as m from 'wgpu-matrix';

export const Camera = d.struct({
view: d.mat4x4f,
proj: d.mat4x4f,
viewInv: d.mat4x4f,
projInv: d.mat4x4f,
});

function halton(index: number, base: number) {
let result = 0;
let f = 1 / base;
let i = index;
while (i > 0) {
result += f * (i % base);
i = Math.floor(i / base);
f = f / base;
}
return result;
}

function* haltonSequence(base: number) {
let index = 1;
while (true) {
yield halton(index, base);
index++;
}
}

export class CameraController {
#uniform: TgpuUniform<typeof Camera>;
#view: d.m4x4f;
#viewInv: d.m4x4f;
#baseProj: d.m4x4f;
#baseProjInv: d.m4x4f;
#haltonX: Generator<number>;
#haltonY: Generator<number>;
#width: number;
#height: number;

constructor(
root: TgpuRoot,
position: d.v3f,
target: d.v3f,
up: d.v3f,
fov: number,
width: number,
height: number,
near = 0.1,
far = 10,
) {
this.#width = width;
this.#height = height;

this.#view = m.mat4.lookAt(position, target, up, d.mat4x4f());
this.#baseProj = m.mat4.perspective(fov, width / height, near, far, d.mat4x4f());

this.#viewInv = m.mat4.invert(this.#view, d.mat4x4f());
this.#baseProjInv = m.mat4.invert(this.#baseProj, d.mat4x4f());

this.#uniform = root.createUniform(Camera, {
view: this.#view,
proj: this.#baseProj,
viewInv: this.#viewInv,
projInv: this.#baseProjInv,
});

this.#haltonX = haltonSequence(2);
this.#haltonY = haltonSequence(3);
}

jitter() {
const [jx, jy] = [this.#haltonX.next().value, this.#haltonY.next().value] as [number, number];

const jitterX = ((jx - 0.5) * 2.0) / this.#width;
const jitterY = ((jy - 0.5) * 2.0) / this.#height;

const jitterMatrix = m.mat4.identity(d.mat4x4f());
jitterMatrix[12] = jitterX; // x translation in NDC
jitterMatrix[13] = jitterY; // y translation in NDC

const jitteredProj = m.mat4.mul(jitterMatrix, this.#baseProj, d.mat4x4f());
const jitteredProjInv = m.mat4.invert(jitteredProj, d.mat4x4f());

this.#uniform.patch({
proj: jitteredProj,
projInv: jitteredProjInv,
});
}

updateView(position: d.v3f, target: d.v3f, up: d.v3f) {
this.#view = m.mat4.lookAt(position, target, up, d.mat4x4f());
this.#viewInv = m.mat4.invert(this.#view, d.mat4x4f());

this.#uniform.patch({
view: this.#view,
viewInv: this.#viewInv,
});
}

updateProjection(fov: number, width: number, height: number, near = 0.1, far = 10) {
this.#width = width;
this.#height = height;

this.#baseProj = m.mat4.perspective(fov, width / height, near, far, d.mat4x4f());
this.#baseProjInv = m.mat4.invert(this.#baseProj, d.mat4x4f());

this.#uniform.patch({
proj: this.#baseProj,
projInv: this.#baseProjInv,
});
}

get cameraUniform() {
return this.#uniform;
}
}
54 changes: 54 additions & 0 deletions apps/typegpu-docs/src/examples/rendering/jelly-knob/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { d, std } from 'typegpu';
import type { SpringProperties } from './spring.ts';

// Rendering constants
export const MAX_STEPS = 64;
export const MAX_DIST = 10;
export const SURF_DIST = 0.001;

// Fixed camera, looking at the origin with +Y up
export const CAMERA_POSITION = d.vec3f(0, 2.7, 0.8);

// Ground material constants
// Linear blue-gray, tuned for the example page's dark #1b1f2c background.
export const GROUND_ALBEDO = d.vec3f(0.025, 0.032, 0.054);
export const LIGHT_GROUND_ALBEDO = d.vec3f(0.65, 0.7, 0.79);
export const EXPOSURE = 1.5;
export const METER_TICKS = 16;

export const GroundParams = {
groundThickness: 0.01,
groundRoundness: 0.01,
jellyCutoutRadius: 0.38,
meterCutoutRadius: 0.6,
meterCutoutGirth: 0.04,
};

// Lighting constants
export const AMBIENT_COLOR = d.vec3f(0.6);
export const AMBIENT_INTENSITY = 0.6;
export const SPECULAR_POWER = 10;
export const SPECULAR_INTENSITY = 0.6;
export const LIGHT_DIR = std.normalize(d.vec3f(-0.5, -0.14, -0.8));
// Light travels from above and behind the knob toward the camera (+Z).
export const LIGHT_MODE_LIGHT_DIR = std.normalize(d.vec3f(-0.12, -0.75, 0.7));

// Jelly material constants
export const JELLY_IOR = 1.42;
export const JELLY_SCATTER_STRENGTH = 3;

// Ambient occlusion constants
export const AO_STEPS = 3;
export const AO_RADIUS = 0.1;
export const AO_INTENSITY = 0.5;
export const AO_BIAS = SURF_DIST * 5;

// Jelly constants
export const JELLY_HALFSIZE = d.vec3f(0.3, 0.3, 0.3);

// Spring dynamics constants
export const twistProperties: SpringProperties = {
mass: 4,
stiffness: 700,
damping: 50,
};
70 changes: 70 additions & 0 deletions apps/typegpu-docs/src/examples/rendering/jelly-knob/dataTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { tgpu, d, type TgpuUniform } from 'typegpu';
import type { KnobBehavior } from './knob.ts';
import type { Camera } from './camera.ts';

export const DirectionalLight = d.struct({
direction: d.vec3f,
color: d.vec3f,
});

export const ObjectType = {
JELLY: 1,
BACKGROUND: 3,
} as const;

export const HitInfo = d.struct({
distance: d.f32,
objectType: d.i32,
});

export const BoxIntersection = d.struct({
hit: d.bool,
tMin: d.f32,
tMax: d.f32,
});

export const Ray = d.struct({
origin: d.vec3f,
direction: d.vec3f,
});

export const RayMarchResult = d.struct({
point: d.vec3f,
color: d.vec3f,
});

export type BoundingBox = d.Infer<typeof BoundingBox>;
export const BoundingBox = d.struct({
min: d.vec3f,
max: d.vec3f,
});

export const KnobState = d.struct({
topDisplacement: d.vec3f,
topProgress: d.f32,
bottomProgress: d.f32,
});

export const taaResolveLayout = tgpu.bindGroupLayout({
currentTexture: {
texture: d.texture2d(),
},
historyTexture: {
texture: d.texture2d(),
},
outputTexture: {
storageTexture: d.textureStorage2d('rgba16float', 'write-only'),
},
});

export const sampleLayout = tgpu.bindGroupLayout({
currentTexture: {
texture: d.texture2d(),
},
});

export const knobBehaviorSlot = tgpu.slot<KnobBehavior>();
export const cameraUniformSlot = tgpu.slot<TgpuUniform<typeof Camera>>();
export const lightUniformSlot = tgpu.slot<TgpuUniform<typeof DirectionalLight>>();
export const jellyColorUniformSlot = tgpu.slot<TgpuUniform<typeof d.vec4f>>();
export const darkModeUniformSlot = tgpu.slot<TgpuUniform<typeof d.u32>>();
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<canvas></canvas>
Loading
Loading