@multiplekex/shallot 0.1.12 → 0.2.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.
Files changed (62) hide show
  1. package/package.json +3 -4
  2. package/src/core/builder.ts +71 -32
  3. package/src/core/component.ts +25 -11
  4. package/src/core/index.ts +14 -13
  5. package/src/core/math.ts +135 -0
  6. package/src/core/runtime.ts +0 -1
  7. package/src/core/state.ts +9 -68
  8. package/src/core/xml.ts +381 -265
  9. package/src/editor/format.ts +5 -0
  10. package/src/editor/index.ts +101 -0
  11. package/src/extras/arrows/index.ts +28 -69
  12. package/src/extras/gradient/index.ts +36 -52
  13. package/src/extras/lines/index.ts +51 -122
  14. package/src/extras/orbit/index.ts +40 -15
  15. package/src/extras/text/font.ts +546 -0
  16. package/src/extras/text/index.ts +158 -204
  17. package/src/extras/text/sdf.ts +429 -0
  18. package/src/standard/activity/index.ts +172 -0
  19. package/src/standard/compute/graph.ts +23 -23
  20. package/src/standard/compute/index.ts +76 -61
  21. package/src/standard/defaults.ts +8 -5
  22. package/src/standard/index.ts +1 -0
  23. package/src/standard/input/index.ts +30 -19
  24. package/src/standard/loading/index.ts +18 -13
  25. package/src/standard/render/bvh/blas.ts +752 -0
  26. package/src/standard/render/bvh/radix.ts +476 -0
  27. package/src/standard/render/bvh/structs.ts +167 -0
  28. package/src/standard/render/bvh/tlas.ts +886 -0
  29. package/src/standard/render/bvh/traverse.ts +467 -0
  30. package/src/standard/render/camera.ts +302 -27
  31. package/src/standard/render/data.ts +93 -0
  32. package/src/standard/render/depth.ts +117 -0
  33. package/src/standard/render/forward/index.ts +259 -0
  34. package/src/standard/render/forward/raster.ts +228 -0
  35. package/src/standard/render/index.ts +443 -70
  36. package/src/standard/render/indirect.ts +40 -0
  37. package/src/standard/render/instance.ts +214 -0
  38. package/src/standard/render/intersection.ts +72 -0
  39. package/src/standard/render/light.ts +16 -16
  40. package/src/standard/render/mesh/index.ts +67 -75
  41. package/src/standard/render/mesh/unified.ts +96 -0
  42. package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
  43. package/src/standard/render/pass.ts +10 -4
  44. package/src/standard/render/postprocess.ts +142 -64
  45. package/src/standard/render/ray.ts +61 -0
  46. package/src/standard/render/scene.ts +38 -164
  47. package/src/standard/render/shaders.ts +484 -0
  48. package/src/standard/render/surface/compile.ts +3 -10
  49. package/src/standard/render/surface/index.ts +60 -30
  50. package/src/standard/render/surface/noise.ts +45 -0
  51. package/src/standard/render/surface/structs.ts +60 -19
  52. package/src/standard/render/surface/wgsl.ts +573 -0
  53. package/src/standard/render/triangle.ts +84 -0
  54. package/src/standard/transforms/index.ts +4 -6
  55. package/src/standard/tween/index.ts +10 -1
  56. package/src/standard/tween/sequence.ts +24 -16
  57. package/src/standard/tween/tween.ts +67 -16
  58. package/src/core/types.ts +0 -37
  59. package/src/standard/compute/inspect.ts +0 -201
  60. package/src/standard/compute/pass.ts +0 -23
  61. package/src/standard/compute/timing.ts +0 -139
  62. package/src/standard/render/forward.ts +0 -273
