@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,253 @@
|
|
|
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.js';
|
|
21
|
+
import { SKINBLUR_BINDINGS, SKIN_BLUR_FRAG_WGSL } from '../../shaders/generated/skinBlur.wgsl.js';
|
|
22
|
+
import { SCENE_ALPHA_KEEPS } from '../../sceneCoverage.js';
|
|
23
|
+
import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
|
|
24
|
+
import { createPostStageLayout, postPipeline, postPipelineTargets } from './postPass.js';
|
|
25
|
+
import { DYNAMIC_ALIGNMENT } from './uniformRing.js';
|
|
26
|
+
const BINDINGS = SKINBLUR_BINDINGS.SKIN_BLUR_FRAG;
|
|
27
|
+
const SLOT = Math.ceil(BINDINGS.uniformSize / DYNAMIC_ALIGNMENT) * DYNAMIC_ALIGNMENT;
|
|
28
|
+
const USAGE_TARGET = 0x10 | 0x4; // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
29
|
+
const USAGE_UNIFORM_DST = 0x40 | 0x8; // UNIFORM | COPY_DST
|
|
30
|
+
/** The format the diffuse lands in: half floats, because it is linear light before any grade. */
|
|
31
|
+
export const SKIN_TARGET_FORMAT = 'rgba16float';
|
|
32
|
+
export class SkinScatterPass {
|
|
33
|
+
device;
|
|
34
|
+
layout;
|
|
35
|
+
uniforms;
|
|
36
|
+
staging = new Float32Array((SLOT * 2) / 4);
|
|
37
|
+
linear;
|
|
38
|
+
nearest;
|
|
39
|
+
commands = createCommandPool(16);
|
|
40
|
+
/** Each profile's scatter distance per channel, in metres: four floats a profile. */
|
|
41
|
+
profiles = new Float32Array(SKIN_PROFILES * 4);
|
|
42
|
+
diffuse = null;
|
|
43
|
+
diffuseMsaa = null;
|
|
44
|
+
across = null;
|
|
45
|
+
diffuseView = null;
|
|
46
|
+
diffuseMsaaView = null;
|
|
47
|
+
acrossView = null;
|
|
48
|
+
groupAcross = null;
|
|
49
|
+
groupDown = null;
|
|
50
|
+
groupsDepth = null;
|
|
51
|
+
constructor(device) {
|
|
52
|
+
this.device = device;
|
|
53
|
+
this.layout = createPostStageLayout(device, BINDINGS.uniforms, BINDINGS.uniformSize, [
|
|
54
|
+
{ binding: BINDINGS.textures.uSkin },
|
|
55
|
+
{ binding: BINDINGS.textures.uDepth, filterable: false },
|
|
56
|
+
]);
|
|
57
|
+
this.uniforms = device.createBuffer({
|
|
58
|
+
label: 'skin.blurUniforms',
|
|
59
|
+
size: SLOT * 2,
|
|
60
|
+
usage: USAGE_UNIFORM_DST,
|
|
61
|
+
});
|
|
62
|
+
this.linear = device.createSampler({
|
|
63
|
+
label: 'skin.linear',
|
|
64
|
+
magFilter: 'linear',
|
|
65
|
+
minFilter: 'linear',
|
|
66
|
+
addressModeU: 'clamp-to-edge',
|
|
67
|
+
addressModeV: 'clamp-to-edge',
|
|
68
|
+
});
|
|
69
|
+
this.nearest = device.createSampler({
|
|
70
|
+
label: 'skin.nearest',
|
|
71
|
+
addressModeU: 'clamp-to-edge',
|
|
72
|
+
addressModeV: 'clamp-to-edge',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Whether any skin was kept for this frame. */
|
|
76
|
+
get pending() {
|
|
77
|
+
return this.commands.taken > 0;
|
|
78
|
+
}
|
|
79
|
+
/** A command for one skin draw's diffuse half, filled by the caller. */
|
|
80
|
+
take() {
|
|
81
|
+
return this.commands.commands[takeCommand(this.commands)];
|
|
82
|
+
}
|
|
83
|
+
/** What one profile scatters, per channel, in metres: the last material to name it says. */
|
|
84
|
+
setProfile(index, r, g, b) {
|
|
85
|
+
const at = Math.min(Math.max(Math.round(index), 0), SKIN_PROFILES - 1) * 4;
|
|
86
|
+
this.profiles[at] = r;
|
|
87
|
+
this.profiles[at + 1] = g;
|
|
88
|
+
this.profiles[at + 2] = b;
|
|
89
|
+
}
|
|
90
|
+
/** The targets at the frame's size and sample count, made again only when either changes. */
|
|
91
|
+
size(width, height, samples) {
|
|
92
|
+
if (this.diffuse !== null &&
|
|
93
|
+
this.diffuse.width === width &&
|
|
94
|
+
this.diffuse.height === height &&
|
|
95
|
+
samples > 1 === (this.diffuseMsaa !== null) &&
|
|
96
|
+
(this.diffuseMsaa === null || this.diffuseMsaa.sampleCount === samples)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.releaseTargets();
|
|
100
|
+
const make = (label, count) => this.device.createTexture({
|
|
101
|
+
label,
|
|
102
|
+
size: [width, height],
|
|
103
|
+
format: SKIN_TARGET_FORMAT,
|
|
104
|
+
sampleCount: count,
|
|
105
|
+
usage: count > 1 ? 0x10 : USAGE_TARGET,
|
|
106
|
+
});
|
|
107
|
+
this.diffuse = make('skin.diffuse', 1);
|
|
108
|
+
this.across = make('skin.across', 1);
|
|
109
|
+
this.diffuseMsaa = samples > 1 ? make('skin.diffuseMsaa', samples) : null;
|
|
110
|
+
this.diffuseView = this.diffuse.createView();
|
|
111
|
+
this.acrossView = this.across.createView();
|
|
112
|
+
this.diffuseMsaaView = this.diffuseMsaa?.createView() ?? null;
|
|
113
|
+
this.groupAcross = null;
|
|
114
|
+
this.groupDown = null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Every kept draw's diffuse into the target, against the frame's own depth, which it reads and
|
|
118
|
+
* does not write. Called with no pass open on `encoder`.
|
|
119
|
+
*/
|
|
120
|
+
drawDiffuse(encoder, depth, issue, timestampWrites) {
|
|
121
|
+
const target = this.diffuseView;
|
|
122
|
+
if (target === null)
|
|
123
|
+
return;
|
|
124
|
+
const pass = encoder.beginRenderPass({
|
|
125
|
+
label: 'skin.diffuse',
|
|
126
|
+
timestampWrites,
|
|
127
|
+
colorAttachments: [
|
|
128
|
+
{
|
|
129
|
+
view: this.diffuseMsaaView ?? target,
|
|
130
|
+
resolveTarget: this.diffuseMsaaView === null ? undefined : target,
|
|
131
|
+
clearValue: [0, 0, 0, 0],
|
|
132
|
+
loadOp: 'clear',
|
|
133
|
+
storeOp: this.diffuseMsaaView === null ? 'store' : 'discard',
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
depthStencilAttachment: { view: depth, depthLoadOp: 'load', depthStoreOp: 'store' },
|
|
137
|
+
});
|
|
138
|
+
for (let i = 0; i < this.commands.taken; i += 1) {
|
|
139
|
+
issue(pass, this.commands.commands[i]);
|
|
140
|
+
}
|
|
141
|
+
pass.end();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Across into the second target, then down and added into the frame's colour — `frame` the
|
|
145
|
+
* attachment its pass draws into, `resolve` where a multisampled one resolves, `samples` its
|
|
146
|
+
* count. `projection` is the frame's, `height` the frame's height in pixels.
|
|
147
|
+
*/
|
|
148
|
+
spread(encoder, pipelines, depth, frame, resolve, samples, projection, inverseProjection, timestampWrites) {
|
|
149
|
+
const diffuse = this.diffuse;
|
|
150
|
+
if (diffuse === null || this.diffuseView === null || this.acrossView === null)
|
|
151
|
+
return;
|
|
152
|
+
if (this.groupAcross === null || this.groupDown === null || this.groupsDepth !== depth) {
|
|
153
|
+
this.groupAcross = this.group('skin.blurAcross', this.diffuseView, depth);
|
|
154
|
+
this.groupDown = this.group('skin.blurDown', this.acrossView, depth);
|
|
155
|
+
this.groupsDepth = depth;
|
|
156
|
+
}
|
|
157
|
+
this.writeUniforms(diffuse.width, diffuse.height, projection, inverseProjection);
|
|
158
|
+
const across = postPipeline(pipelines, this.device, this.layout, 'skin.blurAcross', SKIN_BLUR_FRAG_WGSL, SKIN_TARGET_FORMAT);
|
|
159
|
+
const down = postPipelineTargets(pipelines, this.device, this.layout, `skin.blurDown:${pipelines.format}:${samples}`, SKIN_BLUR_FRAG_WGSL, [
|
|
160
|
+
{
|
|
161
|
+
format: pipelines.format,
|
|
162
|
+
/* Added: its colour onto the frame's, the frame's alpha kept. */
|
|
163
|
+
blend: {
|
|
164
|
+
color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
|
|
165
|
+
alpha: SCENE_ALPHA_KEEPS,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
], samples);
|
|
169
|
+
const first = encoder.beginRenderPass({
|
|
170
|
+
label: 'skin.blurAcross',
|
|
171
|
+
timestampWrites: timestampWrites(),
|
|
172
|
+
colorAttachments: [
|
|
173
|
+
{ view: this.acrossView, clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store' },
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
first.setPipeline(across);
|
|
177
|
+
first.setBindGroup(0, this.groupAcross, [0]);
|
|
178
|
+
first.draw(3);
|
|
179
|
+
first.end();
|
|
180
|
+
const second = encoder.beginRenderPass({
|
|
181
|
+
label: 'skin.blurDown',
|
|
182
|
+
timestampWrites: timestampWrites(),
|
|
183
|
+
colorAttachments: [
|
|
184
|
+
{
|
|
185
|
+
view: frame,
|
|
186
|
+
resolveTarget: resolve ?? undefined,
|
|
187
|
+
loadOp: 'load',
|
|
188
|
+
storeOp: 'store',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
second.setPipeline(down);
|
|
193
|
+
second.setBindGroup(0, this.groupDown, [SLOT]);
|
|
194
|
+
second.draw(3);
|
|
195
|
+
second.end();
|
|
196
|
+
}
|
|
197
|
+
/** Spent at the frame's start: the kept draws were this frame's. */
|
|
198
|
+
reset() {
|
|
199
|
+
resetPool(this.commands);
|
|
200
|
+
}
|
|
201
|
+
dispose() {
|
|
202
|
+
this.releaseTargets();
|
|
203
|
+
this.uniforms.destroy();
|
|
204
|
+
}
|
|
205
|
+
group(label, skin, depth) {
|
|
206
|
+
return this.device.createBindGroup({
|
|
207
|
+
label,
|
|
208
|
+
layout: this.layout,
|
|
209
|
+
entries: [
|
|
210
|
+
{
|
|
211
|
+
binding: BINDINGS.uniforms,
|
|
212
|
+
resource: { buffer: this.uniforms, size: BINDINGS.uniformSize },
|
|
213
|
+
},
|
|
214
|
+
{ binding: BINDINGS.textures.uSkin.texture, resource: skin },
|
|
215
|
+
{ binding: BINDINGS.textures.uSkin.sampler, resource: this.linear },
|
|
216
|
+
{ binding: BINDINGS.textures.uDepth.texture, resource: depth },
|
|
217
|
+
{ binding: BINDINGS.textures.uDepth.sampler, resource: this.nearest },
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/** Both axes' blocks: the step along each, and what they share. Column-major matrices. */
|
|
222
|
+
writeUniforms(width, height, projection, inverse) {
|
|
223
|
+
const f = this.staging;
|
|
224
|
+
const fields = BINDINGS.fields;
|
|
225
|
+
for (let axis = 0; axis < 2; axis += 1) {
|
|
226
|
+
const base = (axis * SLOT) / 4;
|
|
227
|
+
f[base + fields.uStep.offset / 4] = axis === 0 ? 1 / width : 0;
|
|
228
|
+
f[base + fields.uStep.offset / 4 + 1] = axis === 0 ? 0 : 1 / height;
|
|
229
|
+
/* The four terms that carry a depth back to view-space metres: the occlusion blur's own. */
|
|
230
|
+
const toZ = base + fields.uDepthToViewZ.offset / 4;
|
|
231
|
+
f[toZ] = inverse[10] ?? 0;
|
|
232
|
+
f[toZ + 1] = inverse[14] ?? 0;
|
|
233
|
+
f[toZ + 2] = inverse[11] ?? 0;
|
|
234
|
+
f[toZ + 3] = inverse[15] ?? 1;
|
|
235
|
+
/* Pixels a metre spans at a metre: the vertical focal length, which is the horizontal one
|
|
236
|
+
too wherever a pixel is square. */
|
|
237
|
+
f[base + fields.uFocal.offset / 4] = (projection[5] * height) / 2;
|
|
238
|
+
f.set(this.profiles, base + fields.uProfiles.offset / 4);
|
|
239
|
+
}
|
|
240
|
+
this.device.queue.writeBuffer(this.uniforms, 0, f);
|
|
241
|
+
}
|
|
242
|
+
releaseTargets() {
|
|
243
|
+
this.diffuse?.destroy();
|
|
244
|
+
this.diffuseMsaa?.destroy();
|
|
245
|
+
this.across?.destroy();
|
|
246
|
+
this.diffuse = null;
|
|
247
|
+
this.diffuseMsaa = null;
|
|
248
|
+
this.across = null;
|
|
249
|
+
this.diffuseView = null;
|
|
250
|
+
this.diffuseMsaaView = null;
|
|
251
|
+
this.acrossView = null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SurfaceTextureOptions } from '../../surfaceTexture.ts';
|
|
2
|
+
import type { CompressedTextureFormat, SurfaceSource } from '../../compressedSource.ts';
|
|
2
3
|
import type { PipelineCache } from './pipelineCache.ts';
|
|
3
4
|
/** How many levels an image of this size has, counting the one it arrives with. */
|
|
4
5
|
export declare function mipLevelCount(width: number, height: number): number;
|
|
@@ -59,7 +60,9 @@ export declare class GpuSurfaceTexture {
|
|
|
59
60
|
private effectsTable;
|
|
60
61
|
/** Its view, which the flat group binds beside this image when it is a material's albedo. */
|
|
61
62
|
effectsView: GPUTextureView | null;
|
|
62
|
-
|
|
63
|
+
/** The block format a compressed texture holds, or null for one uploaded from images. */
|
|
64
|
+
private readonly blockFormat;
|
|
65
|
+
constructor(device: GPUDevice, pipelines: PipelineCache, source: SurfaceSource | readonly SurfaceSource[], options?: SurfaceTextureOptions, compressed?: readonly CompressedTextureFormat[]);
|
|
63
66
|
/** A view of every layer, as the shaders' `texture_2d_array` bindings read it. */
|
|
64
67
|
private arrayView;
|
|
65
68
|
/** The view every binding of this image holds. A new one when `update` changes the size. */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { SURFACE_EFFECT_TEXELS, packSurfaceEffects } from '../../surfaceEffects.js';
|
|
2
2
|
import { toHalfFloats } from '../../halfFloat.js';
|
|
3
|
-
import {
|
|
3
|
+
import { layerSize, refuseArrayUpdate, sourceSize } from '../../textureSource.js';
|
|
4
|
+
import { compressedLayers, planBlocks, refuseBlockUpdate } from '../../compressedSource.js';
|
|
5
|
+
import { createCompressedTexture } from './compressedUpload.js';
|
|
4
6
|
import { shaderModule } from './shaderModules.js';
|
|
5
7
|
/**
|
|
6
8
|
* A caller's image on the device, and the mip chain WebGPU will not build for it.
|
|
@@ -148,20 +150,41 @@ export class GpuSurfaceTexture {
|
|
|
148
150
|
effectsTable = null;
|
|
149
151
|
/** Its view, which the flat group binds beside this image when it is a material's albedo. */
|
|
150
152
|
effectsView = null;
|
|
151
|
-
|
|
153
|
+
/** The block format a compressed texture holds, or null for one uploaded from images. */
|
|
154
|
+
blockFormat;
|
|
155
|
+
constructor(device, pipelines, source, options = {}, compressed = []) {
|
|
152
156
|
this.device = device;
|
|
153
157
|
this.pipelines = pipelines;
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
this.mipmapped = options.mipmap ?? true;
|
|
158
|
+
const listed = (Array.isArray(source) ? source : [source]);
|
|
159
|
+
const blocks = compressedLayers(listed);
|
|
160
|
+
const srgb = (options.colorSpace ?? 'linear') === 'srgb';
|
|
158
161
|
/* `SRGB8_ALPHA8`'s equivalent. Decoded in the sampler, before filtering, which is the only
|
|
159
162
|
place it is correct — `surfaceTexture.ts` makes the argument in full. */
|
|
160
|
-
this.format =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
this.format = srgb ? 'rgba8unorm-srgb' : 'rgba8unorm';
|
|
164
|
+
if (blocks !== null) {
|
|
165
|
+
/* Refused before anything is allocated; the stored chain, or level 0 alone where no chain
|
|
166
|
+
was asked for. See `compressedUpload.ts`. */
|
|
167
|
+
const plan = planBlocks(blocks, srgb, compressed);
|
|
168
|
+
const first = blocks[0];
|
|
169
|
+
this.layers = blocks.length;
|
|
170
|
+
this.blockFormat = first.format;
|
|
171
|
+
this.mipmapped = (options.mipmap ?? true) && plan.levels > 1;
|
|
172
|
+
this.width = first.width;
|
|
173
|
+
this.height = first.height;
|
|
174
|
+
this.levels = this.mipmapped ? plan.levels : 1;
|
|
175
|
+
this.texture = createCompressedTexture(device, plan.name, blocks, this.levels);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const sources = listed;
|
|
179
|
+
const { width, height } = layerSize(sources);
|
|
180
|
+
this.layers = sources.length;
|
|
181
|
+
this.blockFormat = null;
|
|
182
|
+
this.mipmapped = options.mipmap ?? true;
|
|
183
|
+
this.width = width;
|
|
184
|
+
this.height = height;
|
|
185
|
+
this.levels = this.mipmapped ? mipLevelCount(width, height) : 1;
|
|
186
|
+
this.texture = this.allocate();
|
|
187
|
+
}
|
|
165
188
|
this.current = this.arrayView(this.texture);
|
|
166
189
|
const wrap = (options.wrap ?? 'repeat') === 'repeat' ? 'repeat' : 'clamp-to-edge';
|
|
167
190
|
/*
|
|
@@ -193,7 +216,8 @@ export class GpuSurfaceTexture {
|
|
|
193
216
|
? Math.max(1, Math.floor(options.anisotropy ?? DEFAULT_ANISOTROPY))
|
|
194
217
|
: 1,
|
|
195
218
|
});
|
|
196
|
-
|
|
219
|
+
if (blocks === null)
|
|
220
|
+
this.upload(listed);
|
|
197
221
|
/* Half floats, as `renderer.ts` uploads it: filterable, so the ordinary float layout takes it,
|
|
198
222
|
and read by `textureLoad` alone, so the filter never runs. */
|
|
199
223
|
const effects = options.effects ?? [];
|
|
@@ -236,6 +260,7 @@ export class GpuSurfaceTexture {
|
|
|
236
260
|
if (replaced === null)
|
|
237
261
|
return null;
|
|
238
262
|
refuseArrayUpdate(this.layers);
|
|
263
|
+
refuseBlockUpdate(this.blockFormat);
|
|
239
264
|
const { width, height } = sourceSize(source);
|
|
240
265
|
if (width === this.width && height === this.height) {
|
|
241
266
|
this.upload([source]);
|
|
@@ -3,6 +3,7 @@ import { TEXT_BINDINGS, TEXT_FRAG_WGSL, TEXT_VERT_WGSL, } from '../../shaders/ge
|
|
|
3
3
|
import { MAX_CELLS, TEXT_CUBE, TextLayout } from '../../textLayout.js';
|
|
4
4
|
import { DEPTH_FORMAT } from './flatPass.js';
|
|
5
5
|
import { shaderModule } from './shaderModules.js';
|
|
6
|
+
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
|
|
6
7
|
/**
|
|
7
8
|
* The text pass: a string as instanced glyph cubes, drawn over the scene.
|
|
8
9
|
*
|
|
@@ -174,7 +175,7 @@ export function textPipeline(cache, device, layout) {
|
|
|
174
175
|
format: cache.format,
|
|
175
176
|
blend: {
|
|
176
177
|
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
177
|
-
alpha:
|
|
178
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
178
179
|
},
|
|
179
180
|
},
|
|
180
181
|
],
|
|
@@ -3,6 +3,7 @@ import { WATER_BINDINGS, WATER_FRAG_WGSL, WATER_VERT_WGSL, } from '../../shaders
|
|
|
3
3
|
import { PATCH_RESOLUTION, buildUnitSheet, buildWaterGrid } from '../../waterGrid.js';
|
|
4
4
|
import { DEPTH_FORMAT } from './flatPass.js';
|
|
5
5
|
import { shaderModule } from './shaderModules.js';
|
|
6
|
+
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
|
|
6
7
|
/**
|
|
7
8
|
* The water pass: two sheets and a patch, displaced by a wave field in the vertex stage.
|
|
8
9
|
*
|
|
@@ -148,7 +149,7 @@ export function waterPipeline(cache, device, layout) {
|
|
|
148
149
|
format: cache.format,
|
|
149
150
|
blend: {
|
|
150
151
|
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
151
|
-
alpha:
|
|
152
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
152
153
|
},
|
|
153
154
|
},
|
|
154
155
|
],
|
|
@@ -3,6 +3,7 @@ import { WINDSTREAKS_BINDINGS, WIND_STREAK_FRAG_WGSL, WIND_STREAK_VERT_WGSL, } f
|
|
|
3
3
|
import { buildWindStreakLattice } from '../../windStreakDraw.js';
|
|
4
4
|
import { DEPTH_FORMAT } from './flatPass.js';
|
|
5
5
|
import { shaderModule } from './shaderModules.js';
|
|
6
|
+
import { SCENE_ALPHA_COVERS } from '../../sceneCoverage.js';
|
|
6
7
|
/**
|
|
7
8
|
* The wind-streak pass: a lattice of debris that follows the camera and wraps.
|
|
8
9
|
*
|
|
@@ -141,7 +142,7 @@ export function windStreakPipeline(cache, device, layout) {
|
|
|
141
142
|
dstFactor: 'one-minus-src-alpha',
|
|
142
143
|
operation: 'add',
|
|
143
144
|
},
|
|
144
|
-
alpha:
|
|
145
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
145
146
|
},
|
|
146
147
|
},
|
|
147
148
|
],
|
package/dist/render/boltBatch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compileProgram, uniformLocations } from './shader.js';
|
|
2
2
|
import { BOLT_FRAG, BOLT_VERT } from './shaders/bolt.js';
|
|
3
3
|
import { buildSegmentQuads, expandBoltSegments } from './segmentQuads.js';
|
|
4
|
+
import { blendKeeping } from './sceneCoverage.js';
|
|
4
5
|
/**
|
|
5
6
|
* One draw call for a pool of electrical arcs.
|
|
6
7
|
*
|
|
@@ -80,7 +81,7 @@ export class BoltBatch {
|
|
|
80
81
|
if (segmentCount === 0)
|
|
81
82
|
return;
|
|
82
83
|
gl.enable(gl.BLEND);
|
|
83
|
-
|
|
84
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
84
85
|
gl.depthMask(false);
|
|
85
86
|
gl.disable(gl.CULL_FACE);
|
|
86
87
|
gl.bindVertexArray(this.vao);
|
|
@@ -3,6 +3,7 @@ import { CAUSTICS_FRAG, CAUSTICS_VERT } from './shaders/caustics.js';
|
|
|
3
3
|
import { bindAtmosphere } from './atmosphere.js';
|
|
4
4
|
import { buildSheets } from './surfaceSheet.js';
|
|
5
5
|
import { seaStateForWind } from './seaState.js';
|
|
6
|
+
import { blendKeeping } from './sceneCoverage.js';
|
|
6
7
|
/**
|
|
7
8
|
* How far from the water the effect has died out.
|
|
8
9
|
*
|
|
@@ -97,7 +98,7 @@ export class CausticsRenderer {
|
|
|
97
98
|
gl.uniform1f(u['uWaveGain'] ?? null, sea.steepness);
|
|
98
99
|
bindAtmosphere(gl, u, atmosphere, camera.position[1] ?? 0, underwaterEnabled);
|
|
99
100
|
gl.enable(gl.BLEND);
|
|
100
|
-
|
|
101
|
+
blendKeeping(gl, gl.ONE, gl.ONE);
|
|
101
102
|
/*
|
|
102
103
|
* No depth write, but depth *testing* stays on: the sheet sits a couple of
|
|
103
104
|
* centimetres under the surface it lights, so geometry in front of it — the
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
/** A mesh's cloth binding, one entry a render vertex unless said otherwise. */
|
|
17
|
+
export interface ClothBindingData {
|
|
18
|
+
/** Three particle indices a vertex. */
|
|
19
|
+
readonly triangles: Uint32Array;
|
|
20
|
+
/** Barycentric u and v a vertex, for the second and third particle; the first takes the rest. */
|
|
21
|
+
readonly coordinates: Float32Array;
|
|
22
|
+
/** Metres along the triangle's normal. */
|
|
23
|
+
readonly offsets: Float32Array;
|
|
24
|
+
/** How much of the cloth's place a vertex takes: 0 skinned, 1 cloth. */
|
|
25
|
+
readonly weights: Float32Array;
|
|
26
|
+
/** The particles at rest, three floats each, in the mesh's model space. */
|
|
27
|
+
readonly rest: Float32Array;
|
|
28
|
+
}
|
|
29
|
+
/** The widest row a texture is wrapped onto. */
|
|
30
|
+
export declare const CLOTH_TEXTURE_WIDTH = 2048;
|
|
31
|
+
/** A texture of `texels`, wrapped onto rows. */
|
|
32
|
+
export declare function clothTextureSize(texels: number): {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
};
|
|
36
|
+
/** Refuse a binding whose arrays disagree with a mesh of `vertices`, or that names a missing particle. */
|
|
37
|
+
export declare function validateClothBinding(data: ClothBindingData, vertices: number): void;
|
|
38
|
+
/** The binding as RGBA float texels, two a vertex, padded to whole rows. */
|
|
39
|
+
export declare function packClothBinding(data: ClothBindingData): Float32Array;
|
|
40
|
+
/** Particles, three floats each, into RGBA texels in `out`. Allocation-free, for every frame. */
|
|
41
|
+
export declare function packClothParticles(positions: Float32Array, out: Float32Array): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
/** The widest row a texture is wrapped onto. */
|
|
17
|
+
export const CLOTH_TEXTURE_WIDTH = 2048;
|
|
18
|
+
/** A texture of `texels`, wrapped onto rows. */
|
|
19
|
+
export function clothTextureSize(texels) {
|
|
20
|
+
const width = Math.max(1, Math.min(CLOTH_TEXTURE_WIDTH, texels));
|
|
21
|
+
return { width, height: Math.max(1, Math.ceil(texels / width)) };
|
|
22
|
+
}
|
|
23
|
+
/** Refuse a binding whose arrays disagree with a mesh of `vertices`, or that names a missing particle. */
|
|
24
|
+
export function validateClothBinding(data, vertices) {
|
|
25
|
+
const count = data.weights.length;
|
|
26
|
+
const fail = (what) => {
|
|
27
|
+
throw new Error(`cloth binding: ${what}`);
|
|
28
|
+
};
|
|
29
|
+
if (count !== vertices)
|
|
30
|
+
fail(`binds ${count} vertices and the mesh has ${vertices}`);
|
|
31
|
+
if (data.triangles.length !== count * 3)
|
|
32
|
+
fail(`${data.triangles.length / 3} triangles for ${count} vertices`);
|
|
33
|
+
if (data.coordinates.length !== count * 2)
|
|
34
|
+
fail(`${data.coordinates.length / 2} coordinates for ${count} vertices`);
|
|
35
|
+
if (data.offsets.length !== count)
|
|
36
|
+
fail(`${data.offsets.length} offsets for ${count} vertices`);
|
|
37
|
+
const particles = data.rest.length / 3;
|
|
38
|
+
if (!Number.isInteger(particles) || particles === 0)
|
|
39
|
+
fail(`rest holds ${data.rest.length} floats`);
|
|
40
|
+
for (let i = 0; i < data.triangles.length; i++) {
|
|
41
|
+
if (data.triangles[i] >= particles) {
|
|
42
|
+
fail(`vertex ${Math.floor(i / 3)} names particle ${data.triangles[i]} of ${particles}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** The binding as RGBA float texels, two a vertex, padded to whole rows. */
|
|
47
|
+
export function packClothBinding(data) {
|
|
48
|
+
const vertices = data.weights.length;
|
|
49
|
+
const { width, height } = clothTextureSize(vertices * 2);
|
|
50
|
+
const out = new Float32Array(width * height * 4);
|
|
51
|
+
for (let v = 0; v < vertices; v++) {
|
|
52
|
+
const at = v * 8;
|
|
53
|
+
out[at] = data.triangles[v * 3];
|
|
54
|
+
out[at + 1] = data.triangles[v * 3 + 1];
|
|
55
|
+
out[at + 2] = data.triangles[v * 3 + 2];
|
|
56
|
+
out[at + 3] = data.weights[v];
|
|
57
|
+
out[at + 4] = data.coordinates[v * 2];
|
|
58
|
+
out[at + 5] = data.coordinates[v * 2 + 1];
|
|
59
|
+
out[at + 6] = data.offsets[v];
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/** Particles, three floats each, into RGBA texels in `out`. Allocation-free, for every frame. */
|
|
64
|
+
export function packClothParticles(positions, out) {
|
|
65
|
+
const count = positions.length / 3;
|
|
66
|
+
for (let i = 0; i < count; i++) {
|
|
67
|
+
out[i * 4] = positions[i * 3];
|
|
68
|
+
out[i * 4 + 1] = positions[i * 3 + 1];
|
|
69
|
+
out[i * 4 + 2] = positions[i * 3 + 2];
|
|
70
|
+
out[i * 4 + 3] = 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a cloth-bound vertex goes: the reference for what the vertex stage computes.
|
|
3
|
+
*
|
|
4
|
+
* A render vertex bound to a simulation triangle (particles a, b, c) sits at its barycentric point
|
|
5
|
+
* — `a·(1 − u − v) + b·u + c·v` — raised `offset` along the triangle's normal; its normal turns as
|
|
6
|
+
* the triangle turned since rest. The turn is the rotation from the triangle's rest frame
|
|
7
|
+
* to its frame now, each frame built from its first edge and its normal, so a vertex beside the
|
|
8
|
+
* triangle, a fold's width away, moves as the cloth moves rather than as its skeleton does.
|
|
9
|
+
*
|
|
10
|
+
* **A vertex bound to one particle** — all three indices equal — sits on it and keeps its normal:
|
|
11
|
+
* a single point has no frame to turn by. That is how a binding says "follow this particle".
|
|
12
|
+
*
|
|
13
|
+
* **A reference, as `recon/motionVectors.ts` is**: nothing at run time calls it. The vertex stage in
|
|
14
|
+
* `shaders/clothBinding.ts` and the cloth motion stage in `shaders/recon/motion.wgsl.ts` are the
|
|
15
|
+
* same arithmetic for the device, and nothing but a device holds them to this — the source tests
|
|
16
|
+
* pin where the shader moves a vertex, not the numbers it moves it by. `demo/dev/skinnedCloth.html`
|
|
17
|
+
* is where the two backends were compared, and they agree to two levels in 255.
|
|
18
|
+
*/
|
|
19
|
+
/** Where a placement lands, written into caller-owned arrays. */
|
|
20
|
+
export interface ClothPlacement {
|
|
21
|
+
readonly position: Float64Array;
|
|
22
|
+
readonly normal: Float64Array;
|
|
23
|
+
}
|
|
24
|
+
/** Place one vertex: `now` and `rest` are particle positions, three floats each. */
|
|
25
|
+
export declare function clothPlace(now: ArrayLike<number>, rest: ArrayLike<number>, a: number, b: number, c: number, u: number, v: number, offset: number, restNormal: readonly [number, number, number], out: ClothPlacement): void;
|