Skip to content
Draft
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
147 changes: 147 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,147 @@
import type { TgpuRoot, TgpuUniform } from 'typegpu';
import * as d from 'typegpu/data';
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;
#proj: d.m4x4f;
#viewInv: d.m4x4f;
#projInv: 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.#proj = this.#baseProj;

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

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

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.writePartial({
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.writePartial({
view: this.#view,
viewInv: this.#viewInv,
});
}

updateProjection(
fov: number,
width: number,
height: number,
near = 0.1,
far = 100,
) {
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.writePartial({
proj: this.#baseProj,
projInv: this.#baseProjInv,
});
}

get cameraUniform() {
return this.#uniform;
}
}
70 changes: 70 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,70 @@
import * as d from 'typegpu/data';
import * as std from 'typegpu/std';
import type { SpringProperties } from './spring.ts';

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

// Ground material constants
export const LIGHT_GROUND_ALBEDO = d.vec3f(1);
export const DARK_GROUND_ALBEDO = d.vec3f(0.2);

export const GroundParams = {
groundThickness: 0.03,
groundRoundness: 0.02,
jellyCutoutRadius: 0.38,
meterCutoutRadius: 0.7,
meterCutoutGirth: 0.08,
};

// 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_MODE_LIGHT_DIR = std.normalize(d.vec3f(0.18, -0.30, 0.64));
export const DARK_MODE_LIGHT_DIR = std.normalize(d.vec3f(-0.5, -0.14, -0.8));

// 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: 1,
stiffness: 700,
damping: 10,
};

export const wiggleXProperties: SpringProperties = {
mass: 1,
stiffness: 1000,
damping: 20,
};

export const wiggleZProperties: SpringProperties = {
mass: 1,
stiffness: 1000,
damping: 20,
};

// Mouse interaction constants
export const MOUSE_SMOOTHING = 0.08;
export const MOUSE_MIN_X = 0.45;
export const MOUSE_MAX_X = 0.9;
export const MOUSE_RANGE_MIN = 0.4;
export const MOUSE_RANGE_MAX = 0.9;
export const TARGET_MIN = -0.7;
export const TARGET_MAX = 1.0;
export const TARGET_OFFSET = -0.5;
81 changes: 81 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,81 @@
import tgpu, { type TgpuUniform } from 'typegpu';
import * as d from 'typegpu/data';
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,
PROGRESS_METER: 2,
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({
topProgress: d.f32,
bottomProgress: d.f32,
time: d.f32,
});

export const rayMarchLayout = tgpu.bindGroupLayout({
backgroundTexture: { texture: d.texture2d(d.f32) },
});

export const taaResolveLayout = tgpu.bindGroupLayout({
currentTexture: {
texture: d.texture2d(),
},
historyTexture: {
texture: d.texture2d(),
},
outputTexture: {
storageTexture: d.textureStorage2d('rgba8unorm', '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>>();
export const randomUniformSlot = tgpu.slot<TgpuUniform<typeof d.vec2f>>();
// shader uses this as time, but it advances faster the more the knob is turned
export const effectTimeUniformSlot = tgpu.slot<TgpuUniform<typeof d.f32>>();
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<canvas></canvas>
Loading
Loading