@driftengine/core 4.8.3 → 4.8.5
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 +82 -0
- package/README.md +1 -1
- package/dist/build/shaderComments.js +0 -0
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +16 -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/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 +136 -3
- package/dist/render/backend/webgl2/renderer.js +604 -63
- 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/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 +16 -6
- package/dist/render/backend/webgpu/depthPass.js +39 -9
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +38 -4
- package/dist/render/backend/webgpu/flatPass.js +109 -12
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/postPass.d.ts +3 -1
- package/dist/render/backend/webgpu/postPass.js +6 -3
- package/dist/render/backend/webgpu/renderer.d.ts +113 -5
- package/dist/render/backend/webgpu/renderer.js +498 -48
- 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/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 +2 -0
- package/dist/render/cutoutCaster.js +2 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- 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/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/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +20 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +42 -6
- 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 +36 -2
- 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 +38 -16
- package/dist/render/shaders/generated/depth.wgsl.js +38 -16
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1790 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1828 -1742
- 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/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- 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/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/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +44 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/vertexDefaults.js +7 -0
- package/package.json +6 -6
- package/src/build/shaderComments.ts +0 -0
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +40 -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/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +693 -64
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- 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 +39 -7
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/flatPass.ts +145 -7
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/postPass.ts +5 -1
- package/src/render/backend/webgpu/renderer.ts +663 -43
- 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/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +5 -0
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/mesh.ts +23 -0
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +28 -0
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +42 -6
- 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 +36 -2
- 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 +38 -16
- package/src/render/shaders/generated/flat.wgsl.ts +1828 -1742
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- 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/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +123 -63
- package/src/render/vertexDefaults.ts +7 -0
- 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,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Generated from ../skinBlur.ts by `npm run wgsl`. Do not edit.
|
|
3
|
+
*
|
|
4
|
+
* The GLSL beside this file is the source of truth. A hand edit here is discarded by
|
|
5
|
+
* the next generation, and `npm run wgsl:check` fails the build when this is stale.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const SKIN_BLUR_FRAG_WGSL = "struct Uniforms {\n uStep: vec2<f32>,\n uDepthToViewZ: vec4<f32>,\n uFocal: f32,\n uProfiles: array<vec4<f32>, 8>,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uSkin_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uSkin_s: sampler;\nvar<private> vUv_1: vec2<f32>;\nvar<private> fragColor: vec4<f32>;\n@group(0) @binding(34) \nvar uDepth_t: texture_2d<f32>;\n@group(0) @binding(35) \nvar uDepth_s: sampler;\n\nfn burleyMass_u0028_f1_u003b_vf3_u003b(x: ptr<function, f32>, d: ptr<function, vec3<f32>>) -> vec3<f32> {\n let _e51 = (*x);\n let _e53 = (*d);\n let _e57 = (*x);\n let _e59 = (*d);\n return (vec3(0.5f) - ((exp((vec3(-(_e51)) / _e53)) + (exp((vec3(-(_e57)) / (_e59 * 3f))) * 3f)) * 0.125f));\n}\n\nfn viewZ_u0028_f1_u003b(depth: ptr<function, f32>) -> f32 {\n var ndc: f32;\n\n let _e51 = (*depth);\n ndc = (1f - (_e51 * 2f));\n let _e56 = unnamed.uDepthToViewZ[0u];\n let _e57 = ndc;\n let _e61 = unnamed.uDepthToViewZ[1u];\n let _e65 = unnamed.uDepthToViewZ[2u];\n let _e66 = ndc;\n let _e70 = unnamed.uDepthToViewZ[3u];\n return (((_e56 * _e57) + _e61) / ((_e65 * _e66) + _e70));\n}\n\nfn main_1() {\n var own: vec4<f32>;\n var depth_1: f32;\n var profile: i32;\n var d_1: vec3<f32>;\n var widest: f32;\n var z: f32;\n var param: f32;\n var pixels: f32;\n var centre: vec3<f32>;\n var param_1: f32;\n var param_2: vec3<f32>;\n var sum: vec3<f32>;\n var weight: vec3<f32>;\n var i: i32;\n var outer: vec3<f32>;\n var local: vec3<f32>;\n var indexable: array<f32, 6>;\n var param_3: f32;\n var param_4: vec3<f32>;\n var w: vec3<f32>;\n var indexable_1: array<f32, 6>;\n var param_5: f32;\n var param_6: vec3<f32>;\n var side: i32;\n var uv: vec2<f32>;\n var indexable_2: array<f32, 7>;\n var tap: vec4<f32>;\n var tapZ: f32;\n var param_7: f32;\n var keep: f32;\n var cover: f32;\n\n let _e80 = vUv_1;\n let _e81 = textureSampleLevel(uSkin_t, uSkin_s, _e80, 0f);\n own = _e81;\n let _e83 = own[3u];\n if (_e83 < 0.0001f) {\n fragColor = vec4<f32>(0f, 0f, 0f, 0f);\n return;\n }\n let _e85 = vUv_1;\n let _e86 = textureSampleLevel(uDepth_t, uDepth_s, _e85, 0f);\n depth_1 = _e86.x;\n let _e88 = depth_1;\n if (_e88 <= 0f) {\n let _e90 = own;\n fragColor = _e90;\n return;\n }\n let _e92 = own[3u];\n profile = clamp((i32(((_e92 * 8f) + 0.5f)) - 1i), 0i, 7i);\n let _e98 = profile;\n let _e101 = unnamed.uProfiles[_e98];\n d_1 = max(_e101.xyz, vec3<f32>(0.000001f, 0.000001f, 0.000001f));\n let _e105 = d_1[0u];\n let _e107 = d_1[1u];\n let _e109 = d_1[2u];\n widest = max(_e105, max(_e107, _e109));\n let _e112 = depth_1;\n param = _e112;\n let _e113 = viewZ_u0028_f1_u003b((¶m));\n z = abs(_e113);\n let _e115 = widest;\n let _e117 = unnamed.uFocal;\n let _e119 = z;\n pixels = ((_e115 * _e117) / max(_e119, 0.0001f));\n let _e122 = pixels;\n if (_e122 < 0.5f) {\n let _e124 = own;\n fragColor = _e124;\n return;\n }\n let _e125 = widest;\n param_1 = (0.2f * _e125);\n let _e127 = d_1;\n param_2 = _e127;\n let _e128 = burleyMass_u0028_f1_u003b_vf3_u003b((¶m_1), (¶m_2));\n centre = (_e128 * 2f);\n let _e130 = own;\n let _e132 = centre;\n sum = (_e130.xyz * _e132);\n let _e134 = centre;\n weight = _e134;\n i = 1i;\n loop {\n let _e135 = i;\n if (_e135 < 7i) {\n let _e137 = i;\n if (_e137 < 6i) {\n let _e139 = i;\n indexable = array<f32, 6>(0.2f, 0.75f, 1.6f, 2.8f, 4.55f, 7.3f);\n let _e142 = indexable[min(_e139, 5i)];\n let _e143 = widest;\n param_3 = (_e142 * _e143);\n let _e145 = d_1;\n param_4 = _e145;\n let _e146 = burleyMass_u0028_f1_u003b_vf3_u003b((¶m_3), (¶m_4));\n local = _e146;\n } else {\n local = vec3<f32>(0.5f, 0.5f, 0.5f);\n }\n let _e147 = local;\n outer = _e147;\n let _e148 = outer;\n let _e149 = i;\n indexable_1 = array<f32, 6>(0.2f, 0.75f, 1.6f, 2.8f, 4.55f, 7.3f);\n let _e152 = indexable_1[(_e149 - 1i)];\n let _e153 = widest;\n param_5 = (_e152 * _e153);\n let _e155 = d_1;\n param_6 = _e155;\n let _e156 = burleyMass_u0028_f1_u003b_vf3_u003b((¶m_5), (¶m_6));\n w = (_e148 - _e156);\n side = -1i;\n loop {\n let _e158 = side;\n if (_e158 <= 1i) {\n let _e160 = vUv_1;\n let _e162 = unnamed.uStep;\n let _e163 = side;\n let _e165 = i;\n indexable_2 = array<f32, 7>(0f, 0.4f, 1.1f, 2.1f, 3.5f, 5.6f, 9f);\n let _e167 = indexable_2[_e165];\n let _e169 = pixels;\n uv = (_e160 + (_e162 * ((f32(_e163) * _e167) * _e169)));\n let _e173 = uv;\n let _e174 = textureSampleLevel(uSkin_t, uSkin_s, _e173, 0f);\n tap = _e174;\n let _e175 = uv;\n let _e176 = textureSampleLevel(uDepth_t, uDepth_s, _e175, 0f);\n param_7 = _e176.x;\n let _e178 = viewZ_u0028_f1_u003b((¶m_7));\n tapZ = abs(_e178);\n let _e180 = tapZ;\n let _e181 = z;\n let _e184 = widest;\n keep = clamp((1f - (abs((_e180 - _e181)) / (4f * _e184))), 0f, 1f);\n let _e190 = tap[3u];\n let _e192 = own[3u];\n cover = clamp((_e190 / _e192), 0f, 1f);\n let _e195 = tap;\n let _e197 = w;\n let _e199 = keep;\n let _e201 = sum;\n sum = (_e201 + ((_e195.xyz * _e197) * _e199));\n let _e203 = w;\n let _e204 = cover;\n let _e205 = keep;\n let _e208 = weight;\n weight = (_e208 + (_e203 * (_e204 * _e205)));\n continue;\n } else {\n break;\n }\n continuing {\n let _e210 = side;\n side = (_e210 + 2i);\n }\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e212 = i;\n i = (_e212 + 1i);\n }\n }\n let _e214 = sum;\n let _e215 = weight;\n let _e216 = (_e214 / _e215);\n let _e218 = own[3u];\n fragColor = vec4<f32>(_e216.x, _e216.y, _e216.z, _e218);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>) -> @location(0) vec4<f32> {\n vUv_1 = vUv;\n main_1();\n let _e3 = fragColor;\n return _e3;\n}\n";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What the transform assigned, so the renderer binds the same numbers.
|
|
12
|
+
*
|
|
13
|
+
* A permuted shader has one entry per variant, keyed as `FLAT_FRAG_WGSL` is.
|
|
14
|
+
*/
|
|
15
|
+
export const SKINBLUR_BINDINGS = {
|
|
16
|
+
"SKIN_BLUR_FRAG": {
|
|
17
|
+
"uniforms": 1,
|
|
18
|
+
"uniformSize": 176,
|
|
19
|
+
"fields": {
|
|
20
|
+
"uStep": {
|
|
21
|
+
"offset": 0,
|
|
22
|
+
"size": 8,
|
|
23
|
+
"type": "vec2"
|
|
24
|
+
},
|
|
25
|
+
"uDepthToViewZ": {
|
|
26
|
+
"offset": 16,
|
|
27
|
+
"size": 16,
|
|
28
|
+
"type": "vec4"
|
|
29
|
+
},
|
|
30
|
+
"uFocal": {
|
|
31
|
+
"offset": 32,
|
|
32
|
+
"size": 4,
|
|
33
|
+
"type": "float"
|
|
34
|
+
},
|
|
35
|
+
"uProfiles": {
|
|
36
|
+
"offset": 48,
|
|
37
|
+
"size": 128,
|
|
38
|
+
"type": "vec4",
|
|
39
|
+
"length": 8,
|
|
40
|
+
"stride": 16
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"textures": {
|
|
44
|
+
"uSkin": {
|
|
45
|
+
"texture": 32,
|
|
46
|
+
"sampler": 33,
|
|
47
|
+
"type": "sampler2D"
|
|
48
|
+
},
|
|
49
|
+
"uDepth": {
|
|
50
|
+
"texture": 34,
|
|
51
|
+
"sampler": 35,
|
|
52
|
+
"type": "sampler2D"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} as const;
|
|
@@ -128,6 +128,108 @@ fn motionSkinnedVert(
|
|
|
128
128
|
return out;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/*
|
|
132
|
+
* The same for a rig with eight influences a vertex, the second four at the scene's own locations
|
|
133
|
+
* 14 and 15 and summed after the first, as \`skinMatrix()\` sums them with \`SKIN_EIGHT\` on — or the
|
|
134
|
+
* motion of a face or a shoulder would be the four heaviest joints' alone.
|
|
135
|
+
*/
|
|
136
|
+
@vertex
|
|
137
|
+
fn motionSkinnedEightVert(
|
|
138
|
+
@location(0) position: vec3<f32>,
|
|
139
|
+
@location(11) joints: vec4<f32>,
|
|
140
|
+
@location(12) weights: vec4<f32>,
|
|
141
|
+
@location(14) joints2: vec4<f32>,
|
|
142
|
+
@location(15) weights2: vec4<f32>,
|
|
143
|
+
) -> Varyings {
|
|
144
|
+
let skin = skinOf(palette, joints, weights) + skinOf(palette, joints2, weights2);
|
|
145
|
+
let wasSkin =
|
|
146
|
+
skinOf(previousPalette, joints, weights) + skinOf(previousPalette, joints2, weights2);
|
|
147
|
+
let world = draw.model * (skin * vec4<f32>(position, 1.0));
|
|
148
|
+
let wasWorld = draw.previousModel * (wasSkin * vec4<f32>(position, 1.0));
|
|
149
|
+
var out: Varyings;
|
|
150
|
+
out.clip = frame.raster * world;
|
|
151
|
+
out.now = frame.viewProj * world;
|
|
152
|
+
out.was = frame.previousViewProj * wasWorld;
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* The cloth-bound stage: a skinned vertex moved toward its simulation triangle by its painted
|
|
158
|
+
* weight, as shaders/clothBinding.ts moves it in the scene, once with this frame's particles and
|
|
159
|
+
* once with last frame's. **The same arithmetic in the same order**, or the depth this stage writes
|
|
160
|
+
* is not the scene's and the equal test admits none of the garment. The binding's turn is not
|
|
161
|
+
* needed: a motion is where a point went, not which way it faces.
|
|
162
|
+
*/
|
|
163
|
+
@group(2) @binding(0) var clothBinding: texture_2d<f32>;
|
|
164
|
+
@group(2) @binding(1) var clothParticles: texture_2d<f32>;
|
|
165
|
+
@group(2) @binding(2) var previousClothParticles: texture_2d<f32>;
|
|
166
|
+
|
|
167
|
+
fn clothTexel(i: i32, width: i32) -> vec2<i32> {
|
|
168
|
+
return vec2<i32>(i % width, i / width);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fn clothPoint(particles: texture_2d<f32>, indices: vec4<f32>, at: vec4<f32>) -> vec3<f32> {
|
|
172
|
+
let width = i32(textureDimensions(particles, 0).x);
|
|
173
|
+
let a = i32(indices.x);
|
|
174
|
+
let b = i32(indices.y);
|
|
175
|
+
let c = i32(indices.z);
|
|
176
|
+
let pa = textureLoad(particles, clothTexel(a, width), 0).xyz;
|
|
177
|
+
if (a == b && b == c) {
|
|
178
|
+
return pa;
|
|
179
|
+
}
|
|
180
|
+
let pb = textureLoad(particles, clothTexel(b, width), 0).xyz;
|
|
181
|
+
let pc = textureLoad(particles, clothTexel(c, width), 0).xyz;
|
|
182
|
+
let n = normalize(cross(pb - pa, pc - pa));
|
|
183
|
+
return pa * (1.0 - at.x - at.y) + pb * at.x + pc * at.y + n * at.z;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* A skinned point now and last frame, carried to the cloth by the vertex's own weight. */
|
|
187
|
+
fn clothVaryings(vertex: u32, world: vec4<f32>, wasWorld: vec4<f32>) -> Varyings {
|
|
188
|
+
let width = i32(textureDimensions(clothBinding, 0).x);
|
|
189
|
+
let indices = textureLoad(clothBinding, clothTexel(i32(vertex) * 2, width), 0);
|
|
190
|
+
let at = textureLoad(clothBinding, clothTexel(i32(vertex) * 2 + 1, width), 0);
|
|
191
|
+
let follow = indices.w;
|
|
192
|
+
let placed = vec4<f32>(mix(world.xyz, clothPoint(clothParticles, indices, at), follow), world.w);
|
|
193
|
+
let wasPlaced = vec4<f32>(
|
|
194
|
+
mix(wasWorld.xyz, clothPoint(previousClothParticles, indices, at), follow),
|
|
195
|
+
wasWorld.w);
|
|
196
|
+
var out: Varyings;
|
|
197
|
+
out.clip = frame.raster * placed;
|
|
198
|
+
out.now = frame.viewProj * placed;
|
|
199
|
+
out.was = frame.previousViewProj * wasPlaced;
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@vertex
|
|
204
|
+
fn motionClothVert(
|
|
205
|
+
@builtin(vertex_index) vertex: u32,
|
|
206
|
+
@location(0) position: vec3<f32>,
|
|
207
|
+
@location(11) joints: vec4<f32>,
|
|
208
|
+
@location(12) weights: vec4<f32>,
|
|
209
|
+
) -> Varyings {
|
|
210
|
+
let world = draw.model * (skinOf(palette, joints, weights) * vec4<f32>(position, 1.0));
|
|
211
|
+
let wasWorld =
|
|
212
|
+
draw.previousModel * (skinOf(previousPalette, joints, weights) * vec4<f32>(position, 1.0));
|
|
213
|
+
return clothVaryings(vertex, world, wasWorld);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@vertex
|
|
217
|
+
fn motionClothEightVert(
|
|
218
|
+
@builtin(vertex_index) vertex: u32,
|
|
219
|
+
@location(0) position: vec3<f32>,
|
|
220
|
+
@location(11) joints: vec4<f32>,
|
|
221
|
+
@location(12) weights: vec4<f32>,
|
|
222
|
+
@location(14) joints2: vec4<f32>,
|
|
223
|
+
@location(15) weights2: vec4<f32>,
|
|
224
|
+
) -> Varyings {
|
|
225
|
+
let skin = skinOf(palette, joints, weights) + skinOf(palette, joints2, weights2);
|
|
226
|
+
let wasSkin =
|
|
227
|
+
skinOf(previousPalette, joints, weights) + skinOf(previousPalette, joints2, weights2);
|
|
228
|
+
let world = draw.model * (skin * vec4<f32>(position, 1.0));
|
|
229
|
+
let wasWorld = draw.previousModel * (wasSkin * vec4<f32>(position, 1.0));
|
|
230
|
+
return clothVaryings(vertex, world, wasWorld);
|
|
231
|
+
}
|
|
232
|
+
|
|
131
233
|
/*
|
|
132
234
|
* The rewritten stage: a mesh whose vertices were rewritten this frame reads last frame's rows from
|
|
133
235
|
* a second buffer of the same stride, so each vertex carries both of its positions.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skin's screen-space scattering: one axis of the blur that spreads the diffuse pass by Burley's
|
|
3
|
+
* profile, and on the second axis the sum the frame takes back. `skinBlur.ts` beside the render
|
|
4
|
+
* directory is the kernel's reference and says what it gives up.
|
|
5
|
+
*
|
|
6
|
+
* **One pass, run twice**: along x into a target of its own, keeping the profile in the alpha, then
|
|
7
|
+
* along y blended additively into the frame — its colour added, the frame's alpha kept.
|
|
8
|
+
*
|
|
9
|
+
* **Refuses what is not the same skin**: a neighbour that wrote no skin, or one further in depth
|
|
10
|
+
* than four of the profile's widest distances, takes no weight, and what is kept is divided by
|
|
11
|
+
* what was kept, so a cheek's light does not pull toward the black beside its silhouette. A tap
|
|
12
|
+
* part skin is counted for the part it is: the target is read as coverage-weighted, which is what
|
|
13
|
+
* a filtered tap across an edge already is.
|
|
14
|
+
*/
|
|
15
|
+
import { glslIsFarDepth, glslSceneDepthToNdc } from '../depthConvention.ts';
|
|
16
|
+
import { SKIN_BLUR_EDGES, SKIN_BLUR_TAPS, SKIN_PROFILES } from '../skinBlur.ts';
|
|
17
|
+
|
|
18
|
+
const list = (values: readonly number[]): string => values.map((v) => v.toFixed(2)).join(', ');
|
|
19
|
+
|
|
20
|
+
export const SKIN_BLUR_FRAG = `#version 300 es
|
|
21
|
+
precision highp float;
|
|
22
|
+
|
|
23
|
+
in vec2 vUv;
|
|
24
|
+
|
|
25
|
+
/** The diffuse pass, or the first axis' result: diffuse in rgb, the profile as (index + 1) / 8. */
|
|
26
|
+
uniform sampler2D uSkin;
|
|
27
|
+
/** highp, for the reason the ambient occlusion blur gives: eight bits of depth find false edges. */
|
|
28
|
+
uniform highp sampler2D uDepth;
|
|
29
|
+
/** One texel along the axis being blurred, in UV. The other component is zero. */
|
|
30
|
+
uniform vec2 uStep;
|
|
31
|
+
/** A depth sample to view-space metres, as the ambient occlusion blur takes it. */
|
|
32
|
+
uniform vec4 uDepthToViewZ;
|
|
33
|
+
/** How many pixels a metre spans at a metre's distance. */
|
|
34
|
+
uniform float uFocal;
|
|
35
|
+
/** Each profile's scatter distance per channel, in metres. */
|
|
36
|
+
uniform vec4 uProfiles[${SKIN_PROFILES}];
|
|
37
|
+
|
|
38
|
+
out vec4 fragColor;
|
|
39
|
+
|
|
40
|
+
const float TAPS[${SKIN_BLUR_TAPS.length}] = float[${SKIN_BLUR_TAPS.length}](${list(SKIN_BLUR_TAPS)});
|
|
41
|
+
const float EDGES[${SKIN_BLUR_EDGES.length}] = float[${SKIN_BLUR_EDGES.length}](${list(SKIN_BLUR_EDGES)});
|
|
42
|
+
|
|
43
|
+
float viewZ(float depth) {
|
|
44
|
+
float ndc = ${glslSceneDepthToNdc('depth')};
|
|
45
|
+
return (uDepthToViewZ.x * ndc + uDepthToViewZ.y) / (uDepthToViewZ.z * ndc + uDepthToViewZ.w);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Burley's profile across a line, integrated from 0 to x: one half at infinity. */
|
|
49
|
+
vec3 burleyMass(float x, vec3 d) {
|
|
50
|
+
return 0.5 - (exp(-x / d) + 3.0 * exp(-x / (3.0 * d))) * 0.125;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
void main() {
|
|
54
|
+
vec4 own = textureLod(uSkin, vUv, 0.0);
|
|
55
|
+
/*
|
|
56
|
+
* Not skin: nothing here to spread, and nothing to add. Any alpha is skin, because a pixel a
|
|
57
|
+
* silhouette crosses under multisampling resolves to its covered share of the profile's code, and
|
|
58
|
+
* its colour to that share of the diffuse, which is exactly what it owes the frame.
|
|
59
|
+
*/
|
|
60
|
+
if (own.a < 1e-4) {
|
|
61
|
+
fragColor = vec4(0.0);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
float depth = textureLod(uDepth, vUv, 0.0).r;
|
|
65
|
+
if (${glslIsFarDepth('depth')}) {
|
|
66
|
+
fragColor = own;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
/* A part-covered pixel's code is its share of the whole: read it as the nearest, which is the
|
|
70
|
+
right profile at half coverage and up, and a neighbouring one only on a silhouette below it. */
|
|
71
|
+
int profile = clamp(int(own.a * 8.0 + 0.5) - 1, 0, ${SKIN_PROFILES - 1});
|
|
72
|
+
vec3 d = max(uProfiles[profile].xyz, vec3(1e-6));
|
|
73
|
+
float widest = max(d.r, max(d.g, d.b));
|
|
74
|
+
float z = abs(viewZ(depth));
|
|
75
|
+
/* The widest distance in pixels here: under half of one, there is nothing to spread across. */
|
|
76
|
+
float pixels = widest * uFocal / max(z, 1e-4);
|
|
77
|
+
if (pixels < 0.5) {
|
|
78
|
+
fragColor = own;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
vec3 centre = 2.0 * burleyMass(EDGES[0] * widest, d);
|
|
82
|
+
vec3 sum = own.rgb * centre;
|
|
83
|
+
vec3 weight = centre;
|
|
84
|
+
for (int i = 1; i < ${SKIN_BLUR_TAPS.length}; i++) {
|
|
85
|
+
vec3 outer = i < ${SKIN_BLUR_EDGES.length} ? burleyMass(EDGES[min(i, ${SKIN_BLUR_EDGES.length - 1})] * widest, d) : vec3(0.5);
|
|
86
|
+
vec3 w = outer - burleyMass(EDGES[i - 1] * widest, d);
|
|
87
|
+
for (int side = -1; side <= 1; side += 2) {
|
|
88
|
+
vec2 uv = vUv + uStep * (float(side) * TAPS[i] * pixels);
|
|
89
|
+
vec4 tap = textureLod(uSkin, uv, 0.0);
|
|
90
|
+
float tapZ = abs(viewZ(textureLod(uDepth, uv, 0.0).r));
|
|
91
|
+
float keep = clamp(1.0 - abs(tapZ - z) / (4.0 * widest), 0.0, 1.0);
|
|
92
|
+
/*
|
|
93
|
+
* How much of the tap is skin: a filtered tap astride a silhouette is part skin and part
|
|
94
|
+
* the black beside it, in colour and in alpha alike, so its alpha over this pixel's says the
|
|
95
|
+
* share, and the colour is already that share of the skin's — counted as such, it neither
|
|
96
|
+
* darkens the edge nor drops out.
|
|
97
|
+
*/
|
|
98
|
+
float cover = clamp(tap.a / own.a, 0.0, 1.0);
|
|
99
|
+
sum += tap.rgb * w * keep;
|
|
100
|
+
weight += w * (cover * keep);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
fragColor = vec4(sum / weight, own.a);
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
@@ -31,6 +31,18 @@ uniform highp sampler2D uJointPalette;
|
|
|
31
31
|
|
|
32
32
|
layout(location = 11) in vec4 aJoints;
|
|
33
33
|
layout(location = 12) in vec4 aWeights;
|
|
34
|
+
/*
|
|
35
|
+
* **The fifth to eighth influences, read only by a pipeline built for a mesh that has them.**
|
|
36
|
+
*
|
|
37
|
+
* Declared in every skinned variant at the last two locations WebGL2 guarantees — 13 is the channel,
|
|
38
|
+
* and an instanced draw, which takes 11 to 15, cannot skin — and fed zeros by the absent-attribute
|
|
39
|
+
* constants for a mesh with four. \`SKIN_EIGHT\` is a pipeline constant rather than a permutation,
|
|
40
|
+
* so the generated vertex stages do not double: off, the device compiles the second sum away and a
|
|
41
|
+
* four-influence mesh pays nothing; on, it is four more palette reads a vertex.
|
|
42
|
+
*/
|
|
43
|
+
const bool SKIN_EIGHT = false; // wgsl:override
|
|
44
|
+
layout(location = 14) in vec4 aJoints2;
|
|
45
|
+
layout(location = 15) in vec4 aWeights2;
|
|
34
46
|
|
|
35
47
|
/** One joint's matrix, read as four consecutive texels of row 0. */
|
|
36
48
|
mat4 jointMatrix(int index) {
|
|
@@ -51,9 +63,24 @@ mat4 jointMatrix(int index) {
|
|
|
51
63
|
* by hand with unnormalised weights, which shades slightly dark or bright rather than breaking.
|
|
52
64
|
*/
|
|
53
65
|
mat4 skinMatrix() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
mat4 m = jointMatrix(int(aJoints.x)) * aWeights.x
|
|
67
|
+
+ jointMatrix(int(aJoints.y)) * aWeights.y
|
|
68
|
+
+ jointMatrix(int(aJoints.z)) * aWeights.z
|
|
69
|
+
+ jointMatrix(int(aJoints.w)) * aWeights.w;
|
|
70
|
+
if (SKIN_EIGHT) {
|
|
71
|
+
m += jointMatrix(int(aJoints2.x)) * aWeights2.x
|
|
72
|
+
+ jointMatrix(int(aJoints2.y)) * aWeights2.y
|
|
73
|
+
+ jointMatrix(int(aJoints2.z)) * aWeights2.z
|
|
74
|
+
+ jointMatrix(int(aJoints2.w)) * aWeights2.w;
|
|
75
|
+
}
|
|
76
|
+
return m;
|
|
58
77
|
}
|
|
78
|
+
|
|
59
79
|
`;
|
|
80
|
+
|
|
81
|
+
/** The source above with the eight-influence switch on, for a WebGL2 program built for such a mesh. */
|
|
82
|
+
export function skinEight(source: string): string {
|
|
83
|
+
const off = 'const bool SKIN_EIGHT = false;';
|
|
84
|
+
if (!source.includes(off)) throw new Error('skinEight: the source carries no SKIN_EIGHT switch');
|
|
85
|
+
return source.replace(off, 'const bool SKIN_EIGHT = true;');
|
|
86
|
+
}
|
|
@@ -2,6 +2,7 @@ import { resolveGlass, type GlassOptions, type ResolvedGlass } from './glass.ts'
|
|
|
2
2
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
3
3
|
import type { InstanceData } from './instancedMesh.ts';
|
|
4
4
|
import type { InstancedHandle, MeshHandle, ScatterHandle } from './backend/api.ts';
|
|
5
|
+
import type { ClothBindingHandle, ClothParticlesHandle } from './backend/api.ts';
|
|
5
6
|
import type { MeshInstances } from './instances.ts';
|
|
6
7
|
import type { SurfaceMaterial } from './surfaceTexture.ts';
|
|
7
8
|
import type { SurfaceTextureHandle } from './backend/api.ts';
|
|
@@ -70,6 +71,12 @@ export function glassOf(material: SceneCasterMaterial | undefined, out: Resolved
|
|
|
70
71
|
return resolveGlass(material?.glass, out);
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/** A skinned caster's cloth: the mesh's binding and the character's particles. See `setCloth`. */
|
|
75
|
+
export interface ShadowCasterCloth {
|
|
76
|
+
readonly binding: ClothBindingHandle;
|
|
77
|
+
readonly particles: ClothParticlesHandle;
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
export interface ShadowCasterSink {
|
|
74
81
|
/** A rigid mesh at a model transform: the world, a prop, a static piece of scenery. */
|
|
75
82
|
mesh(mesh: MeshHandle, model: ReadonlyMat4, material?: SceneCasterMaterial): void;
|
|
@@ -88,6 +95,12 @@ export interface ShadowCasterSink {
|
|
|
88
95
|
model: ReadonlyMat4,
|
|
89
96
|
palette: Float32Array,
|
|
90
97
|
material?: SceneCasterMaterial,
|
|
98
|
+
/**
|
|
99
|
+
* The cloth the visible draw places this mesh by, if any — for the reason the palette is not
|
|
100
|
+
* optional in spirit: a garment cast from its skinning alone throws a shadow that does not
|
|
101
|
+
* move as the cloth does.
|
|
102
|
+
*/
|
|
103
|
+
cloth?: ShadowCasterCloth,
|
|
91
104
|
): void;
|
|
92
105
|
/**
|
|
93
106
|
* An instanced batch of rigid meshes, placed by the same matrices the visible draw uses.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skin's screen-space scattering, as `shaders/skinBlur.ts` spreads it: the reference its tests hold
|
|
3
|
+
* the kernel to, and nothing at run time calls it.
|
|
4
|
+
*
|
|
5
|
+
* **What it spreads.** With `skinScattering: 'screen-space'`, a skin draw writes its specular to the
|
|
6
|
+
* frame and its diffuse — the light that went beneath the surface — to a target of its own, with the
|
|
7
|
+
* profile it scatters by in the alpha. Before anything blended is drawn, that target is blurred
|
|
8
|
+
* along the screen's two axes by Burley's normalised diffusion, one width per channel, and added
|
|
9
|
+
* back: light that entered a cheek leaves it a centimetre away, reddest furthest, across the
|
|
10
|
+
* pixels that centimetre covers at the cheek's distance. The pre-integrated fit stays in the lit
|
|
11
|
+
* stage for what this cannot see — a terminator's curvature — and this carries what that cannot:
|
|
12
|
+
* light crossing a shadow's edge, or a nostril's rim, in the picture rather than in a fit.
|
|
13
|
+
*
|
|
14
|
+
* **Separable, which the profile is not**: two 1D passes with Burley's profile integrated across a
|
|
15
|
+
* line, as Jimenez's separable subsurface scattering does. What it gives up is the radial shape, a
|
|
16
|
+
* blur a little squarer than the profile; what it buys is thirteen taps an axis rather than a disc.
|
|
17
|
+
*
|
|
18
|
+
* **Each tap is the profile's mass over its cell, in closed form**, so a channel's weights sum to
|
|
19
|
+
* one before any are refused, and a channel that travels a quarter as far as the widest keeps most
|
|
20
|
+
* of its light at the centre. The taps are placed in units of the widest channel's distance and
|
|
21
|
+
* closer together near the centre, where the profile's sharp peak is.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** The taps' distances from the centre, one side, in units of the widest channel's distance. */
|
|
25
|
+
export const SKIN_BLUR_TAPS = [0, 0.4, 1.1, 2.1, 3.5, 5.6, 9] as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Where each tap's cell ends, in the same units: halfway to the next tap, and the last cell runs
|
|
29
|
+
* to infinity, so the profile's whole mass is somewhere.
|
|
30
|
+
*/
|
|
31
|
+
export const SKIN_BLUR_EDGES = [0.2, 0.75, 1.6, 2.8, 4.55, 7.3] as const;
|
|
32
|
+
|
|
33
|
+
/** How many profiles a frame's skin can name: `SkinModel.profile`. */
|
|
34
|
+
export const SKIN_PROFILES = 8;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Burley's profile across a line, `(e^(−x/d) + e^(−x/3d)) / 8d`, integrated from 0 to `x`: half of
|
|
38
|
+
* it lies on each side, so this reaches ½ at infinity.
|
|
39
|
+
*/
|
|
40
|
+
export function burleyMass(x: number, d: number): number {
|
|
41
|
+
return 0.5 - (Math.exp(-x / d) + 3 * Math.exp(-x / (3 * d))) / 8;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tap `tap`'s weight for one channel scattering `d`, the taps spaced by `widest`: the centre's cell
|
|
46
|
+
* is both sides of it, every other cell one side, and a tap on each side takes this weight.
|
|
47
|
+
*/
|
|
48
|
+
export function skinBlurWeight(tap: number, d: number, widest: number): number {
|
|
49
|
+
if (tap === 0) return 2 * burleyMass((SKIN_BLUR_EDGES[0] as number) * widest, d);
|
|
50
|
+
const inner = (SKIN_BLUR_EDGES[tap - 1] as number) * widest;
|
|
51
|
+
const outer = tap < SKIN_BLUR_EDGES.length ? (SKIN_BLUR_EDGES[tap] as number) * widest : Infinity;
|
|
52
|
+
return (outer === Infinity ? 0.5 : burleyMass(outer, d)) - burleyMass(inner, d);
|
|
53
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skin model's arithmetic, as `shaders/flat/skin.ts` computes it: the reference its tests hold
|
|
3
|
+
* the fit to, and nothing at run time calls it.
|
|
4
|
+
*
|
|
5
|
+
* **Pre-integrated, after Penner 2011, with the profile integrated rather than looked up.** Light
|
|
6
|
+
* entering skin leaves it a little way off, so on a curved surface a point is lit by its
|
|
7
|
+
* neighbours' `N·L` as well as its own: the clamped cosine averaged around a ring of the surface's
|
|
8
|
+
* curvature, weighted by the diffusion profile at each neighbour's distance. The profile is Burley's
|
|
9
|
+
* normalised diffusion, one per channel, at a scatter distance `d` — the model's `radius` times its
|
|
10
|
+
* scatter colour — projected onto the ring's plane, as a cylinder's cross-section sees it. The
|
|
11
|
+
* result depends on `N·L` and on one number per channel, `k = d × curvature`, and at `k = 0` it is
|
|
12
|
+
* Lambert's.
|
|
13
|
+
*
|
|
14
|
+
* **Fitted rather than tabulated**, so no seventeenth sampler and no image: the ring average is the
|
|
15
|
+
* clamped cosine convolved with an angular kernel, so it splits into the cosine's first harmonic —
|
|
16
|
+
* scaled by one number, `κ₁(k)` — and a smoothed `|cos θ|` carrying every even harmonic:
|
|
17
|
+
*
|
|
18
|
+
* D(c, k) = ½ (κ₁ c + A √(c² + ε²) + B)
|
|
19
|
+
*
|
|
20
|
+
* with `κ₁`, `A`, `ε` and `B` closed forms in `k` that are exactly `1, 1, 0, 0` at `k = 0`. The
|
|
21
|
+
* test measures it against the integral done numerically: **worst 0.0105 over `k` to 10**, beyond
|
|
22
|
+
* which the shader holds `k` — a scatter ten times the surface's own radius, where the answer is
|
|
23
|
+
* nearly flat already.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Schlick's base for skin, at index 1.4: `((1 − n) / (1 + n))²`. */
|
|
27
|
+
export const SKIN_F0 = ((1 - 1.4) / (1 + 1.4)) ** 2;
|
|
28
|
+
|
|
29
|
+
/** The largest `k` the fit is held to, and where the shader clamps it. */
|
|
30
|
+
export const SKIN_MAX_K = 10;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The fit's eight numbers, found by minimising its worst error against the numeric integral over
|
|
34
|
+
* `c` in [−1, 1] and `k` in (0, 10]:
|
|
35
|
+
*
|
|
36
|
+
* - `kappa`: `κ₁ = (1 + a k² + b k³)^(−1/6)` — the first harmonic's share, quadratic at zero and
|
|
37
|
+
* falling as `1/√k`, as the integral's does;
|
|
38
|
+
* - `spread`: `A = (1 + a k³)^(−1/6)`;
|
|
39
|
+
* - `soften`: `ε = a k / (1 + b k)`, how far `|c|` is rounded at the terminator;
|
|
40
|
+
* - `floor`: `B = (1 − A)(a + b (1 − A))`, the even part's floor as `A` gives up `|c|`.
|
|
41
|
+
*/
|
|
42
|
+
export const SKIN_FIT = {
|
|
43
|
+
kappa: [22.6772, 101.349],
|
|
44
|
+
spread: 838.87,
|
|
45
|
+
soften: [1.3081, 3.65743],
|
|
46
|
+
floor: [0.508537, 0.129496],
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
/** The ring-averaged `N·L` for one channel: `c` is `N·L`, `k` that channel's scatter × curvature. */
|
|
50
|
+
export function skinRing(c: number, k: number): number {
|
|
51
|
+
const q = Math.min(Math.max(k, 0), SKIN_MAX_K);
|
|
52
|
+
const k2 = q * q;
|
|
53
|
+
const kappa = (1 + SKIN_FIT.kappa[0] * k2 + SKIN_FIT.kappa[1] * k2 * q) ** (-1 / 6);
|
|
54
|
+
const spread = (1 + SKIN_FIT.spread * k2 * q) ** (-1 / 6);
|
|
55
|
+
const soften = (SKIN_FIT.soften[0] * q) / (1 + SKIN_FIT.soften[1] * q);
|
|
56
|
+
const given = 1 - spread;
|
|
57
|
+
const floor = given * (SKIN_FIT.floor[0] + SKIN_FIT.floor[1] * given);
|
|
58
|
+
return 0.5 * (kappa * c + spread * Math.sqrt(c * c + soften * soften) + floor);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* How far into a penumbra skin's light reaches, as a width the shadow is assumed to have: the
|
|
63
|
+
* lamp loop reads a shadow where its derivative cannot be taken, so its width in the world is not
|
|
64
|
+
* known there. Two centimetres is a soft edge across a face.
|
|
65
|
+
*/
|
|
66
|
+
export const SKIN_PENUMBRA_WIDTH = 0.02;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A shadow on skin, one channel at scatter distance `d`: the shadow's own value raised to
|
|
70
|
+
* `1 / (1 + d / width)`, so light reaches further into the penumbra the further that channel
|
|
71
|
+
* travels beneath the surface — red furthest, which is the red edge a shadow on skin has — and a
|
|
72
|
+
* shadow stays whole at 0 and absent at 1. **What it gives up** is the profile itself: Penner
|
|
73
|
+
* convolves a sharpened shadow with it, which needs the penumbra's width in the world and hardens
|
|
74
|
+
* the narrow channels' edge where nothing widened the map to make room.
|
|
75
|
+
*/
|
|
76
|
+
export function skinPenumbra(shadow: number, d: number): number {
|
|
77
|
+
return Math.max(shadow, 0) ** (1 / (1 + d / SKIN_PENUMBRA_WIDTH));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Light through a thin part, one channel: `transmission × exp(−thickness / d)` of the light behind,
|
|
82
|
+
* by how squarely it is behind. A part as thick as its curvature's diameter — an ear's rim at a few
|
|
83
|
+
* millimetres, a cheek at ten centimetres — passes red through the one and nothing through the other.
|
|
84
|
+
*/
|
|
85
|
+
export function skinTransmission(
|
|
86
|
+
backNdl: number,
|
|
87
|
+
thickness: number,
|
|
88
|
+
d: number,
|
|
89
|
+
transmission: number,
|
|
90
|
+
): number {
|
|
91
|
+
return transmission * Math.exp(-thickness / Math.max(d, 1e-6)) * Math.max(backNdl, 0);
|
|
92
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A skinned cloth for a renderer: `@driftengine/physics`' solver, run as compute on WebGPU — the
|
|
3
|
+
* particles never leave the device — and as `SkinnedCloth` on the CPU under WebGL2, which has no
|
|
4
|
+
* compute. One schedule, `ClothControl`, and the same kernels either way; what a frame draws is
|
|
5
|
+
* `particles`, through `setCloth(binding, cloth.particles)`.
|
|
6
|
+
*
|
|
7
|
+
* **A function rather than renderer methods, so a game pays for it only by importing it.** See
|
|
8
|
+
* `backend/webgpu/computeHost.ts` for the door it reaches the device through, and why.
|
|
9
|
+
*
|
|
10
|
+
* **The CPU fallback's cost**, measured on a desktop part: a few hundred particles are a
|
|
11
|
+
* millisecond a frame on the main thread, and 4,500 are twenty — a garment that size wants the
|
|
12
|
+
* device. The device solver ran 4,500 particles in 0.65 ms a frame, most of it the dispatches
|
|
13
|
+
* rather than the particles (`demo/dev/skinnedCloth.html?bench=1`).
|
|
14
|
+
*/
|
|
15
|
+
import { SkinnedCloth } from '@driftengine/physics';
|
|
16
|
+
import type { SkinnedClothSetup } from '@driftengine/physics';
|
|
17
|
+
|
|
18
|
+
import type { ClothParticlesHandle, RendererApi } from './backend/api.ts';
|
|
19
|
+
import { computeHostOf } from './backend/webgpu/computeHost.ts';
|
|
20
|
+
import type { ComputeHost } from './backend/webgpu/computeHost.ts';
|
|
21
|
+
import { GpuSkinnedCloth } from './backend/webgpu/gpuSkinnedCloth.ts';
|
|
22
|
+
|
|
23
|
+
/** A garment a renderer solves, from `createSkinnedCloth`. */
|
|
24
|
+
export interface SkinnedClothSolver {
|
|
25
|
+
/** What `setCloth(binding, particles)` draws the garment's bound meshes by. */
|
|
26
|
+
readonly particles: ClothParticlesHandle;
|
|
27
|
+
/** Where the solver runs: on the device, or on the CPU beneath a backend without compute. */
|
|
28
|
+
readonly runsOn: 'device' | 'cpu';
|
|
29
|
+
/**
|
|
30
|
+
* One frame: the rig's pose — each joint's global matrix, model space, sixteen floats a joint,
|
|
31
|
+
* and the model matrix — and `dt` seconds, run as whole fixed steps; the particles at the frame's
|
|
32
|
+
* `alpha` are then what the frame draws. **Once a frame, before the draws**, as
|
|
33
|
+
* `updateClothParticles` is: a second in one frame would reach every draw of it on WebGPU.
|
|
34
|
+
*/
|
|
35
|
+
step(globals: Float32Array, model: Float32Array, dt: number): void;
|
|
36
|
+
/**
|
|
37
|
+
* The air the cloth drags toward from the next step, world space, metres a second: the frame's
|
|
38
|
+
* sample of the scene's one `WindField`, so a garment moves with everything else in the air.
|
|
39
|
+
*/
|
|
40
|
+
setWind(x: number, y: number, z: number): void;
|
|
41
|
+
/** Back to the skinned pose, settled, as a teleport resets it. */
|
|
42
|
+
reset(): void;
|
|
43
|
+
dispose(): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A garment for `renderer`, from a set-up checked by name. */
|
|
47
|
+
export function createSkinnedCloth(
|
|
48
|
+
renderer: RendererApi,
|
|
49
|
+
setup: SkinnedClothSetup,
|
|
50
|
+
): SkinnedClothSolver {
|
|
51
|
+
const host = computeHostOf(renderer);
|
|
52
|
+
return host === null ? new CpuCloth(renderer, setup) : new DeviceCloth(host, setup);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class DeviceCloth implements SkinnedClothSolver {
|
|
56
|
+
readonly runsOn = 'device';
|
|
57
|
+
readonly particles: ClothParticlesHandle;
|
|
58
|
+
private readonly cloth: GpuSkinnedCloth;
|
|
59
|
+
|
|
60
|
+
constructor(
|
|
61
|
+
private readonly host: ComputeHost,
|
|
62
|
+
setup: SkinnedClothSetup,
|
|
63
|
+
) {
|
|
64
|
+
this.cloth = new GpuSkinnedCloth(host.device(), setup);
|
|
65
|
+
this.particles = this.cloth.particles;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
step(globals: Float32Array, model: Float32Array, dt: number): void {
|
|
69
|
+
if (this.host.lost()) return;
|
|
70
|
+
this.cloth.advance(globals, model, dt, this.host.changeFrame());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setWind(x: number, y: number, z: number): void {
|
|
74
|
+
this.cloth.setWind(x, y, z);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
reset(): void {
|
|
78
|
+
if (this.host.lost()) return;
|
|
79
|
+
this.cloth.reset();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Its buffers at once — its work is submitted — and its textures with the frame's. */
|
|
83
|
+
dispose(): void {
|
|
84
|
+
this.cloth.dispose();
|
|
85
|
+
this.host.retire(this.cloth.particles.textures);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The particles where the last step left them: the parity check's door, waits on the device. */
|
|
89
|
+
read(): Promise<Float32Array> {
|
|
90
|
+
return this.cloth.read();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class CpuCloth implements SkinnedClothSolver {
|
|
95
|
+
readonly runsOn = 'cpu';
|
|
96
|
+
readonly particles: ClothParticlesHandle;
|
|
97
|
+
private readonly cloth: SkinnedCloth;
|
|
98
|
+
/** The frame's particles at `alpha`, before they go up. */
|
|
99
|
+
private readonly staging: Float32Array;
|
|
100
|
+
|
|
101
|
+
constructor(
|
|
102
|
+
private readonly renderer: RendererApi,
|
|
103
|
+
setup: SkinnedClothSetup,
|
|
104
|
+
) {
|
|
105
|
+
this.cloth = new SkinnedCloth(setup);
|
|
106
|
+
this.particles = renderer.createClothParticles(this.cloth.count);
|
|
107
|
+
this.staging = new Float32Array(this.cloth.count * 3);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
step(globals: Float32Array, model: Float32Array, dt: number): void {
|
|
111
|
+
this.cloth.setPose(globals, model);
|
|
112
|
+
this.cloth.advance(dt);
|
|
113
|
+
this.cloth.interpolate(this.cloth.alpha, this.staging);
|
|
114
|
+
this.renderer.updateClothParticles(this.particles, this.staging);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setWind(x: number, y: number, z: number): void {
|
|
118
|
+
this.cloth.setWind(x, y, z);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
reset(): void {
|
|
122
|
+
this.cloth.reset();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
dispose(): void {
|
|
126
|
+
this.renderer.disposeClothParticles(this.particles);
|
|
127
|
+
}
|
|
128
|
+
}
|