@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
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* the alpha test is not uniform control flow.
|
|
7
7
|
*/
|
|
8
8
|
export declare const SDF_TEXT_VERT = "#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec3 aPosition;\nlayout(location = 1) in vec2 aUv;\n\nuniform mat4 uViewProj;\nuniform mat4 uModel;\n\nout vec2 vUv;\n\nvoid main() {\n vUv = aUv;\n gl_Position = uViewProj * uModel * vec4(aPosition, 1.0);\n}\n";
|
|
9
|
-
export declare const SDF_TEXT_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\n\n/* An array of one: every surface texture is an array, and an atlas is read at layer 0. */\nuniform mediump sampler2DArray uAtlas;\nuniform vec3 uColor;\nuniform float uOpacity;\n/* Texels the field spans, from the atlas metrics. Scales the antialiasing band. */\nuniform float uDistanceRange;\nuniform vec2 uAtlasSize;\n\nout vec4 outColor;\n\n/** 0 none, 1 sRGB, 2 ACES then sRGB. See renderQuality.ts. */\nuniform int uOutputTransform;\n/** Scales the scene into the tone curve. 1 is the reference grade. See renderQuality.ts. */\nuniform float uOutputExposure;\n\n/*\n * ACES: the reference fit, not the cheap one.\n *\n * This was Narkowicz's single rational function, on the argument that the difference from\n * the reference curve is invisible next to the gain. That argument holds for a look and\n * fails for a *match*, which is what this option is for \u2014 aces exists so a world ported\n * from three.js reads the same, and three.js applies the full fit: into AP1 through\n * ACESInputMat, the RRT and ODT together, then back out through ACESOutputMat.\n *\n * The two curves are not a shade apart. Narkowicz is markedly brighter through the\n * midtones, which is exactly where an interior scene lives, and it is a large part of a\n * measured ~1.7x that survived correcting the colour space and the missing Lambert pi.\n * An approximation of a standard is the wrong thing to hold when the standard is the\n * specification.\n */\nvec3 rrtAndOdtFit(vec3 v) {\n vec3 a = v * (v + 0.0245786) - 0.000090537;\n vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n return a / b;\n}\n\nvec3 acesFilmic(vec3 x) {\n /*\n * Exposure first, because a tone curve is only defined relative to one.\n *\n * The reference RRT and ODT are graded for a dark cinema. Every renderer that ships ACES\n * scales its input to suit whatever it is actually shown on, and the factor each one\n * picks is a *look*, not a standard - so this engine takes no position and reads it from\n * the caller. Neutral by default, which is the reference grade unmodified. three.js's own\n * pick is 1 / 0.6, for a consumer matching one.\n */\n x *= uOutputExposure;\n // Columns, as GLSL reads them: sRGB -> XYZ -> D65_2_D60 -> AP1 -> RRT_SAT.\n const mat3 ACES_INPUT = mat3(\n 0.59719, 0.07600, 0.02840,\n 0.35458, 0.90834, 0.13383,\n 0.04823, 0.01566, 0.83777\n );\n // ODT_SAT -> XYZ -> D60_2_D65 -> sRGB.\n const mat3 ACES_OUTPUT = mat3(\n 1.60475, -0.10208, -0.00327,\n -0.53108, 1.10813, -0.07276,\n -0.07367, -0.00605, 1.07602\n );\n return clamp(ACES_OUTPUT * rrtAndOdtFit(ACES_INPUT * x), 0.0, 1.0);\n}\n\n/*\n * Linear to sRGB. The piecewise form rather than a plain pow: the linear toe near black\n * is where banding lives in an 8-bit buffer, and the cheap approximation puts it in the\n * wrong place.\n */\nvec3 linearToSrgb(vec3 c) {\n vec3 low = c * 12.92;\n vec3 high = 1.055 * pow(max(c, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055;\n return mix(high, low, step(c, vec3(0.0031308)));\n}\n\nvec3 applyOutputTransform(vec3 c) {\n if (uOutputTransform == 0) return c;\n if (uOutputTransform == 2) c = acesFilmic(c);\n return linearToSrgb(c);\n}\n\n\n/*\n * The middle of the three channels.\n *\n * A single-channel field rounds every corner off at small sizes, because one distance\n * cannot describe two edges meeting. Three channels carry the corner and the median\n * recovers it.\n */\nfloat median(vec3 rgb) {\n return max(min(rgb.r, rgb.g), min(max(rgb.r, rgb.g), rgb.b));\n}\n\nvoid main() {\n vec3 sampled = textureLod(uAtlas, vec3(vUv, 0.0), 0.0).rgb;\n float distance = median(sampled) - 0.5;\n\n /*\n * The band is one pixel wide *in screen space*, which is what lets one atlas serve every\n * size. A constant band is tuned for one size and is wrong at all the others.\n */\n vec2 texelsPerPixel = fwidth(vUv) * uAtlasSize;\n float pixelRange = max(0.5 * (texelsPerPixel.x + texelsPerPixel.y), 0.0001);\n float coverage = clamp(distance * uDistanceRange / pixelRange + 0.5, 0.0, 1.0);\n\n outColor = vec4(applyOutputTransform(uColor), coverage * uOpacity);\n}\n";
|
|
9
|
+
export declare const SDF_TEXT_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\n\n/* An array of one: every surface texture is an array, and an atlas is read at layer 0. */\nuniform mediump sampler2DArray uAtlas;\nuniform vec3 uColor;\nuniform float uOpacity;\n/* Texels the field spans, from the atlas metrics. Scales the antialiasing band. */\nuniform float uDistanceRange;\nuniform vec2 uAtlasSize;\n\nout vec4 outColor;\n\n/** 0 none, 1 sRGB, 2 ACES then sRGB, 3 the highlight shoulder then sRGB. See renderQuality.ts. */\nuniform int uOutputTransform;\n/** Scales the scene into the tone curve. 1 is the reference grade. See renderQuality.ts. */\nuniform float uOutputExposure;\n\n/*\n * ACES: the reference fit, not the cheap one.\n *\n * This was Narkowicz's single rational function, on the argument that the difference from\n * the reference curve is invisible next to the gain. That argument holds for a look and\n * fails for a *match*, which is what this option is for \u2014 aces exists so a world ported\n * from three.js reads the same, and three.js applies the full fit: into AP1 through\n * ACESInputMat, the RRT and ODT together, then back out through ACESOutputMat.\n *\n * The two curves are not a shade apart. Narkowicz is markedly brighter through the\n * midtones, which is exactly where an interior scene lives, and it is a large part of a\n * measured ~1.7x that survived correcting the colour space and the missing Lambert pi.\n * An approximation of a standard is the wrong thing to hold when the standard is the\n * specification.\n */\nvec3 rrtAndOdtFit(vec3 v) {\n vec3 a = v * (v + 0.0245786) - 0.000090537;\n vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n return a / b;\n}\n\nvec3 acesFilmic(vec3 x) {\n /*\n * Exposure first, because a tone curve is only defined relative to one.\n *\n * The reference RRT and ODT are graded for a dark cinema. Every renderer that ships ACES\n * scales its input to suit whatever it is actually shown on, and the factor each one\n * picks is a *look*, not a standard - so this engine takes no position and reads it from\n * the caller. Neutral by default, which is the reference grade unmodified. three.js's own\n * pick is 1 / 0.6, for a consumer matching one.\n */\n x *= uOutputExposure;\n // Columns, as GLSL reads them: sRGB -> XYZ -> D65_2_D60 -> AP1 -> RRT_SAT.\n const mat3 ACES_INPUT = mat3(\n 0.59719, 0.07600, 0.02840,\n 0.35458, 0.90834, 0.13383,\n 0.04823, 0.01566, 0.83777\n );\n // ODT_SAT -> XYZ -> D60_2_D65 -> sRGB.\n const mat3 ACES_OUTPUT = mat3(\n 1.60475, -0.10208, -0.00327,\n -0.53108, 1.10813, -0.07276,\n -0.07367, -0.00605, 1.07602\n );\n return clamp(ACES_OUTPUT * rrtAndOdtFit(ACES_INPUT * x), 0.0, 1.0);\n}\n\n/*\n * Linear to sRGB. The piecewise form rather than a plain pow: the linear toe near black\n * is where banding lives in an 8-bit buffer, and the cheap approximation puts it in the\n * wrong place.\n */\nvec3 linearToSrgb(vec3 c) {\n vec3 low = c * 12.92;\n vec3 high = 1.055 * pow(max(c, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055;\n return mix(high, low, step(c, vec3(0.0031308)));\n}\n\n\nvec3 highlightShoulder(vec3 c) {\n float m = max(c.r, max(c.g, c.b));\n if (m <= 0.8) return c;\n float e = m - 0.8;\n return c * ((0.8 + 0.2 * e / (e + 0.2)) / m);\n}\n\nvec3 applyOutputTransform(vec3 c) {\n if (uOutputTransform == 0) return c;\n if (uOutputTransform == 2) c = acesFilmic(c);\n if (uOutputTransform == 3) c = highlightShoulder(c * uOutputExposure);\n return linearToSrgb(c);\n}\n\n\n/*\n * The middle of the three channels.\n *\n * A single-channel field rounds every corner off at small sizes, because one distance\n * cannot describe two edges meeting. Three channels carry the corner and the median\n * recovers it.\n */\nfloat median(vec3 rgb) {\n return max(min(rgb.r, rgb.g), min(max(rgb.r, rgb.g), rgb.b));\n}\n\nvoid main() {\n vec3 sampled = textureLod(uAtlas, vec3(vUv, 0.0), 0.0).rgb;\n float distance = median(sampled) - 0.5;\n\n /*\n * The band is one pixel wide *in screen space*, which is what lets one atlas serve every\n * size. A constant band is tuned for one size and is wrong at all the others.\n */\n vec2 texelsPerPixel = fwidth(vUv) * uAtlasSize;\n float pixelRange = max(0.5 * (texelsPerPixel.x + texelsPerPixel.y), 0.0001);\n float coverage = clamp(distance * uDistanceRange / pixelRange + 0.5, 0.0, 1.0);\n\n outColor = vec4(applyOutputTransform(uColor), coverage * uOpacity);\n}\n";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SKIN_BLUR_FRAG: string;
|
|
@@ -0,0 +1,103 @@
|
|
|
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.js';
|
|
16
|
+
import { SKIN_BLUR_EDGES, SKIN_BLUR_TAPS, SKIN_PROFILES } from '../skinBlur.js';
|
|
17
|
+
const list = (values) => values.map((v) => v.toFixed(2)).join(', ');
|
|
18
|
+
export const SKIN_BLUR_FRAG = `#version 300 es
|
|
19
|
+
precision highp float;
|
|
20
|
+
|
|
21
|
+
in vec2 vUv;
|
|
22
|
+
|
|
23
|
+
/** The diffuse pass, or the first axis' result: diffuse in rgb, the profile as (index + 1) / 8. */
|
|
24
|
+
uniform sampler2D uSkin;
|
|
25
|
+
/** highp, for the reason the ambient occlusion blur gives: eight bits of depth find false edges. */
|
|
26
|
+
uniform highp sampler2D uDepth;
|
|
27
|
+
/** One texel along the axis being blurred, in UV. The other component is zero. */
|
|
28
|
+
uniform vec2 uStep;
|
|
29
|
+
/** A depth sample to view-space metres, as the ambient occlusion blur takes it. */
|
|
30
|
+
uniform vec4 uDepthToViewZ;
|
|
31
|
+
/** How many pixels a metre spans at a metre's distance. */
|
|
32
|
+
uniform float uFocal;
|
|
33
|
+
/** Each profile's scatter distance per channel, in metres. */
|
|
34
|
+
uniform vec4 uProfiles[${SKIN_PROFILES}];
|
|
35
|
+
|
|
36
|
+
out vec4 fragColor;
|
|
37
|
+
|
|
38
|
+
const float TAPS[${SKIN_BLUR_TAPS.length}] = float[${SKIN_BLUR_TAPS.length}](${list(SKIN_BLUR_TAPS)});
|
|
39
|
+
const float EDGES[${SKIN_BLUR_EDGES.length}] = float[${SKIN_BLUR_EDGES.length}](${list(SKIN_BLUR_EDGES)});
|
|
40
|
+
|
|
41
|
+
float viewZ(float depth) {
|
|
42
|
+
float ndc = ${glslSceneDepthToNdc('depth')};
|
|
43
|
+
return (uDepthToViewZ.x * ndc + uDepthToViewZ.y) / (uDepthToViewZ.z * ndc + uDepthToViewZ.w);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Burley's profile across a line, integrated from 0 to x: one half at infinity. */
|
|
47
|
+
vec3 burleyMass(float x, vec3 d) {
|
|
48
|
+
return 0.5 - (exp(-x / d) + 3.0 * exp(-x / (3.0 * d))) * 0.125;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void main() {
|
|
52
|
+
vec4 own = textureLod(uSkin, vUv, 0.0);
|
|
53
|
+
/*
|
|
54
|
+
* Not skin: nothing here to spread, and nothing to add. Any alpha is skin, because a pixel a
|
|
55
|
+
* silhouette crosses under multisampling resolves to its covered share of the profile's code, and
|
|
56
|
+
* its colour to that share of the diffuse, which is exactly what it owes the frame.
|
|
57
|
+
*/
|
|
58
|
+
if (own.a < 1e-4) {
|
|
59
|
+
fragColor = vec4(0.0);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
float depth = textureLod(uDepth, vUv, 0.0).r;
|
|
63
|
+
if (${glslIsFarDepth('depth')}) {
|
|
64
|
+
fragColor = own;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
/* A part-covered pixel's code is its share of the whole: read it as the nearest, which is the
|
|
68
|
+
right profile at half coverage and up, and a neighbouring one only on a silhouette below it. */
|
|
69
|
+
int profile = clamp(int(own.a * 8.0 + 0.5) - 1, 0, ${SKIN_PROFILES - 1});
|
|
70
|
+
vec3 d = max(uProfiles[profile].xyz, vec3(1e-6));
|
|
71
|
+
float widest = max(d.r, max(d.g, d.b));
|
|
72
|
+
float z = abs(viewZ(depth));
|
|
73
|
+
/* The widest distance in pixels here: under half of one, there is nothing to spread across. */
|
|
74
|
+
float pixels = widest * uFocal / max(z, 1e-4);
|
|
75
|
+
if (pixels < 0.5) {
|
|
76
|
+
fragColor = own;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
vec3 centre = 2.0 * burleyMass(EDGES[0] * widest, d);
|
|
80
|
+
vec3 sum = own.rgb * centre;
|
|
81
|
+
vec3 weight = centre;
|
|
82
|
+
for (int i = 1; i < ${SKIN_BLUR_TAPS.length}; i++) {
|
|
83
|
+
vec3 outer = i < ${SKIN_BLUR_EDGES.length} ? burleyMass(EDGES[min(i, ${SKIN_BLUR_EDGES.length - 1})] * widest, d) : vec3(0.5);
|
|
84
|
+
vec3 w = outer - burleyMass(EDGES[i - 1] * widest, d);
|
|
85
|
+
for (int side = -1; side <= 1; side += 2) {
|
|
86
|
+
vec2 uv = vUv + uStep * (float(side) * TAPS[i] * pixels);
|
|
87
|
+
vec4 tap = textureLod(uSkin, uv, 0.0);
|
|
88
|
+
float tapZ = abs(viewZ(textureLod(uDepth, uv, 0.0).r));
|
|
89
|
+
float keep = clamp(1.0 - abs(tapZ - z) / (4.0 * widest), 0.0, 1.0);
|
|
90
|
+
/*
|
|
91
|
+
* How much of the tap is skin: a filtered tap astride a silhouette is part skin and part
|
|
92
|
+
* the black beside it, in colour and in alpha alike, so its alpha over this pixel's says the
|
|
93
|
+
* share, and the colour is already that share of the skin's — counted as such, it neither
|
|
94
|
+
* darkens the edge nor drops out.
|
|
95
|
+
*/
|
|
96
|
+
float cover = clamp(tap.a / own.a, 0.0, 1.0);
|
|
97
|
+
sum += tap.rgb * w * keep;
|
|
98
|
+
weight += w * (cover * keep);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
fragColor = vec4(sum / weight, own.a);
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
@@ -14,4 +14,6 @@
|
|
|
14
14
|
* What would make it wrong is a second vertex flag: four variants dedupe less well than two, so
|
|
15
15
|
* re-measure before adding one.
|
|
16
16
|
*/
|
|
17
|
-
export declare const SKINNING_GLSL = "\n/**\n * The joint palette, four texels a matrix, one row.\n *\n * A texture rather than a `uniform mat4[]`. The 2026-08-07 rule that keeps samples out of\n * unprovable branches is about *implicit derivatives*, which a vertex stage does not compute, so\n * it has nothing to say here \u2014 and `texelFetch` takes an integer coordinate with no level\n * inferred at all. What a uniform array would have cost is the cap: MAX_VERTEX_UNIFORM_VECTORS is\n * 256 on the WebGL2 floor and this shader already spends fifteen, which leaves sixty joints.\n *\n * What this costs is a texture upload per skinned draw. What would make it wrong is a profile\n * showing that upload is the limit; the fix is one texture for every character with a row offset.\n */\nuniform highp sampler2D uJointPalette;\n\nlayout(location = 11) in vec4 aJoints;\nlayout(location = 12) in vec4 aWeights;\n\n/** One joint's matrix, read as four consecutive texels of row 0. */\nmat4 jointMatrix(int index) {\n int x = index * 4;\n return mat4(\n texelFetch(uJointPalette, ivec2(x, 0), 0),\n texelFetch(uJointPalette, ivec2(x + 1, 0), 0),\n texelFetch(uJointPalette, ivec2(x + 2, 0), 0),\n texelFetch(uJointPalette, ivec2(x + 3, 0), 0));\n}\n\n/**\n * The four influences, weighted.\n *\n * Weights are normalised by the importer rather than here: normalising per vertex per frame costs\n * a divide on every vertex to correct data an importer should have fixed once, and `gltfSkin.ts`\n * warns when a set does not sum to one. What would make it wrong is a caller building a MeshData\n * by hand with unnormalised weights, which shades slightly dark or bright rather than breaking.\n */\nmat4 skinMatrix() {\n
|
|
17
|
+
export declare const SKINNING_GLSL = "\n/**\n * The joint palette, four texels a matrix, one row.\n *\n * A texture rather than a `uniform mat4[]`. The 2026-08-07 rule that keeps samples out of\n * unprovable branches is about *implicit derivatives*, which a vertex stage does not compute, so\n * it has nothing to say here \u2014 and `texelFetch` takes an integer coordinate with no level\n * inferred at all. What a uniform array would have cost is the cap: MAX_VERTEX_UNIFORM_VECTORS is\n * 256 on the WebGL2 floor and this shader already spends fifteen, which leaves sixty joints.\n *\n * What this costs is a texture upload per skinned draw. What would make it wrong is a profile\n * showing that upload is the limit; the fix is one texture for every character with a row offset.\n */\nuniform highp sampler2D uJointPalette;\n\nlayout(location = 11) in vec4 aJoints;\nlayout(location = 12) in vec4 aWeights;\n/*\n * **The fifth to eighth influences, read only by a pipeline built for a mesh that has them.**\n *\n * Declared in every skinned variant at the last two locations WebGL2 guarantees \u2014 13 is the channel,\n * and an instanced draw, which takes 11 to 15, cannot skin \u2014 and fed zeros by the absent-attribute\n * constants for a mesh with four. `SKIN_EIGHT` is a pipeline constant rather than a permutation,\n * so the generated vertex stages do not double: off, the device compiles the second sum away and a\n * four-influence mesh pays nothing; on, it is four more palette reads a vertex.\n */\nconst bool SKIN_EIGHT = false; // wgsl:override\nlayout(location = 14) in vec4 aJoints2;\nlayout(location = 15) in vec4 aWeights2;\n\n/** One joint's matrix, read as four consecutive texels of row 0. */\nmat4 jointMatrix(int index) {\n int x = index * 4;\n return mat4(\n texelFetch(uJointPalette, ivec2(x, 0), 0),\n texelFetch(uJointPalette, ivec2(x + 1, 0), 0),\n texelFetch(uJointPalette, ivec2(x + 2, 0), 0),\n texelFetch(uJointPalette, ivec2(x + 3, 0), 0));\n}\n\n/**\n * The four influences, weighted.\n *\n * Weights are normalised by the importer rather than here: normalising per vertex per frame costs\n * a divide on every vertex to correct data an importer should have fixed once, and `gltfSkin.ts`\n * warns when a set does not sum to one. What would make it wrong is a caller building a MeshData\n * by hand with unnormalised weights, which shades slightly dark or bright rather than breaking.\n */\nmat4 skinMatrix() {\n mat4 m = jointMatrix(int(aJoints.x)) * aWeights.x\n + jointMatrix(int(aJoints.y)) * aWeights.y\n + jointMatrix(int(aJoints.z)) * aWeights.z\n + jointMatrix(int(aJoints.w)) * aWeights.w;\n if (SKIN_EIGHT) {\n m += jointMatrix(int(aJoints2.x)) * aWeights2.x\n + jointMatrix(int(aJoints2.y)) * aWeights2.y\n + jointMatrix(int(aJoints2.z)) * aWeights2.z\n + jointMatrix(int(aJoints2.w)) * aWeights2.w;\n }\n return m;\n}\n\n";
|
|
18
|
+
/** The source above with the eight-influence switch on, for a WebGL2 program built for such a mesh. */
|
|
19
|
+
export declare function skinEight(source: string): string;
|
|
@@ -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
|
+
/** The source above with the eight-influence switch on, for a WebGL2 program built for such a mesh. */
|
|
81
|
+
export function skinEight(source) {
|
|
82
|
+
const off = 'const bool SKIN_EIGHT = false;';
|
|
83
|
+
if (!source.includes(off))
|
|
84
|
+
throw new Error('skinEight: the source carries no SKIN_EIGHT switch');
|
|
85
|
+
return source.replace(off, 'const bool SKIN_EIGHT = true;');
|
|
86
|
+
}
|
|
@@ -202,11 +202,18 @@ vec3 historyCatmullRom(vec2 uv) {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
void main() {
|
|
205
|
-
|
|
205
|
+
/*
|
|
206
|
+
* **The alpha is this frame's and takes no history.** It is how much of the pixel is still the
|
|
207
|
+
* opaque surface (sceneCoverage.ts), which the composite's occlusion reads after this has
|
|
208
|
+
* replaced the scene, so it passes through untouched. The history is read for its colour alone.
|
|
209
|
+
*/
|
|
210
|
+
vec4 sampled = textureLod(uScene, vUv, 0.0);
|
|
211
|
+
vec3 current = sampled.rgb;
|
|
212
|
+
float share = sampled.a;
|
|
206
213
|
|
|
207
214
|
/* Nothing to blend towards: the first frame, a resize, a cut, or the effect switched off. */
|
|
208
215
|
if (uHistoryBlend <= 0.0) {
|
|
209
|
-
fragColor = vec4(current,
|
|
216
|
+
fragColor = vec4(current, share);
|
|
210
217
|
flickerOut = freshRecord(current);
|
|
211
218
|
flickerMotionOut = vec4(0.0);
|
|
212
219
|
return;
|
|
@@ -239,7 +246,7 @@ void main() {
|
|
|
239
246
|
vec4 previous = uReprojection * clip;
|
|
240
247
|
/* Behind the previous eye: there is no last-frame position to sample. */
|
|
241
248
|
if (previous.w <= 0.0) {
|
|
242
|
-
fragColor = vec4(current,
|
|
249
|
+
fragColor = vec4(current, share);
|
|
243
250
|
flickerOut = freshRecord(current);
|
|
244
251
|
flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
|
|
245
252
|
return;
|
|
@@ -252,7 +259,7 @@ void main() {
|
|
|
252
259
|
* as a smear for as long as the camera kept turning.
|
|
253
260
|
*/
|
|
254
261
|
if (wasUv.x < 0.0 || wasUv.x > 1.0 || wasUv.y < 0.0 || wasUv.y > 1.0) {
|
|
255
|
-
fragColor = vec4(current,
|
|
262
|
+
fragColor = vec4(current, share);
|
|
256
263
|
flickerOut = freshRecord(current);
|
|
257
264
|
flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
|
|
258
265
|
return;
|
|
@@ -311,6 +318,6 @@ void main() {
|
|
|
311
318
|
repeated * still
|
|
312
319
|
);
|
|
313
320
|
flickerMotionOut = vec4(metres, swingOut, 0.0, 0.0);
|
|
314
|
-
fragColor = vec4(mix(current, bounded, blend),
|
|
321
|
+
fragColor = vec4(mix(current, bounded, blend), share);
|
|
315
322
|
}
|
|
316
323
|
`;
|
|
@@ -2,6 +2,7 @@ import { 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';
|
|
@@ -60,6 +61,11 @@ export declare function castsDepth(material: SceneCasterMaterial | undefined): b
|
|
|
60
61
|
* states from the other side, so a caster is exactly one of the two.
|
|
61
62
|
*/
|
|
62
63
|
export declare function glassOf(material: SceneCasterMaterial | undefined, out: ResolvedGlass): boolean;
|
|
64
|
+
/** A skinned caster's cloth: the mesh's binding and the character's particles. See `setCloth`. */
|
|
65
|
+
export interface ShadowCasterCloth {
|
|
66
|
+
readonly binding: ClothBindingHandle;
|
|
67
|
+
readonly particles: ClothParticlesHandle;
|
|
68
|
+
}
|
|
63
69
|
export interface ShadowCasterSink {
|
|
64
70
|
/** A rigid mesh at a model transform: the world, a prop, a static piece of scenery. */
|
|
65
71
|
mesh(mesh: MeshHandle, model: ReadonlyMat4, material?: SceneCasterMaterial): void;
|
|
@@ -73,7 +79,13 @@ export interface ShadowCasterSink {
|
|
|
73
79
|
*
|
|
74
80
|
* Sixteen floats a joint, column-major, exactly as `Skeleton.palette` produces.
|
|
75
81
|
*/
|
|
76
|
-
skinnedMesh(mesh: MeshHandle, model: ReadonlyMat4, palette: Float32Array, material?: SceneCasterMaterial
|
|
82
|
+
skinnedMesh(mesh: MeshHandle, model: ReadonlyMat4, palette: Float32Array, material?: SceneCasterMaterial,
|
|
83
|
+
/**
|
|
84
|
+
* The cloth the visible draw places this mesh by, if any — for the reason the palette is not
|
|
85
|
+
* optional in spirit: a garment cast from its skinning alone throws a shadow that does not
|
|
86
|
+
* move as the cloth does.
|
|
87
|
+
*/
|
|
88
|
+
cloth?: ShadowCasterCloth): void;
|
|
77
89
|
/**
|
|
78
90
|
* An instanced batch of rigid meshes, placed by the same matrices the visible draw uses.
|
|
79
91
|
*
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
/** The taps' distances from the centre, one side, in units of the widest channel's distance. */
|
|
24
|
+
export declare const SKIN_BLUR_TAPS: readonly [0, 0.4, 1.1, 2.1, 3.5, 5.6, 9];
|
|
25
|
+
/**
|
|
26
|
+
* Where each tap's cell ends, in the same units: halfway to the next tap, and the last cell runs
|
|
27
|
+
* to infinity, so the profile's whole mass is somewhere.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SKIN_BLUR_EDGES: readonly [0.2, 0.75, 1.6, 2.8, 4.55, 7.3];
|
|
30
|
+
/** How many profiles a frame's skin can name: `SkinModel.profile`. */
|
|
31
|
+
export declare const SKIN_PROFILES = 8;
|
|
32
|
+
/**
|
|
33
|
+
* Burley's profile across a line, `(e^(−x/d) + e^(−x/3d)) / 8d`, integrated from 0 to `x`: half of
|
|
34
|
+
* it lies on each side, so this reaches ½ at infinity.
|
|
35
|
+
*/
|
|
36
|
+
export declare function burleyMass(x: number, d: number): number;
|
|
37
|
+
/**
|
|
38
|
+
* Tap `tap`'s weight for one channel scattering `d`, the taps spaced by `widest`: the centre's cell
|
|
39
|
+
* is both sides of it, every other cell one side, and a tap on each side takes this weight.
|
|
40
|
+
*/
|
|
41
|
+
export declare function skinBlurWeight(tap: number, d: number, widest: number): number;
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
/** The taps' distances from the centre, one side, in units of the widest channel's distance. */
|
|
24
|
+
export const SKIN_BLUR_TAPS = [0, 0.4, 1.1, 2.1, 3.5, 5.6, 9];
|
|
25
|
+
/**
|
|
26
|
+
* Where each tap's cell ends, in the same units: halfway to the next tap, and the last cell runs
|
|
27
|
+
* to infinity, so the profile's whole mass is somewhere.
|
|
28
|
+
*/
|
|
29
|
+
export const SKIN_BLUR_EDGES = [0.2, 0.75, 1.6, 2.8, 4.55, 7.3];
|
|
30
|
+
/** How many profiles a frame's skin can name: `SkinModel.profile`. */
|
|
31
|
+
export const SKIN_PROFILES = 8;
|
|
32
|
+
/**
|
|
33
|
+
* Burley's profile across a line, `(e^(−x/d) + e^(−x/3d)) / 8d`, integrated from 0 to `x`: half of
|
|
34
|
+
* it lies on each side, so this reaches ½ at infinity.
|
|
35
|
+
*/
|
|
36
|
+
export function burleyMass(x, d) {
|
|
37
|
+
return 0.5 - (Math.exp(-x / d) + 3 * Math.exp(-x / (3 * d))) / 8;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Tap `tap`'s weight for one channel scattering `d`, the taps spaced by `widest`: the centre's cell
|
|
41
|
+
* is both sides of it, every other cell one side, and a tap on each side takes this weight.
|
|
42
|
+
*/
|
|
43
|
+
export function skinBlurWeight(tap, d, widest) {
|
|
44
|
+
if (tap === 0)
|
|
45
|
+
return 2 * burleyMass(SKIN_BLUR_EDGES[0] * widest, d);
|
|
46
|
+
const inner = SKIN_BLUR_EDGES[tap - 1] * widest;
|
|
47
|
+
const outer = tap < SKIN_BLUR_EDGES.length ? SKIN_BLUR_EDGES[tap] * widest : Infinity;
|
|
48
|
+
return (outer === Infinity ? 0.5 : burleyMass(outer, d)) - burleyMass(inner, d);
|
|
49
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
/** Schlick's base for skin, at index 1.4: `((1 − n) / (1 + n))²`. */
|
|
26
|
+
export declare const SKIN_F0: number;
|
|
27
|
+
/** The largest `k` the fit is held to, and where the shader clamps it. */
|
|
28
|
+
export declare const SKIN_MAX_K = 10;
|
|
29
|
+
/**
|
|
30
|
+
* The fit's eight numbers, found by minimising its worst error against the numeric integral over
|
|
31
|
+
* `c` in [−1, 1] and `k` in (0, 10]:
|
|
32
|
+
*
|
|
33
|
+
* - `kappa`: `κ₁ = (1 + a k² + b k³)^(−1/6)` — the first harmonic's share, quadratic at zero and
|
|
34
|
+
* falling as `1/√k`, as the integral's does;
|
|
35
|
+
* - `spread`: `A = (1 + a k³)^(−1/6)`;
|
|
36
|
+
* - `soften`: `ε = a k / (1 + b k)`, how far `|c|` is rounded at the terminator;
|
|
37
|
+
* - `floor`: `B = (1 − A)(a + b (1 − A))`, the even part's floor as `A` gives up `|c|`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const SKIN_FIT: {
|
|
40
|
+
readonly kappa: readonly [22.6772, 101.349];
|
|
41
|
+
readonly spread: 838.87;
|
|
42
|
+
readonly soften: readonly [1.3081, 3.65743];
|
|
43
|
+
readonly floor: readonly [0.508537, 0.129496];
|
|
44
|
+
};
|
|
45
|
+
/** The ring-averaged `N·L` for one channel: `c` is `N·L`, `k` that channel's scatter × curvature. */
|
|
46
|
+
export declare function skinRing(c: number, k: number): number;
|
|
47
|
+
/**
|
|
48
|
+
* How far into a penumbra skin's light reaches, as a width the shadow is assumed to have: the
|
|
49
|
+
* lamp loop reads a shadow where its derivative cannot be taken, so its width in the world is not
|
|
50
|
+
* known there. Two centimetres is a soft edge across a face.
|
|
51
|
+
*/
|
|
52
|
+
export declare const SKIN_PENUMBRA_WIDTH = 0.02;
|
|
53
|
+
/**
|
|
54
|
+
* A shadow on skin, one channel at scatter distance `d`: the shadow's own value raised to
|
|
55
|
+
* `1 / (1 + d / width)`, so light reaches further into the penumbra the further that channel
|
|
56
|
+
* travels beneath the surface — red furthest, which is the red edge a shadow on skin has — and a
|
|
57
|
+
* shadow stays whole at 0 and absent at 1. **What it gives up** is the profile itself: Penner
|
|
58
|
+
* convolves a sharpened shadow with it, which needs the penumbra's width in the world and hardens
|
|
59
|
+
* the narrow channels' edge where nothing widened the map to make room.
|
|
60
|
+
*/
|
|
61
|
+
export declare function skinPenumbra(shadow: number, d: number): number;
|
|
62
|
+
/**
|
|
63
|
+
* Light through a thin part, one channel: `transmission × exp(−thickness / d)` of the light behind,
|
|
64
|
+
* by how squarely it is behind. A part as thick as its curvature's diameter — an ear's rim at a few
|
|
65
|
+
* millimetres, a cheek at ten centimetres — passes red through the one and nothing through the other.
|
|
66
|
+
*/
|
|
67
|
+
export declare function skinTransmission(backNdl: number, thickness: number, d: number, transmission: number): number;
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
/** Schlick's base for skin, at index 1.4: `((1 − n) / (1 + n))²`. */
|
|
26
|
+
export const SKIN_F0 = ((1 - 1.4) / (1 + 1.4)) ** 2;
|
|
27
|
+
/** The largest `k` the fit is held to, and where the shader clamps it. */
|
|
28
|
+
export const SKIN_MAX_K = 10;
|
|
29
|
+
/**
|
|
30
|
+
* The fit's eight numbers, found by minimising its worst error against the numeric integral over
|
|
31
|
+
* `c` in [−1, 1] and `k` in (0, 10]:
|
|
32
|
+
*
|
|
33
|
+
* - `kappa`: `κ₁ = (1 + a k² + b k³)^(−1/6)` — the first harmonic's share, quadratic at zero and
|
|
34
|
+
* falling as `1/√k`, as the integral's does;
|
|
35
|
+
* - `spread`: `A = (1 + a k³)^(−1/6)`;
|
|
36
|
+
* - `soften`: `ε = a k / (1 + b k)`, how far `|c|` is rounded at the terminator;
|
|
37
|
+
* - `floor`: `B = (1 − A)(a + b (1 − A))`, the even part's floor as `A` gives up `|c|`.
|
|
38
|
+
*/
|
|
39
|
+
export const SKIN_FIT = {
|
|
40
|
+
kappa: [22.6772, 101.349],
|
|
41
|
+
spread: 838.87,
|
|
42
|
+
soften: [1.3081, 3.65743],
|
|
43
|
+
floor: [0.508537, 0.129496],
|
|
44
|
+
};
|
|
45
|
+
/** The ring-averaged `N·L` for one channel: `c` is `N·L`, `k` that channel's scatter × curvature. */
|
|
46
|
+
export function skinRing(c, k) {
|
|
47
|
+
const q = Math.min(Math.max(k, 0), SKIN_MAX_K);
|
|
48
|
+
const k2 = q * q;
|
|
49
|
+
const kappa = (1 + SKIN_FIT.kappa[0] * k2 + SKIN_FIT.kappa[1] * k2 * q) ** (-1 / 6);
|
|
50
|
+
const spread = (1 + SKIN_FIT.spread * k2 * q) ** (-1 / 6);
|
|
51
|
+
const soften = (SKIN_FIT.soften[0] * q) / (1 + SKIN_FIT.soften[1] * q);
|
|
52
|
+
const given = 1 - spread;
|
|
53
|
+
const floor = given * (SKIN_FIT.floor[0] + SKIN_FIT.floor[1] * given);
|
|
54
|
+
return 0.5 * (kappa * c + spread * Math.sqrt(c * c + soften * soften) + floor);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* How far into a penumbra skin's light reaches, as a width the shadow is assumed to have: the
|
|
58
|
+
* lamp loop reads a shadow where its derivative cannot be taken, so its width in the world is not
|
|
59
|
+
* known there. Two centimetres is a soft edge across a face.
|
|
60
|
+
*/
|
|
61
|
+
export const SKIN_PENUMBRA_WIDTH = 0.02;
|
|
62
|
+
/**
|
|
63
|
+
* A shadow on skin, one channel at scatter distance `d`: the shadow's own value raised to
|
|
64
|
+
* `1 / (1 + d / width)`, so light reaches further into the penumbra the further that channel
|
|
65
|
+
* travels beneath the surface — red furthest, which is the red edge a shadow on skin has — and a
|
|
66
|
+
* shadow stays whole at 0 and absent at 1. **What it gives up** is the profile itself: Penner
|
|
67
|
+
* convolves a sharpened shadow with it, which needs the penumbra's width in the world and hardens
|
|
68
|
+
* the narrow channels' edge where nothing widened the map to make room.
|
|
69
|
+
*/
|
|
70
|
+
export function skinPenumbra(shadow, d) {
|
|
71
|
+
return Math.max(shadow, 0) ** (1 / (1 + d / SKIN_PENUMBRA_WIDTH));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Light through a thin part, one channel: `transmission × exp(−thickness / d)` of the light behind,
|
|
75
|
+
* by how squarely it is behind. A part as thick as its curvature's diameter — an ear's rim at a few
|
|
76
|
+
* millimetres, a cheek at ten centimetres — passes red through the one and nothing through the other.
|
|
77
|
+
*/
|
|
78
|
+
export function skinTransmission(backNdl, thickness, d, transmission) {
|
|
79
|
+
return transmission * Math.exp(-thickness / Math.max(d, 1e-6)) * Math.max(backNdl, 0);
|
|
80
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SkinnedClothSetup } from '@driftengine/physics';
|
|
2
|
+
import type { ClothParticlesHandle, RendererApi } from './backend/api.ts';
|
|
3
|
+
/** A garment a renderer solves, from `createSkinnedCloth`. */
|
|
4
|
+
export interface SkinnedClothSolver {
|
|
5
|
+
/** What `setCloth(binding, particles)` draws the garment's bound meshes by. */
|
|
6
|
+
readonly particles: ClothParticlesHandle;
|
|
7
|
+
/** Where the solver runs: on the device, or on the CPU beneath a backend without compute. */
|
|
8
|
+
readonly runsOn: 'device' | 'cpu';
|
|
9
|
+
/**
|
|
10
|
+
* One frame: the rig's pose — each joint's global matrix, model space, sixteen floats a joint,
|
|
11
|
+
* and the model matrix — and `dt` seconds, run as whole fixed steps; the particles at the frame's
|
|
12
|
+
* `alpha` are then what the frame draws. **Once a frame, before the draws**, as
|
|
13
|
+
* `updateClothParticles` is: a second in one frame would reach every draw of it on WebGPU.
|
|
14
|
+
*/
|
|
15
|
+
step(globals: Float32Array, model: Float32Array, dt: number): void;
|
|
16
|
+
/**
|
|
17
|
+
* The air the cloth drags toward from the next step, world space, metres a second: the frame's
|
|
18
|
+
* sample of the scene's one `WindField`, so a garment moves with everything else in the air.
|
|
19
|
+
*/
|
|
20
|
+
setWind(x: number, y: number, z: number): void;
|
|
21
|
+
/** Back to the skinned pose, settled, as a teleport resets it. */
|
|
22
|
+
reset(): void;
|
|
23
|
+
dispose(): void;
|
|
24
|
+
}
|
|
25
|
+
/** A garment for `renderer`, from a set-up checked by name. */
|
|
26
|
+
export declare function createSkinnedCloth(renderer: RendererApi, setup: SkinnedClothSetup): SkinnedClothSolver;
|
|
@@ -0,0 +1,84 @@
|
|
|
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 { computeHostOf } from './backend/webgpu/computeHost.js';
|
|
17
|
+
import { GpuSkinnedCloth } from './backend/webgpu/gpuSkinnedCloth.js';
|
|
18
|
+
/** A garment for `renderer`, from a set-up checked by name. */
|
|
19
|
+
export function createSkinnedCloth(renderer, setup) {
|
|
20
|
+
const host = computeHostOf(renderer);
|
|
21
|
+
return host === null ? new CpuCloth(renderer, setup) : new DeviceCloth(host, setup);
|
|
22
|
+
}
|
|
23
|
+
class DeviceCloth {
|
|
24
|
+
host;
|
|
25
|
+
runsOn = 'device';
|
|
26
|
+
particles;
|
|
27
|
+
cloth;
|
|
28
|
+
constructor(host, setup) {
|
|
29
|
+
this.host = host;
|
|
30
|
+
this.cloth = new GpuSkinnedCloth(host.device(), setup);
|
|
31
|
+
this.particles = this.cloth.particles;
|
|
32
|
+
}
|
|
33
|
+
step(globals, model, dt) {
|
|
34
|
+
if (this.host.lost())
|
|
35
|
+
return;
|
|
36
|
+
this.cloth.advance(globals, model, dt, this.host.changeFrame());
|
|
37
|
+
}
|
|
38
|
+
setWind(x, y, z) {
|
|
39
|
+
this.cloth.setWind(x, y, z);
|
|
40
|
+
}
|
|
41
|
+
reset() {
|
|
42
|
+
if (this.host.lost())
|
|
43
|
+
return;
|
|
44
|
+
this.cloth.reset();
|
|
45
|
+
}
|
|
46
|
+
/** Its buffers at once — its work is submitted — and its textures with the frame's. */
|
|
47
|
+
dispose() {
|
|
48
|
+
this.cloth.dispose();
|
|
49
|
+
this.host.retire(this.cloth.particles.textures);
|
|
50
|
+
}
|
|
51
|
+
/** The particles where the last step left them: the parity check's door, waits on the device. */
|
|
52
|
+
read() {
|
|
53
|
+
return this.cloth.read();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
class CpuCloth {
|
|
57
|
+
renderer;
|
|
58
|
+
runsOn = 'cpu';
|
|
59
|
+
particles;
|
|
60
|
+
cloth;
|
|
61
|
+
/** The frame's particles at `alpha`, before they go up. */
|
|
62
|
+
staging;
|
|
63
|
+
constructor(renderer, setup) {
|
|
64
|
+
this.renderer = renderer;
|
|
65
|
+
this.cloth = new SkinnedCloth(setup);
|
|
66
|
+
this.particles = renderer.createClothParticles(this.cloth.count);
|
|
67
|
+
this.staging = new Float32Array(this.cloth.count * 3);
|
|
68
|
+
}
|
|
69
|
+
step(globals, model, dt) {
|
|
70
|
+
this.cloth.setPose(globals, model);
|
|
71
|
+
this.cloth.advance(dt);
|
|
72
|
+
this.cloth.interpolate(this.cloth.alpha, this.staging);
|
|
73
|
+
this.renderer.updateClothParticles(this.particles, this.staging);
|
|
74
|
+
}
|
|
75
|
+
setWind(x, y, z) {
|
|
76
|
+
this.cloth.setWind(x, y, z);
|
|
77
|
+
}
|
|
78
|
+
reset() {
|
|
79
|
+
this.cloth.reset();
|
|
80
|
+
}
|
|
81
|
+
dispose() {
|
|
82
|
+
this.renderer.disposeClothParticles(this.particles);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/render/ssrPass.js
CHANGED
|
@@ -5,6 +5,7 @@ import { compileProgram, uniformLocations } from './shader.js';
|
|
|
5
5
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.js';
|
|
6
6
|
import { SSR_RESOLVE_FRAG } from './shaders/ssrResolve.js';
|
|
7
7
|
import { SSR_TRACE_FRAG } from './shaders/ssrTrace.js';
|
|
8
|
+
import { blendKeeping } from './sceneCoverage.js';
|
|
8
9
|
/**
|
|
9
10
|
* Screen-space reflection on WebGL2: a scissored trace per surface, then one composite.
|
|
10
11
|
*
|
|
@@ -160,7 +161,7 @@ export class SsrPass {
|
|
|
160
161
|
gl.disable(gl.CULL_FACE);
|
|
161
162
|
gl.enable(gl.BLEND);
|
|
162
163
|
/* Premultiplied `over`: the trace already multiplied its colour by the coverage it found. */
|
|
163
|
-
|
|
164
|
+
blendKeeping(gl, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
164
165
|
gl.useProgram(this.resolve);
|
|
165
166
|
gl.activeTexture(gl.TEXTURE0);
|
|
166
167
|
gl.bindTexture(gl.TEXTURE_2D, this.reflection);
|