@@ -0,0 +1,96 @@
1
+ import { getMesh } from ".";
2
+
3
+ export interface ShapeMeta {
4
+ vertexOffset: number;
5
+ indexOffset: number;
6
+ triCount: number;
7
+ _pad: number;
8
+ }
9
+
10
+ export interface ShapeAtlas {
11
+ vertices: GPUBuffer;
12
+ indices: GPUBuffer;
13
+ meta: GPUBuffer;
14
+ shapeCount: number;
15
+ maxTriangles: number;
16
+ }
17
+
18
+ const MAX_SHAPES = 16;
19
+
20
+ export function createShapeAtlas(device: GPUDevice): ShapeAtlas {
21
+ const allVertices: number[] = [];
22
+ const allIndices: number[] = [];
23
+ const shapeMetas: ShapeMeta[] = [];
24
+
25
+ let vertexOffset = 0;
26
+ let indexOffset = 0;
27
+ let maxTriangles = 0;
28
+
29
+ for (let shapeId = 0; shapeId < MAX_SHAPES; shapeId++) {
30
+ const mesh = getMesh(shapeId);
31
+ if (!mesh) {
32
+ shapeMetas.push({ vertexOffset: 0, indexOffset: 0, triCount: 0, _pad: 0 });
33
+ continue;
34
+ }
35
+
36
+ const triCount = mesh.indexCount / 3;
37
+ shapeMetas.push({
38
+ vertexOffset,
39
+ indexOffset,
40
+ triCount,
41
+ _pad: 0,
42
+ });
43
+
44
+ for (let i = 0; i < mesh.vertices.length; i++) {
45
+ allVertices.push(mesh.vertices[i]);
46
+ }
47
+
48
+ for (let i = 0; i < mesh.indices.length; i++) {
49
+ allIndices.push(mesh.indices[i]);
50
+ }
51
+
52
+ vertexOffset += mesh.vertices.length;
53
+ indexOffset += mesh.indices.length;
54
+ maxTriangles += triCount;
55
+ }
56
+
57
+ const verticesData = new Float32Array(allVertices);
58
+ const indicesData = new Uint32Array(allIndices);
59
+ const metaData = new Uint32Array(MAX_SHAPES * 4);
60
+
61
+ for (let i = 0; i < shapeMetas.length; i++) {
62
+ metaData[i * 4] = shapeMetas[i].vertexOffset;
63
+ metaData[i * 4 + 1] = shapeMetas[i].indexOffset;
64
+ metaData[i * 4 + 2] = shapeMetas[i].triCount;
65
+ metaData[i * 4 + 3] = 0;
66
+ }
67
+
68
+ const vertices = device.createBuffer({
69
+ label: "unified-vertices",
70
+ size: Math.max(verticesData.byteLength, 16),
71
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
72
+ });
73
+ device.queue.writeBuffer(vertices, 0, verticesData);
74
+
75
+ const indices = device.createBuffer({
76
+ label: "unified-indices",
77
+ size: Math.max(indicesData.byteLength, 16),
78
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
79
+ });
80
+ device.queue.writeBuffer(indices, 0, indicesData);
81
+
82
+ const meta = device.createBuffer({
83
+ label: "unified-meta",
84
+ size: metaData.byteLength,
85
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
86
+ });
87
+ device.queue.writeBuffer(meta, 0, metaData);
88
+
89
+ return {
90
+ vertices,
91
+ indices,
92
+ meta,
93
+ shapeCount: shapeMetas.filter((m) => m.triCount > 0).length,
94
+ maxTriangles,
95
+ };
96
+ }
@@ -1,30 +1,29 @@
1
1
  import type { State } from "../../core";
2
2
  import type { ComputeNode, ExecutionContext } from "../compute";
3
- import { MASK_FORMAT } from "./scene";
3
+ import { COLOR_FORMAT, MASK_FORMAT } from "./scene";
4
4
  import { Pass, getDrawsByPass, type DrawContext, type SharedPassContext } from "./pass";
5
5
 
6
6
  export { MASK_FORMAT };
7
7
 
