Skip to content

docs: @typegpu/noise #1391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: release
Choose a base branch
from
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
3 changes: 2 additions & 1 deletion apps/typegpu-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@loaders.gl/obj": "^4.3.3",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slider": "^1.3.2",
"@radix-ui/react-slider": "^1.3.5",
"@stackblitz/sdk": "^1.11.0",
"@tailwindcss/vite": "^4.1.6",
"@typegpu/color": "workspace:*",
Expand Down Expand Up @@ -58,6 +58,7 @@
"@types/babel__standalone": "^7.1.9",
"@types/babel__template": "^7.4.4",
"@types/babel__traverse": "^7.20.7",
"@types/node": "^24.0.3",
"@webgpu/types": "catalog:",
"astro-vtbot": "^2.0.6",
"autoprefixer": "^10.4.21",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ ${example.htmlFile.content}
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"typeRoots": ["./node_modules/@webgpu/types", "./node_modules/@types"],
"types": ["@webgpu/types"],
"moduleResolution": "node",
"allowImportingTsExtensions": true,
"isolatedModules": true,
Expand Down
168 changes: 168 additions & 0 deletions apps/typegpu-docs/src/content/docs/ecosystem/typegpu-noise.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,171 @@
title: "@typegpu/noise"
---

The `@typegpu/noise` package offers a set of pseudo-random utilities for use in TypeGPU and WebGPU projects. At its core, the package provides a
pseudo-random number generator for uniformly distributed values (same probability for all numbers) in the range `[0, 1)`, as well as higher-level
utilities built on top.

