@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.ts';
|
|
2
2
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.ts';
|
|
3
|
-
import { FOG_RECEDE, fogModeOf } from '../drawFog.ts';
|
|
3
|
+
import { FOG_OFF, FOG_RECEDE, fogModeOf } from '../drawFog.ts';
|
|
4
|
+
import { blendCovering, blendKeeping } from '../../sceneCoverage.ts';
|
|
4
5
|
import { mat4 } from 'gl-matrix';
|
|
5
6
|
import {
|
|
6
7
|
GL_DEPTH_REMAP,
|
|
@@ -120,8 +121,31 @@ import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/un
|
|
|
120
121
|
|
|
121
122
|
import type { RendererApi } from '../api.ts';
|
|
122
123
|
import { flatFrag, flatVert } from '../../shaders/flat/index.ts';
|
|
123
|
-
import {
|
|
124
|
+
import { skinEight } from '../../shaders/skinning.ts';
|
|
125
|
+
import {
|
|
126
|
+
MODEL_MAP_TEXTURE_UNIT,
|
|
127
|
+
MORPH_DELTA_TEXTURE_UNIT,
|
|
128
|
+
SKIN_PALETTE_TEXTURE_UNIT,
|
|
129
|
+
} from '../../lightBudget.ts';
|
|
130
|
+
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel } from '../../surfaceModel.ts';
|
|
131
|
+
import type { SurfaceModelKind } from '../../surfaceModel.ts';
|
|
132
|
+
import { modelBound, skinHalfBound } from '../../shaders/flat/models.ts';
|
|
133
|
+
import type { SkinHalf } from '../../shaders/flat/models.ts';
|
|
134
|
+
import { GlSkinScatter } from './skinScatter.ts';
|
|
135
|
+
import {
|
|
136
|
+
CLOTH_BINDING_TEXTURE_UNIT,
|
|
137
|
+
CLOTH_PARTICLES_TEXTURE_UNIT,
|
|
138
|
+
CLOTH_REST_TEXTURE_UNIT,
|
|
139
|
+
} from '../../lightBudget.ts';
|
|
124
140
|
import { SkinPaletteTexture } from './skinPaletteTexture.ts';
|
|
141
|
+
import { clothBound } from '../../shaders/clothBinding.ts';
|
|
142
|
+
import { validateClothBinding } from '../../clothBindingData.ts';
|
|
143
|
+
import type { ClothBindingData } from '../../clothBindingData.ts';
|
|
144
|
+
import { GlClothBinding, GlClothParticles } from './clothTextures.ts';
|
|
145
|
+
import { MAX_MORPH_TARGETS } from '../../skinPalette.ts';
|
|
146
|
+
|
|
147
|
+
/** The weights a cloth-bound draw of a morphing mesh deforms by when none were set: none. */
|
|
148
|
+
const NO_MORPH_WEIGHTS = new Float32Array(MAX_MORPH_TARGETS);
|
|
125
149
|
|
|
126
150
|
/** Shader-side encoding of `RenderQuality.outputTransform`. */
|
|
127
151
|
/* The codes the shader reads, stated once in `vertexDefaults.ts` for both backends. */
|
|
@@ -210,6 +234,8 @@ import type { IncrementalMesh } from '../../mesh.ts';
|
|
|
210
234
|
import type { MeshData } from '../../mesh.ts';
|
|
211
235
|
import type { MeshOptions } from '../api.ts';
|
|
212
236
|
import { SurfaceTexture } from '../../surfaceTexture.ts';
|
|
237
|
+
import { glCompressedFormats } from '../../glCompressed.ts';
|
|
238
|
+
import type { CompressedTextureFormat, SurfaceSource } from '../../compressedSource.ts';
|
|
213
239
|
import type { SurfaceMaterial, SurfaceTextureOptions } from '../../surfaceTexture.ts';
|
|
214
240
|
import { PlumeRenderer } from '../../plumeRenderer.ts';
|
|
215
241
|
import { createScatterDeform, resolveScatterDeform } from '../../scatterDeform.ts';
|
|
@@ -255,6 +281,8 @@ import type { FlockParams } from '../../flockRenderer.ts';
|
|
|
255
281
|
import { InstancedBatch } from './instanced.ts';
|
|
256
282
|
import type { InstancedOptions, MeshInstances } from '../../instances.ts';
|
|
257
283
|
import { cutoutOf } from '../../cutoutCaster.ts';
|
|
284
|
+
import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.ts';
|
|
285
|
+
import type { CutoutResolve } from '../../cutoutDither.ts';
|
|
258
286
|
import type { CutoutCaster } from '../../cutoutCaster.ts';
|
|
259
287
|
import type { SurfaceTextureHandle } from '../api.ts';
|
|
260
288
|
import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.ts';
|
|
@@ -1132,6 +1160,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1132
1160
|
private flatMorphedUniforms: Record<string, WebGLUniformLocation> | null = null;
|
|
1133
1161
|
private flatBothProgram: WebGLProgram | null = null;
|
|
1134
1162
|
private flatBothUniforms: Record<string, WebGLUniformLocation> | null = null;
|
|
1163
|
+
/**
|
|
1164
|
+
* The two skinned variants built with `SKIN_EIGHT` on, for a rig whose vertices name eight
|
|
1165
|
+
* joints: compiled the first time such a mesh is created, and only then. See `skinning.ts`.
|
|
1166
|
+
*/
|
|
1167
|
+
private flatSkinned8Program: WebGLProgram | null = null;
|
|
1168
|
+
private flatSkinned8Uniforms: Record<string, WebGLUniformLocation> | null = null;
|
|
1169
|
+
private flatBoth8Program: WebGLProgram | null = null;
|
|
1170
|
+
private flatBoth8Uniforms: Record<string, WebGLUniformLocation> | null = null;
|
|
1171
|
+
/** And its shadow caster, so the shadow parts from nothing the body does. */
|
|
1172
|
+
private depthSkinned8Program: WebGLProgram | null = null;
|
|
1173
|
+
private depthSkinned8Uniforms: Record<string, WebGLUniformLocation | null> | null = null;
|
|
1135
1174
|
/** The morph weights the following draws use, or null for none. */
|
|
1136
1175
|
private morphWeights: Float32Array | null = null;
|
|
1137
1176
|
/**
|
|
@@ -1168,6 +1207,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1168
1207
|
* before this existed.
|
|
1169
1208
|
*/
|
|
1170
1209
|
private readonly sceneTarget: SceneTarget | null;
|
|
1210
|
+
/**
|
|
1211
|
+
* Skin's screen-space scattering, made where the profile asks for it and has a composite to add
|
|
1212
|
+
* into, and null otherwise — which is every profile by default. See `GlSkinScatter`.
|
|
1213
|
+
*/
|
|
1214
|
+
private readonly skinScatter: GlSkinScatter | null;
|
|
1215
|
+
/** Whether this frame has spread its skin: once, at its first blended draw or at its end. */
|
|
1216
|
+
private skinScattered = false;
|
|
1217
|
+
private readonly skinInverseProjection = mat4.create();
|
|
1171
1218
|
/** So the refusal below is once per renderer rather than once per frame. See `setColourGrade`. */
|
|
1172
1219
|
private warnedGradeWithoutComposite = false;
|
|
1173
1220
|
/** Scratch for a volume's `uDepthToLocal`. Built per draw and never allocated in the loop. */
|
|
@@ -1190,6 +1237,30 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1190
1237
|
private readonly skinPalette = new SkinPaletteTexture();
|
|
1191
1238
|
/** Whether a palette is currently chosen. Distinct from whether one was ever uploaded. */
|
|
1192
1239
|
private skinPaletteSet = false;
|
|
1240
|
+
/**
|
|
1241
|
+
* Cloth-bound skinned lit programs, keyed `eight|morphed`, compiled when a binding is made for a
|
|
1242
|
+
* mesh of that kind; and their casters, keyed by `eight`. See `ensureClothPrograms`.
|
|
1243
|
+
*/
|
|
1244
|
+
private readonly clothFlat = new Map<
|
|
1245
|
+
string,
|
|
1246
|
+
{ program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> }
|
|
1247
|
+
>();
|
|
1248
|
+
private readonly clothDepth = new Map<
|
|
1249
|
+
string,
|
|
1250
|
+
{ program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation | null> }
|
|
1251
|
+
>();
|
|
1252
|
+
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
1253
|
+
private cloth: { binding: GlClothBinding; particles: GlClothParticles } | null = null;
|
|
1254
|
+
/**
|
|
1255
|
+
* Each shading model's lit programs, keyed `variant|model` by the vertex variant they pair with,
|
|
1256
|
+
* compiled the first time a draw asks for one. See `modelProgram`.
|
|
1257
|
+
*/
|
|
1258
|
+
private readonly modelFlat = new Map<
|
|
1259
|
+
string,
|
|
1260
|
+
{ program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> }
|
|
1261
|
+
>();
|
|
1262
|
+
/** The last palette chosen, which an eye's axis is turned by. See `setSkinPalette`. */
|
|
1263
|
+
private paletteData: Float32Array | null = null;
|
|
1193
1264
|
|
|
1194
1265
|
private readonly emptyTexture2D: WebGLTexture;
|
|
1195
1266
|
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
@@ -1249,6 +1320,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1249
1320
|
private readonly surfaceScene = new Float32Array(4);
|
|
1250
1321
|
/** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
|
|
1251
1322
|
private oitWeight = 0;
|
|
1323
|
+
/**
|
|
1324
|
+
* How the surfaces drawn next meet the medium: what `setSurfaceFog` set, and `FOG_RECEDE` from
|
|
1325
|
+
* each `bindMeshPass`. A translucent draw's own fog is measured against this and puts it back.
|
|
1326
|
+
*/
|
|
1327
|
+
private surfaceFog = FOG_RECEDE;
|
|
1252
1328
|
private ditherFade = 0;
|
|
1253
1329
|
/** Said once, not per frame, where the context cannot give the float target the sum needs. */
|
|
1254
1330
|
private oitRefused = false;
|
|
@@ -1690,6 +1766,22 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1690
1766
|
private materialHasMaps = false;
|
|
1691
1767
|
/** The material last set, so a lit program rebuilt for glass can be given it. */
|
|
1692
1768
|
private currentMaterial: SurfaceMaterial | null = null;
|
|
1769
|
+
/**
|
|
1770
|
+
* The frame a dithered cutout's pattern is offset by, advanced at `beginFrame` and wrapped at 64
|
|
1771
|
+
* so the noise's argument stays small. See `cutoutDither.ts`.
|
|
1772
|
+
*/
|
|
1773
|
+
private cutoutFrame = 0;
|
|
1774
|
+
/** Scratch for `uCutout`, so a material write allocates nothing. */
|
|
1775
|
+
private readonly cutoutVector = new Float32Array(4);
|
|
1776
|
+
/** The material's model numbers, `uModelParams`, packed by `packModel`. */
|
|
1777
|
+
private readonly modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
1778
|
+
/** The material's four switches, `uMaterialFlags`: albedo, ORM map, emissive map, two-sided. */
|
|
1779
|
+
private readonly materialFlags = new Int32Array(4);
|
|
1780
|
+
/**
|
|
1781
|
+
* The default framebuffer's sample count, read once, for a frame drawn with no scene target: a
|
|
1782
|
+
* dithered cutout there takes alpha-to-coverage when the canvas itself is multisampled.
|
|
1783
|
+
*/
|
|
1784
|
+
private readonly canvasSamples: number;
|
|
1693
1785
|
/**
|
|
1694
1786
|
* The lit stage's switches: what the lit programs were compiled with (`litOn`), and what the
|
|
1695
1787
|
* profile and the content have asked for since (`litWanted`).
|
|
@@ -1817,7 +1909,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1817
1909
|
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1818
1910
|
gl.useProgram(this.depthProgram);
|
|
1819
1911
|
},
|
|
1820
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
1912
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
1821
1913
|
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1822
1914
|
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1823
1915
|
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
@@ -1827,8 +1919,25 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1827
1919
|
}
|
|
1828
1920
|
this.shadowDrawBudget.ask();
|
|
1829
1921
|
const { gl } = this;
|
|
1830
|
-
|
|
1831
|
-
|
|
1922
|
+
/* A rig of eight casts through the caster built with SKIN_EIGHT on, as it draws. */
|
|
1923
|
+
const eightCaster =
|
|
1924
|
+
(mesh as Mesh).isSkinnedEight &&
|
|
1925
|
+
this.depthSkinned8Program !== null &&
|
|
1926
|
+
this.depthSkinned8Uniforms !== null;
|
|
1927
|
+
/* A garment casts through the cloth-bound caster of its rig's kind, as it draws. */
|
|
1928
|
+
const clothCaster =
|
|
1929
|
+
cloth === undefined
|
|
1930
|
+
? undefined
|
|
1931
|
+
: this.clothDepth.get((mesh as Mesh).isSkinnedEight ? '8' : '4');
|
|
1932
|
+
const u =
|
|
1933
|
+
clothCaster?.uniforms ??
|
|
1934
|
+
(eightCaster
|
|
1935
|
+
? (this.depthSkinned8Uniforms as Record<string, WebGLUniformLocation | null>)
|
|
1936
|
+
: this.depthSkinnedUniforms);
|
|
1937
|
+
gl.useProgram(
|
|
1938
|
+
clothCaster?.program ??
|
|
1939
|
+
(eightCaster ? this.depthSkinned8Program : this.depthSkinnedProgram),
|
|
1940
|
+
);
|
|
1832
1941
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1833
1942
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1834
1943
|
/* The peel layer reads the first pass's depths; `beginShadowPass` bound that texture to
|
|
@@ -1838,6 +1947,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1838
1947
|
this.skinPalette.update(gl, palette);
|
|
1839
1948
|
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
1840
1949
|
gl.uniform1i(u['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
1950
|
+
if (clothCaster !== undefined && cloth !== undefined) {
|
|
1951
|
+
this.bindCloth(u, {
|
|
1952
|
+
binding: cloth.binding as unknown as GlClothBinding,
|
|
1953
|
+
particles: cloth.particles as unknown as GlClothParticles,
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1841
1956
|
/* Narrowing is safe for the reason `mesh` above gives: this renderer only ever handed
|
|
1842
1957
|
out its own `Mesh`. */
|
|
1843
1958
|
(mesh as Mesh).draw(gl);
|
|
@@ -1882,7 +1997,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1882
1997
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1883
1998
|
if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1884
1999
|
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
1885
|
-
gl.
|
|
2000
|
+
gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
|
|
2001
|
+
gl.uniform2f(u['uAlphaCutout'] ?? null, cutout.cutoff, cutout.dithered);
|
|
1886
2002
|
/* Narrowed for the reason the sink's own `mesh` gives: this renderer only hands out its own. */
|
|
1887
2003
|
(cutout.albedo as SurfaceTexture).bind(gl, SURFACE_TEXTURE_UNIT);
|
|
1888
2004
|
}
|
|
@@ -2795,11 +2911,22 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2795
2911
|
this.quality.discardResolvedAttachments,
|
|
2796
2912
|
)
|
|
2797
2913
|
: null;
|
|
2914
|
+
/* Screen-space skin needs the composite to add into; without one it is pre-integrated, said once. */
|
|
2915
|
+
if (this.quality.skinScattering === 'screen-space' && this.sceneTarget === null) {
|
|
2916
|
+
console.warn(
|
|
2917
|
+
"WebGL2: skinScattering 'screen-space' needs screenEffects to add into; skin is pre-integrated",
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
this.skinScatter =
|
|
2921
|
+
this.quality.skinScattering === 'screen-space' && this.sceneTarget !== null
|
|
2922
|
+
? new GlSkinScatter(gl)
|
|
2923
|
+
: null;
|
|
2798
2924
|
/*
|
|
2799
2925
|
* Said once at init, because the number a caller asked for and the number the part gave
|
|
2800
2926
|
* them are not always the same, and a silently clamped sample count is a quality setting
|
|
2801
2927
|
* that does not apply — the same failure mode `capabilityClamp` exists to avoid.
|
|
2802
2928
|
*/
|
|
2929
|
+
this.canvasSamples = Math.max(1, (gl.getParameter(gl.SAMPLES) as number | null) ?? 1);
|
|
2803
2930
|
const got = this.sceneTarget?.sampleCount ?? 1;
|
|
2804
2931
|
if (this.quality.sceneSamples > 1 && got !== this.quality.sceneSamples) {
|
|
2805
2932
|
console.warn(
|
|
@@ -3013,6 +3140,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3013
3140
|
this.disposed = true;
|
|
3014
3141
|
|
|
3015
3142
|
const { gl } = this;
|
|
3143
|
+
if (!this.contextLost) this.skinScatter?.dispose();
|
|
3016
3144
|
this.detachContextLoss();
|
|
3017
3145
|
this.contextLostListeners.length = 0;
|
|
3018
3146
|
this.contextRestoredListeners.length = 0;
|
|
@@ -3053,10 +3181,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3053
3181
|
this.flatSkinnedProgram,
|
|
3054
3182
|
this.flatMorphedProgram,
|
|
3055
3183
|
this.flatBothProgram,
|
|
3184
|
+
this.flatSkinned8Program,
|
|
3185
|
+
this.flatBoth8Program,
|
|
3186
|
+
this.depthSkinned8Program,
|
|
3056
3187
|
this.flatInstancedProgram,
|
|
3057
3188
|
]) {
|
|
3058
3189
|
if (extra !== null) gl.deleteProgram(extra);
|
|
3059
3190
|
}
|
|
3191
|
+
for (const entry of [...this.clothFlat.values(), ...this.clothDepth.values()]) {
|
|
3192
|
+
gl.deleteProgram(entry.program);
|
|
3193
|
+
}
|
|
3060
3194
|
for (const program of [
|
|
3061
3195
|
this.flatProgram,
|
|
3062
3196
|
this.panelProgram,
|
|
@@ -3153,12 +3287,27 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3153
3287
|
*/
|
|
3154
3288
|
private warmVariants(data: MeshData): void {
|
|
3155
3289
|
const skinnable = data.joints !== undefined;
|
|
3290
|
+
/* A rig of eight takes the variants built with SKIN_EIGHT on, and a caster of its own. */
|
|
3291
|
+
const eight = skinnable && data.joints2 !== undefined;
|
|
3156
3292
|
const morphable = data.morphTargets !== undefined;
|
|
3157
3293
|
for (const skinned of skinnable ? [false, true] : [false]) {
|
|
3158
3294
|
for (const morphed of morphable ? [false, true] : [false]) {
|
|
3159
|
-
if (skinned || morphed) this.ensureVariant(skinned, morphed);
|
|
3295
|
+
if (skinned || morphed) this.ensureVariant(skinned, morphed, skinned && eight);
|
|
3160
3296
|
}
|
|
3161
3297
|
}
|
|
3298
|
+
if (eight && this.depthSkinned8Program === null && !this.contextLost) {
|
|
3299
|
+
this.depthSkinned8Program = compileProgram(
|
|
3300
|
+
this.gl,
|
|
3301
|
+
skinEight(DEPTH_SKINNED_VERT),
|
|
3302
|
+
DEPTH_FRAG,
|
|
3303
|
+
'depth-skinned8',
|
|
3304
|
+
);
|
|
3305
|
+
this.depthSkinned8Uniforms = uniformLocations(
|
|
3306
|
+
this.gl,
|
|
3307
|
+
this.depthSkinned8Program,
|
|
3308
|
+
'depth-skinned8',
|
|
3309
|
+
);
|
|
3310
|
+
}
|
|
3162
3311
|
}
|
|
3163
3312
|
|
|
3164
3313
|
/**
|
|
@@ -3203,25 +3352,36 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3203
3352
|
* `bindMeshPass` would otherwise carry every uniform at zero, and zero is a real value for most
|
|
3204
3353
|
* of them — the failure this file records for `uGrain` and `uAmbientGround`.
|
|
3205
3354
|
*/
|
|
3206
|
-
private ensureVariant(skinned: boolean, morphed: boolean): void {
|
|
3355
|
+
private ensureVariant(skinned: boolean, morphed: boolean, eight = false): void {
|
|
3207
3356
|
if (this.contextLost) return;
|
|
3208
|
-
if (skinned &&
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3357
|
+
if (skinned && eight) {
|
|
3358
|
+
if (morphed ? this.flatBoth8Program !== null : this.flatSkinned8Program !== null) return;
|
|
3359
|
+
} else {
|
|
3360
|
+
if (skinned && morphed && this.flatBothProgram !== null) return;
|
|
3361
|
+
if (skinned && !morphed && this.flatSkinnedProgram !== null) return;
|
|
3362
|
+
if (!skinned && morphed && this.flatMorphedProgram !== null) return;
|
|
3363
|
+
if (!skinned && !morphed) return;
|
|
3364
|
+
}
|
|
3212
3365
|
|
|
3213
3366
|
const { gl } = this;
|
|
3214
|
-
const label = `flat.${morphed ? 'morphed' : ''}${skinned ? 'skinned' : ''}`;
|
|
3367
|
+
const label = `flat.${morphed ? 'morphed' : ''}${skinned ? (eight ? 'skinned8' : 'skinned') : ''}`;
|
|
3215
3368
|
/* `flatFragSource`, not a fresh `flatFrag`: every variant shades through one fragment stage
|
|
3216
3369
|
at one light budget, and the uploads are written for that budget. */
|
|
3370
|
+
const vertex = flatVert({ skinned, morphed, instanced: false });
|
|
3217
3371
|
const program = compileProgram(
|
|
3218
3372
|
gl,
|
|
3219
|
-
|
|
3373
|
+
skinned && eight ? skinEight(vertex) : vertex,
|
|
3220
3374
|
this.flatFragSource,
|
|
3221
3375
|
label,
|
|
3222
3376
|
);
|
|
3223
3377
|
const uniforms = uniformLocations(gl, program, label);
|
|
3224
|
-
if (skinned && morphed) {
|
|
3378
|
+
if (skinned && eight && morphed) {
|
|
3379
|
+
this.flatBoth8Program = program;
|
|
3380
|
+
this.flatBoth8Uniforms = uniforms;
|
|
3381
|
+
} else if (skinned && eight) {
|
|
3382
|
+
this.flatSkinned8Program = program;
|
|
3383
|
+
this.flatSkinned8Uniforms = uniforms;
|
|
3384
|
+
} else if (skinned && morphed) {
|
|
3225
3385
|
this.flatBothProgram = program;
|
|
3226
3386
|
this.flatBothUniforms = uniforms;
|
|
3227
3387
|
} else if (skinned) {
|
|
@@ -3238,6 +3398,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3238
3398
|
if (camera !== null && env !== null) {
|
|
3239
3399
|
gl.useProgram(program);
|
|
3240
3400
|
this.writeMeshPassState(uniforms, camera, env);
|
|
3401
|
+
/* And the material the caller has set, which a program born mid-pass must carry too. */
|
|
3402
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
3241
3403
|
this.useFlatProgram();
|
|
3242
3404
|
}
|
|
3243
3405
|
}
|
|
@@ -3250,11 +3412,24 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3250
3412
|
* a canvas drawn procedurally at runtime, a decoded bitmap, a video frame — stays
|
|
3251
3413
|
* the consumer's decision. What it gains is a GPU object with sane sampler state.
|
|
3252
3414
|
*/
|
|
3253
|
-
createSurfaceTexture(
|
|
3254
|
-
source
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3415
|
+
createSurfaceTexture(source: SurfaceSource, options: SurfaceTextureOptions = {}): SurfaceTexture {
|
|
3416
|
+
return new SurfaceTexture(this.gl, source, options, this.compressedFormats);
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
private compressedCache: readonly CompressedTextureFormat[] | null = null;
|
|
3420
|
+
|
|
3421
|
+
/**
|
|
3422
|
+
* The BC formats this device takes as blocks, each named with its colour space (`'bc7-srgb'`).
|
|
3423
|
+
*
|
|
3424
|
+
* **Ask before choosing**, because a phone usually answers none: a BC texture handed to
|
|
3425
|
+
* `createSurfaceTexture` where its format is not here is refused by name rather than decoded,
|
|
3426
|
+
* since core ships no decoder. `@driftengine/assets`' loader asks this for every BC texture in a
|
|
3427
|
+
* container and decodes at load where the answer is no. Read from the four extensions WebGL2
|
|
3428
|
+
* spreads BC across; see `glCompressed.ts`.
|
|
3429
|
+
*/
|
|
3430
|
+
get compressedFormats(): readonly CompressedTextureFormat[] {
|
|
3431
|
+
this.compressedCache ??= glCompressedFormats(this.gl);
|
|
3432
|
+
return this.compressedCache;
|
|
3258
3433
|
}
|
|
3259
3434
|
|
|
3260
3435
|
/**
|
|
@@ -3264,10 +3439,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3264
3439
|
* refusal names the first that is not.
|
|
3265
3440
|
*/
|
|
3266
3441
|
createSurfaceTextureArray(
|
|
3267
|
-
sources: readonly
|
|
3442
|
+
sources: readonly SurfaceSource[],
|
|
3268
3443
|
options: SurfaceTextureOptions = {},
|
|
3269
3444
|
): SurfaceTexture {
|
|
3270
|
-
return new SurfaceTexture(this.gl, sources, options);
|
|
3445
|
+
return new SurfaceTexture(this.gl, sources, options, this.compressedFormats);
|
|
3271
3446
|
}
|
|
3272
3447
|
|
|
3273
3448
|
/**
|
|
@@ -3325,6 +3500,176 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3325
3500
|
}
|
|
3326
3501
|
this.skinPalette.update(this.gl, palette);
|
|
3327
3502
|
this.skinPaletteSet = true;
|
|
3503
|
+
this.paletteData = palette;
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
/**
|
|
3507
|
+
* The lit program for one vertex variant shading by a model: the standard program's vertex
|
|
3508
|
+
* stage, and the fragment stage with the model's switch on (`modelBound`). Compiled the first
|
|
3509
|
+
* time a draw asks — a scene that names no model compiles none — and fed the pass and the
|
|
3510
|
+
* material at once, as `ensureClothPrograms` feeds its own.
|
|
3511
|
+
*/
|
|
3512
|
+
private modelProgram(
|
|
3513
|
+
variant: string,
|
|
3514
|
+
kind: SurfaceModelKind,
|
|
3515
|
+
/** Which half of a skin it draws under the screen-space blur. See `SkinHalf`. */
|
|
3516
|
+
half: SkinHalf = 'whole',
|
|
3517
|
+
): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } {
|
|
3518
|
+
const key = `${variant}|${kind}|${half}`;
|
|
3519
|
+
const held = this.modelFlat.get(key);
|
|
3520
|
+
if (held !== undefined) return held;
|
|
3521
|
+
const { gl } = this;
|
|
3522
|
+
const label = `flat.${variant}.${kind}.${half}`;
|
|
3523
|
+
const program = compileProgram(
|
|
3524
|
+
gl,
|
|
3525
|
+
litVertexOf(variant),
|
|
3526
|
+
skinHalfBound(modelBound(this.flatFragSource, kind), half),
|
|
3527
|
+
label,
|
|
3528
|
+
);
|
|
3529
|
+
const made = { program, uniforms: uniformLocations(gl, program, label) };
|
|
3530
|
+
this.modelFlat.set(key, made);
|
|
3531
|
+
this.flatTargets.push(made);
|
|
3532
|
+
gl.useProgram(program);
|
|
3533
|
+
const camera = this.lastPassCamera;
|
|
3534
|
+
const env = this.lastPassEnv;
|
|
3535
|
+
if (camera !== null && env !== null) this.writeMeshPassState(made.uniforms, camera, env);
|
|
3536
|
+
this.writeMaterialState(made.uniforms, this.currentMaterial);
|
|
3537
|
+
return made;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
/**
|
|
3541
|
+
* An eye's axis in the world for this draw, into its program's `uModelParams`: the material's
|
|
3542
|
+
* own numbers with the axis turned by the draw's model matrix and the eye's joint. See
|
|
3543
|
+
* `eyeAxisInWorld`.
|
|
3544
|
+
*/
|
|
3545
|
+
private writeEyeAxis(u: Record<string, WebGLUniformLocation>, model: ArrayLike<number>): void {
|
|
3546
|
+
const eye = this.currentMaterial?.model;
|
|
3547
|
+
if (eye?.kind !== 'eye') return;
|
|
3548
|
+
eyeAxisInWorld(eye, model, this.skinPaletteSet ? this.paletteData : null, this.modelParams, 4);
|
|
3549
|
+
this.gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
/**
|
|
3553
|
+
* A mesh's cloth binding: which simulation triangle each of its vertices follows, where on it,
|
|
3554
|
+
* and by how much. See `ClothBindingData`.
|
|
3555
|
+
*
|
|
3556
|
+
* Refused by name for a mesh with no rig — a binding blends a vertex between its skinning and the
|
|
3557
|
+
* cloth — and for one that disagrees with the mesh. Compiles the programs that draw and cast it
|
|
3558
|
+
* here, at creation, so no frame ever waits on a compile.
|
|
3559
|
+
*/
|
|
3560
|
+
createClothBinding(mesh: Mesh, data: ClothBindingData): GlClothBinding {
|
|
3561
|
+
const target = mesh;
|
|
3562
|
+
if (!target.isSkinned) {
|
|
3563
|
+
throw new Error(
|
|
3564
|
+
'createClothBinding: this mesh has no rig, and a binding blends each vertex between its ' +
|
|
3565
|
+
'skinning and the cloth',
|
|
3566
|
+
);
|
|
3567
|
+
}
|
|
3568
|
+
validateClothBinding(data, target.vertexCount);
|
|
3569
|
+
this.ensureClothPrograms(target.isSkinnedEight, target.morph !== null);
|
|
3570
|
+
return new GlClothBinding(this.gl, data);
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
/** One character's particles, `count` of them, to be updated every frame. */
|
|
3574
|
+
createClothParticles(count: number): GlClothParticles {
|
|
3575
|
+
return new GlClothParticles(this.gl, count);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
/**
|
|
3579
|
+
* This frame's particles, three floats each, world space; last frame's are kept for motion. A
|
|
3580
|
+
* simulation's output, as it is.
|
|
3581
|
+
*/
|
|
3582
|
+
updateClothParticles(particles: GlClothParticles, positions: Float32Array): void {
|
|
3583
|
+
if (this.contextLost) return;
|
|
3584
|
+
particles.update(this.gl, positions);
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
/**
|
|
3588
|
+
* Place the following skinned draws by a cloth: `binding` the mesh's, `particles` the
|
|
3589
|
+
* character's. Null for none, which is every draw until this is called. Both or neither, and
|
|
3590
|
+
* the particles must be as many as the binding names.
|
|
3591
|
+
*/
|
|
3592
|
+
setCloth(binding: GlClothBinding | null, particles: GlClothParticles | null = null): void {
|
|
3593
|
+
if (binding === null || particles === null) {
|
|
3594
|
+
this.cloth = null;
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
if (binding.particles !== particles.count) {
|
|
3598
|
+
throw new Error(
|
|
3599
|
+
`setCloth: the binding names ${binding.particles} particles and these are ${particles.count}`,
|
|
3600
|
+
);
|
|
3601
|
+
}
|
|
3602
|
+
this.cloth = { binding, particles };
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
disposeClothBinding(binding: GlClothBinding): void {
|
|
3606
|
+
if (this.contextLost) return;
|
|
3607
|
+
binding.dispose(this.gl);
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
disposeClothParticles(particles: GlClothParticles): void {
|
|
3611
|
+
if (this.contextLost) return;
|
|
3612
|
+
particles.dispose(this.gl);
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
/**
|
|
3616
|
+
* The cloth-bound lit program and caster for a rig of this kind, once. Fed at once if a pass is
|
|
3617
|
+
* bound, for the reason `ensureVariant` gives: a program compiled after `bindMeshPass` would
|
|
3618
|
+
* otherwise carry every uniform at zero.
|
|
3619
|
+
*/
|
|
3620
|
+
private ensureClothPrograms(eight: boolean, morphed: boolean): void {
|
|
3621
|
+
if (this.contextLost) return;
|
|
3622
|
+
const { gl } = this;
|
|
3623
|
+
const key = `${eight ? 8 : 4}|${morphed ? 'm' : '-'}`;
|
|
3624
|
+
if (!this.clothFlat.has(key)) {
|
|
3625
|
+
const label = `flat.cloth${eight ? '8' : ''}${morphed ? '.morphed' : ''}`;
|
|
3626
|
+
const vertex = flatVert({ skinned: true, morphed, instanced: false });
|
|
3627
|
+
const program = compileProgram(
|
|
3628
|
+
gl,
|
|
3629
|
+
clothBound(eight ? skinEight(vertex) : vertex),
|
|
3630
|
+
this.flatFragSource,
|
|
3631
|
+
label,
|
|
3632
|
+
);
|
|
3633
|
+
const uniforms = uniformLocations(gl, program, label);
|
|
3634
|
+
this.clothFlat.set(key, { program, uniforms });
|
|
3635
|
+
this.flatTargets.push({ program, uniforms });
|
|
3636
|
+
const camera = this.lastPassCamera;
|
|
3637
|
+
const env = this.lastPassEnv;
|
|
3638
|
+
if (camera !== null && env !== null) {
|
|
3639
|
+
gl.useProgram(program);
|
|
3640
|
+
this.writeMeshPassState(uniforms, camera, env);
|
|
3641
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
3642
|
+
this.useFlatProgram();
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
const depthKey = eight ? '8' : '4';
|
|
3646
|
+
if (!this.clothDepth.has(depthKey)) {
|
|
3647
|
+
const label = `depth-cloth${eight ? '8' : ''}`;
|
|
3648
|
+
const program = compileProgram(
|
|
3649
|
+
gl,
|
|
3650
|
+
clothBound(eight ? skinEight(DEPTH_SKINNED_VERT) : DEPTH_SKINNED_VERT),
|
|
3651
|
+
DEPTH_FRAG,
|
|
3652
|
+
label,
|
|
3653
|
+
);
|
|
3654
|
+
this.clothDepth.set(depthKey, { program, uniforms: uniformLocations(gl, program, label) });
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
/** Bind the cloth's three textures to their units and name them to `u`. */
|
|
3659
|
+
private bindCloth(
|
|
3660
|
+
u: Record<string, WebGLUniformLocation | null>,
|
|
3661
|
+
cloth: { binding: GlClothBinding; particles: GlClothParticles },
|
|
3662
|
+
): void {
|
|
3663
|
+
const { gl } = this;
|
|
3664
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_BINDING_TEXTURE_UNIT);
|
|
3665
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.binding.binding);
|
|
3666
|
+
gl.uniform1i(u['uClothBinding'] ?? null, CLOTH_BINDING_TEXTURE_UNIT);
|
|
3667
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_PARTICLES_TEXTURE_UNIT);
|
|
3668
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.particles.current);
|
|
3669
|
+
gl.uniform1i(u['uClothParticles'] ?? null, CLOTH_PARTICLES_TEXTURE_UNIT);
|
|
3670
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_REST_TEXTURE_UNIT);
|
|
3671
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.binding.rest);
|
|
3672
|
+
gl.uniform1i(u['uClothRest'] ?? null, CLOTH_REST_TEXTURE_UNIT);
|
|
3328
3673
|
}
|
|
3329
3674
|
|
|
3330
3675
|
/**
|
|
@@ -3346,6 +3691,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3346
3691
|
this.materialDoubleSided = material?.doubleSided === true;
|
|
3347
3692
|
this.materialHasMaps = materialHasMaps(material);
|
|
3348
3693
|
this.currentMaterial = material;
|
|
3694
|
+
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
3695
|
+
const model = material?.model;
|
|
3696
|
+
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
3697
|
+
this.skinScatter.setProfile(
|
|
3698
|
+
model.profile,
|
|
3699
|
+
model.scatterColor[0] * model.radius,
|
|
3700
|
+
model.scatterColor[1] * model.radius,
|
|
3701
|
+
model.scatterColor[2] * model.radius,
|
|
3702
|
+
);
|
|
3703
|
+
}
|
|
3349
3704
|
/*
|
|
3350
3705
|
* Written to every flat program, because material state persists across draws and a skinned
|
|
3351
3706
|
* draw is entitled to the material the caller set before it. A second program holding none of
|
|
@@ -3363,6 +3718,30 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3363
3718
|
this.useFlatProgram();
|
|
3364
3719
|
}
|
|
3365
3720
|
|
|
3721
|
+
/**
|
|
3722
|
+
* How a material's cutout edge is drawn this frame: the material's mode against what the frame can
|
|
3723
|
+
* resolve. A translucent draw tests hard whatever the material says, because a blended draw has
|
|
3724
|
+
* nothing to average a pattern with. See `cutoutDither.ts`.
|
|
3725
|
+
*/
|
|
3726
|
+
private cutoutResolveNow(material: SurfaceMaterial | null, translucent: boolean): CutoutResolve {
|
|
3727
|
+
if (translucent || material === null) return 'hard';
|
|
3728
|
+
return resolveCutout(
|
|
3729
|
+
material.cutoutMode ?? 'hard',
|
|
3730
|
+
this.temporalJittering,
|
|
3731
|
+
this.sceneTarget?.sampleCount ?? this.canvasSamples,
|
|
3732
|
+
);
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
/** The lit pass's `uCutout` for a material this frame, into scratch. See `uCutout`. */
|
|
3736
|
+
private cutoutUniform(material: SurfaceMaterial | null, translucent: boolean): Float32Array {
|
|
3737
|
+
const v = this.cutoutVector;
|
|
3738
|
+
v[0] = material?.cutout ?? 0;
|
|
3739
|
+
v[1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveNow(material, translucent)];
|
|
3740
|
+
v[2] = this.cutoutFrame;
|
|
3741
|
+
v[3] = 0;
|
|
3742
|
+
return v;
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3366
3745
|
/**
|
|
3367
3746
|
* One flat program's view of the current material.
|
|
3368
3747
|
*
|
|
@@ -3413,7 +3792,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3413
3792
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3414
3793
|
if (orm !== null) orm.bind(gl, ORM_TEXTURE_UNIT);
|
|
3415
3794
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
3416
|
-
|
|
3795
|
+
this.materialFlags[1] = orm === null ? 0 : 1;
|
|
3417
3796
|
gl.uniform3f(
|
|
3418
3797
|
u['uOrmScale'] ?? null,
|
|
3419
3798
|
material?.occlusionStrength ?? 1,
|
|
@@ -3427,7 +3806,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3427
3806
|
*
|
|
3428
3807
|
* **The stand-in is black, so the gate is what keeps a glow on.** The shader multiplies by
|
|
3429
3808
|
* this map, and `emptyTexture2D` is opaque black rather than the multiplicative identity, so an
|
|
3430
|
-
* unbound material keeps the arithmetic it had before only because `
|
|
3809
|
+
* unbound material keeps the arithmetic it had before only because its flag in `uMaterialFlags` is 0
|
|
3431
3810
|
* and the read never happens. What would make that wrong is a read taken outside that branch:
|
|
3432
3811
|
* every glow on a material without a map would go out.
|
|
3433
3812
|
*/
|
|
@@ -3436,7 +3815,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3436
3815
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3437
3816
|
if (emissiveMap !== null) emissiveMap.bind(gl, EMISSIVE_TEXTURE_UNIT);
|
|
3438
3817
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
3439
|
-
|
|
3818
|
+
this.materialFlags[2] = emissiveMap === null ? 0 : 1;
|
|
3440
3819
|
const emissiveScale = material?.emissiveScale ?? null;
|
|
3441
3820
|
gl.uniform3f(
|
|
3442
3821
|
u['uEmissiveScale'] ?? null,
|
|
@@ -3453,7 +3832,21 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3453
3832
|
* how big a dome was — WebGPU writes it before its own early return and WebGL2 did not.
|
|
3454
3833
|
*/
|
|
3455
3834
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
3456
|
-
gl.
|
|
3835
|
+
gl.uniform2f(u['uUvOffset'] ?? null, material?.uOffset ?? 0, material?.vOffset ?? 0);
|
|
3836
|
+
this.materialFlags[3] = material?.doubleSided === true ? 1 : 0;
|
|
3837
|
+
|
|
3838
|
+
/*
|
|
3839
|
+
* The material's shading model: its numbers, and its map or the stand-in, before the albedo
|
|
3840
|
+
* early return as every map above is. Written to the standard programs too, which declare both
|
|
3841
|
+
* and read neither — one decision for every program, as `setMaterial` says.
|
|
3842
|
+
*/
|
|
3843
|
+
const modelMap = material?.modelMap ?? null;
|
|
3844
|
+
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
3845
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3846
|
+
if (modelMap !== null) modelMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
|
|
3847
|
+
gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
|
|
3848
|
+
packModel(material?.model ?? null, modelMap !== null, this.modelParams);
|
|
3849
|
+
gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
|
|
3457
3850
|
|
|
3458
3851
|
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
3459
3852
|
return, so a material with no albedo still leaves a complete texture on the unit. */
|
|
@@ -3471,12 +3864,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3471
3864
|
array target, and with nothing there it is incomplete. */
|
|
3472
3865
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
3473
3866
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3474
|
-
|
|
3867
|
+
this.materialFlags[0] = 0;
|
|
3868
|
+
gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
|
|
3475
3869
|
return;
|
|
3476
3870
|
}
|
|
3477
3871
|
albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
3478
|
-
gl.
|
|
3479
|
-
|
|
3872
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(material, false));
|
|
3873
|
+
this.materialFlags[0] = 1;
|
|
3874
|
+
gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
|
|
3480
3875
|
}
|
|
3481
3876
|
|
|
3482
3877
|
/**
|
|
@@ -3495,7 +3890,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3495
3890
|
* Rebind `SURFACE_TEXTURE_UNIT` to whatever `setSurfaceTexture` last put there.
|
|
3496
3891
|
*
|
|
3497
3892
|
* Only `drawSdfText` calls this, after the atlas has finished borrowing the same unit.
|
|
3498
|
-
* No uniform is written here — the flat program's `uAlbedo
|
|
3893
|
+
* No uniform is written here — the flat program's `uAlbedo` and its flag in `uMaterialFlags` were never
|
|
3499
3894
|
* touched by the text draw and are still correct, since that draw runs its own separate
|
|
3500
3895
|
* program. What moved is unit 15's actual `WebGLTexture` binding, which is GL state, not
|
|
3501
3896
|
* program state, and this puts it back to what `currentSurfaceTexture` says it should be.
|
|
@@ -3585,6 +3980,28 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3585
3980
|
this.writeFlatFloat('uGrain', Math.min(1, Math.max(0, amount)));
|
|
3586
3981
|
}
|
|
3587
3982
|
|
|
3983
|
+
/**
|
|
3984
|
+
* Whether the surfaces drawn next meet the medium: distance fog and the global medium's haze.
|
|
3985
|
+
* True is the default, and what `bindMeshPass` restores.
|
|
3986
|
+
*
|
|
3987
|
+
* **The opaque counterpart of `TranslucentMeshOptions.fog`**, which a translucent draw has had
|
|
3988
|
+
* all along; the shader's per-draw switch was there, and nothing reached it for an ordinary mesh.
|
|
3989
|
+
* What it is for is a medium that darkens one thing and not another — a floor receding into the
|
|
3990
|
+
* distance under figures that must keep their full value however far from the camera they stand.
|
|
3991
|
+
* Material state like `setSurfaceGrain`: instanced and skinned draws read it too, and a
|
|
3992
|
+
* translucent draw's own `fog` is measured against it and puts it back.
|
|
3993
|
+
*
|
|
3994
|
+
* **What it gives up** is the medium's honesty: a surface out of the fog is drawn as though the
|
|
3995
|
+
* air between it and the eye were empty, so it should be a surface the scene means to stand
|
|
3996
|
+
* apart, not one whose distance is the point.
|
|
3997
|
+
*/
|
|
3998
|
+
setSurfaceFog(enabled: boolean): void {
|
|
3999
|
+
if (this.contextLost) return;
|
|
4000
|
+
this.materials.dirty();
|
|
4001
|
+
this.surfaceFog = enabled ? FOG_RECEDE : FOG_OFF;
|
|
4002
|
+
this.writeFlatInt('uFogEnabled', this.surfaceFog);
|
|
4003
|
+
}
|
|
4004
|
+
|
|
3588
4005
|
/**
|
|
3589
4006
|
* One float of material state, into every flat program.
|
|
3590
4007
|
*
|
|
@@ -3602,6 +4019,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3602
4019
|
this.useFlatProgram();
|
|
3603
4020
|
}
|
|
3604
4021
|
|
|
4022
|
+
/** One int of material state, into every flat program, for `writeFlatFloat`'s reason. */
|
|
4023
|
+
private writeFlatInt(name: string, value: number): void {
|
|
4024
|
+
for (const target of this.flatTargets) {
|
|
4025
|
+
this.gl.useProgram(target.program);
|
|
4026
|
+
this.gl.uniform1i(target.uniforms[name] ?? null, value);
|
|
4027
|
+
}
|
|
4028
|
+
this.useFlatProgram();
|
|
4029
|
+
}
|
|
4030
|
+
|
|
3605
4031
|
/**
|
|
3606
4032
|
* How much of the surfaces drawn next survives a screen-door dither, for crossfading two levels
|
|
3607
4033
|
* of detail of one thing.
|
|
@@ -3754,6 +4180,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3754
4180
|
if (camera !== null && env !== null) {
|
|
3755
4181
|
gl.useProgram(program);
|
|
3756
4182
|
this.writeMeshPassState(uniforms, camera, env);
|
|
4183
|
+
/* And the material the caller has set, which a program born mid-pass must carry too. */
|
|
4184
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
3757
4185
|
this.useFlatProgram();
|
|
3758
4186
|
}
|
|
3759
4187
|
}
|
|
@@ -3795,6 +4223,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3795
4223
|
opacity: number,
|
|
3796
4224
|
options: TranslucentMeshOptions = {},
|
|
3797
4225
|
): void {
|
|
4226
|
+
this.scatterSkin();
|
|
3798
4227
|
this.submitInstanced(batch, data, opacity, true, options);
|
|
3799
4228
|
}
|
|
3800
4229
|
|
|
@@ -3837,8 +4266,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3837
4266
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
3838
4267
|
this.drawBudget.ask();
|
|
3839
4268
|
this.ensureInstancedProgram();
|
|
3840
|
-
|
|
3841
|
-
const
|
|
4269
|
+
/* A material with a model takes that model's instanced program; see `modelProgram`. */
|
|
4270
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
4271
|
+
const modelled = modelKind === null ? null : this.modelProgram('instanced', modelKind);
|
|
4272
|
+
const program = modelled?.program ?? this.flatInstancedProgram;
|
|
4273
|
+
const u = modelled?.uniforms ?? this.flatInstancedUniforms;
|
|
3842
4274
|
if (program === null || u === null) return;
|
|
3843
4275
|
|
|
3844
4276
|
const { gl } = this;
|
|
@@ -3852,7 +4284,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3852
4284
|
/* A blended batch shows what is behind it — refraction or glass — as a single draw does, and
|
|
3853
4285
|
counts a material of its own for it by the rule both backends share (`materialChanges.ts`). */
|
|
3854
4286
|
const refracting = blend && this.bindSeeThrough(u, options);
|
|
3855
|
-
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
4287
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
|
|
3856
4288
|
if (own) this.materials.dirty();
|
|
3857
4289
|
this.takeMaterial();
|
|
3858
4290
|
const depthWrite = options.depthWrite ?? true;
|
|
@@ -3876,14 +4308,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3876
4308
|
* part of what the count asks.
|
|
3877
4309
|
*/
|
|
3878
4310
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
3879
|
-
if (fog !==
|
|
4311
|
+
if (fog !== this.surfaceFog) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
3880
4312
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, Math.min(this.gradeCode(), 1));
|
|
3881
4313
|
|
|
3882
4314
|
if (blend) {
|
|
3883
4315
|
gl.enable(gl.BLEND);
|
|
3884
|
-
if (options.additive === true)
|
|
3885
|
-
else
|
|
3886
|
-
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
4316
|
+
if (options.additive === true) blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
4317
|
+
else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
3887
4318
|
if (!depthWrite) gl.depthMask(false);
|
|
3888
4319
|
}
|
|
3889
4320
|
/* The sign follows the compare, which `depthOffsetForLayer` owns — see `drawMesh`, where the
|
|
@@ -3897,8 +4328,19 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3897
4328
|
/* Both sides of a double-sided material, as every mesh path does and WebGPU's pipeline does
|
|
3898
4329
|
for both: without it a batch's far faces were culled here alone. */
|
|
3899
4330
|
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
4331
|
+
/* A dithered cutout: coverage when this batch is opaque in a multisampled frame, the hard test
|
|
4332
|
+
when it is blended. See `cutoutDither.ts`. */
|
|
4333
|
+
const cutoutResolve = this.cutoutResolveNow(this.currentMaterial, false);
|
|
4334
|
+
const coverage = !blend && cutoutResolve === 'coverage';
|
|
4335
|
+
const ditheredBlend = blend && cutoutResolve !== 'hard';
|
|
4336
|
+
if (coverage) gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
4337
|
+
if (ditheredBlend)
|
|
4338
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
|
|
3900
4339
|
if (culling && cullsInstances(count, mesh.indexCount)) batch.drawCulled(gl, data, this.frustum);
|
|
3901
4340
|
else batch.draw(gl, count);
|
|
4341
|
+
if (coverage) gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
4342
|
+
if (ditheredBlend)
|
|
4343
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, false));
|
|
3902
4344
|
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
3903
4345
|
|
|
3904
4346
|
if (layer > 0) {
|
|
@@ -3916,7 +4358,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3916
4358
|
outlives its frame, so a snapshot left on unit 14 is held against the next frame's copy. */
|
|
3917
4359
|
if (refracting) this.unbindSeeThrough(u);
|
|
3918
4360
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
3919
|
-
if (fog !==
|
|
4361
|
+
if (fog !== this.surfaceFog) gl.uniform1i(u['uFogEnabled'] ?? null, this.surfaceFog);
|
|
3920
4362
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
3921
4363
|
if (own) this.materials.dirty();
|
|
3922
4364
|
this.useFlatProgram();
|
|
@@ -4069,6 +4511,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4069
4511
|
tint: Vec3,
|
|
4070
4512
|
env: Environment,
|
|
4071
4513
|
): void {
|
|
4514
|
+
this.scatterSkin();
|
|
4072
4515
|
// The engine decides, not the caller: every pass already knows where the
|
|
4073
4516
|
// waterline is, and leaving it to each call site is how one of them ends up
|
|
4074
4517
|
// raining dust into the sea.
|
|
@@ -4113,6 +4556,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4113
4556
|
style: TextStyle,
|
|
4114
4557
|
timeSec: number,
|
|
4115
4558
|
): void {
|
|
4559
|
+
this.scatterSkin();
|
|
4116
4560
|
if (text.draw(viewportWidth, viewportHeight, originX, originY, style, timeSec)) {
|
|
4117
4561
|
this.textBudget.ask();
|
|
4118
4562
|
}
|
|
@@ -4218,6 +4662,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4218
4662
|
}
|
|
4219
4663
|
|
|
4220
4664
|
drawSdfText(handle: SdfTextRenderer, model: Float32Array, color: Vec3, opacity: number): void {
|
|
4665
|
+
this.scatterSkin();
|
|
4221
4666
|
if (this.contextLost) return;
|
|
4222
4667
|
const viewProj = this.frameViewProj;
|
|
4223
4668
|
if (viewProj === null) return;
|
|
@@ -4317,6 +4762,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4317
4762
|
env: Environment,
|
|
4318
4763
|
timeSeconds: number,
|
|
4319
4764
|
): void {
|
|
4765
|
+
this.scatterSkin();
|
|
4320
4766
|
if (data.count === 0) return;
|
|
4321
4767
|
const { gl } = this;
|
|
4322
4768
|
const u = batch.uniforms;
|
|
@@ -4378,6 +4824,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4378
4824
|
coreGain: number,
|
|
4379
4825
|
minWidthPerMetre = 0.004,
|
|
4380
4826
|
): void {
|
|
4827
|
+
this.scatterSkin();
|
|
4381
4828
|
if (data.count === 0) return;
|
|
4382
4829
|
const { gl } = this;
|
|
4383
4830
|
const u = batch.uniforms;
|
|
@@ -4452,6 +4899,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4452
4899
|
minWidthPerMetre = 0,
|
|
4453
4900
|
additive = false,
|
|
4454
4901
|
): void {
|
|
4902
|
+
this.scatterSkin();
|
|
4455
4903
|
if (data.count === 0) return;
|
|
4456
4904
|
const { gl } = this;
|
|
4457
4905
|
const u = lines.uniforms;
|
|
@@ -4697,6 +5145,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4697
5145
|
originY = 0,
|
|
4698
5146
|
originZ = 0,
|
|
4699
5147
|
): void {
|
|
5148
|
+
this.scatterSkin();
|
|
4700
5149
|
plumes.draw(
|
|
4701
5150
|
this.gl,
|
|
4702
5151
|
this.frameViewFor(camera),
|
|
@@ -4755,6 +5204,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4755
5204
|
windZ = 0,
|
|
4756
5205
|
strength = 1,
|
|
4757
5206
|
): void {
|
|
5207
|
+
this.scatterSkin();
|
|
4758
5208
|
if (caustics === null || !this.quality.water) return;
|
|
4759
5209
|
const drew = caustics.draw(
|
|
4760
5210
|
this.gl,
|
|
@@ -5084,6 +5534,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5084
5534
|
this.uploadWind(this.depthUniforms, this.frameWind);
|
|
5085
5535
|
gl.useProgram(this.depthSkinnedProgram);
|
|
5086
5536
|
this.uploadWind(this.depthSkinnedUniforms, this.frameWind);
|
|
5537
|
+
if (this.depthSkinned8Program !== null && this.depthSkinned8Uniforms !== null) {
|
|
5538
|
+
gl.useProgram(this.depthSkinned8Program);
|
|
5539
|
+
this.uploadWind(this.depthSkinned8Uniforms, this.frameWind);
|
|
5540
|
+
}
|
|
5541
|
+
for (const caster of this.clothDepth.values()) {
|
|
5542
|
+
gl.useProgram(caster.program);
|
|
5543
|
+
this.uploadWind(caster.uniforms, this.frameWind);
|
|
5544
|
+
}
|
|
5087
5545
|
gl.useProgram(this.depthCutoutProgram);
|
|
5088
5546
|
this.uploadWind(this.depthCutoutUniforms, this.frameWind);
|
|
5089
5547
|
gl.useProgram(this.depthProgram);
|
|
@@ -5110,14 +5568,21 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5110
5568
|
this.bindSceneCasterMaterial(material);
|
|
5111
5569
|
this.drawMesh(mesh as Mesh, model);
|
|
5112
5570
|
},
|
|
5113
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
5571
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
5114
5572
|
/* The opaque handle narrowed to this backend's own, for the reason `casterSink`
|
|
5115
5573
|
gives about the mesh: nothing else ever handed one out. */
|
|
5116
5574
|
this.bindSceneCasterMaterial(material);
|
|
5117
5575
|
this.setSkinPalette(palette);
|
|
5576
|
+
if (cloth !== undefined) {
|
|
5577
|
+
this.setCloth(
|
|
5578
|
+
cloth.binding as unknown as GlClothBinding,
|
|
5579
|
+
cloth.particles as unknown as GlClothParticles,
|
|
5580
|
+
);
|
|
5581
|
+
}
|
|
5118
5582
|
this.drawMesh(mesh as Mesh, model);
|
|
5119
5583
|
/* Put back, or the next rigid draw in the replay skins by whatever this one left bound. */
|
|
5120
5584
|
this.setSkinPalette(null);
|
|
5585
|
+
this.setCloth(null);
|
|
5121
5586
|
},
|
|
5122
5587
|
instanced: (batch, data, material) => {
|
|
5123
5588
|
/* The opaque handle narrowed to this backend's own, for the reason `casterSink`
|
|
@@ -5256,7 +5721,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5256
5721
|
gl.uniform4fv(u['uGlassLight'] ?? null, this.glassLight);
|
|
5257
5722
|
if (cutout !== null) {
|
|
5258
5723
|
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
5259
|
-
gl.
|
|
5724
|
+
gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
|
|
5725
|
+
gl.uniform2f(u['uAlphaCutout'] ?? null, cutout.cutoff, cutout.dithered);
|
|
5260
5726
|
(cutout.albedo as SurfaceTexture).bind(gl, SURFACE_TEXTURE_UNIT);
|
|
5261
5727
|
gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
5262
5728
|
}
|
|
@@ -5395,11 +5861,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5395
5861
|
morphed: boolean,
|
|
5396
5862
|
instanced: boolean,
|
|
5397
5863
|
label: string,
|
|
5864
|
+
eight = false,
|
|
5398
5865
|
): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } | null => {
|
|
5399
5866
|
if (old === null) return null;
|
|
5867
|
+
const vertex = flatVert({ skinned, morphed, instanced });
|
|
5400
5868
|
const program = compileProgram(
|
|
5401
5869
|
gl,
|
|
5402
|
-
|
|
5870
|
+
eight ? skinEight(vertex) : vertex,
|
|
5403
5871
|
this.flatFragSource,
|
|
5404
5872
|
label,
|
|
5405
5873
|
);
|
|
@@ -5420,14 +5888,50 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5420
5888
|
const both = recompile(this.flatBothProgram, true, true, false, 'flat.morphedskinned');
|
|
5421
5889
|
this.flatBothProgram = both?.program ?? null;
|
|
5422
5890
|
this.flatBothUniforms = both?.uniforms ?? null;
|
|
5891
|
+
const skinned8 = recompile(this.flatSkinned8Program, true, false, false, 'flat.skinned8', true);
|
|
5892
|
+
this.flatSkinned8Program = skinned8?.program ?? null;
|
|
5893
|
+
this.flatSkinned8Uniforms = skinned8?.uniforms ?? null;
|
|
5894
|
+
const both8 = recompile(this.flatBoth8Program, true, true, false, 'flat.morphedskinned8', true);
|
|
5895
|
+
this.flatBoth8Program = both8?.program ?? null;
|
|
5896
|
+
this.flatBoth8Uniforms = both8?.uniforms ?? null;
|
|
5423
5897
|
const instanced = recompile(this.flatInstancedProgram, false, false, true, 'flat.instanced');
|
|
5424
5898
|
this.flatInstancedProgram = instanced?.program ?? null;
|
|
5425
5899
|
this.flatInstancedUniforms = instanced?.uniforms ?? null;
|
|
5426
5900
|
|
|
5901
|
+
for (const [key, entry] of this.clothFlat) {
|
|
5902
|
+
const eight = key.startsWith('8');
|
|
5903
|
+
const withMorph = key.endsWith('m');
|
|
5904
|
+
const label = `flat.cloth${eight ? '8' : ''}${withMorph ? '.morphed' : ''}`;
|
|
5905
|
+
const vertex = flatVert({ skinned: true, morphed: withMorph, instanced: false });
|
|
5906
|
+
const program = compileProgram(
|
|
5907
|
+
gl,
|
|
5908
|
+
clothBound(eight ? skinEight(vertex) : vertex),
|
|
5909
|
+
this.flatFragSource,
|
|
5910
|
+
label,
|
|
5911
|
+
);
|
|
5912
|
+
gl.deleteProgram(entry.program);
|
|
5913
|
+
this.clothFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
for (const [key, entry] of this.modelFlat) {
|
|
5917
|
+
const [variant, kind, half] = key.split('|') as [string, SurfaceModelKind, SkinHalf];
|
|
5918
|
+
const label = `flat.${variant}.${kind}.${half}`;
|
|
5919
|
+
const program = compileProgram(
|
|
5920
|
+
gl,
|
|
5921
|
+
litVertexOf(variant),
|
|
5922
|
+
skinHalfBound(modelBound(this.flatFragSource, kind), half),
|
|
5923
|
+
label,
|
|
5924
|
+
);
|
|
5925
|
+
gl.deleteProgram(entry.program);
|
|
5926
|
+
this.modelFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5427
5929
|
this.flatTargets.length = 0;
|
|
5428
|
-
for (const target of [plain, skinned, morphed, both, instanced]) {
|
|
5930
|
+
for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced]) {
|
|
5429
5931
|
if (target !== null) this.flatTargets.push(target);
|
|
5430
5932
|
}
|
|
5933
|
+
for (const target of this.clothFlat.values()) this.flatTargets.push(target);
|
|
5934
|
+
for (const target of this.modelFlat.values()) this.flatTargets.push(target);
|
|
5431
5935
|
const camera = this.lastPassCamera;
|
|
5432
5936
|
const env = this.lastPassEnv;
|
|
5433
5937
|
for (const target of this.flatTargets) {
|
|
@@ -6072,7 +6576,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6072
6576
|
*/
|
|
6073
6577
|
gl.disable(gl.CULL_FACE);
|
|
6074
6578
|
gl.enable(gl.BLEND);
|
|
6075
|
-
|
|
6579
|
+
blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
6076
6580
|
|
|
6077
6581
|
gl.uniform4f(this.panelUniforms['uRect'] ?? null, rect.left, rect.top, rect.width, rect.height);
|
|
6078
6582
|
gl.uniform2f(
|
|
@@ -6649,12 +7153,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6649
7153
|
|
|
6650
7154
|
beginFrame(clearColor: Vec3): void {
|
|
6651
7155
|
if (this.contextLost) return;
|
|
7156
|
+
this.skinScattered = false;
|
|
7157
|
+
this.skinScatter?.reset();
|
|
6652
7158
|
if (this.litAsked) {
|
|
6653
7159
|
this.litAsked = false;
|
|
6654
7160
|
this.rebuildLit();
|
|
6655
7161
|
}
|
|
6656
7162
|
this.budget.reset();
|
|
6657
7163
|
this.materials.dirty();
|
|
7164
|
+
this.cutoutFrame = (this.cutoutFrame + 1) % 64;
|
|
6658
7165
|
/*
|
|
6659
7166
|
* Said once, for the reason `refuseOitMultisampled` gives: a quality setting that is enabled
|
|
6660
7167
|
* and does nothing is the fault the capability clamp exists to prevent.
|
|
@@ -6731,6 +7238,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6731
7238
|
if (this.contextLost) return;
|
|
6732
7239
|
/* A view model left open would squeeze every pass after the world, the composite included. */
|
|
6733
7240
|
if (this.viewModelActive) this.endViewModel();
|
|
7241
|
+
/* Skin not yet spread, because nothing blended came after it, is spread now. */
|
|
7242
|
+
this.scatterSkin();
|
|
6734
7243
|
|
|
6735
7244
|
/*
|
|
6736
7245
|
* Camera motion blur, built from the camera the frame was *rendered* with.
|
|
@@ -7291,6 +7800,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7291
7800
|
* is a non-idempotent step entering this body; there is none today, and one added would show
|
|
7292
7801
|
* as a skinned game behaving differently from an unskinned one.
|
|
7293
7802
|
*/
|
|
7803
|
+
/*
|
|
7804
|
+
* **A pass starts with no material**, so it cannot inherit one from the pass before it. Here
|
|
7805
|
+
* rather than in `writeMeshPassState`, which also feeds a program compiled mid-pass — a model's,
|
|
7806
|
+
* a cloth's, the instanced one — and there forgot the material the caller had set, so the next
|
|
7807
|
+
* draws drew without it until somebody called `setMaterial` again.
|
|
7808
|
+
*/
|
|
7809
|
+
this.materialDoubleSided = false;
|
|
7810
|
+
this.materialHasMaps = false;
|
|
7811
|
+
this.currentMaterial = null;
|
|
7812
|
+
this.currentSurfaceTexture = null;
|
|
7294
7813
|
for (const target of this.flatTargets) {
|
|
7295
7814
|
this.gl.useProgram(target.program);
|
|
7296
7815
|
this.writeMeshPassState(target.uniforms, camera, env);
|
|
@@ -7509,7 +8028,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7509
8028
|
* shape `uOpacity` and `uTint` already use.
|
|
7510
8029
|
*/
|
|
7511
8030
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
7512
|
-
gl.uniform1i(u['uFogEnabled'] ?? null,
|
|
8031
|
+
gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
|
|
8032
|
+
this.surfaceFog = FOG_RECEDE;
|
|
7513
8033
|
/* Grain on by default, so every scene written before it was a choice looks unchanged. */
|
|
7514
8034
|
gl.uniform1f(u['uGrain'] ?? null, 1);
|
|
7515
8035
|
/* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
|
|
@@ -7623,7 +8143,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7623
8143
|
* the sort of per-draw cost this renderer avoids everywhere else.
|
|
7624
8144
|
*/
|
|
7625
8145
|
/*
|
|
7626
|
-
* And the surface unit, which starts every pass with no texture on it: `
|
|
8146
|
+
* And the surface unit, which starts every pass with no texture on it: its flag in `uMaterialFlags`
|
|
7627
8147
|
* is 0 until a caller asks for one, and a world that never asks would otherwise leave
|
|
7628
8148
|
* this sampler incomplete for the whole session.
|
|
7629
8149
|
*/
|
|
@@ -7703,18 +8223,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7703
8223
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
7704
8224
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7705
8225
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
7706
|
-
|
|
8226
|
+
/* Every material switch off — the albedo, both maps, two-sided — in their one row. */
|
|
8227
|
+
gl.uniform4i(u['uMaterialFlags'] ?? null, 0, 0, 0, 0);
|
|
7707
8228
|
/* No albedo, so no effects table: the stand-in, which the shader reads as none. */
|
|
7708
8229
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
7709
8230
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
7710
8231
|
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
7711
8232
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
7712
|
-
gl.
|
|
7713
|
-
gl.
|
|
7714
|
-
this.materialDoubleSided = false;
|
|
7715
|
-
this.materialHasMaps = false;
|
|
7716
|
-
this.currentMaterial = null;
|
|
7717
|
-
this.currentSurfaceTexture = null;
|
|
8233
|
+
gl.uniform2f(u['uUvOffset'] ?? null, 0, 0);
|
|
8234
|
+
gl.uniform4f(u['uCutout'] ?? null, 0, 0, 0, 0);
|
|
7718
8235
|
|
|
7719
8236
|
/*
|
|
7720
8237
|
* **And the normal map, which this did not reset and had to.**
|
|
@@ -7742,16 +8259,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7742
8259
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
7743
8260
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7744
8261
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
7745
|
-
gl.uniform1i(u['uOrmEnabled'] ?? null, 0);
|
|
7746
8262
|
gl.uniform3f(u['uOrmScale'] ?? null, 1, 1, 1);
|
|
7747
8263
|
|
|
7748
8264
|
/* And the emissive map. The scale goes back to one, which is the identity it multiplies by. */
|
|
7749
8265
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
7750
8266
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7751
8267
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
7752
|
-
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, 0);
|
|
7753
8268
|
gl.uniform3f(u['uEmissiveScale'] ?? null, 1, 1, 1);
|
|
7754
8269
|
|
|
8270
|
+
/* And the shading model: its map back to the stand-in and its numbers to the standard's. */
|
|
8271
|
+
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
8272
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
8273
|
+
gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
|
|
8274
|
+
gl.uniform4fv(u['uModelParams'] ?? null, NO_MODEL_PARAMS);
|
|
8275
|
+
|
|
7755
8276
|
/*
|
|
7756
8277
|
* White, because a uniform nobody has written is **zero**, and this one multiplies
|
|
7757
8278
|
* vertex colour.
|
|
@@ -7862,6 +8383,66 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7862
8383
|
if (this.quality.cullDraws && this.occluded(mesh.bounds, model)) return;
|
|
7863
8384
|
this.drawBudget.ask();
|
|
7864
8385
|
this.takeMaterial();
|
|
8386
|
+
/* Skin under the screen-space blur draws its two halves: the frame's, then at once its diffuse. */
|
|
8387
|
+
const splits = this.skinSplits();
|
|
8388
|
+
this.drawMeshHalf(mesh, model, depthLayer, tint, splits ? 'scene' : 'whole');
|
|
8389
|
+
if (splits && this.skinScatter !== null) {
|
|
8390
|
+
this.skinScatter.begin();
|
|
8391
|
+
this.drawMeshHalf(mesh, model, depthLayer, tint, 'diffuse');
|
|
8392
|
+
this.sceneTarget?.bind();
|
|
8393
|
+
}
|
|
8394
|
+
}
|
|
8395
|
+
|
|
8396
|
+
/**
|
|
8397
|
+
* Whether a skin draw now draws its two halves: the profile asked, the frame has not spread its
|
|
8398
|
+
* skin yet, this is the frame's own picture — not a mirror's or a probe's, nor an overlay after
|
|
8399
|
+
* the frame — and the blur's target can be had.
|
|
8400
|
+
*/
|
|
8401
|
+
private skinSplits(): boolean {
|
|
8402
|
+
const skin = this.skinScatter;
|
|
8403
|
+
const scene = this.sceneTarget;
|
|
8404
|
+
return (
|
|
8405
|
+
skin !== null &&
|
|
8406
|
+
scene !== null &&
|
|
8407
|
+
this.currentMaterial?.model?.kind === 'skin' &&
|
|
8408
|
+
!this.skinScattered &&
|
|
8409
|
+
!this.reflectionPassActive &&
|
|
8410
|
+
!this.probePassActive &&
|
|
8411
|
+
!this.framePresented &&
|
|
8412
|
+
skin.ready(scene)
|
|
8413
|
+
);
|
|
8414
|
+
}
|
|
8415
|
+
|
|
8416
|
+
/**
|
|
8417
|
+
* Spread the frame's skin and add it back: once, at the first blended draw or at the frame's end.
|
|
8418
|
+
* Called first in every draw WebGPU records as blended — translucent meshes and batches, text,
|
|
8419
|
+
* particles, plumes, wind streaks, bolts, lines, caustics, film — so the two backends add skin at
|
|
8420
|
+
* the same point. The frame's depth is copied for the blur to read, and where it cannot be the
|
|
8421
|
+
* diffuse goes back unspread rather than not at all.
|
|
8422
|
+
*/
|
|
8423
|
+
private scatterSkin(): void {
|
|
8424
|
+
const skin = this.skinScatter;
|
|
8425
|
+
const scene = this.sceneTarget;
|
|
8426
|
+
const projection = this.frameProjection;
|
|
8427
|
+
if (skin === null || scene === null || this.skinScattered) return;
|
|
8428
|
+
/* Latched whether or not skin was drawn: a skin drawn after this point is the whole surface. */
|
|
8429
|
+
this.skinScattered = true;
|
|
8430
|
+
if (!skin.pending) return;
|
|
8431
|
+
if (this.reflectionPassActive || this.probePassActive || projection === null) return;
|
|
8432
|
+
const depth = scene.snapshotDepth(true);
|
|
8433
|
+
mat4.invert(this.skinInverseProjection, projection);
|
|
8434
|
+
skin.spread(scene, depth, this.emptyTexture2D, projection, this.skinInverseProjection);
|
|
8435
|
+
this.useFlatProgram();
|
|
8436
|
+
}
|
|
8437
|
+
|
|
8438
|
+
/** One draw of a mesh, as `drawMesh` describes, of the half of a skin `half` names. */
|
|
8439
|
+
private drawMeshHalf(
|
|
8440
|
+
mesh: Mesh,
|
|
8441
|
+
model: ReadonlyMat4,
|
|
8442
|
+
depthLayer: number,
|
|
8443
|
+
tint: Vec3 | null,
|
|
8444
|
+
half: SkinHalf,
|
|
8445
|
+
): void {
|
|
7865
8446
|
const { gl } = this;
|
|
7866
8447
|
/*
|
|
7867
8448
|
* A rigged mesh takes the skinned program, an unrigged one the plain program.
|
|
@@ -7881,40 +8462,86 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7881
8462
|
* belongs to was never compiled. Deriving one from the other, as the first version did, made a
|
|
7882
8463
|
* missing skinned-only program silently switch morph off as well.
|
|
7883
8464
|
*/
|
|
7884
|
-
|
|
7885
|
-
const
|
|
8465
|
+
/* A rig of eight takes the pair built with SKIN_EIGHT on; `warmVariants` compiled them. */
|
|
8466
|
+
const eight = wantSkin && mesh.isSkinnedEight;
|
|
8467
|
+
/*
|
|
8468
|
+
* **A cloth set and a skinned draw take the cloth-bound program of this rig's kind**, compiled
|
|
8469
|
+
* when the binding was made. Its morph half is the mesh's whether weights are set or not: the
|
|
8470
|
+
* program declares the deltas whenever the mesh has them, so they are bound, at zero weight
|
|
8471
|
+
* when none were asked for — an unbound sampler would default to a shadow map's unit.
|
|
8472
|
+
*/
|
|
8473
|
+
const cloth = wantSkin ? this.cloth : null;
|
|
8474
|
+
const clothed =
|
|
8475
|
+
cloth === null
|
|
8476
|
+
? undefined
|
|
8477
|
+
: this.clothFlat.get(`${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`);
|
|
8478
|
+
const bothUniforms = eight ? this.flatBoth8Uniforms : this.flatBothUniforms;
|
|
8479
|
+
const skinnedUniforms = eight ? this.flatSkinned8Uniforms : this.flatSkinnedUniforms;
|
|
8480
|
+
const both = wantSkin && wantMorph && bothUniforms !== null;
|
|
8481
|
+
const skinned = both || (wantSkin && skinnedUniforms !== null);
|
|
7886
8482
|
const morphed = both || (wantMorph && !wantSkin && this.flatMorphedUniforms !== null);
|
|
7887
8483
|
const variant =
|
|
7888
8484
|
skinned && morphed
|
|
7889
|
-
?
|
|
8485
|
+
? bothUniforms
|
|
7890
8486
|
: skinned
|
|
7891
|
-
?
|
|
8487
|
+
? skinnedUniforms
|
|
7892
8488
|
: morphed
|
|
7893
8489
|
? this.flatMorphedUniforms
|
|
7894
8490
|
: null;
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
?
|
|
7899
|
-
: skinned
|
|
7900
|
-
?
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
8491
|
+
let u = clothed?.uniforms ?? variant ?? this.flatUniforms;
|
|
8492
|
+
let program =
|
|
8493
|
+
clothed !== undefined
|
|
8494
|
+
? clothed.program
|
|
8495
|
+
: skinned && morphed
|
|
8496
|
+
? eight
|
|
8497
|
+
? this.flatBoth8Program
|
|
8498
|
+
: this.flatBothProgram
|
|
8499
|
+
: skinned
|
|
8500
|
+
? eight
|
|
8501
|
+
? this.flatSkinned8Program
|
|
8502
|
+
: this.flatSkinnedProgram
|
|
8503
|
+
: morphed
|
|
8504
|
+
? this.flatMorphedProgram
|
|
8505
|
+
: null;
|
|
8506
|
+
/* A material with a model takes that model's program for the same vertex variant. */
|
|
8507
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
8508
|
+
if (modelKind !== null) {
|
|
8509
|
+
const modelled = this.modelProgram(
|
|
8510
|
+
clothed !== undefined
|
|
8511
|
+
? `cloth${eight ? 8 : 4}${mesh.morph !== null ? 'm' : '-'}`
|
|
8512
|
+
: skinned && morphed
|
|
8513
|
+
? eight
|
|
8514
|
+
? 'both8'
|
|
8515
|
+
: 'both'
|
|
8516
|
+
: skinned
|
|
8517
|
+
? eight
|
|
8518
|
+
? 'skinned8'
|
|
8519
|
+
: 'skinned'
|
|
8520
|
+
: morphed
|
|
8521
|
+
? 'morphed'
|
|
8522
|
+
: 'plain',
|
|
8523
|
+
modelKind,
|
|
8524
|
+
half,
|
|
8525
|
+
);
|
|
8526
|
+
program = modelled.program;
|
|
8527
|
+
u = modelled.uniforms;
|
|
8528
|
+
}
|
|
7904
8529
|
|
|
7905
8530
|
if (program !== null) {
|
|
7906
8531
|
gl.useProgram(program);
|
|
7907
|
-
if (skinned) {
|
|
8532
|
+
if (skinned || clothed !== undefined) {
|
|
7908
8533
|
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
7909
8534
|
gl.uniform1i(u['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
7910
8535
|
}
|
|
7911
|
-
if (morphed && mesh.morph !== null) {
|
|
8536
|
+
if ((morphed || clothed !== undefined) && mesh.morph !== null) {
|
|
7912
8537
|
mesh.morph.bind(gl, MORPH_DELTA_TEXTURE_UNIT);
|
|
7913
8538
|
gl.uniform1i(u['uMorphDeltas'] ?? null, MORPH_DELTA_TEXTURE_UNIT);
|
|
7914
8539
|
gl.uniform1i(u['uMorphTargetCount'] ?? null, mesh.morph.targetCount);
|
|
7915
8540
|
gl.uniform1i(u['uMorphTextureWidth'] ?? null, mesh.morph.width);
|
|
7916
|
-
gl.uniform1fv(u['uMorphWeights'] ?? null, this.morphWeights
|
|
8541
|
+
gl.uniform1fv(u['uMorphWeights'] ?? null, this.morphWeights ?? NO_MORPH_WEIGHTS);
|
|
7917
8542
|
}
|
|
8543
|
+
if (clothed !== undefined && cloth !== null) this.bindCloth(u, cloth);
|
|
8544
|
+
if (modelKind === 'eye') this.writeEyeAxis(u, model);
|
|
7918
8545
|
} else {
|
|
7919
8546
|
this.useFlatProgram();
|
|
7920
8547
|
}
|
|
@@ -7947,11 +8574,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7947
8574
|
* depth 1.0 against a buffer cleared to 1.0, and under `LESS` it would not draw at
|
|
7948
8575
|
* all. Film, caustics and text lie flush on surfaces for the same reason.
|
|
7949
8576
|
*/
|
|
7950
|
-
|
|
8577
|
+
/* A skin's diffuse half is the same surface again: it finds the depth its frame half wrote. */
|
|
8578
|
+
const again = half === 'diffuse';
|
|
8579
|
+
gl.depthFunc(again ? gl.EQUAL : this.reversedDepth ? gl.GREATER : gl.LESS);
|
|
8580
|
+
if (again) gl.depthMask(false);
|
|
8581
|
+
/* A dithered cutout in a multisampled frame with no temporal resolve: its share becomes the
|
|
8582
|
+
pixel's samples. Scoped to this draw, as the depth compare above is. See `cutoutDither.ts`. */
|
|
8583
|
+
const coverage = !again && this.cutoutResolveNow(this.currentMaterial, false) === 'coverage';
|
|
8584
|
+
if (coverage) gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
7951
8585
|
if (layer === 0) {
|
|
7952
8586
|
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
7953
8587
|
mesh.draw(gl);
|
|
7954
8588
|
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
8589
|
+
if (coverage) gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
8590
|
+
if (again) gl.depthMask(true);
|
|
7955
8591
|
gl.depthFunc(this.reversedDepth ? gl.GEQUAL : gl.LEQUAL);
|
|
7956
8592
|
if (tint !== null) gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
7957
8593
|
if (program !== null) this.useFlatProgram();
|
|
@@ -7973,6 +8609,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7973
8609
|
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
7974
8610
|
mesh.draw(gl);
|
|
7975
8611
|
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
8612
|
+
if (coverage) gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
8613
|
+
if (again) gl.depthMask(true);
|
|
7976
8614
|
gl.polygonOffset(0, 0);
|
|
7977
8615
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
7978
8616
|
if (program !== null) this.useFlatProgram();
|
|
@@ -8025,6 +8663,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8025
8663
|
opacity: number,
|
|
8026
8664
|
options: TranslucentMeshOptions = {},
|
|
8027
8665
|
): void {
|
|
8666
|
+
this.scatterSkin();
|
|
8028
8667
|
if (this.contextLost) return;
|
|
8029
8668
|
/* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
|
|
8030
8669
|
and `Mesh.indexCount`. */
|
|
@@ -8047,10 +8686,19 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8047
8686
|
this.drawBudget.ask();
|
|
8048
8687
|
|
|
8049
8688
|
const { gl } = this;
|
|
8050
|
-
|
|
8051
|
-
this.
|
|
8689
|
+
/* A material with a model takes that model's plain program; see `modelProgram`. */
|
|
8690
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
8691
|
+
const modelled = modelKind === null ? null : this.modelProgram('plain', modelKind);
|
|
8692
|
+
const u = modelled?.uniforms ?? this.flatUniforms;
|
|
8693
|
+
if (modelled === null) this.useFlatProgram();
|
|
8694
|
+
else gl.useProgram(modelled.program);
|
|
8695
|
+
if (modelKind === 'eye') this.writeEyeAxis(u, model);
|
|
8052
8696
|
gl.uniformMatrix4fv(u['uModel'] ?? null, false, model);
|
|
8053
8697
|
gl.uniform1f(u['uOpacity'] ?? null, Math.min(opacity, 1));
|
|
8698
|
+
/* A dithered cutout tests hard on a blended draw, and is put back below with the rest. */
|
|
8699
|
+
const ditheredHere = this.cutoutResolveNow(this.currentMaterial, false) !== 'hard';
|
|
8700
|
+
if (ditheredHere)
|
|
8701
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
|
|
8054
8702
|
/*
|
|
8055
8703
|
* Both default to what `bindMeshPass` already put there — lit and fogged — so the
|
|
8056
8704
|
* common call with no fourth argument writes the same two values it always implicitly
|
|
@@ -8061,7 +8709,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8061
8709
|
const fog = fogModeOf(options.fog ?? true, options.additive === true);
|
|
8062
8710
|
const toneMapped = options.toneMapped ?? true;
|
|
8063
8711
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
8064
|
-
if (fog !==
|
|
8712
|
+
if (fog !== this.surfaceFog) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
8065
8713
|
/* `1` is sRGB alone: the conversion without the curve, which is what dropping the tone map
|
|
8066
8714
|
means rather than dropping the whole transform. `Math.min` rather than a literal, so a
|
|
8067
8715
|
renderer asked for `none` stays at none and one asked for `srgb` is already there. */
|
|
@@ -8093,15 +8741,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8093
8741
|
*/
|
|
8094
8742
|
/* Taken for itself when any option differs from the pass, and put back below: the rule both
|
|
8095
8743
|
backends count material changes by. See `materialChanges.ts`. */
|
|
8096
|
-
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
8744
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog);
|
|
8097
8745
|
if (own) this.materials.dirty();
|
|
8098
8746
|
this.takeMaterial();
|
|
8099
8747
|
const ownsState = !this.oitReplaying;
|
|
8100
8748
|
if (ownsState) {
|
|
8101
8749
|
gl.enable(gl.BLEND);
|
|
8102
8750
|
/* Added, where asked: colour times alpha onto what is there, and the target's alpha kept. */
|
|
8103
|
-
if (options.additive === true)
|
|
8104
|
-
else
|
|
8751
|
+
if (options.additive === true) blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
8752
|
+
else blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
8105
8753
|
}
|
|
8106
8754
|
/*
|
|
8107
8755
|
* The two depth options, both scoped to this draw and handed straight back — the same
|
|
@@ -8128,12 +8776,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8128
8776
|
if (!depthWrite && ownsState) gl.depthMask(true);
|
|
8129
8777
|
if (ownsState) gl.disable(gl.BLEND);
|
|
8130
8778
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
8779
|
+
if (ditheredHere)
|
|
8780
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, false));
|
|
8131
8781
|
if (tint !== null) gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
8132
8782
|
if (refracting) this.unbindSeeThrough(u);
|
|
8133
8783
|
/* Put back for whatever is drawn next, exactly as `uOpacity` and `uTint` are — a pass
|
|
8134
8784
|
cannot inherit a material from the draw before it. */
|
|
8135
8785
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
8136
|
-
if (fog !==
|
|
8786
|
+
if (fog !== this.surfaceFog) gl.uniform1i(u['uFogEnabled'] ?? null, this.surfaceFog);
|
|
8137
8787
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
8138
8788
|
if (own) this.materials.dirty();
|
|
8139
8789
|
}
|
|
@@ -8561,7 +9211,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8561
9211
|
}
|
|
8562
9212
|
|
|
8563
9213
|
gl.enable(gl.BLEND);
|
|
8564
|
-
|
|
9214
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE);
|
|
8565
9215
|
gl.depthMask(false);
|
|
8566
9216
|
gl.cullFace(inside ? gl.FRONT : gl.BACK);
|
|
8567
9217
|
mesh.draw(gl);
|
|
@@ -8599,6 +9249,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8599
9249
|
sheen: number,
|
|
8600
9250
|
options: FilmOptions = {},
|
|
8601
9251
|
): void {
|
|
9252
|
+
this.scatterSkin();
|
|
8602
9253
|
const { gl } = this;
|
|
8603
9254
|
const u = this.filmUniforms;
|
|
8604
9255
|
gl.useProgram(this.filmProgram);
|
|
@@ -8656,7 +9307,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8656
9307
|
);
|
|
8657
9308
|
|
|
8658
9309
|
gl.enable(gl.BLEND);
|
|
8659
|
-
|
|
9310
|
+
blendKeeping(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
8660
9311
|
gl.depthMask(false);
|
|
8661
9312
|
mesh.draw(gl);
|
|
8662
9313
|
gl.depthMask(true);
|
|
@@ -8761,3 +9412,22 @@ function angularDiscExponent(angularRadius: number): number {
|
|
|
8761
9412
|
* name to prefer. It goes when something else forces a major, and not before.
|
|
8762
9413
|
*/
|
|
8763
9414
|
export { WebGL2Renderer as Renderer };
|
|
9415
|
+
|
|
9416
|
+
/** The standard model's numbers: no model, no map. */
|
|
9417
|
+
const NO_MODEL_PARAMS = new Float32Array(MODEL_PARAM_FLOATS);
|
|
9418
|
+
|
|
9419
|
+
/**
|
|
9420
|
+
* The lit vertex stage a program of this variant compiles: the standard programs' and the cloth's,
|
|
9421
|
+
* which `modelProgram` pairs a model's fragment stage with.
|
|
9422
|
+
*/
|
|
9423
|
+
function litVertexOf(variant: string): string {
|
|
9424
|
+
if (variant.startsWith('cloth')) {
|
|
9425
|
+
const eight = variant[5] === '8';
|
|
9426
|
+
const vertex = flatVert({ skinned: true, morphed: variant.endsWith('m'), instanced: false });
|
|
9427
|
+
return clothBound(eight ? skinEight(vertex) : vertex);
|
|
9428
|
+
}
|
|
9429
|
+
const skinned = variant.startsWith('skinned') || variant.startsWith('both');
|
|
9430
|
+
const morphed = variant === 'morphed' || variant.startsWith('both');
|
|
9431
|
+
const vertex = flatVert({ skinned, morphed, instanced: variant === 'instanced' });
|
|
9432
|
+
return variant.endsWith('8') ? skinEight(vertex) : vertex;
|
|
9433
|
+
}
|