luxscura 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +658 -73
- package/dist/appearances/glossy/appearance.d.ts +1 -0
- package/dist/appearances/glossy/index.d.ts +1 -1
- package/dist/appearances/glossy/material.d.ts +5 -1
- package/dist/appearances/matte.d.ts +4 -3
- package/dist/appearances/pbr/appearance.d.ts +2 -0
- package/dist/appearances/pbr/material.d.ts +3 -1
- package/dist/glossy.js +158 -26
- package/dist/glossy.js.map +1 -1
- package/dist/index.js +45 -36
- package/dist/index.js.map +1 -1
- package/dist/matte.js +36 -23
- package/dist/matte.js.map +1 -1
- package/dist/pbr.js +112 -85
- package/dist/pbr.js.map +1 -1
- package/dist/program.d.ts +34 -19
- package/dist/renderer.d.ts +8 -11
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,99 +1,684 @@
|
|
|
1
1
|
# Luxscura
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A TypeGPU ray marching renderer for custom WebGPU applications. Supply a camera,
|
|
4
|
+
geometry as a signed-distance function, and an appearance; Luxscura handles
|
|
5
|
+
ray marching, surface normals, depth, and drawing. Use it alongside your other
|
|
6
|
+
rendering pipelines, with your application's TypeGPU root and render attachments.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
may change.
|
|
8
|
+
See the [website and interactive examples](https://alexjwayne.github.io/luxscura/).
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
⚠️ Luxscura is in early development; its public API may change.
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
## Setup
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
```sh
|
|
15
|
+
npm install luxscura typegpu
|
|
16
|
+
npm install --save-dev unplugin-typegpu
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Configure TypeGPU's build plugin so GPU callbacks can compile from TypeScript:
|
|
20
|
+
[TypeGPU setup](https://docs.swmansion.com/TypeGPU/getting-started/) ·
|
|
21
|
+
[Build plugin](https://docs.swmansion.com/TypeGPU/tooling/unplugin-typegpu/).
|
|
22
|
+
Configuring `tsover` is highly recommended for operator overloading on vectors
|
|
23
|
+
and matrices in GPU callbacks, as used in the examples below. Follow the
|
|
24
|
+
[TypeGPU setup guide](https://docs.swmansion.com/TypeGPU/getting-started/) for configuration.
|
|
25
|
+
|
|
26
|
+
Your application needs a browser with WebGPU support.
|
|
14
27
|
|
|
15
|
-
##
|
|
28
|
+
## Quick start
|
|
16
29
|
|
|
17
|
-
|
|
30
|
+
A program combines a camera callback, a surface, and an appearance. The surface's
|
|
31
|
+
`sd` callback returns the signed distance at a world-space point; its `bounds`
|
|
32
|
+
callback provides an axis-aligned bounding box enclosing the surface. Marching
|
|
33
|
+
stops at the box's exit by default. GPU callbacks use `'use gpu'`.
|
|
34
|
+
|
|
35
|
+
This example uses `@typegpu/sdf` for a sphere.
|
|
36
|
+
`createCamera` belongs to your application; see [Camera](#camera) for an example.
|
|
18
37
|
|
|
19
38
|
```ts
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
import { sdSphere } from '@typegpu/sdf'
|
|
40
|
+
import {
|
|
41
|
+
AABB,
|
|
42
|
+
createRaymarchConstantLighting,
|
|
43
|
+
createRaymarchProgram,
|
|
44
|
+
createRaymarchRenderer,
|
|
45
|
+
RaymarchCamera,
|
|
46
|
+
} from 'luxscura'
|
|
47
|
+
import { createMatteAppearance } from 'luxscura/matte'
|
|
48
|
+
import { vec3f, vec4f } from 'typegpu/data'
|
|
49
|
+
|
|
50
|
+
// A canvas with its drawing-buffer width and height already set.
|
|
51
|
+
const camera = createCamera(canvas)
|
|
52
|
+
const lighting = createRaymarchConstantLighting({
|
|
53
|
+
ambient: vec3f(0.2),
|
|
54
|
+
directionalLights: [{
|
|
55
|
+
direction: vec3f(-1, 1, -1),
|
|
56
|
+
color: vec3f(1, 0.9, 0.75),
|
|
57
|
+
intensity: 1,
|
|
58
|
+
}],
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const program = createRaymarchProgram({
|
|
62
|
+
options: { epsilon: 0.001 },
|
|
63
|
+
camera: () => {
|
|
64
|
+
'use gpu'
|
|
65
|
+
return RaymarchCamera({
|
|
66
|
+
position: camera.position,
|
|
67
|
+
viewProjectionMatrix: camera.viewProjectionMatrix,
|
|
68
|
+
})
|
|
69
|
+
},
|
|
70
|
+
surface: {
|
|
71
|
+
bounds: () => {
|
|
72
|
+
'use gpu'
|
|
73
|
+
return AABB({ min: vec3f(-1.01), max: vec3f(1.01) })
|
|
74
|
+
},
|
|
75
|
+
sd: (point) => {
|
|
76
|
+
'use gpu'
|
|
77
|
+
return sdSphere(point, 1)
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
appearance: createMatteAppearance({
|
|
81
|
+
color: () => {
|
|
82
|
+
'use gpu'
|
|
83
|
+
return vec4f(0.15, 0.5, 0.85, 1)
|
|
84
|
+
},
|
|
85
|
+
lighting,
|
|
86
|
+
}),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Use your application's existing TypeGPU root.
|
|
90
|
+
const render = createRaymarchRenderer({ root, program })
|
|
91
|
+
render({
|
|
92
|
+
colorAttachment: { view: canvasContext },
|
|
93
|
+
depthStencilAttachment: { view: depthTexture },
|
|
63
94
|
})
|
|
64
95
|
```
|
|
65
96
|
|
|
66
|
-
|
|
97
|
+
The application owns render targets and scheduling. Call `render` from your render
|
|
98
|
+
loop when needed. A depth attachment is required, with format `depth24plus` by
|
|
99
|
+
default. Use `renderTarget` when creating the renderer to specify a different
|
|
100
|
+
color format, depth format, or sample count matching your attachments.
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary>Standalone root and attachment setup</summary>
|
|
104
|
+
|
|
105
|
+
Run this before the quick start, with a sized `<canvas id="canvas">`:
|
|
67
106
|
|
|
68
107
|
```ts
|
|
69
|
-
import {
|
|
108
|
+
import { tgpu } from 'typegpu'
|
|
70
109
|
|
|
71
110
|
const canvas = document.getElementById('canvas') as HTMLCanvasElement
|
|
72
111
|
const root = await tgpu.init()
|
|
73
112
|
const canvasContext = root.configureContext({ canvas })
|
|
74
|
-
|
|
75
|
-
// A depth buffer is currently required
|
|
76
113
|
const depthTexture = root
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
114
|
+
.createTexture({
|
|
115
|
+
size: [canvas.width, canvas.height],
|
|
116
|
+
format: 'depth24plus',
|
|
117
|
+
})
|
|
118
|
+
.$usage('render')
|
|
119
|
+
```
|
|
82
120
|
|
|
83
|
-
|
|
84
|
-
|
|
121
|
+
When resizing, recreate the depth texture to match the canvas and update the
|
|
122
|
+
camera's aspect ratio. When disposing the application, stop its render loop and
|
|
123
|
+
release its resources (`root.destroy()` when the root is no longer needed).
|
|
124
|
+
|
|
125
|
+
</details>
|
|
126
|
+
|
|
127
|
+
## Camera
|
|
128
|
+
|
|
129
|
+
Your application owns the camera, its controls, and its matrices. Luxscura only
|
|
130
|
+
needs a GPU callback returning its world-space `position` and
|
|
131
|
+
`viewProjectionMatrix`, using WebGPU's depth range of 0–1. Adapt your existing
|
|
132
|
+
perspective camera to those two fields. For a moving camera, the callback can read
|
|
133
|
+
a TypeGPU buffer that your application updates, without rebuilding the rendering
|
|
134
|
+
pipeline.
|
|
135
|
+
|
|
136
|
+
Here is one way to create a fixed perspective camera using `wgpu-matrix`:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { mat4x4f, vec3f } from 'typegpu/data'
|
|
140
|
+
import { mat4 } from 'wgpu-matrix'
|
|
141
|
+
|
|
142
|
+
function createCamera({ width, height }: HTMLCanvasElement) {
|
|
143
|
+
const position = vec3f(0, -3, 0)
|
|
144
|
+
const projection = mat4.perspective(Math.PI / 4, width / height, 0.1, 10)
|
|
145
|
+
const view = mat4.lookAt(position, vec3f(0), vec3f(0, 0, 1))
|
|
146
|
+
const viewProjectionMatrix = mat4x4f()
|
|
147
|
+
mat4.multiply(projection, view, viewProjectionMatrix)
|
|
148
|
+
return { position, viewProjectionMatrix }
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Appearances
|
|
153
|
+
|
|
154
|
+
### Custom appearances
|
|
155
|
+
|
|
156
|
+
Supply an appearance callback directly to control surface shading. It receives
|
|
157
|
+
a `RaymarchResult`, including world-space `position`, `normal`, `rayDirection`,
|
|
158
|
+
and `instanceIndex`, so appearance can vary across a surface:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import type { RaymarchAppearance } from 'luxscura'
|
|
162
|
+
|
|
163
|
+
const appearance: RaymarchAppearance = (result) => {
|
|
164
|
+
'use gpu'
|
|
165
|
+
return vec4f(result.normal * 0.5 + vec3f(0.5), 1)
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
A custom appearance returns unpremultiplied linear RGB and opacity (alpha in
|
|
170
|
+
0–1). Appearance and material/color sampling callbacks run on both hits and
|
|
171
|
+
misses; the renderer only uses color on hits. Keep derivative-dependent sampling
|
|
172
|
+
outside hit-dependent branches. Surface hits write depth by default, even with
|
|
173
|
+
zero opacity; use `depthWriteEnabled: false` when appropriate for your rendering
|
|
174
|
+
setup.
|
|
175
|
+
|
|
176
|
+
### Built-in appearances
|
|
177
|
+
|
|
178
|
+
Built-in appearances provide color or material callbacks that receive the same
|
|
179
|
+
`RaymarchResult`. They share ambient and directional lighting. Light `direction`
|
|
180
|
+
is the world-space direction the light travels. All color and lighting values
|
|
181
|
+
use linear RGB.
|
|
182
|
+
|
|
183
|
+
| Appearance | Import | Inputs |
|
|
184
|
+
| --- | --- | --- |
|
|
185
|
+
| Matte — diffuse lighting | `luxscura/matte` | Color callback and lighting |
|
|
186
|
+
| Glossy — adjustable shiny highlights | `luxscura/glossy` | Material callback and lighting |
|
|
187
|
+
| PBR (experimental) — metallic/roughness shading and environment reflections | `luxscura/pbr` | Material callback, lighting, and environment callback |
|
|
188
|
+
|
|
189
|
+
The quick start uses matte. To use glossy, replace its `appearance` with:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { createGlossyAppearance, GlossyMaterial } from 'luxscura/glossy'
|
|
193
|
+
|
|
194
|
+
const appearance = createGlossyAppearance({
|
|
195
|
+
material: (result) => {
|
|
196
|
+
'use gpu'
|
|
197
|
+
return GlossyMaterial({
|
|
198
|
+
baseColor: vec3f(0.15, 0.5, 0.85),
|
|
199
|
+
specular: vec3f(1),
|
|
200
|
+
shininess: 128,
|
|
201
|
+
emission: vec3f(0),
|
|
202
|
+
opacity: 1,
|
|
203
|
+
})
|
|
204
|
+
},
|
|
205
|
+
lighting,
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`specular` sets highlight color and strength; `shininess` sets highlight tightness
|
|
210
|
+
(at least 1). `emission` adds color independently of lighting.
|
|
211
|
+
|
|
212
|
+
### PBR (experimental)
|
|
213
|
+
|
|
214
|
+
PBR's API and visual results are still being refined. Its `metallic` and
|
|
215
|
+
`roughness` values range from 0 to 1. The required `environment` callback supplies
|
|
216
|
+
incoming linear RGB for a reflection direction and roughness; it can return a
|
|
217
|
+
procedural color or sample an environment texture.
|
|
218
|
+
The caller should use `roughness` to blur the reflection, choosing a filtering
|
|
219
|
+
method appropriate for their environment.
|
|
220
|
+
This approach is still experimental and somewhat awkward; it is likely to
|
|
221
|
+
change in future releases.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { createPbrAppearance, PbrMaterial } from 'luxscura/pbr'
|
|
225
|
+
|
|
226
|
+
const appearance = createPbrAppearance({
|
|
227
|
+
material: (result) => {
|
|
228
|
+
'use gpu'
|
|
229
|
+
return PbrMaterial({
|
|
230
|
+
baseColor: vec3f(0.85, 0.9, 1),
|
|
231
|
+
metallic: 0.5,
|
|
232
|
+
roughness: 0.2,
|
|
233
|
+
emission: vec3f(0),
|
|
234
|
+
opacity: 1,
|
|
235
|
+
})
|
|
236
|
+
},
|
|
237
|
+
lighting,
|
|
238
|
+
environment: (direction, roughness) => {
|
|
239
|
+
'use gpu'
|
|
240
|
+
return vec3f(0.12, 0.35, 0.65)
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## API
|
|
246
|
+
|
|
247
|
+
### Program — `createRaymarchProgram`
|
|
248
|
+
|
|
249
|
+
A program describes what to render. `createRaymarchProgram` accepts either GPU
|
|
250
|
+
callbacks directly or a factory that creates them from typed dependencies. It
|
|
251
|
+
stores a factory and compilation options in a versioned `RaymarchProgramDefinition`;
|
|
252
|
+
the direct form is wrapped in a factory automatically. Defining a program does
|
|
253
|
+
not invoke the factory or allocate GPU resources. Pass the definition to a renderer.
|
|
254
|
+
|
|
255
|
+
Using named GPU callbacks keeps the call small (their implementations follow the
|
|
256
|
+
same pattern as the quick start):
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
const program = createRaymarchProgram({
|
|
260
|
+
camera: getCamera,
|
|
261
|
+
surface: {
|
|
262
|
+
bounds: getBounds,
|
|
263
|
+
sd: distanceToSurface,
|
|
264
|
+
},
|
|
265
|
+
appearance: shadeSurface,
|
|
266
|
+
options: {
|
|
267
|
+
epsilon: 0.001,
|
|
268
|
+
maxSteps: 150,
|
|
269
|
+
stepScale: 0.8,
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
```
|
|
85
273
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
274
|
+
When callbacks need resources supplied later, pass compilation options as the
|
|
275
|
+
first argument and a factory as the second:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import type { TgpuUniform } from 'typegpu'
|
|
279
|
+
import { f32, vec3f, vec4f } from 'typegpu/data'
|
|
280
|
+
import { length } from 'typegpu/std'
|
|
281
|
+
import { AABB, createRaymarchProgram, type RaymarchCamera } from 'luxscura'
|
|
282
|
+
|
|
283
|
+
type SphereContext = {
|
|
284
|
+
camera: () => RaymarchCamera
|
|
285
|
+
radius: TgpuUniform<typeof f32>
|
|
93
286
|
}
|
|
94
|
-
|
|
287
|
+
|
|
288
|
+
const sphereProgram = createRaymarchProgram(
|
|
289
|
+
{ epsilon: 0.001 },
|
|
290
|
+
({ camera, radius }: SphereContext) => ({
|
|
291
|
+
camera,
|
|
292
|
+
surface: {
|
|
293
|
+
bounds: () => {
|
|
294
|
+
'use gpu'
|
|
295
|
+
return AABB({ min: vec3f(-radius.$), max: vec3f(radius.$) })
|
|
296
|
+
},
|
|
297
|
+
sd: (point) => {
|
|
298
|
+
'use gpu'
|
|
299
|
+
return length(point) - radius.$
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
appearance: () => {
|
|
303
|
+
'use gpu'
|
|
304
|
+
return vec4f(1, 0.2, 0.1, 1)
|
|
305
|
+
},
|
|
306
|
+
}),
|
|
307
|
+
)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Create the resources in application setup and supply them to the renderer:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
const radius = root.createUniform(f32, 0.5)
|
|
314
|
+
const renderSphere = createRaymarchRenderer({
|
|
315
|
+
root,
|
|
316
|
+
program: sphereProgram,
|
|
317
|
+
context: { camera: getCamera, radius },
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The program determines the required context type. Missing or incompatible
|
|
322
|
+
context is a type error. Direct programs and zero-argument factories do not
|
|
323
|
+
require a `context` argument.
|
|
324
|
+
|
|
325
|
+
The factory is a synchronous JavaScript setup function; the callbacks it returns
|
|
326
|
+
are GPU functions. Luxscura invokes it during renderer creation and again when
|
|
327
|
+
the program version changes, before running `prepare` and compiling the pipeline.
|
|
328
|
+
It does not run on ordinary draws. Updating `radius` with `radius.write(...)`
|
|
329
|
+
changes what the shaders read without rebuilding the program.
|
|
330
|
+
|
|
331
|
+
Context belongs to each renderer, so several renderers can share one program
|
|
332
|
+
with different resources. Luxscura retains and passes the supplied context; it
|
|
333
|
+
does not create or destroy its resources. Allocate caller-owned GPU resources
|
|
334
|
+
outside the factory so shader rebuilds can reuse them.
|
|
335
|
+
|
|
336
|
+
The property paths below follow the nesting in the call above. Callback types
|
|
337
|
+
include argument and return types; `v3f` and `v4f` are TypeGPU float vector types
|
|
338
|
+
from `typegpu/data`. Values in `options` are used when compiling the pipeline.
|
|
339
|
+
|
|
340
|
+
#### `camera`
|
|
341
|
+
|
|
342
|
+
| Type | Default |
|
|
343
|
+
| --- | --- |
|
|
344
|
+
| `() => RaymarchCamera` | Required |
|
|
345
|
+
|
|
346
|
+
Returns the current `RaymarchCamera`: world-space position and view-projection matrix.
|
|
347
|
+
|
|
348
|
+
#### `surface`
|
|
349
|
+
|
|
350
|
+
| Type | Default |
|
|
351
|
+
| --- | --- |
|
|
352
|
+
| `RaymarchSurface` | Required |
|
|
353
|
+
|
|
354
|
+
Supplies geometry, bounds, and optional per-fragment initialization and visibility.
|
|
355
|
+
|
|
356
|
+
#### `surface.bounds`
|
|
357
|
+
|
|
358
|
+
| Type | Default |
|
|
359
|
+
| --- | --- |
|
|
360
|
+
| `(instanceIndex: number) => AABB` | Required |
|
|
361
|
+
|
|
362
|
+
Returns the instance's world-space `AABB`, which determines the area rasterized and
|
|
363
|
+
normally limits marching.
|
|
364
|
+
|
|
365
|
+
#### `surface.sd`
|
|
366
|
+
|
|
367
|
+
| Type | Default |
|
|
368
|
+
| --- | --- |
|
|
369
|
+
| `(point: v3f, instanceIndex: number, bounds: AABB) => number` | Required |
|
|
370
|
+
|
|
371
|
+
Returns the signed distance to the surface from a world-space point. Also used to
|
|
372
|
+
estimate normals.
|
|
373
|
+
|
|
374
|
+
#### `surface.init`
|
|
375
|
+
|
|
376
|
+
| Type | Default |
|
|
377
|
+
| --- | --- |
|
|
378
|
+
| `(instanceIndex: number) => void` | None |
|
|
379
|
+
|
|
380
|
+
Initializes per-fragment state before visibility testing and marching.
|
|
381
|
+
If omitted, no initialization callback runs.
|
|
382
|
+
|
|
383
|
+
#### `surface.isRayVisible`
|
|
384
|
+
|
|
385
|
+
| Type | Default |
|
|
386
|
+
| --- | --- |
|
|
387
|
+
| `(point: v3f, instanceIndex: number, bounds: AABB) => boolean` | None |
|
|
388
|
+
|
|
389
|
+
Returns false to discard a fragment before marching. The point is on the rasterized
|
|
390
|
+
bounding box.
|
|
391
|
+
If omitted, all rasterized fragments are eligible for marching.
|
|
392
|
+
|
|
393
|
+
#### `appearance`
|
|
394
|
+
|
|
395
|
+
| Type | Default |
|
|
396
|
+
| --- | --- |
|
|
397
|
+
| `(result: RaymarchResult) => v4f` | Required |
|
|
398
|
+
|
|
399
|
+
Returns linear RGBA after marching. See [Appearances](#appearances).
|
|
400
|
+
|
|
401
|
+
#### `options`
|
|
402
|
+
|
|
403
|
+
| Type | Default |
|
|
404
|
+
| --- | --- |
|
|
405
|
+
| `RaymarchProgramOptions` | Required |
|
|
406
|
+
|
|
407
|
+
Controls marching and depth behavior.
|
|
408
|
+
|
|
409
|
+
#### `options.label`
|
|
410
|
+
|
|
411
|
+
| Type | Default |
|
|
412
|
+
| --- | --- |
|
|
413
|
+
| `string` | None |
|
|
414
|
+
|
|
415
|
+
Diagnostic metadata for your application; unused by the renderer.
|
|
416
|
+
If omitted, the program has no label.
|
|
417
|
+
|
|
418
|
+
#### `options.epsilon`
|
|
419
|
+
|
|
420
|
+
| Type | Default |
|
|
421
|
+
| --- | --- |
|
|
422
|
+
| `number` | Required |
|
|
423
|
+
|
|
424
|
+
Surface hit tolerance in world units. Smaller values give a tighter tolerance but may
|
|
425
|
+
require more steps.
|
|
426
|
+
|
|
427
|
+
#### `options.epsilonNormal`
|
|
428
|
+
|
|
429
|
+
| Type | Default |
|
|
430
|
+
| --- | --- |
|
|
431
|
+
| `number` | None |
|
|
432
|
+
|
|
433
|
+
Offset in world units used to sample the distance function for surface normals. Adjust
|
|
434
|
+
separately when normal detail needs a different scale.
|
|
435
|
+
If omitted, normal sampling uses `options.epsilon`.
|
|
436
|
+
|
|
437
|
+
#### `options.maxSteps`
|
|
438
|
+
|
|
439
|
+
| Type | Default |
|
|
440
|
+
| --- | --- |
|
|
441
|
+
| `number` | `100` |
|
|
442
|
+
|
|
443
|
+
Maximum marching iterations per ray. Marching stops without a hit if this limit is reached.
|
|
444
|
+
|
|
445
|
+
#### `options.stepScale`
|
|
446
|
+
|
|
447
|
+
| Type | Default |
|
|
448
|
+
| --- | --- |
|
|
449
|
+
| `number` | `1` |
|
|
450
|
+
|
|
451
|
+
Multiplies each distance-based step. A value between 0 and 1 may be necessary when
|
|
452
|
+
your distance function overestimates the distance to the surface, for example after
|
|
453
|
+
coordinate-space warping or when using an approximate SDF. Smaller steps help avoid
|
|
454
|
+
stepping past the surface, at the cost of more marching iterations.
|
|
455
|
+
|
|
456
|
+
#### `options.maxDistance`
|
|
457
|
+
|
|
458
|
+
| Type | Default |
|
|
459
|
+
| --- | --- |
|
|
460
|
+
| `number` | None |
|
|
461
|
+
|
|
462
|
+
If present, applies an additional maximum marching distance from the bounding-box
|
|
463
|
+
entry point, in world units. If absent, rays stop marching at the bounds. With
|
|
464
|
+
`options.marchBeyondBounds: true`, this value is required and replaces the bounds
|
|
465
|
+
exit limit.
|
|
466
|
+
|
|
467
|
+
#### `options.marchBeyondBounds`
|
|
468
|
+
|
|
469
|
+
| Type | Default |
|
|
470
|
+
| --- | --- |
|
|
471
|
+
| `boolean` | `false` |
|
|
472
|
+
|
|
473
|
+
Allows marching past the box exit; requires an explicit `options.maxDistance` when true.
|
|
474
|
+
This extends the march along existing rays; the bounding box still controls which pixels
|
|
475
|
+
are rasterized.
|
|
476
|
+
|
|
477
|
+
#### `options.depthWriteEnabled`
|
|
478
|
+
|
|
479
|
+
| Type | Default |
|
|
480
|
+
| --- | --- |
|
|
481
|
+
| `boolean` | `true` |
|
|
482
|
+
|
|
483
|
+
Writes surface hit depth, allowing other pipelines to share depth with raymarched
|
|
484
|
+
geometry. Disable when your rendering setup needs depth testing without writes.
|
|
485
|
+
|
|
486
|
+
#### `options.depthCompare`
|
|
487
|
+
|
|
488
|
+
| Type | Default |
|
|
489
|
+
| --- | --- |
|
|
490
|
+
| `GPUCompareFunction` | `'less'` |
|
|
491
|
+
|
|
492
|
+
WebGPU depth comparison used to test surface hits against the depth attachment. Match
|
|
493
|
+
your application's depth convention.
|
|
494
|
+
|
|
495
|
+
### Renderer — `createRaymarchRenderer`
|
|
496
|
+
|
|
497
|
+
A renderer compiles a program into a GPU pipeline and returns a draw function.
|
|
498
|
+
Its configuration connects that program to your application's GPU resources.
|
|
499
|
+
|
|
500
|
+
Pass the `program` from above here. This example specifies the render target settings;
|
|
501
|
+
the preparation hooks simply return their inputs to show where they belong:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
const render = createRaymarchRenderer({
|
|
505
|
+
root,
|
|
506
|
+
program,
|
|
507
|
+
renderTarget: {
|
|
508
|
+
colorFormat: navigator.gpu.getPreferredCanvasFormat(),
|
|
509
|
+
depthStencilFormat: 'depth24plus',
|
|
510
|
+
sampleCount: 1,
|
|
511
|
+
},
|
|
512
|
+
prepare: (body) => body, // Optionally adapt the program callbacks.
|
|
513
|
+
preparePipeline: (pipeline) => pipeline, // Optionally attach performance monitoring.
|
|
514
|
+
})
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### `root`
|
|
518
|
+
|
|
519
|
+
| Type | Default |
|
|
520
|
+
| --- | --- |
|
|
521
|
+
| `TgpuRoot` | Required |
|
|
522
|
+
|
|
523
|
+
Your application's TypeGPU root, used to create the pipeline.
|
|
524
|
+
|
|
525
|
+
#### `program`
|
|
526
|
+
|
|
527
|
+
| Type | Default |
|
|
528
|
+
| --- | --- |
|
|
529
|
+
| `Readonly<RaymarchProgramDefinition<TContext>>` | Required |
|
|
530
|
+
|
|
531
|
+
The definition returned by `createRaymarchProgram`.
|
|
532
|
+
|
|
533
|
+
#### `context`
|
|
534
|
+
|
|
535
|
+
| Type | Default |
|
|
536
|
+
| --- | --- |
|
|
537
|
+
| `TContext` (inferred from `program`) | Required unless the type accepts `undefined` |
|
|
538
|
+
|
|
539
|
+
Setup-time dependencies passed to the program factory. The same context is used
|
|
540
|
+
when this renderer rebuilds after a program update. Changing the context object
|
|
541
|
+
itself does not trigger recompilation; use buffers or uniforms for changing GPU
|
|
542
|
+
data. If an edit adds required dependencies, recreate the renderer with a context
|
|
543
|
+
that supplies them.
|
|
544
|
+
|
|
545
|
+
#### `renderTarget`
|
|
546
|
+
|
|
547
|
+
| Type | Default |
|
|
548
|
+
| --- | --- |
|
|
549
|
+
| `Readonly<RaymarchRenderTargetOptions>` | None |
|
|
550
|
+
|
|
551
|
+
Attachment formats and sample count used for pipeline compatibility. They must match the
|
|
552
|
+
attachments used when drawing.
|
|
553
|
+
If omitted, the pipeline uses the preferred canvas color format, `'depth24plus'`,
|
|
554
|
+
and one sample per pixel.
|
|
555
|
+
|
|
556
|
+
#### `renderTarget.colorFormat`
|
|
557
|
+
|
|
558
|
+
| Type | Default |
|
|
559
|
+
| --- | --- |
|
|
560
|
+
| `GPUTextureFormat` | `navigator.gpu.getPreferredCanvasFormat()` |
|
|
561
|
+
|
|
562
|
+
Color attachment format. Set explicitly for targets using another format.
|
|
563
|
+
|
|
564
|
+
#### `renderTarget.depthStencilFormat`
|
|
565
|
+
|
|
566
|
+
| Type | Default |
|
|
567
|
+
| --- | --- |
|
|
568
|
+
| ``Extract<GPUTextureFormat, `depth${string}`>`` | `'depth24plus'` |
|
|
569
|
+
|
|
570
|
+
Depth/stencil attachment format. A depth attachment is always required.
|
|
571
|
+
|
|
572
|
+
#### `renderTarget.sampleCount`
|
|
573
|
+
|
|
574
|
+
| Type | Default |
|
|
575
|
+
| --- | --- |
|
|
576
|
+
| `1 \| 4` | `1` |
|
|
577
|
+
|
|
578
|
+
Samples per pixel: `1` or `4`. Use `4` with matching multisampled attachments and a
|
|
579
|
+
color resolve target.
|
|
580
|
+
|
|
581
|
+
#### `prepare`
|
|
582
|
+
|
|
583
|
+
| Type | Default |
|
|
584
|
+
| --- | --- |
|
|
585
|
+
| `(program: RaymarchProgram) => RaymarchProgram` | None |
|
|
586
|
+
|
|
587
|
+
Transforms the camera, surface, or appearance callbacks after the factory runs
|
|
588
|
+
and before compilation. Return the resulting `RaymarchProgram`, for example to
|
|
589
|
+
decorate its appearance or adapt its geometry.
|
|
590
|
+
|
|
591
|
+
#### `preparePipeline`
|
|
592
|
+
|
|
593
|
+
| Type | Default |
|
|
594
|
+
| --- | --- |
|
|
595
|
+
| `(pipeline: TgpuRenderPipeline<{ color: Vec4f }>) => TgpuRenderPipeline<{ color: Vec4f }>` | None |
|
|
596
|
+
|
|
597
|
+
Transforms the created TypeGPU pipeline before it is captured for drawing. Return the
|
|
598
|
+
resulting pipeline. For example, attach GPU performance monitoring with
|
|
599
|
+
`pipeline.withPerformanceCallback(...)`. This requires enabling the `timestamp-query`
|
|
600
|
+
device feature; see TypeGPU's [Timing Your Pipelines guide](https://docs.swmansion.com/TypeGPU/advanced/timestamp-queries/).
|
|
601
|
+
|
|
602
|
+
`TgpuRenderPipeline` comes from `typegpu`; `Vec4f` is the vector schema type from
|
|
603
|
+
`typegpu/data`. Both preparation hooks run during initial creation and again when
|
|
604
|
+
a program version change triggers recompilation. They do not run on every frame.
|
|
605
|
+
|
|
606
|
+
### Draw — `render(options)`
|
|
607
|
+
|
|
608
|
+
Call the returned function whenever your application needs to submit a raymarch
|
|
609
|
+
draw. It uses TypeGPU attachment descriptors, so you can configure clearing,
|
|
610
|
+
loading, and storage to fit the surrounding rendering passes.
|
|
611
|
+
|
|
612
|
+
Call `render` from above in your application's render loop, supplying attachments
|
|
613
|
+
that match its `renderTarget` configuration:
|
|
614
|
+
|
|
615
|
+
```ts
|
|
616
|
+
render({
|
|
617
|
+
colorAttachment: { view: colorTexture }, // Uses the preferred canvas format.
|
|
618
|
+
depthStencilAttachment: { view: depthTexture }, // depth24plus.
|
|
619
|
+
instances: 5,
|
|
620
|
+
})
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
#### `colorAttachment`
|
|
624
|
+
|
|
625
|
+
| Type | Default |
|
|
626
|
+
| --- | --- |
|
|
627
|
+
| `ColorAttachment` | Required |
|
|
628
|
+
|
|
629
|
+
TypeGPU color attachment descriptor: the target view and its load/store settings.
|
|
630
|
+
|
|
631
|
+
#### `depthStencilAttachment`
|
|
632
|
+
|
|
633
|
+
| Type | Default |
|
|
634
|
+
| --- | --- |
|
|
635
|
+
| `DepthStencilAttachment` | Required |
|
|
636
|
+
|
|
637
|
+
TypeGPU depth/stencil attachment descriptor: the depth view and its load/store settings.
|
|
638
|
+
Required because Luxscura is designed to work alongside other pipelines in a TypeGPU
|
|
639
|
+
application. Sharing a depth buffer lets raymarched surfaces and other rendered objects
|
|
640
|
+
occlude one another according to their depth.
|
|
641
|
+
|
|
642
|
+
#### `instances`
|
|
643
|
+
|
|
644
|
+
| Type | Default |
|
|
645
|
+
| --- | --- |
|
|
646
|
+
| `number` | `1` |
|
|
647
|
+
|
|
648
|
+
Number of bounds instances to draw. The instance index lets your callbacks look up
|
|
649
|
+
per-instance values in a GPU buffer to change how the program renders that instance.
|
|
650
|
+
|
|
651
|
+
## Hot module reloading
|
|
652
|
+
|
|
653
|
+
If shader edits cause a full page reload or do not update the renderer, enable
|
|
654
|
+
Luxscura's hot reload support. With Vite, pass `import.meta.hot` as the final
|
|
655
|
+
argument to `createRaymarchProgram` and call `import.meta.hot?.accept()`. It is
|
|
656
|
+
the second argument for the direct form:
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
const program = createRaymarchProgram({
|
|
660
|
+
options: { epsilon: 0.001 },
|
|
661
|
+
camera,
|
|
662
|
+
surface,
|
|
663
|
+
appearance,
|
|
664
|
+
}, import.meta.hot)
|
|
665
|
+
|
|
666
|
+
import.meta.hot?.accept()
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
For the factory form, it is the third argument:
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
const program = createRaymarchProgram(options, createProgram, import.meta.hot)
|
|
673
|
+
|
|
674
|
+
import.meta.hot?.accept()
|
|
95
675
|
```
|
|
96
676
|
|
|
97
|
-
|
|
677
|
+
On a module update, Luxscura preserves the program definition and increments its
|
|
678
|
+
version. Each existing renderer invokes the updated factory with its original
|
|
679
|
+
context and rebuilds its pipeline on its next draw, using the updated options.
|
|
680
|
+
Both preparation hooks run again; caller-owned buffers and uniforms remain in
|
|
681
|
+
place. Changing the required context shape may require recreating the renderer
|
|
682
|
+
or reloading the application.
|
|
98
683
|
|
|
99
|
-
|
|
684
|
+
Currently, only one HMR-backed program can be defined per module.
|