@driftengine/core 4.8.2 → 4.8.4
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/CHANGELOG.json +127 -0
- package/README.md +1 -1
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +22 -1
- package/dist/math/scalar.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +7 -0
- package/dist/render/anisotropicLobe.js +33 -0
- package/dist/render/backend/api.d.ts +41 -4
- package/dist/render/backend/materialChanges.d.ts +7 -2
- package/dist/render/backend/materialChanges.js +8 -3
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
- package/dist/render/backend/webgl2/clothTextures.js +93 -0
- package/dist/render/backend/webgl2/renderer.d.ts +159 -3
- package/dist/render/backend/webgl2/renderer.js +662 -80
- package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
- package/dist/render/backend/webgl2/skinScatter.js +256 -0
- package/dist/render/backend/webgpu/buffers.d.ts +25 -11
- package/dist/render/backend/webgpu/buffers.js +98 -49
- package/dist/render/backend/webgpu/causticsPass.js +2 -1
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
- package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
- package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
- package/dist/render/backend/webgpu/clothTextures.js +145 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
- package/dist/render/backend/webgpu/compressedUpload.js +38 -0
- package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
- package/dist/render/backend/webgpu/computeHost.js +5 -0
- package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
- package/dist/render/backend/webgpu/depthPass.js +43 -12
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/filmPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
- package/dist/render/backend/webgpu/flatPass.js +111 -13
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/linePass.js +2 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/panelPass.js +2 -1
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/plumePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +15 -3
- package/dist/render/backend/webgpu/postPass.js +25 -8
- package/dist/render/backend/webgpu/renderer.d.ts +123 -6
- package/dist/render/backend/webgpu/renderer.js +534 -58
- package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
- package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
- package/dist/render/backend/webgpu/textPass.js +2 -1
- package/dist/render/backend/webgpu/waterPass.js +2 -1
- package/dist/render/backend/webgpu/windStreakPass.js +2 -1
- package/dist/render/boltBatch.js +2 -1
- package/dist/render/causticsRenderer.js +2 -1
- package/dist/render/clothBindingData.d.ts +41 -0
- package/dist/render/clothBindingData.js +72 -0
- package/dist/render/clothPlacement.d.ts +25 -0
- package/dist/render/clothPlacement.js +85 -0
- package/dist/render/compressedSource.d.ts +66 -0
- package/dist/render/compressedSource.js +138 -0
- package/dist/render/cutoutCaster.d.ts +5 -1
- package/dist/render/cutoutCaster.js +6 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- package/dist/render/decalPass.js +2 -1
- package/dist/render/eyeRefraction.d.ts +35 -0
- package/dist/render/eyeRefraction.js +40 -0
- package/dist/render/glCompressed.d.ts +27 -0
- package/dist/render/glCompressed.js +53 -0
- package/dist/render/globalMediumPass.js +3 -2
- package/dist/render/hairLobes.d.ts +70 -0
- package/dist/render/hairLobes.js +120 -0
- package/dist/render/lightBudget.d.ts +22 -0
- package/dist/render/lightBudget.js +22 -0
- package/dist/render/lineBatch.js +5 -1
- package/dist/render/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/oitPass.js +2 -1
- package/dist/render/particleBatch.js +3 -2
- package/dist/render/particlePool.d.ts +6 -2
- package/dist/render/plumeRenderer.js +3 -2
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +29 -2
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneCoverage.d.ts +54 -0
- package/dist/render/sceneCoverage.js +72 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/sdfTextRenderer.js +2 -1
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +45 -8
- package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
- package/dist/render/shaders/flat/anisotropic.js +68 -0
- package/dist/render/shaders/flat/eye.d.ts +1 -0
- package/dist/render/shaders/flat/eye.js +81 -0
- package/dist/render/shaders/flat/hair.d.ts +1 -0
- package/dist/render/shaders/flat/hair.js +101 -0
- package/dist/render/shaders/flat/index.js +42 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +113 -17
- package/dist/render/shaders/flat/models.d.ts +14 -0
- package/dist/render/shaders/flat/models.js +273 -0
- package/dist/render/shaders/flat/preamble.js +26 -24
- package/dist/render/shaders/flat/skin.d.ts +1 -0
- package/dist/render/shaders/flat/skin.js +114 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
- package/dist/render/shaders/generated/depth.wgsl.js +66 -24
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
- package/dist/render/shaders/generated/particle.wgsl.js +3 -3
- package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/rush.wgsl.js +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
- package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +14 -1
- package/dist/render/shaders/outputTransform.js +23 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
- package/dist/render/shaders/rush.js +13 -2
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/skinBlur.d.ts +1 -0
- package/dist/render/shaders/skinBlur.js +103 -0
- package/dist/render/shaders/skinning.d.ts +3 -1
- package/dist/render/shaders/skinning.js +31 -4
- package/dist/render/shaders/temporalResolve.js +12 -5
- package/dist/render/shadowCasters.d.ts +13 -1
- package/dist/render/skinBlur.d.ts +41 -0
- package/dist/render/skinBlur.js +49 -0
- package/dist/render/skinScatter.d.ts +67 -0
- package/dist/render/skinScatter.js +80 -0
- package/dist/render/skinnedClothRun.d.ts +26 -0
- package/dist/render/skinnedClothRun.js +84 -0
- package/dist/render/ssrPass.js +2 -1
- package/dist/render/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +55 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/textRenderer.js +2 -1
- package/dist/render/vertexDefaults.js +8 -0
- package/dist/render/waterRenderer.js +2 -1
- package/dist/render/windStreakRenderer.js +2 -1
- package/package.json +6 -6
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +53 -1
- package/src/math/scalar.ts +1 -1
- package/src/render/anisotropicLobe.ts +41 -0
- package/src/render/backend/api.ts +52 -3
- package/src/render/backend/materialChanges.ts +8 -3
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +750 -80
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- package/src/render/backend/webgpu/causticsPass.ts +2 -1
- package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
- package/src/render/backend/webgpu/clothTextures.ts +194 -0
- package/src/render/backend/webgpu/compressedUpload.ts +65 -0
- package/src/render/backend/webgpu/computeHost.ts +25 -0
- package/src/render/backend/webgpu/depthPass.ts +43 -10
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/filmPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +147 -8
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/linePass.ts +2 -1
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/panelPass.ts +2 -1
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/plumePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +25 -6
- package/src/render/backend/webgpu/renderer.ts +701 -52
- package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
- package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
- package/src/render/backend/webgpu/textPass.ts +2 -1
- package/src/render/backend/webgpu/waterPass.ts +2 -1
- package/src/render/backend/webgpu/windStreakPass.ts +2 -1
- package/src/render/boltBatch.ts +2 -1
- package/src/render/causticsRenderer.ts +2 -1
- package/src/render/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +20 -2
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/decalPass.ts +2 -1
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/globalMediumPass.ts +3 -2
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/lineBatch.ts +3 -1
- package/src/render/mesh.ts +23 -0
- package/src/render/oitPass.ts +2 -1
- package/src/render/particleBatch.ts +3 -2
- package/src/render/particlePool.ts +6 -2
- package/src/render/plumeRenderer.ts +3 -2
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +37 -2
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneCoverage.ts +78 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/sdfTextRenderer.ts +2 -1
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +45 -8
- package/src/render/shaders/flat/anisotropic.ts +68 -0
- package/src/render/shaders/flat/eye.ts +82 -0
- package/src/render/shaders/flat/hair.ts +102 -0
- package/src/render/shaders/flat/index.ts +42 -3
- package/src/render/shaders/flat/main.ts +113 -17
- package/src/render/shaders/flat/models.ts +286 -0
- package/src/render/shaders/flat/preamble.ts +26 -24
- package/src/render/shaders/flat/skin.ts +116 -0
- package/src/render/shaders/generated/depth.wgsl.ts +66 -24
- package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +3 -3
- package/src/render/shaders/generated/rush.wgsl.ts +1 -1
- package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/outputTransform.ts +24 -1
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
- package/src/render/shaders/rush.ts +13 -2
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- package/src/render/shaders/temporalResolve.ts +12 -5
- package/src/render/shadowCasters.ts +13 -0
- package/src/render/skinBlur.ts +53 -0
- package/src/render/skinScatter.ts +92 -0
- package/src/render/skinnedClothRun.ts +128 -0
- package/src/render/ssrPass.ts +2 -1
- package/src/render/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +134 -63
- package/src/render/textRenderer.ts +2 -1
- package/src/render/vertexDefaults.ts +8 -0
- package/src/render/waterRenderer.ts +2 -1
- package/src/render/windStreakRenderer.ts +2 -1
- package/dist/math/exact.d.ts +0 -76
- package/dist/math/exact.js +0 -327
- package/src/math/exact.ts +0 -340
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skin's screen-space scattering on this backend: the targets, the kept draws and the two passes
|
|
3
|
+
* that spread them. `skinBlur.ts` is what the blur computes and why; this is where it runs.
|
|
4
|
+
*
|
|
5
|
+
* **What the renderer hands it.** Each skin draw, recorded into the frame with its diffuse taken
|
|
6
|
+
* out, is recorded a second time here with the diffuse alone. When the frame first draws something
|
|
7
|
+
* blended — or ends, if nothing is — the renderer ends its pass and calls `drawDiffuse`, which
|
|
8
|
+
* replays them into a half-float target against the frame's own depth; resolves the depth; and
|
|
9
|
+
* calls `spread`, which blurs across into a second target and then down, adding into the frame.
|
|
10
|
+
*
|
|
11
|
+
* **The draws are commands, not calls**, for the reason `LatePass` keeps its own pool: the frame
|
|
12
|
+
* graph's is spent at every flush, and these are replayed after the flush that ends the frame's
|
|
13
|
+
* pass. Each holds the per-draw ring slot its frame half used, and those stay valid until the
|
|
14
|
+
* encoder is replaced, which is after this has run.
|
|
15
|
+
*
|
|
16
|
+
* **What it costs**: every skin draw twice; two half-float targets the size of the frame, and a
|
|
17
|
+
* third multisampled one under multisampling; a depth resolve; and two full-screen passes that
|
|
18
|
+
* leave at once wherever no skin was drawn.
|
|
19
|
+
*/
|
|
20
|
+
import { SKIN_PROFILES } from '../../skinBlur.ts';
|
|
21
|
+
import { SKINBLUR_BINDINGS, SKIN_BLUR_FRAG_WGSL } from '../../shaders/generated/skinBlur.wgsl.ts';
|
|
22
|
+
import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.ts';
|
|
23
|
+
import type { CommandPool, DrawCommand } from './drawCommand.ts';
|
|
24
|
+
import { createCommandPool, resetPool, takeCommand } from './drawCommand.ts';
|
|
25
|
+
import type { PipelineCache } from './pipelineCache.ts';
|
|
26
|
+
import { createPostStageLayout, postPipeline, postPipelineTargets } from './postPass.ts';
|
|
27
|
+
import { DYNAMIC_ALIGNMENT } from './uniformRing.ts';
|
|
28
|
+
|
|
29
|
+
const BINDINGS = SKINBLUR_BINDINGS.SKIN_BLUR_FRAG;
|
|
30
|
+
const SLOT = Math.ceil(BINDINGS.uniformSize / DYNAMIC_ALIGNMENT) * DYNAMIC_ALIGNMENT;
|
|
31
|
+
const USAGE_TARGET = 0x10 | 0x4; // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
32
|
+
const USAGE_UNIFORM_DST = 0x40 | 0x8; // UNIFORM | COPY_DST
|
|
33
|
+
|
|
34
|
+
/** The format the diffuse lands in: half floats, because it is linear light before any grade. */
|
|
35
|
+
export const SKIN_TARGET_FORMAT: GPUTextureFormat = 'rgba16float';
|
|
36
|
+
|
|
37
|
+
export class SkinScatterPass {
|
|
38
|
+
private readonly device: GPUDevice;
|
|
39
|
+
private readonly layout: GPUBindGroupLayout;
|
|
40
|
+
private readonly uniforms: GPUBuffer;
|
|
41
|
+
private readonly staging = new Float32Array((SLOT * 2) / 4);
|
|
42
|
+
private readonly linear: GPUSampler;
|
|
43
|
+
private readonly nearest: GPUSampler;
|
|
44
|
+
private readonly commands: CommandPool = createCommandPool(16);
|
|
45
|
+
/** Each profile's scatter distance per channel, in metres: four floats a profile. */
|
|
46
|
+
private readonly profiles = new Float32Array(SKIN_PROFILES * 4);
|
|
47
|
+
private diffuse: GPUTexture | null = null;
|
|
48
|
+
private diffuseMsaa: GPUTexture | null = null;
|
|
49
|
+
private across: GPUTexture | null = null;
|
|
50
|
+
private diffuseView: GPUTextureView | null = null;
|
|
51
|
+
private diffuseMsaaView: GPUTextureView | null = null;
|
|
52
|
+
private acrossView: GPUTextureView | null = null;
|
|
53
|
+
private groupAcross: GPUBindGroup | null = null;
|
|
54
|
+
private groupDown: GPUBindGroup | null = null;
|
|
55
|
+
private groupsDepth: GPUTextureView | null = null;
|
|
56
|
+
|
|
57
|
+
constructor(device: GPUDevice) {
|
|
58
|
+
this.device = device;
|
|
59
|
+
this.layout = createPostStageLayout(device, BINDINGS.uniforms, BINDINGS.uniformSize, [
|
|
60
|
+
{ binding: BINDINGS.textures.uSkin },
|
|
61
|
+
{ binding: BINDINGS.textures.uDepth, filterable: false },
|
|
62
|
+
]);
|
|
63
|
+
this.uniforms = device.createBuffer({
|
|
64
|
+
label: 'skin.blurUniforms',
|
|
65
|
+
size: SLOT * 2,
|
|
66
|
+
usage: USAGE_UNIFORM_DST,
|
|
67
|
+
});
|
|
68
|
+
this.linear = device.createSampler({
|
|
69
|
+
label: 'skin.linear',
|
|
70
|
+
magFilter: 'linear',
|
|
71
|
+
minFilter: 'linear',
|
|
72
|
+
addressModeU: 'clamp-to-edge',
|
|
73
|
+
addressModeV: 'clamp-to-edge',
|
|
74
|
+
});
|
|
75
|
+
this.nearest = device.createSampler({
|
|
76
|
+
label: 'skin.nearest',
|
|
77
|
+
addressModeU: 'clamp-to-edge',
|
|
78
|
+
addressModeV: 'clamp-to-edge',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Whether any skin was kept for this frame. */
|
|
83
|
+
get pending(): boolean {
|
|
84
|
+
return this.commands.taken > 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A command for one skin draw's diffuse half, filled by the caller. */
|
|
88
|
+
take(): DrawCommand {
|
|
89
|
+
return this.commands.commands[takeCommand(this.commands)] as DrawCommand;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** What one profile scatters, per channel, in metres: the last material to name it says. */
|
|
93
|
+
setProfile(index: number, r: number, g: number, b: number): void {
|
|
94
|
+
const at = Math.min(Math.max(Math.round(index), 0), SKIN_PROFILES - 1) * 4;
|
|
95
|
+
this.profiles[at] = r;
|
|
96
|
+
this.profiles[at + 1] = g;
|
|
97
|
+
this.profiles[at + 2] = b;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The targets at the frame's size and sample count, made again only when either changes. */
|
|
101
|
+
size(width: number, height: number, samples: number): void {
|
|
102
|
+
if (
|
|
103
|
+
this.diffuse !== null &&
|
|
104
|
+
this.diffuse.width === width &&
|
|
105
|
+
this.diffuse.height === height &&
|
|
106
|
+
samples > 1 === (this.diffuseMsaa !== null) &&
|
|
107
|
+
(this.diffuseMsaa === null || this.diffuseMsaa.sampleCount === samples)
|
|
108
|
+
) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.releaseTargets();
|
|
112
|
+
const make = (label: string, count: number): GPUTexture =>
|
|
113
|
+
this.device.createTexture({
|
|
114
|
+
label,
|
|
115
|
+
size: [width, height],
|
|
116
|
+
format: SKIN_TARGET_FORMAT,
|
|
117
|
+
sampleCount: count,
|
|
118
|
+
usage: count > 1 ? 0x10 : USAGE_TARGET,
|
|
119
|
+
});
|
|
120
|
+
this.diffuse = make('skin.diffuse', 1);
|
|
121
|
+
this.across = make('skin.across', 1);
|
|
122
|
+
this.diffuseMsaa = samples > 1 ? make('skin.diffuseMsaa', samples) : null;
|
|
123
|
+
this.diffuseView = this.diffuse.createView();
|
|
124
|
+
this.acrossView = this.across.createView();
|
|
125
|
+
this.diffuseMsaaView = this.diffuseMsaa?.createView() ?? null;
|
|
126
|
+
this.groupAcross = null;
|
|
127
|
+
this.groupDown = null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Every kept draw's diffuse into the target, against the frame's own depth, which it reads and
|
|
132
|
+
* does not write. Called with no pass open on `encoder`.
|
|
133
|
+
*/
|
|
134
|
+
drawDiffuse(
|
|
135
|
+
encoder: GPUCommandEncoder,
|
|
136
|
+
depth: GPUTextureView,
|
|
137
|
+
issue: (pass: GPURenderPassEncoder, command: DrawCommand) => void,
|
|
138
|
+
timestampWrites: GPURenderPassTimestampWrites | undefined,
|
|
139
|
+
): void {
|
|
140
|
+
const target = this.diffuseView;
|
|
141
|
+
if (target === null) return;
|
|
142
|
+
const pass = encoder.beginRenderPass({
|
|
143
|
+
label: 'skin.diffuse',
|
|
144
|
+
timestampWrites,
|
|
145
|
+
colorAttachments: [
|
|
146
|
+
{
|
|
147
|
+
view: this.diffuseMsaaView ?? target,
|
|
148
|
+
resolveTarget: this.diffuseMsaaView === null ? undefined : target,
|
|
149
|
+
clearValue: [0, 0, 0, 0],
|
|
150
|
+
loadOp: 'clear',
|
|
151
|
+
storeOp: this.diffuseMsaaView === null ? 'store' : 'discard',
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
depthStencilAttachment: { view: depth, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
155
|
+
});
|
|
156
|
+
for (let i = 0; i < this.commands.taken; i += 1) {
|
|
157
|
+
issue(pass, this.commands.commands[i] as DrawCommand);
|
|
158
|
+
}
|
|
159
|
+
pass.end();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Across into the second target, then down and added into the frame's colour — `frame` the
|
|
164
|
+
* attachment its pass draws into, `resolve` where a multisampled one resolves, `samples` its
|
|
165
|
+
* count. `projection` is the frame's, `height` the frame's height in pixels.
|
|
166
|
+
*/
|
|
167
|
+
spread(
|
|
168
|
+
encoder: GPUCommandEncoder,
|
|
169
|
+
pipelines: PipelineCache,
|
|
170
|
+
depth: GPUTextureView,
|
|
171
|
+
frame: GPUTextureView,
|
|
172
|
+
resolve: GPUTextureView | null,
|
|
173
|
+
samples: number,
|
|
174
|
+
projection: ArrayLike<number>,
|
|
175
|
+
inverseProjection: ArrayLike<number>,
|
|
176
|
+
timestampWrites: () => GPURenderPassTimestampWrites | undefined,
|
|
177
|
+
): void {
|
|
178
|
+
const diffuse = this.diffuse;
|
|
179
|
+
if (diffuse === null || this.diffuseView === null || this.acrossView === null) return;
|
|
180
|
+
if (this.groupAcross === null || this.groupDown === null || this.groupsDepth !== depth) {
|
|
181
|
+
this.groupAcross = this.group('skin.blurAcross', this.diffuseView, depth);
|
|
182
|
+
this.groupDown = this.group('skin.blurDown', this.acrossView, depth);
|
|
183
|
+
this.groupsDepth = depth;
|
|
184
|
+
}
|
|
185
|
+
this.writeUniforms(diffuse.width, diffuse.height, projection, inverseProjection);
|
|
186
|
+
|
|
187
|
+
const across = postPipeline(
|
|
188
|
+
pipelines,
|
|
189
|
+
this.device,
|
|
190
|
+
this.layout,
|
|
191
|
+
'skin.blurAcross',
|
|
192
|
+
SKIN_BLUR_FRAG_WGSL,
|
|
193
|
+
SKIN_TARGET_FORMAT,
|
|
194
|
+
);
|
|
195
|
+
const down = postPipelineTargets(
|
|
196
|
+
pipelines,
|
|
197
|
+
this.device,
|
|
198
|
+
this.layout,
|
|
199
|
+
`skin.blurDown:${pipelines.format}:${samples}`,
|
|
200
|
+
SKIN_BLUR_FRAG_WGSL,
|
|
201
|
+
[
|
|
202
|
+
{
|
|
203
|
+
format: pipelines.format,
|
|
204
|
+
/* Added: its colour onto the frame's, the frame's alpha kept. */
|
|
205
|
+
blend: {
|
|
206
|
+
color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
|
|
207
|
+
alpha: SCENE_ALPHA_KEEPS,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
samples,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const first = encoder.beginRenderPass({
|
|
215
|
+
label: 'skin.blurAcross',
|
|
216
|
+
timestampWrites: timestampWrites(),
|
|
217
|
+
colorAttachments: [
|
|
218
|
+
{ view: this.acrossView, clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store' },
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
first.setPipeline(across);
|
|
222
|
+
first.setBindGroup(0, this.groupAcross, [0]);
|
|
223
|
+
first.draw(3);
|
|
224
|
+
first.end();
|
|
225
|
+
|
|
226
|
+
const second = encoder.beginRenderPass({
|
|
227
|
+
label: 'skin.blurDown',
|
|
228
|
+
timestampWrites: timestampWrites(),
|
|
229
|
+
colorAttachments: [
|
|
230
|
+
{
|
|
231
|
+
view: frame,
|
|
232
|
+
resolveTarget: resolve ?? undefined,
|
|
233
|
+
loadOp: 'load',
|
|
234
|
+
storeOp: 'store',
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
second.setPipeline(down);
|
|
239
|
+
second.setBindGroup(0, this.groupDown, [SLOT]);
|
|
240
|
+
second.draw(3);
|
|
241
|
+
second.end();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Spent at the frame's start: the kept draws were this frame's. */
|
|
245
|
+
reset(): void {
|
|
246
|
+
resetPool(this.commands);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
dispose(): void {
|
|
250
|
+
this.releaseTargets();
|
|
251
|
+
this.uniforms.destroy();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private group(label: string, skin: GPUTextureView, depth: GPUTextureView): GPUBindGroup {
|
|
255
|
+
return this.device.createBindGroup({
|
|
256
|
+
label,
|
|
257
|
+
layout: this.layout,
|
|
258
|
+
entries: [
|
|
259
|
+
{
|
|
260
|
+
binding: BINDINGS.uniforms,
|
|
261
|
+
resource: { buffer: this.uniforms, size: BINDINGS.uniformSize },
|
|
262
|
+
},
|
|
263
|
+
{ binding: BINDINGS.textures.uSkin.texture, resource: skin },
|
|
264
|
+
{ binding: BINDINGS.textures.uSkin.sampler, resource: this.linear },
|
|
265
|
+
{ binding: BINDINGS.textures.uDepth.texture, resource: depth },
|
|
266
|
+
{ binding: BINDINGS.textures.uDepth.sampler, resource: this.nearest },
|
|
267
|
+
],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Both axes' blocks: the step along each, and what they share. Column-major matrices. */
|
|
272
|
+
private writeUniforms(
|
|
273
|
+
width: number,
|
|
274
|
+
height: number,
|
|
275
|
+
projection: ArrayLike<number>,
|
|
276
|
+
inverse: ArrayLike<number>,
|
|
277
|
+
): void {
|
|
278
|
+
const f = this.staging;
|
|
279
|
+
const fields = BINDINGS.fields;
|
|
280
|
+
for (let axis = 0; axis < 2; axis += 1) {
|
|
281
|
+
const base = (axis * SLOT) / 4;
|
|
282
|
+
f[base + fields.uStep.offset / 4] = axis === 0 ? 1 / width : 0;
|
|
283
|
+
f[base + fields.uStep.offset / 4 + 1] = axis === 0 ? 0 : 1 / height;
|
|
284
|
+
/* The four terms that carry a depth back to view-space metres: the occlusion blur's own. */
|
|
285
|
+
const toZ = base + fields.uDepthToViewZ.offset / 4;
|
|
286
|
+
f[toZ] = (inverse[10] as number) ?? 0;
|
|
287
|
+
f[toZ + 1] = (inverse[14] as number) ?? 0;
|
|
288
|
+
f[toZ + 2] = (inverse[11] as number) ?? 0;
|
|
289
|
+
f[toZ + 3] = (inverse[15] as number) ?? 1;
|
|
290
|
+
/* Pixels a metre spans at a metre: the vertical focal length, which is the horizontal one
|
|
291
|
+
too wherever a pixel is square. */
|
|
292
|
+
f[base + fields.uFocal.offset / 4] = ((projection[5] as number) * height) / 2;
|
|
293
|
+
f.set(this.profiles, base + fields.uProfiles.offset / 4);
|
|
294
|
+
}
|
|
295
|
+
this.device.queue.writeBuffer(this.uniforms, 0, f);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private releaseTargets(): void {
|
|
299
|
+
this.diffuse?.destroy();
|
|
300
|
+
this.diffuseMsaa?.destroy();
|
|
301
|
+
this.across?.destroy();
|
|
302
|
+
this.diffuse = null;
|
|
303
|
+
this.diffuseMsaa = null;
|
|
304
|
+
this.across = null;
|
|
305
|
+
this.diffuseView = null;
|
|
306
|
+
this.diffuseMsaaView = null;
|
|
307
|
+
this.acrossView = null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { SurfaceTextureOptions } from '../../surfaceTexture.ts';
|
|
2
2
|
import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from '../../surfaceEffects.ts';
|
|
3
3
|
import { toHalfFloats } from '../../halfFloat.ts';
|
|
4
|
-
import {
|
|
4
|
+
import { layerSize, refuseArrayUpdate, sourceSize } from '../../textureSource.ts';
|
|
5
|
+
import { compressedLayers, planBlocks, refuseBlockUpdate } from '../../compressedSource.ts';
|
|
6
|
+
import type {
|
|
7
|
+
BlockFormat,
|
|
8
|
+
CompressedTextureFormat,
|
|
9
|
+
CompressedTextureSource,
|
|
10
|
+
SurfaceSource,
|
|
11
|
+
} from '../../compressedSource.ts';
|
|
12
|
+
import { createCompressedTexture } from './compressedUpload.ts';
|
|
5
13
|
import type { PipelineCache } from './pipelineCache.ts';
|
|
6
14
|
import { shaderModule } from './shaderModules.ts';
|
|
7
15
|
|
|
@@ -179,24 +187,45 @@ export class GpuSurfaceTexture {
|
|
|
179
187
|
private effectsTable: GPUTexture | null = null;
|
|
180
188
|
/** Its view, which the flat group binds beside this image when it is a material's albedo. */
|
|
181
189
|
effectsView: GPUTextureView | null = null;
|
|
190
|
+
/** The block format a compressed texture holds, or null for one uploaded from images. */
|
|
191
|
+
private readonly blockFormat: BlockFormat | null;
|
|
182
192
|
|
|
183
193
|
constructor(
|
|
184
194
|
private readonly device: GPUDevice,
|
|
185
195
|
private readonly pipelines: PipelineCache,
|
|
186
|
-
source:
|
|
196
|
+
source: SurfaceSource | readonly SurfaceSource[],
|
|
187
197
|
options: SurfaceTextureOptions = {},
|
|
198
|
+
compressed: readonly CompressedTextureFormat[] = [],
|
|
188
199
|
) {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
this.mipmapped = options.mipmap ?? true;
|
|
200
|
+
const listed = (Array.isArray(source) ? source : [source]) as readonly SurfaceSource[];
|
|
201
|
+
const blocks = compressedLayers(listed);
|
|
202
|
+
const srgb = (options.colorSpace ?? 'linear') === 'srgb';
|
|
193
203
|
/* `SRGB8_ALPHA8`'s equivalent. Decoded in the sampler, before filtering, which is the only
|
|
194
204
|
place it is correct — `surfaceTexture.ts` makes the argument in full. */
|
|
195
|
-
this.format =
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
this.format = srgb ? 'rgba8unorm-srgb' : 'rgba8unorm';
|
|
206
|
+
if (blocks !== null) {
|
|
207
|
+
/* Refused before anything is allocated; the stored chain, or level 0 alone where no chain
|
|
208
|
+
was asked for. See `compressedUpload.ts`. */
|
|
209
|
+
const plan = planBlocks(blocks, srgb, compressed);
|
|
210
|
+
const first = blocks[0] as CompressedTextureSource;
|
|
211
|
+
this.layers = blocks.length;
|
|
212
|
+
this.blockFormat = first.format;
|
|
213
|
+
this.mipmapped = (options.mipmap ?? true) && plan.levels > 1;
|
|
214
|
+
this.width = first.width;
|
|
215
|
+
this.height = first.height;
|
|
216
|
+
this.levels = this.mipmapped ? plan.levels : 1;
|
|
217
|
+
this.texture = createCompressedTexture(device, plan.name, blocks, this.levels);
|
|
218
|
+
} else {
|
|
219
|
+
const sources = listed as readonly TexImageSource[];
|
|
220
|
+
const { width, height } = layerSize(sources);
|
|
221
|
+
this.layers = sources.length;
|
|
222
|
+
this.blockFormat = null;
|
|
223
|
+
this.mipmapped = options.mipmap ?? true;
|
|
224
|
+
this.width = width;
|
|
225
|
+
this.height = height;
|
|
226
|
+
this.levels = this.mipmapped ? mipLevelCount(width, height) : 1;
|
|
227
|
+
this.texture = this.allocate();
|
|
228
|
+
}
|
|
200
229
|
this.current = this.arrayView(this.texture);
|
|
201
230
|
|
|
202
231
|
const wrap: GPUAddressMode =
|
|
@@ -232,7 +261,7 @@ export class GpuSurfaceTexture {
|
|
|
232
261
|
: 1,
|
|
233
262
|
});
|
|
234
263
|
|
|
235
|
-
this.upload(
|
|
264
|
+
if (blocks === null) this.upload(listed as readonly TexImageSource[]);
|
|
236
265
|
|
|
237
266
|
/* Half floats, as `renderer.ts` uploads it: filterable, so the ordinary float layout takes it,
|
|
238
267
|
and read by `textureLoad` alone, so the filter never runs. */
|
|
@@ -283,6 +312,7 @@ export class GpuSurfaceTexture {
|
|
|
283
312
|
const replaced = this.texture;
|
|
284
313
|
if (replaced === null) return null;
|
|
285
314
|
refuseArrayUpdate(this.layers);
|
|
315
|
+
refuseBlockUpdate(this.blockFormat);
|
|
286
316
|
const { width, height } = sourceSize(source);
|
|
287
317
|
if (width === this.width && height === this.height) {
|
|
288
318
|
this.upload([source]);
|
|
@@ -9,6 +9,7 @@ import type { UniformFields } from './scatterPass.ts';
|
|
|
9
9
|
import { DEPTH_FORMAT } from './flatPass.ts';
|
|
10
10
|
import type { PipelineCache } from './pipelineCache.ts';
|
|
11
11
|
import { shaderModule } from './shaderModules.ts';
|
|
12
|
+
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The text pass: a string as instanced glyph cubes, drawn over the scene.
|
|
@@ -208,7 +209,7 @@ export function textPipeline(
|
|
|
208
209
|
format: cache.format,
|
|
209
210
|
blend: {
|
|
210
211
|
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
211
|
-
alpha:
|
|
212
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
212
213
|
},
|
|
213
214
|
},
|
|
214
215
|
],
|
|
@@ -9,6 +9,7 @@ import { DEPTH_FORMAT } from './flatPass.ts';
|
|
|
9
9
|
import type { PipelineCache } from './pipelineCache.ts';
|
|
10
10
|
import type { UniformFields } from './scatterPass.ts';
|
|
11
11
|
import { shaderModule } from './shaderModules.ts';
|
|
12
|
+
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The water pass: two sheets and a patch, displaced by a wave field in the vertex stage.
|
|
@@ -199,7 +200,7 @@ export function waterPipeline(
|
|
|
199
200
|
format: cache.format,
|
|
200
201
|
blend: {
|
|
201
202
|
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
202
|
-
alpha:
|
|
203
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
203
204
|
},
|
|
204
205
|
},
|
|
205
206
|
],
|
|
@@ -9,6 +9,7 @@ import type { UniformFields } from './scatterPass.ts';
|
|
|
9
9
|
import { DEPTH_FORMAT } from './flatPass.ts';
|
|
10
10
|
import type { PipelineCache } from './pipelineCache.ts';
|
|
11
11
|
import { shaderModule } from './shaderModules.ts';
|
|
12
|
+
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.ts';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The wind-streak pass: a lattice of debris that follows the camera and wraps.
|
|
@@ -184,7 +185,7 @@ export function windStreakPipeline(
|
|
|
184
185
|
dstFactor: 'one-minus-src-alpha',
|
|
185
186
|
operation: 'add',
|
|
186
187
|
},
|
|
187
|
-
alpha:
|
|
188
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
188
189
|
},
|
|
189
190
|
},
|
|
190
191
|
],
|
package/src/render/boltBatch.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { compileProgram, uniformLocations } from './shader.ts';
|
|
|
2
2
|
import { BOLT_FRAG, BOLT_VERT } from './shaders/bolt.ts';
|
|
3
3
|
import { buildSegmentQuads, expandBoltSegments } from './segmentQuads.ts';
|
|
4
4
|
import type { BoltSegments } from './boltPool.ts';
|
|
5
|
+
import { blendKeeping } from './sceneCoverage.ts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* One draw call for a pool of electrical arcs.
|
|
@@ -86,7 +87,7 @@ export class BoltBatch {
|
|
|
86
87
|
drawTo(gl: WebGL2RenderingContext, segmentCount: number): void {
|
|
87
88
|
if (segmentCount === 0) return;
|
|
88
89
|
gl.enable(gl.BLEND);
|
|
89
|
-
|
|
90
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
90
91
|
gl.depthMask(false);
|
|
91
92
|
gl.disable(gl.CULL_FACE);
|
|
92
93
|
|
|
@@ -7,6 +7,7 @@ import type { Atmosphere } from './atmosphere.ts';
|
|
|
7
7
|
import { buildSheets } from './surfaceSheet.ts';
|
|
8
8
|
import type { SheetSpan } from './surfaceSheet.ts';
|
|
9
9
|
import { seaStateForWind } from './seaState.ts';
|
|
10
|
+
import { blendKeeping } from './sceneCoverage.ts';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* A surface lit by nearby water: the floor of a pool, the bottom of a flooded
|
|
@@ -149,7 +150,7 @@ export class CausticsRenderer {
|
|
|
149
150
|
bindAtmosphere(gl, u, atmosphere, camera.position[1] ?? 0, underwaterEnabled);
|
|
150
151
|
|
|
151
152
|
gl.enable(gl.BLEND);
|
|
152
|
-
|
|
153
|
+
blendKeeping(gl, gl.ONE, gl.ONE);
|
|
153
154
|
/*
|
|
154
155
|
* No depth write, but depth *testing* stays on: the sheet sits a couple of
|
|
155
156
|
* centimetres under the surface it lights, so geometry in front of it — the
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mesh's cloth binding as data, and the texel layouts both backends upload it in.
|
|
3
|
+
*
|
|
4
|
+
* **What a consumer hands over** is per render vertex: the simulation triangle it follows (three
|
|
5
|
+
* particle indices — the same index three times for a vertex that follows one particle), where on
|
|
6
|
+
* it (barycentric u and v for the second and third particle), how far along the triangle's normal,
|
|
7
|
+
* and how much it follows at all (0 skinned, 1 cloth — commonly painted into a vertex colour); and
|
|
8
|
+
* the particles' rest positions in the mesh's model space. A garment's port decodes all of this
|
|
9
|
+
* from the cooked data; the engine owns placing the vertices, in `shaders/clothBinding.ts`.
|
|
10
|
+
*
|
|
11
|
+
* **Textures wrap onto rows of at most 2048 texels**, the width WebGL2 guarantees, padded to whole
|
|
12
|
+
* rows: a garment of 40,000 vertices is 80,000 binding texels, 40 rows. Validated by name before a
|
|
13
|
+
* byte goes to a device, because an index past the particles reads the padding — a vertex pinned
|
|
14
|
+
* to the origin, which looks like a broken rig rather than a bad binding.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** A mesh's cloth binding, one entry a render vertex unless said otherwise. */
|
|
18
|
+
export interface ClothBindingData {
|
|
19
|
+
/** Three particle indices a vertex. */
|
|
20
|
+
readonly triangles: Uint32Array;
|
|
21
|
+
/** Barycentric u and v a vertex, for the second and third particle; the first takes the rest. */
|
|
22
|
+
readonly coordinates: Float32Array;
|
|
23
|
+
/** Metres along the triangle's normal. */
|
|
24
|
+
readonly offsets: Float32Array;
|
|
25
|
+
/** How much of the cloth's place a vertex takes: 0 skinned, 1 cloth. */
|
|
26
|
+
readonly weights: Float32Array;
|
|
27
|
+
/** The particles at rest, three floats each, in the mesh's model space. */
|
|
28
|
+
readonly rest: Float32Array;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The widest row a texture is wrapped onto. */
|
|
32
|
+
export const CLOTH_TEXTURE_WIDTH = 2048;
|
|
33
|
+
|
|
34
|
+
/** A texture of `texels`, wrapped onto rows. */
|
|
35
|
+
export function clothTextureSize(texels: number): { width: number; height: number } {
|
|
36
|
+
const width = Math.max(1, Math.min(CLOTH_TEXTURE_WIDTH, texels));
|
|
37
|
+
return { width, height: Math.max(1, Math.ceil(texels / width)) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Refuse a binding whose arrays disagree with a mesh of `vertices`, or that names a missing particle. */
|
|
41
|
+
export function validateClothBinding(data: ClothBindingData, vertices: number): void {
|
|
42
|
+
const count = data.weights.length;
|
|
43
|
+
const fail = (what: string): never => {
|
|
44
|
+
throw new Error(`cloth binding: ${what}`);
|
|
45
|
+
};
|
|
46
|
+
if (count !== vertices) fail(`binds ${count} vertices and the mesh has ${vertices}`);
|
|
47
|
+
if (data.triangles.length !== count * 3)
|
|
48
|
+
fail(`${data.triangles.length / 3} triangles for ${count} vertices`);
|
|
49
|
+
if (data.coordinates.length !== count * 2)
|
|
50
|
+
fail(`${data.coordinates.length / 2} coordinates for ${count} vertices`);
|
|
51
|
+
if (data.offsets.length !== count) fail(`${data.offsets.length} offsets for ${count} vertices`);
|
|
52
|
+
const particles = data.rest.length / 3;
|
|
53
|
+
if (!Number.isInteger(particles) || particles === 0)
|
|
54
|
+
fail(`rest holds ${data.rest.length} floats`);
|
|
55
|
+
for (let i = 0; i < data.triangles.length; i++) {
|
|
56
|
+
if ((data.triangles[i] as number) >= particles) {
|
|
57
|
+
fail(`vertex ${Math.floor(i / 3)} names particle ${data.triangles[i]} of ${particles}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The binding as RGBA float texels, two a vertex, padded to whole rows. */
|
|
63
|
+
export function packClothBinding(data: ClothBindingData): Float32Array {
|
|
64
|
+
const vertices = data.weights.length;
|
|
65
|
+
const { width, height } = clothTextureSize(vertices * 2);
|
|
66
|
+
const out = new Float32Array(width * height * 4);
|
|
67
|
+
for (let v = 0; v < vertices; v++) {
|
|
68
|
+
const at = v * 8;
|
|
69
|
+
out[at] = data.triangles[v * 3] as number;
|
|
70
|
+
out[at + 1] = data.triangles[v * 3 + 1] as number;
|
|
71
|
+
out[at + 2] = data.triangles[v * 3 + 2] as number;
|
|
72
|
+
out[at + 3] = data.weights[v] as number;
|
|
73
|
+
out[at + 4] = data.coordinates[v * 2] as number;
|
|
74
|
+
out[at + 5] = data.coordinates[v * 2 + 1] as number;
|
|
75
|
+
out[at + 6] = data.offsets[v] as number;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Particles, three floats each, into RGBA texels in `out`. Allocation-free, for every frame. */
|
|
81
|
+
export function packClothParticles(positions: Float32Array, out: Float32Array): void {
|
|
82
|
+
const count = positions.length / 3;
|
|
83
|
+
for (let i = 0; i < count; i++) {
|
|
84
|
+
out[i * 4] = positions[i * 3] as number;
|
|
85
|
+
out[i * 4 + 1] = positions[i * 3 + 1] as number;
|
|
86
|
+
out[i * 4 + 2] = positions[i * 3 + 2] as number;
|
|
87
|
+
out[i * 4 + 3] = 0;
|
|
88
|
+
}
|
|
89
|
+
}
|