It also features a [Perlin noise](#perlin-noise) implementation, which is useful for generating smooth, natural-looking variations in visual
effects, terrains, and other procedural elements.

## Use with either TypeGPU or WebGPU

Each utility function described in this guide is usable from the context of both TypeGPU and vanilla WebGPU. This makes it really simple to
leverage the TypeGPU ecosystem in your WebGPU projects, without needing to migrate large parts of your codebase.

### TypeGPU

Calling utility functions from [TypeGPU functions](/TypeGPU/fundamentals/functions/) links them automatically.
In the example below, resolving `randomVec2f` into a shader will include the code for `randf.sample` and all of its dependencies.

```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
// ---cut---
import { randf } from '@typegpu/noise';

const randomVec2f = tgpu['~unstable'].fn([], d.vec2f)(() => {
const x = randf.sample(); // returns a random float in [0, 1)
const y = randf.sample(); // returns the next random float in [0, 1)
return d.vec2f(x, y);
});

// ...
```

### WebGPU

The `tgpu.resolve` API can be used to inject TypeGPU resources (constants, functions, etc.) into a WGSL shader.

In the example below, the `sample` function is accessed both as a named function, and as part of the `randf` object.
The resolution mechanism handles deduplication out of the box, as well as omits code that is unused by your shader,
so only one definition of `sample` will be included in the final shader.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
import { randf } from '@typegpu/noise';
// `typegpu` is necessary to inject library code into your custom shader
import tgpu from 'typegpu';

const shader = tgpu.resolve({
template: `
fn random_vec2f() -> vec2f {
// Accessing the 'sample' function directly
let x = sample();
// Accessing the 'sample' function as part of the 'randf' object
let y = randf.sample();
return vec2f(x, y);
}

// ...
`,
externals: { sample: randf.sample, randf },
});

// The shader is just a WGSL string
shader;
// ^?
```

Does this mean we allow object access inside of WGSL shaders?... yes, yes we do 🙈. [To learn more about resolution, check our "Resolve" guide](/TypeGPU/fundamentals/resolve/)

## Pseudo-random number generator

The `@typegpu/noise` package provides a pseudo-random number generator (PRNG) that generates uniformly distributed random numbers in the range `[0, 1)`.
Each call to `randf.sample` returns the next random float in the sequence, allowing for predictable and repeatable results. The seed can be set or reset
using a set of `randf.seedN` functions, where `N` is the number of components our seed has.
```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
import { randf } from '@typegpu/noise';

const main = tgpu['~unstable'].fragmentFn({
in: { pos: d.builtin.position },
out: d.vec4f,
})(({ pos }) => {
randf.seed2(pos.xy); // Generate a different sequence for each pixel

return d.vec4f(
randf.sample(), // returns a random float in [0, 1)
randf.sample(), // returns the next random float in [0, 1)
0.0,
1.0
);
});
```

There are higher-level utilities built on top of `randf.sample`:
- `inUnitCircle` - returns a random 2D vector uniformly distributed inside a unit circle
- `inUnitCube` - returns a random 3D vector uniformly distributed inside a unit cube
- `onHemisphere` - returns a random 3D vector uniformly distributed on the surface of a hemisphere
- `onUnitSphere` - returns a random 3D vector uniformly distributed on the surface of a unit sphere

## Perlin noise

The package exports an implementation for both 2D and 3D Perlin noise, `perlin2d` and `perlin3d`, respectively.
Using it is as simple as calling the `.sample` function with the desired coordinates, and it returns a value in the range `[-1, 1]`.

```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
import { mul } from 'typegpu/std';
// ---cut---
import { perlin2d } from '@typegpu/noise';

const main = tgpu['~unstable'].fragmentFn({
in: { pos: d.builtin.position },
out: d.vec4f,
})(({ pos }) => {
const noise = perlin2d.sample(mul(pos.xy, 0.1)); // Scale the coordinates for smoother noise
return d.vec4f(noise, noise, noise, 1); // Use the noise value for RGB channels
});
```

This simple usage is enough for most cases, but by default, `perlin.sample` computes the underlying gradients on-demand, per pixel.
This can be inefficient for large images or when the same noise is sampled multiple times.
To improve performance, you can precompute the gradients using either a *Static* or a *Dynamic* cache (currently only available for perlin3d).

### Static cache
A static cache presumes that the domain of the noise function is fixed, and cannot change between shader invocations.

```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
// ---cut---
import { perlin3d } from '@typegpu/noise';

const root = await tgpu.init();
const cache = perlin3d.staticCache({ root, size: d.vec3u(10, 10, 1) });

const main = tgpu['~unstable'].fn([])(() => {
const value = perlin3d.sample(d.vec3f(0.5, 0, 0));
const wrappedValue = perlin3d.sample(d.vec3f(10.5, 0, 0)); // the same as `value`!
});
```
Or in WebGPU:
```ts twoslash
/// <reference types="@webgpu/types" />
import * as d from 'typegpu/data';

declare const device: GPUDevice;
// ---cut---
import tgpu from 'typegpu';
import { perlin3d } from '@typegpu/noise';

const root = tgpu.initFromDevice({ device });
const cache = perlin3d.staticCache({ root, size: d.vec3u(10, 10, 1) });

const { code, usedBindGroupLayouts, catchall } = tgpu.resolveWithContext({
template: `
fn main() {
let value = perlin3d.sample(vec3f(0.5, 0., 0.));
let wrappedValue = perlin3d.sample(vec3f(10.5, 0., 0.)); // the same as 'value'!
// ...
}

// ...
`,
externals: { perlin3d },
});
```
29 changes: 13 additions & 16 deletions apps/typegpu-docs/src/content/docs/fundamentals/tgsl.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,17 @@ import * as d from 'typegpu/data';
import * as std from 'typegpu/std';

const MAX_OBSTACLES = 4;
const obstaclesReadonly = root
.createBuffer(d.arrayOf(d.struct({
const obstacles = root
.createReadonly(d.arrayOf(d.struct({
center: d.vec2i,
size: d.vec2i,
enabled: d.u32,
}), MAX_OBSTACLES))
.$usage('storage')
.as('readonly');
}), MAX_OBSTACLES));

const isInsideObstacle = tgpu['~unstable'].fn([d.i32, d.i32], d.bool)(
(x, y) => {
for (let obsIdx = 0; obsIdx < MAX_OBSTACLES; obsIdx++) {
const obs = obstaclesReadonly.value[obsIdx];
const obs = obstacles.$[obsIdx];
if (obs.enabled === 0) {
continue;
}
Expand Down Expand Up @@ -120,18 +118,17 @@ import * as d from 'typegpu/data';

const root = await tgpu.init();

const backgroundColorUniform = root['~unstable'].createUniform(d.vec4f, d.vec4f(0.114, 0.447, 0.941, 1));
const bgColor = root.createUniform(d.vec4f, d.vec4f(0.114, 0.447, 0.941, 1));

const fragmentTgsl = tgpu['~unstable'].fragmentFn({
out: d.location(0, d.vec4f),
})(() => backgroundColorUniform.value);
// ^?
const fragmentTgsl = tgpu['~unstable'].fragmentFn({ out: d.vec4f })(() => {
return bgColor.$;
// ^?
});

const fragmentWgsl = tgpu['~unstable'].fragmentFn({
out: d.location(0, d.vec4f),
})`{
return backgroundColorUniform;
}`.$uses({ backgroundColorUniform });
const fragmentWgsl = tgpu['~unstable'].fragmentFn({ out: d.vec4f })`{
return bgColor;
}
`.$uses({ bgColor });
```

* **Operators** --
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ npm install react-native-wgpu
npm install typegpu
```

If you use TypeScript, then it's also recommended to install WebGPU types:
Since WebGPU is still yet to reach baseline in browsers, types for it need to be installed separately:

```sh
npm i --save-dev @webgpu/types
Expand All @@ -54,9 +54,7 @@ npm i --save-dev @webgpu/types
```js title="tsconfig.json"
{
"compilerOptions": {
"typeRoots": [
"./node_modules/@webgpu/types"
]
"types": ["@webgpu/types"]
},
}
```
Expand Down
29 changes: 15 additions & 14 deletions apps/typegpu-docs/src/content/docs/reference/naming-convention.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,37 +49,38 @@ We give buffer usages *camelCase* names with a suffix that corresponds to their
const boidsUniform = boidsBuffer.as('uniform');
const boidsMutable = boidsBuffer.as('mutable');
const boidsReadonly = boidsBuffer.as('readonly');
```

## Buffer shorthands

const cellsUniform = root['~unstable'].createUniform(Cells);
const cellsMutable = root['~unstable'].createMutable(Cells);
const cellsReadonly = root['~unstable'].createReadonly(Cells);
We give buffer shorthands *camelCase* names with no special suffix, since they represent both
the buffer, and the single permitted usage.

```ts
const cells1 = root.createUniform(Cells);
const cells2 = root.createMutable(Cells);
const cells3 = root.createReadonly(Cells);
```

## Slots

We give slots *camelCase* names, without any special prefix or suffix. Our justification is
that if there's a name clash, meaning there's a concrete value of the same name, then there's
no need for the slot. We can just use the value instead.
We give slots *camelCase* names with the `Slot` suffix.

```ts
const color = tgpu['~unstable'].slot(vec4f(1, 0, 0, 1));
const colorSlot = tgpu['~unstable'].slot(vec4f(1, 0, 0, 1));
```

## Accessors

We give accessors *camelCase* names, without any special prefix or suffix. Our justification is
that if there's a name clash, meaning there's a concrete value of the same name, then there's
no need for the accessor. We can just use the value instead.
We give accessors *camelCase* names with the `Access` suffix.

```ts
const color = tgpu['~unstable'].accessor(vec4f);
const colorAccess = tgpu['~unstable'].accessor(vec4f);
```

## Derived

We give derived values *camelCase* names, without any special prefix or suffix. Our justification is
that if there's a name clash, meaning there's a concrete value of the same name, then there's
no need for the derived value. We can just use the value instead.
We give derived values *camelCase* names, without any special prefix or suffix.

```ts
const lighterColor = tgpu['~unstable'].derived(() => add(color.value, vec4f(0.2, 0.2, 0.2, 0)));
Expand Down
Loading