8
- export interface TransparentNodeConfig {
8
+ export interface OverlayNodeConfig {
9
9
  state: State;
10
10
  }
11
11
 
12
- export function createTransparentNode(config: TransparentNodeConfig): ComputeNode {
12
+ export function createOverlayNode(config: OverlayNodeConfig): ComputeNode {
13
13
  return {
14
- id: "transparent",
15
- pass: Pass.Transparent,
14
+ id: "overlay",
16
15
  inputs: [{ id: "depth", access: "read" }],
17
16
  outputs: [
18
- { id: "scene", access: "write" },
17
+ { id: "color", access: "write" },
19
18
  { id: "mask", access: "write" },
20
19
  ],
21
20
 
22
21
  execute(ctx: ExecutionContext) {
23
22
  const { device, encoder, format, context } = ctx;
24
- const targetView = ctx.getTextureView("scene") ?? ctx.canvasView;
23
+ const targetView = ctx.getTextureView("color") ?? ctx.canvasView;
25
24
  const depthView = ctx.getTextureView("depth")!;
26
25
  const maskView = ctx.getTextureView("mask")!;
27
- const entityIdView = ctx.getTextureView("entityId")!;
26
+ const eidView = ctx.getTextureView("eid")!;
28
27
 
29
28
  const drawCtx: DrawContext = {
30
29
  device,
@@ -34,17 +33,17 @@ export function createTransparentNode(config: TransparentNodeConfig): ComputeNod
34
33
  height: context.canvas.height,
35
34
  sceneView: targetView,
36
35
  depthView,
37
- entityIdView,
36
+ entityIdView: eidView,
38
37
  maskView,
39
38
  canvasView: ctx.canvasView,
40
39
  };
41
40
 
42
- const beforeTransparents = getDrawsByPass(config.state, Pass.BeforeTransparent);
43
- for (const draw of beforeTransparents) {
41
+ const beforeOverlays = getDrawsByPass(config.state, Pass.BeforeOverlay);
42
+ for (const draw of beforeOverlays) {
44
43
  draw.execute(drawCtx);
45
44
  }
46
45
 
47
- const draws = getDrawsByPass(config.state, Pass.Transparent);
46
+ const draws = getDrawsByPass(config.state, Pass.Overlay);
48
47
  if (draws.length > 0) {
49
48
  const pass = encoder.beginRenderPass({
50
49
  colorAttachments: [
@@ -69,7 +68,7 @@ export function createTransparentNode(config: TransparentNodeConfig): ComputeNod
69
68
 
70
69
  const sharedCtx: SharedPassContext = {
71
70
  device,
72
- format,
71
+ format: COLOR_FORMAT,
73
72
  maskFormat: MASK_FORMAT,
74
73
  };
75
74
 
@@ -82,8 +81,8 @@ export function createTransparentNode(config: TransparentNodeConfig): ComputeNod
82
81
  pass.end();
83
82
  }
84
83
 
85
- const afterTransparents = getDrawsByPass(config.state, Pass.AfterTransparent);
86
- for (const draw of afterTransparents) {
84
+ const afterOverlays = getDrawsByPass(config.state, Pass.AfterOverlay);
85
+ for (const draw of afterOverlays) {
87
86
  draw.execute(drawCtx);
88
87
  }
89
88
  },
@@ -1,7 +1,13 @@
1
1
  import { resource, type State } from "../../core";
2
- import { Pass } from "../compute/pass";
3
2
 
4
- export { Pass };
3
+ export enum Pass {
4
+ BeforeOverlay,
5
+ Overlay,
6
+ AfterOverlay,
7
+ BeforePost,
8
+ Post,
9
+ AfterPost,
10
+ }
5
11
 
6
12
  export interface DrawContext {
7
13
  readonly device: GPUDevice;
@@ -34,11 +40,11 @@ export interface Draw {
34
40
  draw?(pass: GPURenderPassEncoder, ctx: SharedPassContext): void;
35
41
  }
36
42
 
37
- export interface DrawState {
43
+ export interface Draws {
38
44
  draws: Map<string, Draw>;
39
45
  }
40
46
 
41
- export const Draws = resource<DrawState>("draws");
47
+ export const Draws = resource<Draws>("draws");
42
48
 
43
49
  export function registerDraw(state: State, draw: Draw): void {
44
50
  const draws = Draws.from(state);
@@ -16,7 +16,11 @@ struct Uniforms {
16
16
  texelSizeX: f32,
17
17
  texelSizeY: f32,
18
18
  flags: u32,
19
- _pad: u32,
19
+ bloomIntensity: f32,
20
+ bloomThreshold: f32,
21
+ bloomRadius: f32,
22
+ quantizeBands: f32,
23
+ _pad: f32,
20
24
  }
21
25
 
22
26
  @group(0) @binding(0) var inputTexture: texture_2d<f32>;
@@ -27,6 +31,8 @@ struct Uniforms {
27
31
  const FLAG_TONEMAP: u32 = 1u;
28
32
  const FLAG_FXAA: u32 = 2u;
29
33
  const FLAG_VIGNETTE: u32 = 4u;
34
+ const FLAG_BLOOM: u32 = 8u;
35
+ const FLAG_QUANTIZE: u32 = 16u;
30
36
 
31
37
  @vertex
32
38
  fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
@@ -112,6 +118,43 @@ fn applyVignette(color: vec3f, uv: vec2f) -> vec3f {
112
118
  return color * vignette;
113
119
  }
114
120
 
121
+ fn sampleBloom(uv: vec2f) -> vec3f {
122
+ let texelSize = vec2f(uniforms.texelSizeX, uniforms.texelSizeY);
123
+ // Normalize to reference height (1080p) for resolution-independent bloom
124
+ let height = 1.0 / uniforms.texelSizeY;
125
+ let resolutionScale = height / 1080.0;
126
+ let spread = uniforms.bloomRadius * 4.0 * resolutionScale;
127
+ let threshold = uniforms.bloomThreshold;
128
+ let knee = 0.15;
129
+
130
+ var bloom = vec3f(0.0);
131
+ var totalWeight = 0.0;
132
+
133
+ // 9x9 kernel with gaussian weights
134
+ for (var y = -4; y <= 4; y++) {
135
+ for (var x = -4; x <= 4; x++) {
136
+ let offset = vec2f(f32(x), f32(y)) * texelSize * spread;
137
+ let dist2 = f32(x * x + y * y);
138
+ let weight = exp(-dist2 * 0.125);
139
+
140
+ let sampleColor = textureSample(inputTexture, inputSampler, uv + offset).rgb;
141
+ let brightness = max(max(sampleColor.r, sampleColor.g), sampleColor.b);
142
+ let soft = clamp((brightness - threshold + knee) / (2.0 * knee), 0.0, 1.0);
143
+ let contribution = soft * soft;
144
+
145
+ bloom += sampleColor * contribution * weight;
146
+ totalWeight += weight;
147
+ }
148
+ }
149
+
150
+ return bloom / totalWeight;
151
+ }
152
+
153
+ fn applyQuantize(color: vec3f) -> vec3f {
154
+ let bands = uniforms.quantizeBands;
155
+ return floor(color * bands + 0.5) / bands;
156
+ }
157
+
115
158
  @fragment
116
159
  fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
117
160
  var color = textureSample(inputTexture, inputSampler, input.uv).rgb;
@@ -122,6 +165,16 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
122
165
  color = select(fxaaColor, color, maskValue >= 0.5);
123
166
  }
124
167
 
168
+ // Bloom applied before tonemapping
169
+ if (uniforms.flags & FLAG_BLOOM) != 0u {
170
+ let bloom = sampleBloom(input.uv);
171
+ color += bloom * uniforms.bloomIntensity;
172
+ }
173
+
174
+ if (uniforms.flags & FLAG_QUANTIZE) != 0u {
175
+ color = applyQuantize(color);
176
+ }
177
+
125
178
  if (uniforms.flags & FLAG_TONEMAP) != 0u {
126
179
  color = aces(color * uniforms.exposure);
127
180
  }
@@ -169,6 +222,8 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
169
222
  const FLAG_TONEMAP = 1;
170
223
  const FLAG_FXAA = 2;
171
224
  const FLAG_VIGNETTE = 4;
225
+ const FLAG_BLOOM = 8;
226
+ const FLAG_QUANTIZE = 16;
172
227
 
173
228
  export interface PostProcessUniforms {
174
229
  tonemap: boolean;
@@ -177,63 +232,99 @@ export interface PostProcessUniforms {
177
232
  vignetteStrength: number;
178
233
  vignetteInner: number;
179
234
  vignetteOuter: number;
235
+ bloomIntensity: number;
236
+ bloomThreshold: number;
237
+ bloomRadius: number;
238
+ quantize: number;
180
239
  }
181
240
 
182
241
  export interface PostProcessConfig {
183
242
  state: State;
184
243
  uniforms: PostProcessUniforms;
244
+ getRenderSize?: () => { width: number; height: number };
185
245
  }
186
246
 
187
247
  export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
188
248
  let pipeline: GPURenderPipeline | null = null;
189
249
  let blitPipeline: GPURenderPipeline | null = null;
190
250
  let uniformBuffer: GPUBuffer | null = null;
191
- let sampler: GPUSampler | null = null;
251
+ let linearSampler: GPUSampler | null = null;
252
+ let nearestSampler: GPUSampler | null = null;
192
253
 
193
254
  return {
194
255
  id: "postprocess",
195
- pass: Pass.Post,
196
256
  inputs: [
197
- { id: "scene", access: "read" },
257
+ { id: "color", access: "read" },
198
258
  { id: "mask", access: "read" },
199
259
  { id: "pingA", access: "read" },
200
260
  { id: "pingB", access: "read" },
201
261
  ],
202
262
  outputs: [{ id: "framebuffer", access: "write" }],
203
263
 
264
+ async prepare(device: GPUDevice) {
265
+ const format: GPUTextureFormat = "bgra8unorm";
266
+
267
+ const [mainModule, blitModule] = await Promise.all([
268
+ device.createShaderModule({ code: shader }),
269
+ device.createShaderModule({ code: blitShader }),
270
+ ]);
271
+
272
+ [pipeline, blitPipeline] = await Promise.all([
273
+ device.createRenderPipelineAsync({
274
+ layout: "auto",
275
+ vertex: { module: mainModule, entryPoint: "vertexMain" },
276
+ fragment: {
277
+ module: mainModule,
278
+ entryPoint: "fragmentMain",
279
+ targets: [{ format }],
280
+ },
281
+ primitive: { topology: "triangle-list" },
282
+ }),
283
+ device.createRenderPipelineAsync({
284
+ layout: "auto",
285
+ vertex: { module: blitModule, entryPoint: "vertexMain" },
286
+ fragment: {
287
+ module: blitModule,
288
+ entryPoint: "fragmentMain",
289
+ targets: [{ format }],
290
+ },
291
+ primitive: { topology: "triangle-list" },
292
+ }),
293
+ ]);
294
+
295
+ uniformBuffer = device.createBuffer({
296
+ size: 48,
297
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
298
+ });
299
+
300
+ linearSampler = device.createSampler({
301
+ magFilter: "linear",
302
+ minFilter: "linear",
303
+ });
304
+
305
+ nearestSampler = device.createSampler({
306
+ magFilter: "nearest",
307
+ minFilter: "nearest",
308
+ });
309
+ },
310
+
204
311
  execute(ctx: ExecutionContext) {
205
312
  const { device, encoder, canvasView, format, context } = ctx;
206
313
  const width = context.canvas.width;
207
314
  const height = context.canvas.height;
208
- const sceneView = ctx.getTextureView("scene")!;
315
+ const colorView = ctx.getTextureView("color")!;
209
316
  const maskView = ctx.getTextureView("mask")!;
210
317
  const depthView = ctx.getTextureView("depth")!;
211
- const entityIdView = ctx.getTextureView("entityId")!;
318
+ const eidView = ctx.getTextureView("eid")!;
212
319
  const pingAView = ctx.getTextureView("pingA")!;
213
320
  const pingBView = ctx.getTextureView("pingB")!;
214
321
 
215
- if (!sampler) {
216
- sampler = device.createSampler({
217
- magFilter: "linear",
218
- minFilter: "linear",
219
- });
220
- }
221
-
222
- if (!blitPipeline) {
223
- const module = device.createShaderModule({ code: blitShader });
224
- blitPipeline = device.createRenderPipeline({
225
- layout: "auto",
226
- vertex: { module, entryPoint: "vertexMain" },
227
- fragment: {
228
- module,
229
- entryPoint: "fragmentMain",
230
- targets: [{ format }],
231
- },
232
- primitive: { topology: "triangle-list" },
233
- });
234
- }
322
+ const renderSize = config.getRenderSize?.();
323
+ const isUpscaling =
324
+ renderSize && (renderSize.width !== width || renderSize.height !== height);
325
+ const sampler = isUpscaling ? nearestSampler! : linearSampler!;
235
326
 
236
- let currentInput = sceneView;
327
+ let currentInput = colorView;
237
328
  let currentOutput = pingAView;
238
329
  let pingPong = false;
239
330
 
@@ -245,9 +336,9 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
245
336
  format,
246
337
  width,
247
338
  height,
248
- sceneView,
339
+ sceneView: colorView,
249
340
  depthView,
250
- entityIdView,
341
+ entityIdView: eidView,
251
342
  maskView,
252
343
  canvasView,
253
344
  inputView: currentInput,
@@ -264,7 +355,9 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
264
355
  const hasBuiltinEffects =
265
356
  config.uniforms.tonemap ||
266
357
  config.uniforms.fxaa ||
267
- config.uniforms.vignetteStrength > 0;
358
+ config.uniforms.vignetteStrength > 0 ||
359
+ config.uniforms.bloomIntensity > 0 ||
360
+ config.uniforms.quantize > 0;
268
361
 
269
362
  if (hasBuiltinEffects || postProcessContributors.length > 0) {
270
363
  for (const contributor of postProcessContributors) {
@@ -274,9 +367,9 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
274
367
  format,
275
368
  width,
276
369
  height,
277
- sceneView,
370
+ sceneView: colorView,
278
371
  depthView,
279
- entityIdView,
372
+ entityIdView: eidView,
280
373
  maskView,
281
374
  canvasView,
282
375
  inputView: currentInput,
@@ -290,33 +383,14 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
290
383
  }
291
384
 
292
385
  if (hasBuiltinEffects) {
293
- if (!pipeline) {
294
- const module = device.createShaderModule({ code: shader });
295
- pipeline = device.createRenderPipeline({
296
- layout: "auto",
297
- vertex: { module, entryPoint: "vertexMain" },
298
- fragment: {
299
- module,
300
- entryPoint: "fragmentMain",
301
- targets: [{ format }],
302
- },
303
- primitive: { topology: "triangle-list" },
304
- });
305
- }
306
-
307
- if (!uniformBuffer) {
308
- uniformBuffer = device.createBuffer({
309
- size: 32,
310
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
311
- });
312
- }
313
-
314
386
  let flags = 0;
315
387
  if (config.uniforms.tonemap) flags |= FLAG_TONEMAP;
316
388
  if (config.uniforms.fxaa) flags |= FLAG_FXAA;
317
389
  if (config.uniforms.vignetteStrength > 0) flags |= FLAG_VIGNETTE;
390
+ if (config.uniforms.bloomIntensity > 0) flags |= FLAG_BLOOM;
391
+ if (config.uniforms.quantize > 0) flags |= FLAG_QUANTIZE;
318
392
 
319
- const data = new ArrayBuffer(32);
393
+ const data = new ArrayBuffer(48);
320
394
  const floats = new Float32Array(data);
321
395
  const uints = new Uint32Array(data);
322
396
  floats[0] = config.uniforms.exposure;
@@ -326,15 +400,19 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
326
400
  floats[4] = 1.0 / width;
327
401
  floats[5] = 1.0 / height;
328
402
  uints[6] = flags;
403
+ floats[7] = config.uniforms.bloomIntensity;
404
+ floats[8] = config.uniforms.bloomThreshold;
405
+ floats[9] = config.uniforms.bloomRadius;
406
+ floats[10] = config.uniforms.quantize;
329
407
 
330
- device.queue.writeBuffer(uniformBuffer, 0, data);
408
+ device.queue.writeBuffer(uniformBuffer!, 0, data);
331
409
 
332
410
  const bindGroup = device.createBindGroup({
333
- layout: pipeline.getBindGroupLayout(0),
411
+ layout: pipeline!.getBindGroupLayout(0),
334
412
  entries: [
335
413
  { binding: 0, resource: currentInput },
336
414
  { binding: 1, resource: sampler },
337
- { binding: 2, resource: { buffer: uniformBuffer } },
415
+ { binding: 2, resource: { buffer: uniformBuffer! } },
338
416
  { binding: 3, resource: maskView },
339
417
  ],
340
418
  });
@@ -350,13 +428,13 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
350
428
  ],
351
429
  });
352
430
 
353
- pass.setPipeline(pipeline);
431
+ pass.setPipeline(pipeline!);
354
432
  pass.setBindGroup(0, bindGroup);
355
433
  pass.draw(3);
356
434
  pass.end();
357
435
  } else {
358
436
  const bindGroup = device.createBindGroup({
359
- layout: blitPipeline.getBindGroupLayout(0),
437
+ layout: blitPipeline!.getBindGroupLayout(0),
360
438
  entries: [
361
439
  { binding: 0, resource: currentInput },
362
440
  { binding: 1, resource: sampler },
@@ -374,14 +452,14 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
374
452
  ],
375
453
  });
376
454
 
377
- pass.setPipeline(blitPipeline);
455
+ pass.setPipeline(blitPipeline!);
378
456
  pass.setBindGroup(0, bindGroup);
379
457
  pass.draw(3);
380
458
  pass.end();
381
459
  }
382
460
  } else {
383
461
  const bindGroup = device.createBindGroup({
384
- layout: blitPipeline.getBindGroupLayout(0),
462
+ layout: blitPipeline!.getBindGroupLayout(0),
385
463
  entries: [
386
464
  { binding: 0, resource: currentInput },
387
465
  { binding: 1, resource: sampler },
@@ -399,7 +477,7 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
399
477
  ],
400
478
  });
401
479
 
402
- pass.setPipeline(blitPipeline);
480
+ pass.setPipeline(blitPipeline!);
403
481
  pass.setBindGroup(0, bindGroup);
404
482
  pass.draw(3);
405
483
  pass.end();
@@ -413,9 +491,9 @@ export function createPostProcessNode(config: PostProcessConfig): ComputeNode {
413
491
  format,
414
492
  width,
415
493
  height,
416
- sceneView,
494
+ sceneView: colorView,
417
495
  depthView,
418
- entityIdView,
496
+ entityIdView: eidView,
419
497
  maskView,
420
498
  canvasView,
421
499
  };
@@ -0,0 +1,61 @@
1
+ import type { Vec3, Ray } from "./bvh/structs";
2
+
3
+ export type { Ray };
4
+
5
+ const DEG_TO_RAD = Math.PI / 180;
6
+
7
+ export function generateRay(
8
+ screenX: number,
9
+ screenY: number,
10
+ width: number,
11
+ height: number,
12
+ fov: number,
13
+ near: number,
14
+ cameraWorld: Float32Array
15
+ ): Ray {
16
+ const ndcX = screenX * 2 - 1;
17
+ const ndcY = 1 - screenY * 2;
18
+
19
+ const aspect = width / height;
20
+ const tanHalfFov = Math.tan((fov * DEG_TO_RAD) / 2);
21
+
22
+ const camDirX = ndcX * aspect * tanHalfFov;
23
+ const camDirY = ndcY * tanHalfFov;
24
+ const camDirZ = -1;
25
+
26
+ const r00 = cameraWorld[0];
27
+ const r10 = cameraWorld[1];
28
+ const r20 = cameraWorld[2];
29
+ const r01 = cameraWorld[4];
30
+ const r11 = cameraWorld[5];
31
+ const r21 = cameraWorld[6];
32
+ const r02 = cameraWorld[8];
33
+ const r12 = cameraWorld[9];
34
+ const r22 = cameraWorld[10];
35
+
36
+ let dirX = r00 * camDirX + r01 * camDirY + r02 * camDirZ;
37
+ let dirY = r10 * camDirX + r11 * camDirY + r12 * camDirZ;
38
+ let dirZ = r20 * camDirX + r21 * camDirY + r22 * camDirZ;
39
+
40
+ const len = Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);
41
+ dirX /= len;
42
+ dirY /= len;
43
+ dirZ /= len;
44
+
45
+ const originX = cameraWorld[12];
46
+ const originY = cameraWorld[13];
47
+ const originZ = cameraWorld[14];
48
+
49
+ return {
50
+ origin: {
51
+ x: originX + dirX * near,
52
+ y: originY + dirY * near,
53
+ z: originZ + dirZ * near,
54
+ },
55
+ direction: {
56
+ x: dirX,
57
+ y: dirY,
58
+ z: dirZ,
59
+ },
60
+ };
61
+ }