@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FrameBudget } from '../budget.js';
|
|
2
2
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
|
|
3
|
-
import { FOG_RECEDE, fogModeOf } from '../drawFog.js';
|
|
3
|
+
import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.js';
|
|
4
|
+
import { blendCovering, blendKeeping } from '../../sceneCoverage.js';
|
|
4
5
|
import { mat4 } from 'gl-matrix';
|
|
5
6
|
import { GL_DEPTH_REMAP, MAX_DEPTH_LAYER, depthClearFor, depthOffsetForLayer, GL_SHADOW_REMAP, REVERSED_DEPTH, SHADOW_DEPTH_CLEAR, glDepthFuncEqual, glShadowDepthFunc, } from '../../depthConvention.js';
|
|
6
7
|
import { resolveGlass } from '../../glass.js';
|
|
@@ -37,8 +38,19 @@ import { LightField } from '../../driftLight/lightField.js';
|
|
|
37
38
|
import { WorldLightField } from '../../driftLight/worldLightField.js';
|
|
38
39
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.js';
|
|
39
40
|
import { flatFrag, flatVert } from '../../shaders/flat/index.js';
|
|
40
|
-
import {
|
|
41
|
+
import { skinEight } from '../../shaders/skinning.js';
|
|
42
|
+
import { MODEL_MAP_TEXTURE_UNIT, MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT, } from '../../lightBudget.js';
|
|
43
|
+
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel } from '../../surfaceModel.js';
|
|
44
|
+
import { modelBound, skinHalfBound } from '../../shaders/flat/models.js';
|
|
45
|
+
import { GlSkinScatter } from './skinScatter.js';
|
|
46
|
+
import { CLOTH_BINDING_TEXTURE_UNIT, CLOTH_PARTICLES_TEXTURE_UNIT, CLOTH_REST_TEXTURE_UNIT, } from '../../lightBudget.js';
|
|
41
47
|
import { SkinPaletteTexture } from './skinPaletteTexture.js';
|
|
48
|
+
import { clothBound } from '../../shaders/clothBinding.js';
|
|
49
|
+
import { validateClothBinding } from '../../clothBindingData.js';
|
|
50
|
+
import { GlClothBinding, GlClothParticles } from './clothTextures.js';
|
|
51
|
+
import { MAX_MORPH_TARGETS } from '../../skinPalette.js';
|
|
52
|
+
/** The weights a cloth-bound draw of a morphing mesh deforms by when none were set: none. */
|
|
53
|
+
const NO_MORPH_WEIGHTS = new Float32Array(MAX_MORPH_TARGETS);
|
|
42
54
|
/** Shader-side encoding of `RenderQuality.outputTransform`. */
|
|
43
55
|
/* The codes the shader reads, stated once in `vertexDefaults.ts` for both backends. */
|
|
44
56
|
import { LIGHT_VOLUME_VERT, lightVolumeFrag } from '../../shaders/lightVolume.js';
|
|
@@ -72,6 +84,7 @@ import { resolveRenderQuality } from '../../renderQuality.js';
|
|
|
72
84
|
import { drawingBufferSize } from '../../drawingBuffer.js';
|
|
73
85
|
import { Mesh, createMeshIncremental } from '../../mesh.js';
|
|
74
86
|
import { SurfaceTexture } from '../../surfaceTexture.js';
|
|
87
|
+
import { glCompressedFormats } from '../../glCompressed.js';
|
|
75
88
|
import { PlumeRenderer } from '../../plumeRenderer.js';
|
|
76
89
|
import { createScatterDeform, resolveScatterDeform } from '../../scatterDeform.js';
|
|
77
90
|
import { DEPTH_01_TO_CLIP, createResolvedLightVolume, resolveLightVolume, volumeMediumGain, } from '../../lightVolumeDraw.js';
|
|
@@ -93,6 +106,7 @@ import { deviceSnappedCellSize } from '../../textLayout.js';
|
|
|
93
106
|
import { WindStreakRenderer } from '../../windStreakRenderer.js';
|
|
94
107
|
import { InstancedBatch } from './instanced.js';
|
|
95
108
|
import { cutoutOf } from '../../cutoutCaster.js';
|
|
109
|
+
import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.js';
|
|
96
110
|
import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
|
|
97
111
|
import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js';
|
|
98
112
|
import { noLitSwitches } from '../../shaders/flat/glassTint.js';
|
|
@@ -447,6 +461,17 @@ export class WebGL2Renderer {
|
|
|
447
461
|
flatMorphedUniforms = null;
|
|
448
462
|
flatBothProgram = null;
|
|
449
463
|
flatBothUniforms = null;
|
|
464
|
+
/**
|
|
465
|
+
* The two skinned variants built with `SKIN_EIGHT` on, for a rig whose vertices name eight
|
|
466
|
+
* joints: compiled the first time such a mesh is created, and only then. See `skinning.ts`.
|
|
467
|
+
*/
|
|
468
|
+
flatSkinned8Program = null;
|
|
469
|
+
flatSkinned8Uniforms = null;
|
|
470
|
+
flatBoth8Program = null;
|
|
471
|
+
flatBoth8Uniforms = null;
|
|
472
|
+
/** And its shadow caster, so the shadow parts from nothing the body does. */
|
|
473
|
+
depthSkinned8Program = null;
|
|
474
|
+
depthSkinned8Uniforms = null;
|
|
450
475
|
/** The morph weights the following draws use, or null for none. */
|
|
451
476
|
morphWeights = null;
|
|
452
477
|
/**
|
|
@@ -480,6 +505,14 @@ export class WebGL2Renderer {
|
|
|
480
505
|
* before this existed.
|
|
481
506
|
*/
|
|
482
507
|
sceneTarget;
|
|
508
|
+
/**
|
|
509
|
+
* Skin's screen-space scattering, made where the profile asks for it and has a composite to add
|
|
510
|
+
* into, and null otherwise — which is every profile by default. See `GlSkinScatter`.
|
|
511
|
+
*/
|
|
512
|
+
skinScatter;
|
|
513
|
+
/** Whether this frame has spread its skin: once, at its first blended draw or at its end. */
|
|
514
|
+
skinScattered = false;
|
|
515
|
+
skinInverseProjection = mat4.create();
|
|
483
516
|
/** So the refusal below is once per renderer rather than once per frame. See `setColourGrade`. */
|
|
484
517
|
warnedGradeWithoutComposite = false;
|
|
485
518
|
/** Scratch for a volume's `uDepthToLocal`. Built per draw and never allocated in the loop. */
|
|
@@ -502,6 +535,21 @@ export class WebGL2Renderer {
|
|
|
502
535
|
skinPalette = new SkinPaletteTexture();
|
|
503
536
|
/** Whether a palette is currently chosen. Distinct from whether one was ever uploaded. */
|
|
504
537
|
skinPaletteSet = false;
|
|
538
|
+
/**
|
|
539
|
+
* Cloth-bound skinned lit programs, keyed `eight|morphed`, compiled when a binding is made for a
|
|
540
|
+
* mesh of that kind; and their casters, keyed by `eight`. See `ensureClothPrograms`.
|
|
541
|
+
*/
|
|
542
|
+
clothFlat = new Map();
|
|
543
|
+
clothDepth = new Map();
|
|
544
|
+
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
545
|
+
cloth = null;
|
|
546
|
+
/**
|
|
547
|
+
* Each shading model's lit programs, keyed `variant|model` by the vertex variant they pair with,
|
|
548
|
+
* compiled the first time a draw asks for one. See `modelProgram`.
|
|
549
|
+
*/
|
|
550
|
+
modelFlat = new Map();
|
|
551
|
+
/** The last palette chosen, which an eye's axis is turned by. See `setSkinPalette`. */
|
|
552
|
+
paletteData = null;
|
|
505
553
|
emptyTexture2D;
|
|
506
554
|
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
507
555
|
glassScratch = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
@@ -560,6 +608,11 @@ export class WebGL2Renderer {
|
|
|
560
608
|
surfaceScene = new Float32Array(4);
|
|
561
609
|
/** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
|
|
562
610
|
oitWeight = 0;
|
|
611
|
+
/**
|
|
612
|
+
* How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
|
|
613
|
+
* each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
|
|
614
|
+
*/
|
|
615
|
+
surfaceFog = FOG_RECEDE;
|
|
563
616
|
ditherFade = 0;
|
|
564
617
|
/** Said once, not per frame, where the context cannot give the float target the sum needs. */
|
|
565
618
|
oitRefused = false;
|
|
@@ -968,6 +1021,22 @@ export class WebGL2Renderer {
|
|
|
968
1021
|
materialHasMaps = false;
|
|
969
1022
|
/** The material last set, so a lit program rebuilt for glass can be given it. */
|
|
970
1023
|
currentMaterial = null;
|
|
1024
|
+
/**
|
|
1025
|
+
* The frame a dithered cutout's pattern is offset by, advanced at `beginFrame` and wrapped at 64
|
|
1026
|
+
* so the noise's argument stays small. See `cutoutDither.ts`.
|
|
1027
|
+
*/
|
|
1028
|
+
cutoutFrame = 0;
|
|
1029
|
+
/** Scratch for `uCutout`, so a material write allocates nothing. */
|
|
1030
|
+
cutoutVector = new Float32Array(4);
|
|
1031
|
+
/** The material's model numbers, `uModelParams`, packed by `packModel`. */
|
|
1032
|
+
modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
1033
|
+
/** The material's four switches, `uMaterialFlags`: albedo, ORM map, emissive map, two-sided. */
|
|
1034
|
+
materialFlags = new Int32Array(4);
|
|
1035
|
+
/**
|
|
1036
|
+
* The default framebuffer's sample count, read once, for a frame drawn with no scene target: a
|
|
1037
|
+
* dithered cutout there takes alpha-to-coverage when the canvas itself is multisampled.
|
|
1038
|
+
*/
|
|
1039
|
+
canvasSamples;
|
|
971
1040
|
/**
|
|
972
1041
|
* The lit stage's switches: what the lit programs were compiled with (`litOn`), and what the
|
|
973
1042
|
* profile and the content have asked for since (`litWanted`).
|
|
@@ -1104,7 +1173,7 @@ export class WebGL2Renderer {
|
|
|
1104
1173
|
gl.enable(gl.CULL_FACE);
|
|
1105
1174
|
gl.useProgram(this.depthProgram);
|
|
1106
1175
|
},
|
|
1107
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
1176
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
1108
1177
|
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1109
1178
|
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1110
1179
|
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
@@ -1114,8 +1183,20 @@ export class WebGL2Renderer {
|
|
|
1114
1183
|
}
|
|
1115
1184
|
this.shadowDrawBudget.ask();
|
|
1116
1185
|
const { gl } = this;
|
|
1117
|
-
|
|
1118
|
-
|
|
1186
|
+
/* A rig of eight casts through the caster built with SKIN_EIGHT on, as it draws. */
|
|
1187
|
+
const eightCaster = mesh.isSkinnedEight &&
|
|
1188
|
+
this.depthSkinned8Program !== null &&
|
|
1189
|
+
this.depthSkinned8Uniforms !== null;
|
|
1190
|
+
/* A garment casts through the cloth-bound caster of its rig's kind, as it draws. */
|
|
1191
|
+
const clothCaster = cloth === undefined
|
|
1192
|
+
? undefined
|
|
1193
|
+
: this.clothDepth.get(mesh.isSkinnedEight ? '8' : '4');
|
|
1194
|
+
const u = clothCaster?.uniforms ??
|
|
1195
|
+
(eightCaster
|
|
1196
|
+
? this.depthSkinned8Uniforms
|
|
1197
|
+
: this.depthSkinnedUniforms);
|
|
1198
|
+
gl.useProgram(clothCaster?.program ??
|
|
1199
|
+
(eightCaster ? this.depthSkinned8Program : this.depthSkinnedProgram));
|
|
1119
1200
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1120
1201
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1121
1202
|
/* The peel layer reads the first pass's depths; `beginShadowPass` bound that texture to
|
|
@@ -1126,6 +1207,12 @@ export class WebGL2Renderer {
|
|
|
1126
1207
|
this.skinPalette.update(gl, palette);
|
|
1127
1208
|
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
1128
1209
|
gl.uniform1i(u['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
1210
|
+
if (clothCaster !== undefined && cloth !== undefined) {
|
|
1211
|
+
this.bindCloth(u, {
|
|
1212
|
+
binding: cloth.binding,
|
|
1213
|
+
particles: cloth.particles,
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1129
1216
|
/* Narrowing is safe for the reason `mesh` above gives: this renderer only ever handed
|
|
1130
1217
|
out its own `Mesh`. */
|
|
1131
1218
|
mesh.draw(gl);
|
|
@@ -1169,7 +1256,8 @@ export class WebGL2Renderer {
|
|
|
1169
1256
|
if (this.activeDepthPeel)
|
|
1170
1257
|
gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1171
1258
|
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
1172
|
-
gl.
|
|
1259
|
+
gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
|
|
1260
|
+
gl.uniform2f(u['uAlphaCutout'] ?? null, cutout.cutoff, cutout.dithered);
|
|
1173
1261
|
/* Narrowed for the reason the sink's own `mesh` gives: this renderer only hands out its own. */
|
|
1174
1262
|
cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
1175
1263
|
}
|
|
@@ -1964,11 +2052,20 @@ export class WebGL2Renderer {
|
|
|
1964
2052
|
this.sceneTarget = this.quality.screenEffects
|
|
1965
2053
|
? new SceneTarget(gl, this.quality.sceneSamples, this.quality.hdrScene, this.quality.discardResolvedAttachments)
|
|
1966
2054
|
: null;
|
|
2055
|
+
/* Screen-space skin needs the composite to add into; without one it is pre-integrated, said once. */
|
|
2056
|
+
if (this.quality.skinScattering === 'screen-space' && this.sceneTarget === null) {
|
|
2057
|
+
console.warn("WebGL2: skinScattering 'screen-space' needs screenEffects to add into; skin is pre-integrated");
|
|
2058
|
+
}
|
|
2059
|
+
this.skinScatter =
|
|
2060
|
+
this.quality.skinScattering === 'screen-space' && this.sceneTarget !== null
|
|
2061
|
+
? new GlSkinScatter(gl)
|
|
2062
|
+
: null;
|
|
1967
2063
|
/*
|
|
1968
2064
|
* Said once at init, because the number a caller asked for and the number the part gave
|
|
1969
2065
|
* them are not always the same, and a silently clamped sample count is a quality setting
|
|
1970
2066
|
* that does not apply — the same failure mode `capabilityClamp` exists to avoid.
|
|
1971
2067
|
*/
|
|
2068
|
+
this.canvasSamples = Math.max(1, gl.getParameter(gl.SAMPLES) ?? 1);
|
|
1972
2069
|
const got = this.sceneTarget?.sampleCount ?? 1;
|
|
1973
2070
|
if (this.quality.sceneSamples > 1 && got !== this.quality.sceneSamples) {
|
|
1974
2071
|
console.warn(`Renderer: ${this.quality.sceneSamples}x scene multisampling was requested and this ` +
|
|
@@ -2129,6 +2226,8 @@ export class WebGL2Renderer {
|
|
|
2129
2226
|
return;
|
|
2130
2227
|
this.disposed = true;
|
|
2131
2228
|
const { gl } = this;
|
|
2229
|
+
if (!this.contextLost)
|
|
2230
|
+
this.skinScatter?.dispose();
|
|
2132
2231
|
this.detachContextLoss();
|
|
2133
2232
|
this.contextLostListeners.length = 0;
|
|
2134
2233
|
this.contextRestoredListeners.length = 0;
|
|
@@ -2168,11 +2267,17 @@ export class WebGL2Renderer {
|
|
|
2168
2267
|
this.flatSkinnedProgram,
|
|
2169
2268
|
this.flatMorphedProgram,
|
|
2170
2269
|
this.flatBothProgram,
|
|
2270
|
+
this.flatSkinned8Program,
|
|
2271
|
+
this.flatBoth8Program,
|
|
2272
|
+
this.depthSkinned8Program,
|
|
2171
2273
|
this.flatInstancedProgram,
|
|
2172
2274
|
]) {
|
|
2173
2275
|
if (extra !== null)
|
|
2174
2276
|
gl.deleteProgram(extra);
|
|
2175
2277
|
}
|
|
2278
|
+
for (const entry of [...this.clothFlat.values(), ...this.clothDepth.values()]) {
|
|
2279
|
+
gl.deleteProgram(entry.program);
|
|
2280
|
+
}
|
|
2176
2281
|
for (const program of [
|
|
2177
2282
|
this.flatProgram,
|
|
2178
2283
|
this.panelProgram,
|
|
@@ -2269,13 +2374,19 @@ export class WebGL2Renderer {
|
|
|
2269
2374
|
*/
|
|
2270
2375
|
warmVariants(data) {
|
|
2271
2376
|
const skinnable = data.joints !== undefined;
|
|
2377
|
+
/* A rig of eight takes the variants built with SKIN_EIGHT on, and a caster of its own. */
|
|
2378
|
+
const eight = skinnable && data.joints2 !== undefined;
|
|
2272
2379
|
const morphable = data.morphTargets !== undefined;
|
|
2273
2380
|
for (const skinned of skinnable ? [false, true] : [false]) {
|
|
2274
2381
|
for (const morphed of morphable ? [false, true] : [false]) {
|
|
2275
2382
|
if (skinned || morphed)
|
|
2276
|
-
this.ensureVariant(skinned, morphed);
|
|
2383
|
+
this.ensureVariant(skinned, morphed, skinned && eight);
|
|
2277
2384
|
}
|
|
2278
2385
|
}
|
|
2386
|
+
if (eight && this.depthSkinned8Program === null && !this.contextLost) {
|
|
2387
|
+
this.depthSkinned8Program = compileProgram(this.gl, skinEight(DEPTH_SKINNED_VERT), DEPTH_FRAG, 'depth-skinned8');
|
|
2388
|
+
this.depthSkinned8Uniforms = uniformLocations(this.gl, this.depthSkinned8Program, 'depth-skinned8');
|
|
2389
|
+
}
|
|
2279
2390
|
}
|
|
2280
2391
|
/**
|
|
2281
2392
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
@@ -2317,24 +2428,39 @@ export class WebGL2Renderer {
|
|
|
2317
2428
|
* `bindMeshPass` would otherwise carry every uniform at zero, and zero is a real value for most
|
|
2318
2429
|
* of them — the failure this file records for `uGrain` and `uAmbientGround`.
|
|
2319
2430
|
*/
|
|
2320
|
-
ensureVariant(skinned, morphed) {
|
|
2431
|
+
ensureVariant(skinned, morphed, eight = false) {
|
|
2321
2432
|
if (this.contextLost)
|
|
2322
2433
|
return;
|
|
2323
|
-
if (skinned &&
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2434
|
+
if (skinned && eight) {
|
|
2435
|
+
if (morphed ? this.flatBoth8Program !== null : this.flatSkinned8Program !== null)
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
else {
|
|
2439
|
+
if (skinned && morphed && this.flatBothProgram !== null)
|
|
2440
|
+
return;
|
|
2441
|
+
if (skinned && !morphed && this.flatSkinnedProgram !== null)
|
|
2442
|
+
return;
|
|
2443
|
+
if (!skinned && morphed && this.flatMorphedProgram !== null)
|
|
2444
|
+
return;
|
|
2445
|
+
if (!skinned && !morphed)
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2331
2448
|
const { gl } = this;
|
|
2332
|
-
const label = `flat.${morphed ? 'morphed' : ''}${skinned ? 'skinned' : ''}`;
|
|
2449
|
+
const label = `flat.${morphed ? 'morphed' : ''}${skinned ? (eight ? 'skinned8' : 'skinned') : ''}`;
|
|
2333
2450
|
/* `flatFragSource`, not a fresh `flatFrag`: every variant shades through one fragment stage
|
|
2334
2451
|
at one light budget, and the uploads are written for that budget. */
|
|
2335
|
-
const
|
|
2452
|
+
const vertex = flatVert({ skinned, morphed, instanced: false });
|
|
2453
|
+
const program = compileProgram(gl, skinned && eight ? skinEight(vertex) : vertex, this.flatFragSource, label);
|
|
2336
2454
|
const uniforms = uniformLocations(gl, program, label);
|
|
2337
|
-
if (skinned && morphed) {
|
|
2455
|
+
if (skinned && eight && morphed) {
|
|
2456
|
+
this.flatBoth8Program = program;
|
|
2457
|
+
this.flatBoth8Uniforms = uniforms;
|
|
2458
|
+
}
|
|
2459
|
+
else if (skinned && eight) {
|
|
2460
|
+
this.flatSkinned8Program = program;
|
|
2461
|
+
this.flatSkinned8Uniforms = uniforms;
|
|
2462
|
+
}
|
|
2463
|
+
else if (skinned && morphed) {
|
|
2338
2464
|
this.flatBothProgram = program;
|
|
2339
2465
|
this.flatBothUniforms = uniforms;
|
|
2340
2466
|
}
|
|
@@ -2352,6 +2478,8 @@ export class WebGL2Renderer {
|
|
|
2352
2478
|
if (camera !== null && env !== null) {
|
|
2353
2479
|
gl.useProgram(program);
|
|
2354
2480
|
this.writeMeshPassState(uniforms, camera, env);
|
|
2481
|
+
/* And the material the caller has set, which a program born mid-pass must carry too. */
|
|
2482
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
2355
2483
|
this.useFlatProgram();
|
|
2356
2484
|
}
|
|
2357
2485
|
}
|
|
@@ -2364,7 +2492,21 @@ export class WebGL2Renderer {
|
|
|
2364
2492
|
* the consumer's decision. What it gains is a GPU object with sane sampler state.
|
|
2365
2493
|
*/
|
|
2366
2494
|
createSurfaceTexture(source, options = {}) {
|
|
2367
|
-
return new SurfaceTexture(this.gl, source, options);
|
|
2495
|
+
return new SurfaceTexture(this.gl, source, options, this.compressedFormats);
|
|
2496
|
+
}
|
|
2497
|
+
compressedCache = null;
|
|
2498
|
+
/**
|
|
2499
|
+
* The BC formats this device takes as blocks, each named with its colour space (`'bc7-srgb'`).
|
|
2500
|
+
*
|
|
2501
|
+
* **Ask before choosing**, because a phone usually answers none: a BC texture handed to
|
|
2502
|
+
* `createSurfaceTexture` where its format is not here is refused by name rather than decoded,
|
|
2503
|
+
* since core ships no decoder. `@driftengine/assets`' loader asks this for every BC texture in a
|
|
2504
|
+
* container and decodes at load where the answer is no. Read from the four extensions WebGL2
|
|
2505
|
+
* spreads BC across; see `glCompressed.ts`.
|
|
2506
|
+
*/
|
|
2507
|
+
get compressedFormats() {
|
|
2508
|
+
this.compressedCache ??= glCompressedFormats(this.gl);
|
|
2509
|
+
return this.compressedCache;
|
|
2368
2510
|
}
|
|
2369
2511
|
/**
|
|
2370
2512
|
* Upload several images of one size as the layers of one array, for a merged mesh that names a
|
|
@@ -2373,7 +2515,7 @@ export class WebGL2Renderer {
|
|
|
2373
2515
|
* refusal names the first that is not.
|
|
2374
2516
|
*/
|
|
2375
2517
|
createSurfaceTextureArray(sources, options = {}) {
|
|
2376
|
-
return new SurfaceTexture(this.gl, sources, options);
|
|
2518
|
+
return new SurfaceTexture(this.gl, sources, options, this.compressedFormats);
|
|
2377
2519
|
}
|
|
2378
2520
|
/**
|
|
2379
2521
|
* Replace a surface texture's pixels, keeping the GPU object and its sampler state.
|
|
@@ -2431,6 +2573,148 @@ export class WebGL2Renderer {
|
|
|
2431
2573
|
}
|
|
2432
2574
|
this.skinPalette.update(this.gl, palette);
|
|
2433
2575
|
this.skinPaletteSet = true;
|
|
2576
|
+
this.paletteData = palette;
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* The lit program for one vertex variant shading by a model: the standard program's vertex
|
|
2580
|
+
* stage, and the fragment stage with the model's switch on (`modelBound`). Compiled the first
|
|
2581
|
+
* time a draw asks — a scene that names no model compiles none — and fed the pass and the
|
|
2582
|
+
* material at once, as `ensureClothPrograms` feeds its own.
|
|
2583
|
+
*/
|
|
2584
|
+
modelProgram(variant, kind,
|
|
2585
|
+
/** Which half of a skin it draws under the screen-space blur. See `SkinHalf`. */
|
|
2586
|
+
half = 'whole') {
|
|
2587
|
+
const key = `${variant}|${kind}|${half}`;
|
|
2588
|
+
const held = this.modelFlat.get(key);
|
|
2589
|
+
if (held !== undefined)
|
|
2590
|
+
return held;
|
|
2591
|
+
const { gl } = this;
|
|
2592
|
+
const label = `flat.${variant}.${kind}.${half}`;
|
|
2593
|
+
const program = compileProgram(gl, litVertexOf(variant), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
|
|
2594
|
+
const made = { program, uniforms: uniformLocations(gl, program, label) };
|
|
2595
|
+
this.modelFlat.set(key, made);
|
|
2596
|
+
this.flatTargets.push(made);
|
|
2597
|
+
gl.useProgram(program);
|
|
2598
|
+
const camera = this.lastPassCamera;
|
|
2599
|
+
const env = this.lastPassEnv;
|
|
2600
|
+
if (camera !== null && env !== null)
|
|
2601
|
+
this.writeMeshPassState(made.uniforms, camera, env);
|
|
2602
|
+
this.writeMaterialState(made.uniforms, this.currentMaterial);
|
|
2603
|
+
return made;
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* An eye's axis in the world for this draw, into its program's `uModelParams`: the material's
|
|
2607
|
+
* own numbers with the axis turned by the draw's model matrix and the eye's joint. See
|
|
2608
|
+
* `eyeAxisInWorld`.
|
|
2609
|
+
*/
|
|
2610
|
+
writeEyeAxis(u, model) {
|
|
2611
|
+
const eye = this.currentMaterial?.model;
|
|
2612
|
+
if (eye?.kind !== 'eye')
|
|
2613
|
+
return;
|
|
2614
|
+
eyeAxisInWorld(eye, model, this.skinPaletteSet ? this.paletteData : null, this.modelParams, 4);
|
|
2615
|
+
this.gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
|
|
2616
|
+
}
|
|
2617
|
+
/**
|
|
2618
|
+
* A mesh's cloth binding: which simulation triangle each of its vertices follows, where on it,
|
|
2619
|
+
* and by how much. See `ClothBindingData`.
|
|
2620
|
+
*
|
|
2621
|
+
* Refused by name for a mesh with no rig — a binding blends a vertex between its skinning and the
|
|
2622
|
+
* cloth — and for one that disagrees with the mesh. Compiles the programs that draw and cast it
|
|
2623
|
+
* here, at creation, so no frame ever waits on a compile.
|
|
2624
|
+
*/
|
|
2625
|
+
createClothBinding(mesh, data) {
|
|
2626
|
+
const target = mesh;
|
|
2627
|
+
if (!target.isSkinned) {
|
|
2628
|
+
throw new Error('createClothBinding: this mesh has no rig, and a binding blends each vertex between its ' +
|
|
2629
|
+
'skinning and the cloth');
|
|
2630
|
+
}
|
|
2631
|
+
validateClothBinding(data, target.vertexCount);
|
|
2632
|
+
this.ensureClothPrograms(target.isSkinnedEight, target.morph !== null);
|
|
2633
|
+
return new GlClothBinding(this.gl, data);
|
|
2634
|
+
}
|
|
2635
|
+
/** One character's particles, `count` of them, to be updated every frame. */
|
|
2636
|
+
createClothParticles(count) {
|
|
2637
|
+
return new GlClothParticles(this.gl, count);
|
|
2638
|
+
}
|
|
2639
|
+
/**
|
|
2640
|
+
* This frame's particles, three floats each, world space; last frame's are kept for motion. A
|
|
2641
|
+
* simulation's output, as it is.
|
|
2642
|
+
*/
|
|
2643
|
+
updateClothParticles(particles, positions) {
|
|
2644
|
+
if (this.contextLost)
|
|
2645
|
+
return;
|
|
2646
|
+
particles.update(this.gl, positions);
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* Place the following skinned draws by a cloth: `binding` the mesh's, `particles` the
|
|
2650
|
+
* character's. Null for none, which is every draw until this is called. Both or neither, and
|
|
2651
|
+
* the particles must be as many as the binding names.
|
|
2652
|
+
*/
|
|
2653
|
+
setCloth(binding, particles = null) {
|
|
2654
|
+
if (binding === null || particles === null) {
|
|
2655
|
+
this.cloth = null;
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
if (binding.particles !== particles.count) {
|
|
2659
|
+
throw new Error(`setCloth: the binding names ${binding.particles} particles and these are ${particles.count}`);
|
|
2660
|
+
}
|
|
2661
|
+
this.cloth = { binding, particles };
|
|
2662
|
+
}
|
|
2663
|
+
disposeClothBinding(binding) {
|
|
2664
|
+
if (this.contextLost)
|
|
2665
|
+
return;
|
|
2666
|
+
binding.dispose(this.gl);
|
|
2667
|
+
}
|
|
2668
|
+
disposeClothParticles(particles) {
|
|
2669
|
+
if (this.contextLost)
|
|
2670
|
+
return;
|
|
2671
|
+
particles.dispose(this.gl);
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* The cloth-bound lit program and caster for a rig of this kind, once. Fed at once if a pass is
|
|
2675
|
+
* bound, for the reason `ensureVariant` gives: a program compiled after `bindMeshPass` would
|
|
2676
|
+
* otherwise carry every uniform at zero.
|
|
2677
|
+
*/
|
|
2678
|
+
ensureClothPrograms(eight, morphed) {
|
|
2679
|
+
if (this.contextLost)
|
|
2680
|
+
return;
|
|
2681
|
+
const { gl } = this;
|
|
2682
|
+
const key = `${eight ? 8 : 4}|${morphed ? 'm' : '-'}`;
|
|
2683
|
+
if (!this.clothFlat.has(key)) {
|
|
2684
|
+
const label = `flat.cloth${eight ? '8' : ''}${morphed ? '.morphed' : ''}`;
|
|
2685
|
+
const vertex = flatVert({ skinned: true, morphed, instanced: false });
|
|
2686
|
+
const program = compileProgram(gl, clothBound(eight ? skinEight(vertex) : vertex), this.flatFragSource, label);
|
|
2687
|
+
const uniforms = uniformLocations(gl, program, label);
|
|
2688
|
+
this.clothFlat.set(key, { program, uniforms });
|
|
2689
|
+
this.flatTargets.push({ program, uniforms });
|
|
2690
|
+
const camera = this.lastPassCamera;
|
|
2691
|
+
const env = this.lastPassEnv;
|
|
2692
|
+
if (camera !== null && env !== null) {
|
|
2693
|
+
gl.useProgram(program);
|
|
2694
|
+
this.writeMeshPassState(uniforms, camera, env);
|
|
2695
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
2696
|
+
this.useFlatProgram();
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
const depthKey = eight ? '8' : '4';
|
|
2700
|
+
if (!this.clothDepth.has(depthKey)) {
|
|
2701
|
+
const label = `depth-cloth${eight ? '8' : ''}`;
|
|
2702
|
+
const program = compileProgram(gl, clothBound(eight ? skinEight(DEPTH_SKINNED_VERT) : DEPTH_SKINNED_VERT), DEPTH_FRAG, label);
|
|
2703
|
+
this.clothDepth.set(depthKey, { program, uniforms: uniformLocations(gl, program, label) });
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
/** Bind the cloth's three textures to their units and name them to `u`. */
|
|
2707
|
+
bindCloth(u, cloth) {
|
|
2708
|
+
const { gl } = this;
|
|
2709
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_BINDING_TEXTURE_UNIT);
|
|
2710
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.binding.binding);
|
|
2711
|
+
gl.uniform1i(u['uClothBinding'] ?? null, CLOTH_BINDING_TEXTURE_UNIT);
|
|
2712
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_PARTICLES_TEXTURE_UNIT);
|
|
2713
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.particles.current);
|
|
2714
|
+
gl.uniform1i(u['uClothParticles'] ?? null, CLOTH_PARTICLES_TEXTURE_UNIT);
|
|
2715
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_REST_TEXTURE_UNIT);
|
|
2716
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.binding.rest);
|
|
2717
|
+
gl.uniform1i(u['uClothRest'] ?? null, CLOTH_REST_TEXTURE_UNIT);
|
|
2434
2718
|
}
|
|
2435
2719
|
/**
|
|
2436
2720
|
* Choose the morph weights the following `drawMesh` calls deform by, or null for none.
|
|
@@ -2452,6 +2736,11 @@ export class WebGL2Renderer {
|
|
|
2452
2736
|
this.materialDoubleSided = material?.doubleSided === true;
|
|
2453
2737
|
this.materialHasMaps = materialHasMaps(material);
|
|
2454
2738
|
this.currentMaterial = material;
|
|
2739
|
+
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
2740
|
+
const model = material?.model;
|
|
2741
|
+
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
2742
|
+
this.skinScatter.setProfile(model.profile, model.scatterColor[0] * model.radius, model.scatterColor[1] * model.radius, model.scatterColor[2] * model.radius);
|
|
2743
|
+
}
|
|
2455
2744
|
/*
|
|
2456
2745
|
* Written to every flat program, because material state persists across draws and a skinned
|
|
2457
2746
|
* draw is entitled to the material the caller set before it. A second program holding none of
|
|
@@ -2468,6 +2757,25 @@ export class WebGL2Renderer {
|
|
|
2468
2757
|
}
|
|
2469
2758
|
this.useFlatProgram();
|
|
2470
2759
|
}
|
|
2760
|
+
/**
|
|
2761
|
+
* How a material's cutout edge is drawn this frame: the material's mode against what the frame can
|
|
2762
|
+
* resolve. A translucent draw tests hard whatever the material says, because a blended draw has
|
|
2763
|
+
* nothing to average a pattern with. See `cutoutDither.ts`.
|
|
2764
|
+
*/
|
|
2765
|
+
cutoutResolveNow(material, translucent) {
|
|
2766
|
+
if (translucent || material === null)
|
|
2767
|
+
return 'hard';
|
|
2768
|
+
return resolveCutout(material.cutoutMode ?? 'hard', this.temporalJittering, this.sceneTarget?.sampleCount ?? this.canvasSamples);
|
|
2769
|
+
}
|
|
2770
|
+
/** The lit pass's `uCutout` for a material this frame, into scratch. See `uCutout`. */
|
|
2771
|
+
cutoutUniform(material, translucent) {
|
|
2772
|
+
const v = this.cutoutVector;
|
|
2773
|
+
v[0] = material?.cutout ?? 0;
|
|
2774
|
+
v[1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveNow(material, translucent)];
|
|
2775
|
+
v[2] = this.cutoutFrame;
|
|
2776
|
+
v[3] = 0;
|
|
2777
|
+
return v;
|
|
2778
|
+
}
|
|
2471
2779
|
/**
|
|
2472
2780
|
* One flat program's view of the current material.
|
|
2473
2781
|
*
|
|
@@ -2512,7 +2820,7 @@ export class WebGL2Renderer {
|
|
|
2512
2820
|
if (orm !== null)
|
|
2513
2821
|
orm.bind(gl, ORM_TEXTURE_UNIT);
|
|
2514
2822
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
2515
|
-
|
|
2823
|
+
this.materialFlags[1] = orm === null ? 0 : 1;
|
|
2516
2824
|
gl.uniform3f(u['uOrmScale'] ?? null, material?.occlusionStrength ?? 1, material?.roughnessScale ?? 1, material?.metallicScale ?? 1);
|
|
2517
2825
|
/*
|
|
2518
2826
|
* The emissive map, on exactly the terms the two maps above it are bound: before the albedo
|
|
@@ -2520,7 +2828,7 @@ export class WebGL2Renderer {
|
|
|
2520
2828
|
*
|
|
2521
2829
|
* **The stand-in is black, so the gate is what keeps a glow on.** The shader multiplies by
|
|
2522
2830
|
* this map, and `emptyTexture2D` is opaque black rather than the multiplicative identity, so an
|
|
2523
|
-
* unbound material keeps the arithmetic it had before only because `
|
|
2831
|
+
* unbound material keeps the arithmetic it had before only because its flag in `uMaterialFlags` is 0
|
|
2524
2832
|
* and the read never happens. What would make that wrong is a read taken outside that branch:
|
|
2525
2833
|
* every glow on a material without a map would go out.
|
|
2526
2834
|
*/
|
|
@@ -2530,7 +2838,7 @@ export class WebGL2Renderer {
|
|
|
2530
2838
|
if (emissiveMap !== null)
|
|
2531
2839
|
emissiveMap.bind(gl, EMISSIVE_TEXTURE_UNIT);
|
|
2532
2840
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
2533
|
-
|
|
2841
|
+
this.materialFlags[2] = emissiveMap === null ? 0 : 1;
|
|
2534
2842
|
const emissiveScale = material?.emissiveScale ?? null;
|
|
2535
2843
|
gl.uniform3f(u['uEmissiveScale'] ?? null, emissiveScale?.[0] ?? 1, emissiveScale?.[1] ?? 1, emissiveScale?.[2] ?? 1);
|
|
2536
2844
|
/*
|
|
@@ -2541,7 +2849,21 @@ export class WebGL2Renderer {
|
|
|
2541
2849
|
* how big a dome was — WebGPU writes it before its own early return and WebGL2 did not.
|
|
2542
2850
|
*/
|
|
2543
2851
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
2544
|
-
gl.
|
|
2852
|
+
gl.uniform2f(u['uUvOffset'] ?? null, material?.uOffset ?? 0, material?.vOffset ?? 0);
|
|
2853
|
+
this.materialFlags[3] = material?.doubleSided === true ? 1 : 0;
|
|
2854
|
+
/*
|
|
2855
|
+
* The material's shading model: its numbers, and its map or the stand-in, before the albedo
|
|
2856
|
+
* early return as every map above is. Written to the standard programs too, which declare both
|
|
2857
|
+
* and read neither — one decision for every program, as `setMaterial` says.
|
|
2858
|
+
*/
|
|
2859
|
+
const modelMap = material?.modelMap ?? null;
|
|
2860
|
+
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
2861
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2862
|
+
if (modelMap !== null)
|
|
2863
|
+
modelMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
|
|
2864
|
+
gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
|
|
2865
|
+
packModel(material?.model ?? null, modelMap !== null, this.modelParams);
|
|
2866
|
+
gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
|
|
2545
2867
|
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
2546
2868
|
return, so a material with no albedo still leaves a complete texture on the unit. */
|
|
2547
2869
|
if (albedo === null) {
|
|
@@ -2560,12 +2882,14 @@ export class WebGL2Renderer {
|
|
|
2560
2882
|
array target, and with nothing there it is incomplete. */
|
|
2561
2883
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
2562
2884
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2563
|
-
|
|
2885
|
+
this.materialFlags[0] = 0;
|
|
2886
|
+
gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
|
|
2564
2887
|
return;
|
|
2565
2888
|
}
|
|
2566
2889
|
albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
2567
|
-
gl.
|
|
2568
|
-
|
|
2890
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(material, false));
|
|
2891
|
+
this.materialFlags[0] = 1;
|
|
2892
|
+
gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
|
|
2569
2893
|
}
|
|
2570
2894
|
/**
|
|
2571
2895
|
* @deprecated Use `setMaterial`.
|
|
@@ -2582,7 +2906,7 @@ export class WebGL2Renderer {
|
|
|
2582
2906
|
* Rebind `SURFACE_TEXTURE_UNIT` to whatever `setSurfaceTexture` last put there.
|
|
2583
2907
|
*
|
|
2584
2908
|
* Only `drawSdfText` calls this, after the atlas has finished borrowing the same unit.
|
|
2585
|
-
* No uniform is written here — the flat program's `uAlbedo
|
|
2909
|
+
* No uniform is written here — the flat program's `uAlbedo` and its flag in `uMaterialFlags` were never
|
|
2586
2910
|
* touched by the text draw and are still correct, since that draw runs its own separate
|
|
2587
2911
|
* program. What moved is unit 15's actual `WebGLTexture` binding, which is GL state, not
|
|
2588
2912
|
* program state, and this puts it back to what `currentSurfaceTexture` says it should be.
|
|
@@ -2669,6 +2993,28 @@ export class WebGL2Renderer {
|
|
|
2669
2993
|
this.materials.dirty();
|
|
2670
2994
|
this.writeFlatFloat('uGrain', Math.min(1, Math.max(0, amount)));
|
|
2671
2995
|
}
|
|
2996
|
+
/**
|
|
2997
|
+
* Whether the surfaces drawn next meet the medium: distance fog and the global medium's haze.
|
|
2998
|
+
* True is the default, and what `bindMeshPass` restores.
|
|
2999
|
+
*
|
|
3000
|
+
* **The opaque counterpart of `TranslucentMeshOptions.fog`**, which a translucent draw has had
|
|
3001
|
+
* all along; the shader's per-draw switch was there, and nothing reached it for an ordinary mesh.
|
|
3002
|
+
* What it is for is a medium that darkens one thing and not another — a floor receding into the
|
|
3003
|
+
* distance under figures that must keep their full value however far from the camera they stand.
|
|
3004
|
+
* Material state like `setSurfaceGrain`: instanced and skinned draws read it too, and a
|
|
3005
|
+
* translucent draw's own `fog` is measured against it and puts it back.
|
|
3006
|
+
*
|
|
3007
|
+
* **What it gives up** is the medium's honesty: a surface out of the fog is drawn as though the
|
|
3008
|
+
* air between it and the eye were empty, so it should be a surface the scene means to stand
|
|
3009
|
+
* apart, not one whose distance is the point.
|
|
3010
|
+
*/
|
|
3011
|
+
setSurfaceFog(enabled) {
|
|
3012
|
+
if (this.contextLost)
|
|
3013
|
+
return;
|
|
3014
|
+
this.materials.dirty();
|
|
3015
|
+
this.surfaceFog = enabled ? FOG_RECEDE : FOG_OFF;
|
|
3016
|
+
this.writeFlatInt('uFogEnabled', this.surfaceFog);
|
|
3017
|
+
}
|
|
2672
3018
|
/**
|
|
2673
3019
|
* One float of material state, into every flat program.
|
|
2674
3020
|
*
|
|
@@ -2685,6 +3031,14 @@ export class WebGL2Renderer {
|
|
|
2685
3031
|
}
|
|
2686
3032
|
this.useFlatProgram();
|
|
2687
3033
|
}
|
|
3034
|
+
/** One int of material state, into every flat program, for `writeFlatFloat`'s reason. */
|
|
3035
|
+
writeFlatInt(name, value) {
|
|
3036
|
+
for (const target of this.flatTargets) {
|
|
3037
|
+
this.gl.useProgram(target.program);
|
|
3038
|
+
this.gl.uniform1i(target.uniforms[name] ?? null, value);
|
|
3039
|
+
}
|
|
3040
|
+
this.useFlatProgram();
|
|
3041
|
+
}
|
|
2688
3042
|
/**
|
|
2689
3043
|
* How much of the surfaces drawn next survives a screen-door dither, for crossfading two levels
|
|
2690
3044
|
* of detail of one thing.
|
|
@@ -2829,6 +3183,8 @@ export class WebGL2Renderer {
|
|
|
2829
3183
|
if (camera !== null && env !== null) {
|
|
2830
3184
|
gl.useProgram(program);
|
|
2831
3185
|
this.writeMeshPassState(uniforms, camera, env);
|
|
3186
|
+
/* And the material the caller has set, which a program born mid-pass must carry too. */
|
|
3187
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
2832
3188
|
this.useFlatProgram();
|
|
2833
3189
|
}
|
|
2834
3190
|
}
|
|
@@ -2860,6 +3216,7 @@ export class WebGL2Renderer {
|
|
|
2860
3216
|
}
|
|
2861
3217
|
/** Draw every live instance, blended. See `drawTranslucentMesh` for what the options mean. */
|
|
2862
3218
|
drawTranslucentInstanced(batch, data, opacity, options = {}) {
|
|
3219
|
+
this.scatterSkin();
|
|
2863
3220
|
this.submitInstanced(batch, data, opacity, true, options);
|
|
2864
3221
|
}
|
|
2865
3222
|
/** Release the placement. The mesh is the caller's and is not released. */
|
|
@@ -2899,8 +3256,11 @@ export class WebGL2Renderer {
|
|
|
2899
3256
|
return;
|
|
2900
3257
|
this.drawBudget.ask();
|
|
2901
3258
|
this.ensureInstancedProgram();
|
|
2902
|
-
|
|
2903
|
-
const
|
|
3259
|
+
/* A material with a model takes that model's instanced program; see `modelProgram`. */
|
|
3260
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
3261
|
+
const modelled = modelKind === null ? null : this.modelProgram('instanced', modelKind);
|
|
3262
|
+
const program = modelled?.program ?? this.flatInstancedProgram;
|
|
3263
|
+
const u = modelled?.uniforms ?? this.flatInstancedUniforms;
|
|
2904
3264
|
if (program === null || u === null)
|
|
2905
3265
|
return;
|
|
2906
3266
|
const { gl } = this;
|
|
@@ -2913,7 +3273,7 @@ export class WebGL2Renderer {
|
|
|
2913
3273
|
/* A blended batch shows what is behind it — refraction or glass — as a single draw does, and
|
|
2914
3274
|
counts a material of its own for it by the rule both backends share (`materialChanges.ts`). */
|
|
2915
3275
|
const refracting = blend && this.bindSeeThrough(u, options);
|
|
2916
|
-
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
3276
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
|
|
2917
3277
|
if (own)
|
|
2918
3278
|
this.materials.dirty();
|
|
2919
3279
|
this.takeMaterial();
|
|
@@ -2939,16 +3299,16 @@ export class WebGL2Renderer {
|
|
|
2939
3299
|
*/
|
|
2940
3300
|
if (!lit)
|
|
2941
3301
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
2942
|
-
if (fog !==
|
|
3302
|
+
if (fog !== this.surfaceFog)
|
|
2943
3303
|
gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
2944
3304
|
if (!toneMapped)
|
|
2945
3305
|
gl.uniform1i(u['uOutputTransform'] ?? null, Math.min(this.gradeCode(), 1));
|
|
2946
3306
|
if (blend) {
|
|
2947
3307
|
gl.enable(gl.BLEND);
|
|
2948
3308
|
if (options.additive === true)
|
|
2949
|
-
|
|
3309
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
2950
3310
|
else
|
|
2951
|
-
|
|
3311
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
2952
3312
|
if (!depthWrite)
|
|
2953
3313
|
gl.depthMask(false);
|
|
2954
3314
|
}
|
|
@@ -2963,10 +3323,23 @@ export class WebGL2Renderer {
|
|
|
2963
3323
|
for both: without it a batch's far faces were culled here alone. */
|
|
2964
3324
|
if (this.materialDoubleSided)
|
|
2965
3325
|
gl.disable(gl.CULL_FACE);
|
|
3326
|
+
/* A dithered cutout: coverage when this batch is opaque in a multisampled frame, the hard test
|
|
3327
|
+
when it is blended. See `cutoutDither.ts`. */
|
|
3328
|
+
const cutoutResolve = this.cutoutResolveNow(this.currentMaterial, false);
|
|
3329
|
+
const coverage = !blend && cutoutResolve === 'coverage';
|
|
3330
|
+
const ditheredBlend = blend && cutoutResolve !== 'hard';
|
|
3331
|
+
if (coverage)
|
|
3332
|
+
gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
3333
|
+
if (ditheredBlend)
|
|
3334
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
|
|
2966
3335
|
if (culling && cullsInstances(count, mesh.indexCount))
|
|
2967
3336
|
batch.drawCulled(gl, data, this.frustum);
|
|
2968
3337
|
else
|
|
2969
3338
|
batch.draw(gl, count);
|
|
3339
|
+
if (coverage)
|
|
3340
|
+
gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
3341
|
+
if (ditheredBlend)
|
|
3342
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, false));
|
|
2970
3343
|
if (this.materialDoubleSided)
|
|
2971
3344
|
gl.enable(gl.CULL_FACE);
|
|
2972
3345
|
if (layer > 0) {
|
|
@@ -2988,8 +3361,8 @@ export class WebGL2Renderer {
|
|
|
2988
3361
|
this.unbindSeeThrough(u);
|
|
2989
3362
|
if (!lit)
|
|
2990
3363
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
2991
|
-
if (fog !==
|
|
2992
|
-
gl.uniform1i(u['uFogEnabled'] ?? null,
|
|
3364
|
+
if (fog !== this.surfaceFog)
|
|
3365
|
+
gl.uniform1i(u['uFogEnabled'] ?? null, this.surfaceFog);
|
|
2993
3366
|
if (!toneMapped)
|
|
2994
3367
|
gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
2995
3368
|
if (own)
|
|
@@ -3126,6 +3499,7 @@ export class WebGL2Renderer {
|
|
|
3126
3499
|
}
|
|
3127
3500
|
/** Draw after the opaque scene: streaks are blended and write no depth. */
|
|
3128
3501
|
drawWindStreaks(streaks, camera, wind, timeSeconds, tint, env) {
|
|
3502
|
+
this.scatterSkin();
|
|
3129
3503
|
// The engine decides, not the caller: every pass already knows where the
|
|
3130
3504
|
// waterline is, and leaving it to each call site is how one of them ends up
|
|
3131
3505
|
// raining dust into the sea.
|
|
@@ -3158,6 +3532,7 @@ export class WebGL2Renderer {
|
|
|
3158
3532
|
* whatever box the caller lays out in, and the game's own card is not always the canvas.
|
|
3159
3533
|
*/
|
|
3160
3534
|
drawText(text, viewportWidth, viewportHeight, originX, originY, style, timeSec) {
|
|
3535
|
+
this.scatterSkin();
|
|
3161
3536
|
if (text.draw(viewportWidth, viewportHeight, originX, originY, style, timeSec)) {
|
|
3162
3537
|
this.textBudget.ask();
|
|
3163
3538
|
}
|
|
@@ -3249,6 +3624,7 @@ export class WebGL2Renderer {
|
|
|
3249
3624
|
return this.sceneTarget === null || !this.quality.hdrScene ? this.exposure : 1;
|
|
3250
3625
|
}
|
|
3251
3626
|
drawSdfText(handle, model, color, opacity) {
|
|
3627
|
+
this.scatterSkin();
|
|
3252
3628
|
if (this.contextLost)
|
|
3253
3629
|
return;
|
|
3254
3630
|
const viewProj = this.frameViewProj;
|
|
@@ -3320,6 +3696,7 @@ export class WebGL2Renderer {
|
|
|
3320
3696
|
* whole milestone from a buffer nothing had ever written.
|
|
3321
3697
|
*/
|
|
3322
3698
|
drawParticles(batch, data, camera, env, timeSeconds) {
|
|
3699
|
+
this.scatterSkin();
|
|
3323
3700
|
if (data.count === 0)
|
|
3324
3701
|
return;
|
|
3325
3702
|
const { gl } = this;
|
|
@@ -3365,6 +3742,7 @@ export class WebGL2Renderer {
|
|
|
3365
3742
|
* it strobes as the rasteriser catches it on some frames and not others.
|
|
3366
3743
|
*/
|
|
3367
3744
|
drawBolts(batch, data, camera, env, timeSeconds, core, edge, widthM, coreGain, minWidthPerMetre = 0.004) {
|
|
3745
|
+
this.scatterSkin();
|
|
3368
3746
|
if (data.count === 0)
|
|
3369
3747
|
return;
|
|
3370
3748
|
const { gl } = this;
|
|
@@ -3421,6 +3799,7 @@ export class WebGL2Renderer {
|
|
|
3421
3799
|
* edge inward, as a fraction of the half-width; zero is a clean edge.
|
|
3422
3800
|
*/
|
|
3423
3801
|
drawLines(lines, data, model, camera, env, color, widthM, opacity, softness = 0, minWidthPerMetre = 0, additive = false) {
|
|
3802
|
+
this.scatterSkin();
|
|
3424
3803
|
if (data.count === 0)
|
|
3425
3804
|
return;
|
|
3426
3805
|
const { gl } = this;
|
|
@@ -3604,6 +3983,7 @@ export class WebGL2Renderer {
|
|
|
3604
3983
|
}
|
|
3605
3984
|
// --- Draws that own their context ----------------------------------------
|
|
3606
3985
|
drawPlumes(plumes, camera, timeSeconds, env, windX = 0, windZ = 0, originX = 0, originY = 0, originZ = 0) {
|
|
3986
|
+
this.scatterSkin();
|
|
3607
3987
|
plumes.draw(this.gl, this.frameViewFor(camera), timeSeconds, env, this.quality.underwaterAtmosphere, this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0), this.reflectionPassActive ? (this.planarReflection?.clipPlane ?? null) : null, windX, windZ, originX, originY, originZ);
|
|
3608
3988
|
}
|
|
3609
3989
|
drawWater(water, camera, timeSeconds, settings, env, windX = 0, windZ = 0) {
|
|
@@ -3617,6 +3997,7 @@ export class WebGL2Renderer {
|
|
|
3617
3997
|
* from the water under them.
|
|
3618
3998
|
*/
|
|
3619
3999
|
drawCaustics(caustics, camera, timeSeconds, env, windX = 0, windZ = 0, strength = 1) {
|
|
4000
|
+
this.scatterSkin();
|
|
3620
4001
|
if (caustics === null || !this.quality.water)
|
|
3621
4002
|
return;
|
|
3622
4003
|
const drew = caustics.draw(this.gl, this.frameViewFor(camera), timeSeconds, env.directionalDir, env.directionalColor, env.ambient, env, this.quality.underwaterAtmosphere, windX, windZ, strength);
|
|
@@ -3912,6 +4293,14 @@ export class WebGL2Renderer {
|
|
|
3912
4293
|
this.uploadWind(this.depthUniforms, this.frameWind);
|
|
3913
4294
|
gl.useProgram(this.depthSkinnedProgram);
|
|
3914
4295
|
this.uploadWind(this.depthSkinnedUniforms, this.frameWind);
|
|
4296
|
+
if (this.depthSkinned8Program !== null && this.depthSkinned8Uniforms !== null) {
|
|
4297
|
+
gl.useProgram(this.depthSkinned8Program);
|
|
4298
|
+
this.uploadWind(this.depthSkinned8Uniforms, this.frameWind);
|
|
4299
|
+
}
|
|
4300
|
+
for (const caster of this.clothDepth.values()) {
|
|
4301
|
+
gl.useProgram(caster.program);
|
|
4302
|
+
this.uploadWind(caster.uniforms, this.frameWind);
|
|
4303
|
+
}
|
|
3915
4304
|
gl.useProgram(this.depthCutoutProgram);
|
|
3916
4305
|
this.uploadWind(this.depthCutoutUniforms, this.frameWind);
|
|
3917
4306
|
gl.useProgram(this.depthProgram);
|
|
@@ -3937,14 +4326,18 @@ export class WebGL2Renderer {
|
|
|
3937
4326
|
this.bindSceneCasterMaterial(material);
|
|
3938
4327
|
this.drawMesh(mesh, model);
|
|
3939
4328
|
},
|
|
3940
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
4329
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
3941
4330
|
/* The opaque handle narrowed to this backend's own, for the reason `casterSink`
|
|
3942
4331
|
gives about the mesh: nothing else ever handed one out. */
|
|
3943
4332
|
this.bindSceneCasterMaterial(material);
|
|
3944
4333
|
this.setSkinPalette(palette);
|
|
4334
|
+
if (cloth !== undefined) {
|
|
4335
|
+
this.setCloth(cloth.binding, cloth.particles);
|
|
4336
|
+
}
|
|
3945
4337
|
this.drawMesh(mesh, model);
|
|
3946
4338
|
/* Put back, or the next rigid draw in the replay skins by whatever this one left bound. */
|
|
3947
4339
|
this.setSkinPalette(null);
|
|
4340
|
+
this.setCloth(null);
|
|
3948
4341
|
},
|
|
3949
4342
|
instanced: (batch, data, material) => {
|
|
3950
4343
|
/* The opaque handle narrowed to this backend's own, for the reason `casterSink`
|
|
@@ -4068,7 +4461,8 @@ export class WebGL2Renderer {
|
|
|
4068
4461
|
gl.uniform4fv(u['uGlassLight'] ?? null, this.glassLight);
|
|
4069
4462
|
if (cutout !== null) {
|
|
4070
4463
|
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
4071
|
-
gl.
|
|
4464
|
+
gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
|
|
4465
|
+
gl.uniform2f(u['uAlphaCutout'] ?? null, cutout.cutoff, cutout.dithered);
|
|
4072
4466
|
cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
4073
4467
|
gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
4074
4468
|
}
|
|
@@ -4206,10 +4600,11 @@ export class WebGL2Renderer {
|
|
|
4206
4600
|
const { gl } = this;
|
|
4207
4601
|
Object.assign(this.litOn, this.litWanted);
|
|
4208
4602
|
this.flatFragSource = this.flatFragAt(this.lightBudget);
|
|
4209
|
-
const recompile = (old, skinned, morphed, instanced, label) => {
|
|
4603
|
+
const recompile = (old, skinned, morphed, instanced, label, eight = false) => {
|
|
4210
4604
|
if (old === null)
|
|
4211
4605
|
return null;
|
|
4212
|
-
const
|
|
4606
|
+
const vertex = flatVert({ skinned, morphed, instanced });
|
|
4607
|
+
const program = compileProgram(gl, eight ? skinEight(vertex) : vertex, this.flatFragSource, label);
|
|
4213
4608
|
gl.deleteProgram(old);
|
|
4214
4609
|
return { program, uniforms: uniformLocations(gl, program, label) };
|
|
4215
4610
|
};
|
|
@@ -4227,14 +4622,40 @@ export class WebGL2Renderer {
|
|
|
4227
4622
|
const both = recompile(this.flatBothProgram, true, true, false, 'flat.morphedskinned');
|
|
4228
4623
|
this.flatBothProgram = both?.program ?? null;
|
|
4229
4624
|
this.flatBothUniforms = both?.uniforms ?? null;
|
|
4625
|
+
const skinned8 = recompile(this.flatSkinned8Program, true, false, false, 'flat.skinned8', true);
|
|
4626
|
+
this.flatSkinned8Program = skinned8?.program ?? null;
|
|
4627
|
+
this.flatSkinned8Uniforms = skinned8?.uniforms ?? null;
|
|
4628
|
+
const both8 = recompile(this.flatBoth8Program, true, true, false, 'flat.morphedskinned8', true);
|
|
4629
|
+
this.flatBoth8Program = both8?.program ?? null;
|
|
4630
|
+
this.flatBoth8Uniforms = both8?.uniforms ?? null;
|
|
4230
4631
|
const instanced = recompile(this.flatInstancedProgram, false, false, true, 'flat.instanced');
|
|
4231
4632
|
this.flatInstancedProgram = instanced?.program ?? null;
|
|
4232
4633
|
this.flatInstancedUniforms = instanced?.uniforms ?? null;
|
|
4634
|
+
for (const [key, entry] of this.clothFlat) {
|
|
4635
|
+
const eight = key.startsWith('8');
|
|
4636
|
+
const withMorph = key.endsWith('m');
|
|
4637
|
+
const label = `flat.cloth${eight ? '8' : ''}${withMorph ? '.morphed' : ''}`;
|
|
4638
|
+
const vertex = flatVert({ skinned: true, morphed: withMorph, instanced: false });
|
|
4639
|
+
const program = compileProgram(gl, clothBound(eight ? skinEight(vertex) : vertex), this.flatFragSource, label);
|
|
4640
|
+
gl.deleteProgram(entry.program);
|
|
4641
|
+
this.clothFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
|
|
4642
|
+
}
|
|
4643
|
+
for (const [key, entry] of this.modelFlat) {
|
|
4644
|
+
const [variant, kind, half] = key.split('|');
|
|
4645
|
+
const label = `flat.${variant}.${kind}.${half}`;
|
|
4646
|
+
const program = compileProgram(gl, litVertexOf(variant), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
|
|
4647
|
+
gl.deleteProgram(entry.program);
|
|
4648
|
+
this.modelFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
|
|
4649
|
+
}
|
|
4233
4650
|
this.flatTargets.length = 0;
|
|
4234
|
-
for (const target of [plain, skinned, morphed, both, instanced]) {
|
|
4651
|
+
for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced]) {
|
|
4235
4652
|
if (target !== null)
|
|
4236
4653
|
this.flatTargets.push(target);
|
|
4237
4654
|
}
|
|
4655
|
+
for (const target of this.clothFlat.values())
|
|
4656
|
+
this.flatTargets.push(target);
|
|
4657
|
+
for (const target of this.modelFlat.values())
|
|
4658
|
+
this.flatTargets.push(target);
|
|
4238
4659
|
const camera = this.lastPassCamera;
|
|
4239
4660
|
const env = this.lastPassEnv;
|
|
4240
4661
|
for (const target of this.flatTargets) {
|
|
@@ -4757,7 +5178,7 @@ export class WebGL2Renderer {
|
|
|
4757
5178
|
*/
|
|
4758
5179
|
gl.disable(gl.CULL_FACE);
|
|
4759
5180
|
gl.enable(gl.BLEND);
|
|
4760
|
-
|
|
5181
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
4761
5182
|
gl.uniform4f(this.panelUniforms['uRect'] ?? null, rect.left, rect.top, rect.width, rect.height);
|
|
4762
5183
|
gl.uniform2f(this.panelUniforms['uViewport'] ?? null, Math.max(this.canvas.clientWidth, 1), Math.max(this.canvas.clientHeight, 1));
|
|
4763
5184
|
/* Identity: this backend's clip space is the one the shader was written against. */
|
|
@@ -5295,12 +5716,15 @@ export class WebGL2Renderer {
|
|
|
5295
5716
|
beginFrame(clearColor) {
|
|
5296
5717
|
if (this.contextLost)
|
|
5297
5718
|
return;
|
|
5719
|
+
this.skinScattered = false;
|
|
5720
|
+
this.skinScatter?.reset();
|
|
5298
5721
|
if (this.litAsked) {
|
|
5299
5722
|
this.litAsked = false;
|
|
5300
5723
|
this.rebuildLit();
|
|
5301
5724
|
}
|
|
5302
5725
|
this.budget.reset();
|
|
5303
5726
|
this.materials.dirty();
|
|
5727
|
+
this.cutoutFrame = (this.cutoutFrame + 1) % 64;
|
|
5304
5728
|
/*
|
|
5305
5729
|
* Said once, for the reason `refuseOitMultisampled` gives: a quality setting that is enabled
|
|
5306
5730
|
* and does nothing is the fault the capability clamp exists to prevent.
|
|
@@ -5375,6 +5799,8 @@ export class WebGL2Renderer {
|
|
|
5375
5799
|
/* A view model left open would squeeze every pass after the world, the composite included. */
|
|
5376
5800
|
if (this.viewModelActive)
|
|
5377
5801
|
this.endViewModel();
|
|
5802
|
+
/* Skin not yet spread, because nothing blended came after it, is spread now. */
|
|
5803
|
+
this.scatterSkin();
|
|
5378
5804
|
/*
|
|
5379
5805
|
* Camera motion blur, built from the camera the frame was *rendered* with.
|
|
5380
5806
|
*
|
|
@@ -5861,6 +6287,16 @@ export class WebGL2Renderer {
|
|
|
5861
6287
|
* is a non-idempotent step entering this body; there is none today, and one added would show
|
|
5862
6288
|
* as a skinned game behaving differently from an unskinned one.
|
|
5863
6289
|
*/
|
|
6290
|
+
/*
|
|
6291
|
+
* **A pass starts with no material**, so it cannot inherit one from the pass before it. Here
|
|
6292
|
+
* rather than in `writeMeshPassState`, which also feeds a program compiled mid-pass — a model's,
|
|
6293
|
+
* a cloth's, the instanced one — and there forgot the material the caller had set, so the next
|
|
6294
|
+
* draws drew without it until somebody called `setMaterial` again.
|
|
6295
|
+
*/
|
|
6296
|
+
this.materialDoubleSided = false;
|
|
6297
|
+
this.materialHasMaps = false;
|
|
6298
|
+
this.currentMaterial = null;
|
|
6299
|
+
this.currentSurfaceTexture = null;
|
|
5864
6300
|
for (const target of this.flatTargets) {
|
|
5865
6301
|
this.gl.useProgram(target.program);
|
|
5866
6302
|
this.writeMeshPassState(target.uniforms, camera, env);
|
|
@@ -6048,7 +6484,8 @@ export class WebGL2Renderer {
|
|
|
6048
6484
|
* shape `uOpacity` and `uTint` already use.
|
|
6049
6485
|
*/
|
|
6050
6486
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
6051
|
-
gl.uniform1i(u['uFogEnabled'] ?? null,
|
|
6487
|
+
gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
|
|
6488
|
+
this.surfaceFog = FOG_RECEDE;
|
|
6052
6489
|
/* Grain on by default, so every scene written before it was a choice looks unchanged. */
|
|
6053
6490
|
gl.uniform1f(u['uGrain'] ?? null, 1);
|
|
6054
6491
|
/* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
|
|
@@ -6150,7 +6587,7 @@ export class WebGL2Renderer {
|
|
|
6150
6587
|
* the sort of per-draw cost this renderer avoids everywhere else.
|
|
6151
6588
|
*/
|
|
6152
6589
|
/*
|
|
6153
|
-
* And the surface unit, which starts every pass with no texture on it: `
|
|
6590
|
+
* And the surface unit, which starts every pass with no texture on it: its flag in `uMaterialFlags`
|
|
6154
6591
|
* is 0 until a caller asks for one, and a world that never asks would otherwise leave
|
|
6155
6592
|
* this sampler incomplete for the whole session.
|
|
6156
6593
|
*/
|
|
@@ -6227,18 +6664,15 @@ export class WebGL2Renderer {
|
|
|
6227
6664
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
6228
6665
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
6229
6666
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
6230
|
-
|
|
6667
|
+
/* Every material switch off — the albedo, both maps, two-sided — in their one row. */
|
|
6668
|
+
gl.uniform4i(u['uMaterialFlags'] ?? null, 0, 0, 0, 0);
|
|
6231
6669
|
/* No albedo, so no effects table: the stand-in, which the shader reads as none. */
|
|
6232
6670
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
6233
6671
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
6234
6672
|
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
6235
6673
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
6236
|
-
gl.
|
|
6237
|
-
gl.
|
|
6238
|
-
this.materialDoubleSided = false;
|
|
6239
|
-
this.materialHasMaps = false;
|
|
6240
|
-
this.currentMaterial = null;
|
|
6241
|
-
this.currentSurfaceTexture = null;
|
|
6674
|
+
gl.uniform2f(u['uUvOffset'] ?? null, 0, 0);
|
|
6675
|
+
gl.uniform4f(u['uCutout'] ?? null, 0, 0, 0, 0);
|
|
6242
6676
|
/*
|
|
6243
6677
|
* **And the normal map, which this did not reset and had to.**
|
|
6244
6678
|
*
|
|
@@ -6264,14 +6698,17 @@ export class WebGL2Renderer {
|
|
|
6264
6698
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
6265
6699
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
6266
6700
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
6267
|
-
gl.uniform1i(u['uOrmEnabled'] ?? null, 0);
|
|
6268
6701
|
gl.uniform3f(u['uOrmScale'] ?? null, 1, 1, 1);
|
|
6269
6702
|
/* And the emissive map. The scale goes back to one, which is the identity it multiplies by. */
|
|
6270
6703
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
6271
6704
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
6272
6705
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
6273
|
-
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, 0);
|
|
6274
6706
|
gl.uniform3f(u['uEmissiveScale'] ?? null, 1, 1, 1);
|
|
6707
|
+
/* And the shading model: its map back to the stand-in and its numbers to the standard's. */
|
|
6708
|
+
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
6709
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
6710
|
+
gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
|
|
6711
|
+
gl.uniform4fv(u['uModelParams'] ?? null, NO_MODEL_PARAMS);
|
|
6275
6712
|
/*
|
|
6276
6713
|
* White, because a uniform nobody has written is **zero**, and this one multiplies
|
|
6277
6714
|
* vertex colour.
|
|
@@ -6380,6 +6817,58 @@ export class WebGL2Renderer {
|
|
|
6380
6817
|
return;
|
|
6381
6818
|
this.drawBudget.ask();
|
|
6382
6819
|
this.takeMaterial();
|
|
6820
|
+
/* Skin under the screen-space blur draws its two halves: the frame's, then at once its diffuse. */
|
|
6821
|
+
const splits = this.skinSplits();
|
|
6822
|
+
this.drawMeshHalf(mesh, model, depthLayer, tint, splits ? 'scene' : 'whole');
|
|
6823
|
+
if (splits && this.skinScatter !== null) {
|
|
6824
|
+
this.skinScatter.begin();
|
|
6825
|
+
this.drawMeshHalf(mesh, model, depthLayer, tint, 'diffuse');
|
|
6826
|
+
this.sceneTarget?.bind();
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6829
|
+
/**
|
|
6830
|
+
* Whether a skin draw now draws its two halves: the profile asked, the frame has not spread its
|
|
6831
|
+
* skin yet, this is the frame's own picture — not a mirror's or a probe's, nor an overlay after
|
|
6832
|
+
* the frame — and the blur's target can be had.
|
|
6833
|
+
*/
|
|
6834
|
+
skinSplits() {
|
|
6835
|
+
const skin = this.skinScatter;
|
|
6836
|
+
const scene = this.sceneTarget;
|
|
6837
|
+
return (skin !== null &&
|
|
6838
|
+
scene !== null &&
|
|
6839
|
+
this.currentMaterial?.model?.kind === 'skin' &&
|
|
6840
|
+
!this.skinScattered &&
|
|
6841
|
+
!this.reflectionPassActive &&
|
|
6842
|
+
!this.probePassActive &&
|
|
6843
|
+
!this.framePresented &&
|
|
6844
|
+
skin.ready(scene));
|
|
6845
|
+
}
|
|
6846
|
+
/**
|
|
6847
|
+
* Spread the frame's skin and add it back: once, at the first blended draw or at the frame's end.
|
|
6848
|
+
* Called first in every draw WebGPU records as blended — translucent meshes and batches, text,
|
|
6849
|
+
* particles, plumes, wind streaks, bolts, lines, caustics, film — so the two backends add skin at
|
|
6850
|
+
* the same point. The frame's depth is copied for the blur to read, and where it cannot be the
|
|
6851
|
+
* diffuse goes back unspread rather than not at all.
|
|
6852
|
+
*/
|
|
6853
|
+
scatterSkin() {
|
|
6854
|
+
const skin = this.skinScatter;
|
|
6855
|
+
const scene = this.sceneTarget;
|
|
6856
|
+
const projection = this.frameProjection;
|
|
6857
|
+
if (skin === null || scene === null || this.skinScattered)
|
|
6858
|
+
return;
|
|
6859
|
+
/* Latched whether or not skin was drawn: a skin drawn after this point is the whole surface. */
|
|
6860
|
+
this.skinScattered = true;
|
|
6861
|
+
if (!skin.pending)
|
|
6862
|
+
return;
|
|
6863
|
+
if (this.reflectionPassActive || this.probePassActive || projection === null)
|
|
6864
|
+
return;
|
|
6865
|
+
const depth = scene.snapshotDepth(true);
|
|
6866
|
+
mat4.invert(this.skinInverseProjection, projection);
|
|
6867
|
+
skin.spread(scene, depth, this.emptyTexture2D, projection, this.skinInverseProjection);
|
|
6868
|
+
this.useFlatProgram();
|
|
6869
|
+
}
|
|
6870
|
+
/** One draw of a mesh, as `drawMesh` describes, of the half of a skin `half` names. */
|
|
6871
|
+
drawMeshHalf(mesh, model, depthLayer, tint, half) {
|
|
6383
6872
|
const { gl } = this;
|
|
6384
6873
|
/*
|
|
6385
6874
|
* A rigged mesh takes the skinned program, an unrigged one the plain program.
|
|
@@ -6399,37 +6888,80 @@ export class WebGL2Renderer {
|
|
|
6399
6888
|
* belongs to was never compiled. Deriving one from the other, as the first version did, made a
|
|
6400
6889
|
* missing skinned-only program silently switch morph off as well.
|
|
6401
6890
|
*/
|
|
6402
|
-
|
|
6403
|
-
const
|
|
6891
|
+
/* A rig of eight takes the pair built with SKIN_EIGHT on; `warmVariants` compiled them. */
|
|
6892
|
+
const eight = wantSkin && mesh.isSkinnedEight;
|
|
6893
|
+
/*
|
|
6894
|
+
* **A cloth set and a skinned draw take the cloth-bound program of this rig's kind**, compiled
|
|
6895
|
+
* when the binding was made. Its morph half is the mesh's whether weights are set or not: the
|
|
6896
|
+
* program declares the deltas whenever the mesh has them, so they are bound, at zero weight
|
|
6897
|
+
* when none were asked for — an unbound sampler would default to a shadow map's unit.
|
|
6898
|
+
*/
|
|
6899
|
+
const cloth = wantSkin ? this.cloth : null;
|
|
6900
|
+
const clothed = cloth === null
|
|
6901
|
+
? undefined
|
|
6902
|
+
: this.clothFlat.get(`${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`);
|
|
6903
|
+
const bothUniforms = eight ? this.flatBoth8Uniforms : this.flatBothUniforms;
|
|
6904
|
+
const skinnedUniforms = eight ? this.flatSkinned8Uniforms : this.flatSkinnedUniforms;
|
|
6905
|
+
const both = wantSkin && wantMorph && bothUniforms !== null;
|
|
6906
|
+
const skinned = both || (wantSkin && skinnedUniforms !== null);
|
|
6404
6907
|
const morphed = both || (wantMorph && !wantSkin && this.flatMorphedUniforms !== null);
|
|
6405
6908
|
const variant = skinned && morphed
|
|
6406
|
-
?
|
|
6909
|
+
? bothUniforms
|
|
6407
6910
|
: skinned
|
|
6408
|
-
?
|
|
6911
|
+
? skinnedUniforms
|
|
6409
6912
|
: morphed
|
|
6410
6913
|
? this.flatMorphedUniforms
|
|
6411
6914
|
: null;
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
?
|
|
6415
|
-
: skinned
|
|
6416
|
-
?
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6915
|
+
let u = clothed?.uniforms ?? variant ?? this.flatUniforms;
|
|
6916
|
+
let program = clothed !== undefined
|
|
6917
|
+
? clothed.program
|
|
6918
|
+
: skinned && morphed
|
|
6919
|
+
? eight
|
|
6920
|
+
? this.flatBoth8Program
|
|
6921
|
+
: this.flatBothProgram
|
|
6922
|
+
: skinned
|
|
6923
|
+
? eight
|
|
6924
|
+
? this.flatSkinned8Program
|
|
6925
|
+
: this.flatSkinnedProgram
|
|
6926
|
+
: morphed
|
|
6927
|
+
? this.flatMorphedProgram
|
|
6928
|
+
: null;
|
|
6929
|
+
/* A material with a model takes that model's program for the same vertex variant. */
|
|
6930
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
6931
|
+
if (modelKind !== null) {
|
|
6932
|
+
const modelled = this.modelProgram(clothed !== undefined
|
|
6933
|
+
? `cloth${eight ? 8 : 4}${mesh.morph !== null ? 'm' : '-'}`
|
|
6934
|
+
: skinned && morphed
|
|
6935
|
+
? eight
|
|
6936
|
+
? 'both8'
|
|
6937
|
+
: 'both'
|
|
6938
|
+
: skinned
|
|
6939
|
+
? eight
|
|
6940
|
+
? 'skinned8'
|
|
6941
|
+
: 'skinned'
|
|
6942
|
+
: morphed
|
|
6943
|
+
? 'morphed'
|
|
6944
|
+
: 'plain', modelKind, half);
|
|
6945
|
+
program = modelled.program;
|
|
6946
|
+
u = modelled.uniforms;
|
|
6947
|
+
}
|
|
6420
6948
|
if (program !== null) {
|
|
6421
6949
|
gl.useProgram(program);
|
|
6422
|
-
if (skinned) {
|
|
6950
|
+
if (skinned || clothed !== undefined) {
|
|
6423
6951
|
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
6424
6952
|
gl.uniform1i(u['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
6425
6953
|
}
|
|
6426
|
-
if (morphed && mesh.morph !== null) {
|
|
6954
|
+
if ((morphed || clothed !== undefined) && mesh.morph !== null) {
|
|
6427
6955
|
mesh.morph.bind(gl, MORPH_DELTA_TEXTURE_UNIT);
|
|
6428
6956
|
gl.uniform1i(u['uMorphDeltas'] ?? null, MORPH_DELTA_TEXTURE_UNIT);
|
|
6429
6957
|
gl.uniform1i(u['uMorphTargetCount'] ?? null, mesh.morph.targetCount);
|
|
6430
6958
|
gl.uniform1i(u['uMorphTextureWidth'] ?? null, mesh.morph.width);
|
|
6431
|
-
gl.uniform1fv(u['uMorphWeights'] ?? null, this.morphWeights);
|
|
6959
|
+
gl.uniform1fv(u['uMorphWeights'] ?? null, this.morphWeights ?? NO_MORPH_WEIGHTS);
|
|
6432
6960
|
}
|
|
6961
|
+
if (clothed !== undefined && cloth !== null)
|
|
6962
|
+
this.bindCloth(u, cloth);
|
|
6963
|
+
if (modelKind === 'eye')
|
|
6964
|
+
this.writeEyeAxis(u, model);
|
|
6433
6965
|
}
|
|
6434
6966
|
else {
|
|
6435
6967
|
this.useFlatProgram();
|
|
@@ -6464,13 +6996,26 @@ export class WebGL2Renderer {
|
|
|
6464
6996
|
* depth 1.0 against a buffer cleared to 1.0, and under `LESS` it would not draw at
|
|
6465
6997
|
* all. Film, caustics and text lie flush on surfaces for the same reason.
|
|
6466
6998
|
*/
|
|
6467
|
-
|
|
6999
|
+
/* A skin's diffuse half is the same surface again: it finds the depth its frame half wrote. */
|
|
7000
|
+
const again = half === 'diffuse';
|
|
7001
|
+
gl.depthFunc(again ? gl.EQUAL : this.reversedDepth ? gl.GREATER : gl.LESS);
|
|
7002
|
+
if (again)
|
|
7003
|
+
gl.depthMask(false);
|
|
7004
|
+
/* A dithered cutout in a multisampled frame with no temporal resolve: its share becomes the
|
|
7005
|
+
pixel's samples. Scoped to this draw, as the depth compare above is. See `cutoutDither.ts`. */
|
|
7006
|
+
const coverage = !again && this.cutoutResolveNow(this.currentMaterial, false) === 'coverage';
|
|
7007
|
+
if (coverage)
|
|
7008
|
+
gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
6468
7009
|
if (layer === 0) {
|
|
6469
7010
|
if (this.materialDoubleSided)
|
|
6470
7011
|
gl.disable(gl.CULL_FACE);
|
|
6471
7012
|
mesh.draw(gl);
|
|
6472
7013
|
if (this.materialDoubleSided)
|
|
6473
7014
|
gl.enable(gl.CULL_FACE);
|
|
7015
|
+
if (coverage)
|
|
7016
|
+
gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
7017
|
+
if (again)
|
|
7018
|
+
gl.depthMask(true);
|
|
6474
7019
|
gl.depthFunc(this.reversedDepth ? gl.GEQUAL : gl.LEQUAL);
|
|
6475
7020
|
if (tint !== null)
|
|
6476
7021
|
gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
@@ -6496,6 +7041,10 @@ export class WebGL2Renderer {
|
|
|
6496
7041
|
mesh.draw(gl);
|
|
6497
7042
|
if (this.materialDoubleSided)
|
|
6498
7043
|
gl.enable(gl.CULL_FACE);
|
|
7044
|
+
if (coverage)
|
|
7045
|
+
gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
7046
|
+
if (again)
|
|
7047
|
+
gl.depthMask(true);
|
|
6499
7048
|
gl.polygonOffset(0, 0);
|
|
6500
7049
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
6501
7050
|
if (program !== null)
|
|
@@ -6544,6 +7093,7 @@ export class WebGL2Renderer {
|
|
|
6544
7093
|
* handed straight back.
|
|
6545
7094
|
*/
|
|
6546
7095
|
drawTranslucentMesh(mesh, model, opacity, options = {}) {
|
|
7096
|
+
this.scatterSkin();
|
|
6547
7097
|
if (this.contextLost)
|
|
6548
7098
|
return;
|
|
6549
7099
|
/* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
|
|
@@ -6566,10 +7116,22 @@ export class WebGL2Renderer {
|
|
|
6566
7116
|
order-independent transparency, and not once more when it was recorded. */
|
|
6567
7117
|
this.drawBudget.ask();
|
|
6568
7118
|
const { gl } = this;
|
|
6569
|
-
|
|
6570
|
-
this.
|
|
7119
|
+
/* A material with a model takes that model's plain program; see `modelProgram`. */
|
|
7120
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
7121
|
+
const modelled = modelKind === null ? null : this.modelProgram('plain', modelKind);
|
|
7122
|
+
const u = modelled?.uniforms ?? this.flatUniforms;
|
|
7123
|
+
if (modelled === null)
|
|
7124
|
+
this.useFlatProgram();
|
|
7125
|
+
else
|
|
7126
|
+
gl.useProgram(modelled.program);
|
|
7127
|
+
if (modelKind === 'eye')
|
|
7128
|
+
this.writeEyeAxis(u, model);
|
|
6571
7129
|
gl.uniformMatrix4fv(u['uModel'] ?? null, false, model);
|
|
6572
7130
|
gl.uniform1f(u['uOpacity'] ?? null, Math.min(opacity, 1));
|
|
7131
|
+
/* A dithered cutout tests hard on a blended draw, and is put back below with the rest. */
|
|
7132
|
+
const ditheredHere = this.cutoutResolveNow(this.currentMaterial, false) !== 'hard';
|
|
7133
|
+
if (ditheredHere)
|
|
7134
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
|
|
6573
7135
|
/*
|
|
6574
7136
|
* Both default to what `bindMeshPass` already put there — lit and fogged — so the
|
|
6575
7137
|
* common call with no fourth argument writes the same two values it always implicitly
|
|
@@ -6581,7 +7143,7 @@ export class WebGL2Renderer {
|
|
|
6581
7143
|
const toneMapped = options.toneMapped ?? true;
|
|
6582
7144
|
if (!lit)
|
|
6583
7145
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
6584
|
-
if (fog !==
|
|
7146
|
+
if (fog !== this.surfaceFog)
|
|
6585
7147
|
gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
6586
7148
|
/* `1` is sRGB alone: the conversion without the curve, which is what dropping the tone map
|
|
6587
7149
|
means rather than dropping the whole transform. `Math.min` rather than a literal, so a
|
|
@@ -6616,7 +7178,7 @@ export class WebGL2Renderer {
|
|
|
6616
7178
|
*/
|
|
6617
7179
|
/* Taken for itself when any option differs from the pass, and put back below: the rule both
|
|
6618
7180
|
backends count material changes by. See `materialChanges.ts`. */
|
|
6619
|
-
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
7181
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
|
|
6620
7182
|
if (own)
|
|
6621
7183
|
this.materials.dirty();
|
|
6622
7184
|
this.takeMaterial();
|
|
@@ -6625,9 +7187,9 @@ export class WebGL2Renderer {
|
|
|
6625
7187
|
gl.enable(gl.BLEND);
|
|
6626
7188
|
/* Added, where asked: colour times alpha onto what is there, and the target's alpha kept. */
|
|
6627
7189
|
if (options.additive === true)
|
|
6628
|
-
|
|
7190
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
6629
7191
|
else
|
|
6630
|
-
|
|
7192
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
6631
7193
|
}
|
|
6632
7194
|
/*
|
|
6633
7195
|
* The two depth options, both scoped to this draw and handed straight back — the same
|
|
@@ -6659,6 +7221,8 @@ export class WebGL2Renderer {
|
|
|
6659
7221
|
if (ownsState)
|
|
6660
7222
|
gl.disable(gl.BLEND);
|
|
6661
7223
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
7224
|
+
if (ditheredHere)
|
|
7225
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, false));
|
|
6662
7226
|
if (tint !== null)
|
|
6663
7227
|
gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
6664
7228
|
if (refracting)
|
|
@@ -6667,8 +7231,8 @@ export class WebGL2Renderer {
|
|
|
6667
7231
|
cannot inherit a material from the draw before it. */
|
|
6668
7232
|
if (!lit)
|
|
6669
7233
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
6670
|
-
if (fog !==
|
|
6671
|
-
gl.uniform1i(u['uFogEnabled'] ?? null,
|
|
7234
|
+
if (fog !== this.surfaceFog)
|
|
7235
|
+
gl.uniform1i(u['uFogEnabled'] ?? null, this.surfaceFog);
|
|
6672
7236
|
if (!toneMapped)
|
|
6673
7237
|
gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
6674
7238
|
if (own)
|
|
@@ -7036,7 +7600,7 @@ export class WebGL2Renderer {
|
|
|
7036
7600
|
gl.uniform1i(u['uSceneDepth'] ?? null, 3);
|
|
7037
7601
|
}
|
|
7038
7602
|
gl.enable(gl.BLEND);
|
|
7039
|
-
|
|
7603
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
7040
7604
|
gl.depthMask(false);
|
|
7041
7605
|
gl.cullFace(inside ? gl.FRONT : gl.BACK);
|
|
7042
7606
|
mesh.draw(gl);
|
|
@@ -7066,6 +7630,7 @@ export class WebGL2Renderer {
|
|
|
7066
7630
|
* `beginPlanarReflection` for when that target exists.
|
|
7067
7631
|
*/
|
|
7068
7632
|
drawFilm(mesh, camera, timeSeconds, env, sheen, options = {}) {
|
|
7633
|
+
this.scatterSkin();
|
|
7069
7634
|
const { gl } = this;
|
|
7070
7635
|
const u = this.filmUniforms;
|
|
7071
7636
|
gl.useProgram(this.filmProgram);
|
|
@@ -7104,7 +7669,7 @@ export class WebGL2Renderer {
|
|
|
7104
7669
|
}
|
|
7105
7670
|
bindAtmosphere(gl, u, env, this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0), this.quality.underwaterAtmosphere);
|
|
7106
7671
|
gl.enable(gl.BLEND);
|
|
7107
|
-
|
|
7672
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
7108
7673
|
gl.depthMask(false);
|
|
7109
7674
|
mesh.draw(gl);
|
|
7110
7675
|
gl.depthMask(true);
|
|
@@ -7187,3 +7752,20 @@ function angularDiscExponent(angularRadius) {
|
|
|
7187
7752
|
* name to prefer. It goes when something else forces a major, and not before.
|
|
7188
7753
|
*/
|
|
7189
7754
|
export { WebGL2Renderer as Renderer };
|
|
7755
|
+
/** The standard model's numbers: no model, no map. */
|
|
7756
|
+
const NO_MODEL_PARAMS = new Float32Array(MODEL_PARAM_FLOATS);
|
|
7757
|
+
/**
|
|
7758
|
+
* The lit vertex stage a program of this variant compiles: the standard programs' and the cloth's,
|
|
7759
|
+
* which `modelProgram` pairs a model's fragment stage with.
|
|
7760
|
+
*/
|
|
7761
|
+
function litVertexOf(variant) {
|
|
7762
|
+
if (variant.startsWith('cloth')) {
|
|
7763
|
+
const eight = variant[5] === '8';
|
|
7764
|
+
const vertex = flatVert({ skinned: true, morphed: variant.endsWith('m'), instanced: false });
|
|
7765
|
+
return clothBound(eight ? skinEight(vertex) : vertex);
|
|
7766
|
+
}
|
|
7767
|
+
const skinned = variant.startsWith('skinned') || variant.startsWith('both');
|
|
7768
|
+
const morphed = variant === 'morphed' || variant.startsWith('both');
|
|
7769
|
+
const vertex = flatVert({ skinned, morphed, instanced: variant === 'instanced' });
|
|
7770
|
+
return variant.endsWith('8') ? skinEight(vertex) : vertex;
|
|
7771
|
+
}
|