@driftengine/core 4.8.3 → 4.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +82 -0
- package/README.md +1 -1
- package/dist/build/shaderComments.js +0 -0
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +16 -1
- package/dist/math/scalar.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +7 -0
- package/dist/render/anisotropicLobe.js +33 -0
- package/dist/render/backend/api.d.ts +41 -4
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
- package/dist/render/backend/webgl2/clothTextures.js +93 -0
- package/dist/render/backend/webgl2/renderer.d.ts +136 -3
- package/dist/render/backend/webgl2/renderer.js +604 -63
- package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
- package/dist/render/backend/webgl2/skinScatter.js +256 -0
- package/dist/render/backend/webgpu/buffers.d.ts +25 -11
- package/dist/render/backend/webgpu/buffers.js +98 -49
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
- package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
- package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
- package/dist/render/backend/webgpu/clothTextures.js +145 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
- package/dist/render/backend/webgpu/compressedUpload.js +38 -0
- package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
- package/dist/render/backend/webgpu/computeHost.js +5 -0
- package/dist/render/backend/webgpu/depthPass.d.ts +16 -6
- package/dist/render/backend/webgpu/depthPass.js +39 -9
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +38 -4
- package/dist/render/backend/webgpu/flatPass.js +109 -12
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/postPass.d.ts +3 -1
- package/dist/render/backend/webgpu/postPass.js +6 -3
- package/dist/render/backend/webgpu/renderer.d.ts +113 -5
- package/dist/render/backend/webgpu/renderer.js +498 -48
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
- package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
- package/dist/render/clothBindingData.d.ts +41 -0
- package/dist/render/clothBindingData.js +72 -0
- package/dist/render/clothPlacement.d.ts +25 -0
- package/dist/render/clothPlacement.js +85 -0
- package/dist/render/compressedSource.d.ts +66 -0
- package/dist/render/compressedSource.js +138 -0
- package/dist/render/cutoutCaster.d.ts +2 -0
- package/dist/render/cutoutCaster.js +2 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- package/dist/render/eyeRefraction.d.ts +35 -0
- package/dist/render/eyeRefraction.js +40 -0
- package/dist/render/glCompressed.d.ts +27 -0
- package/dist/render/glCompressed.js +53 -0
- package/dist/render/hairLobes.d.ts +70 -0
- package/dist/render/hairLobes.js +120 -0
- package/dist/render/lightBudget.d.ts +22 -0
- package/dist/render/lightBudget.js +22 -0
- package/dist/render/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +20 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +42 -6
- package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
- package/dist/render/shaders/flat/anisotropic.js +68 -0
- package/dist/render/shaders/flat/eye.d.ts +1 -0
- package/dist/render/shaders/flat/eye.js +81 -0
- package/dist/render/shaders/flat/hair.d.ts +1 -0
- package/dist/render/shaders/flat/hair.js +101 -0
- package/dist/render/shaders/flat/index.js +36 -2
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +113 -17
- package/dist/render/shaders/flat/models.d.ts +14 -0
- package/dist/render/shaders/flat/models.js +273 -0
- package/dist/render/shaders/flat/preamble.js +26 -24
- package/dist/render/shaders/flat/skin.d.ts +1 -0
- package/dist/render/shaders/flat/skin.js +114 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +38 -16
- package/dist/render/shaders/generated/depth.wgsl.js +38 -16
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1790 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1828 -1742
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
- package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- package/dist/render/shaders/skinBlur.d.ts +1 -0
- package/dist/render/shaders/skinBlur.js +103 -0
- package/dist/render/shaders/skinning.d.ts +3 -1
- package/dist/render/shaders/skinning.js +31 -4
- package/dist/render/shadowCasters.d.ts +13 -1
- package/dist/render/skinBlur.d.ts +41 -0
- package/dist/render/skinBlur.js +49 -0
- package/dist/render/skinScatter.d.ts +67 -0
- package/dist/render/skinScatter.js +80 -0
- package/dist/render/skinnedClothRun.d.ts +26 -0
- package/dist/render/skinnedClothRun.js +84 -0
- package/dist/render/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +44 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/vertexDefaults.js +7 -0
- package/package.json +6 -6
- package/src/build/shaderComments.ts +0 -0
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +40 -1
- package/src/math/scalar.ts +1 -1
- package/src/render/anisotropicLobe.ts +41 -0
- package/src/render/backend/api.ts +52 -3
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +693 -64
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
- package/src/render/backend/webgpu/clothTextures.ts +194 -0
- package/src/render/backend/webgpu/compressedUpload.ts +65 -0
- package/src/render/backend/webgpu/computeHost.ts +25 -0
- package/src/render/backend/webgpu/depthPass.ts +39 -7
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/flatPass.ts +145 -7
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/postPass.ts +5 -1
- package/src/render/backend/webgpu/renderer.ts +663 -43
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
- package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
- package/src/render/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +5 -0
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/mesh.ts +23 -0
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +28 -0
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +42 -6
- package/src/render/shaders/flat/anisotropic.ts +68 -0
- package/src/render/shaders/flat/eye.ts +82 -0
- package/src/render/shaders/flat/hair.ts +102 -0
- package/src/render/shaders/flat/index.ts +36 -2
- package/src/render/shaders/flat/main.ts +113 -17
- package/src/render/shaders/flat/models.ts +286 -0
- package/src/render/shaders/flat/preamble.ts +26 -24
- package/src/render/shaders/flat/skin.ts +116 -0
- package/src/render/shaders/generated/depth.wgsl.ts +38 -16
- package/src/render/shaders/generated/flat.wgsl.ts +1828 -1742
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- package/src/render/shadowCasters.ts +13 -0
- package/src/render/skinBlur.ts +53 -0
- package/src/render/skinScatter.ts +92 -0
- package/src/render/skinnedClothRun.ts +128 -0
- package/src/render/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +123 -63
- package/src/render/vertexDefaults.ts +7 -0
- package/dist/math/exact.d.ts +0 -76
- package/dist/math/exact.js +0 -327
- package/src/math/exact.ts +0 -340
|
@@ -121,8 +121,31 @@ import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/un
|
|
|
121
121
|
|
|
122
122
|
import type { RendererApi } from '../api.ts';
|
|
123
123
|
import { flatFrag, flatVert } from '../../shaders/flat/index.ts';
|
|
124
|
-
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';
|
|
125
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);
|
|
126
149
|
|
|
127
150
|
/** Shader-side encoding of `RenderQuality.outputTransform`. */
|
|
128
151
|
/* The codes the shader reads, stated once in `vertexDefaults.ts` for both backends. */
|
|
@@ -211,6 +234,8 @@ import type { IncrementalMesh } from '../../mesh.ts';
|
|
|
211
234
|
import type { MeshData } from '../../mesh.ts';
|
|
212
235
|
import type { MeshOptions } from '../api.ts';
|
|
213
236
|
import { SurfaceTexture } from '../../surfaceTexture.ts';
|
|
237
|
+
import { glCompressedFormats } from '../../glCompressed.ts';
|
|
238
|
+
import type { CompressedTextureFormat, SurfaceSource } from '../../compressedSource.ts';
|
|
214
239
|
import type { SurfaceMaterial, SurfaceTextureOptions } from '../../surfaceTexture.ts';
|
|
215
240
|
import { PlumeRenderer } from '../../plumeRenderer.ts';
|
|
216
241
|
import { createScatterDeform, resolveScatterDeform } from '../../scatterDeform.ts';
|
|
@@ -256,6 +281,8 @@ import type { FlockParams } from '../../flockRenderer.ts';
|
|
|
256
281
|
import { InstancedBatch } from './instanced.ts';
|
|
257
282
|
import type { InstancedOptions, MeshInstances } from '../../instances.ts';
|
|
258
283
|
import { cutoutOf } from '../../cutoutCaster.ts';
|
|
284
|
+
import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.ts';
|
|
285
|
+
import type { CutoutResolve } from '../../cutoutDither.ts';
|
|
259
286
|
import type { CutoutCaster } from '../../cutoutCaster.ts';
|
|
260
287
|
import type { SurfaceTextureHandle } from '../api.ts';
|
|
261
288
|
import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.ts';
|
|
@@ -1133,6 +1160,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1133
1160
|
private flatMorphedUniforms: Record<string, WebGLUniformLocation> | null = null;
|
|
1134
1161
|
private flatBothProgram: WebGLProgram | null = null;
|
|
1135
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;
|
|
1136
1174
|
/** The morph weights the following draws use, or null for none. */
|
|
1137
1175
|
private morphWeights: Float32Array | null = null;
|
|
1138
1176
|
/**
|
|
@@ -1169,6 +1207,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1169
1207
|
* before this existed.
|
|
1170
1208
|
*/
|
|
1171
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();
|
|
1172
1218
|
/** So the refusal below is once per renderer rather than once per frame. See `setColourGrade`. */
|
|
1173
1219
|
private warnedGradeWithoutComposite = false;
|
|
1174
1220
|
/** Scratch for a volume's `uDepthToLocal`. Built per draw and never allocated in the loop. */
|
|
@@ -1191,6 +1237,30 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1191
1237
|
private readonly skinPalette = new SkinPaletteTexture();
|
|
1192
1238
|
/** Whether a palette is currently chosen. Distinct from whether one was ever uploaded. */
|
|
1193
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;
|
|
1194
1264
|
|
|
1195
1265
|
private readonly emptyTexture2D: WebGLTexture;
|
|
1196
1266
|
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
@@ -1696,6 +1766,22 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1696
1766
|
private materialHasMaps = false;
|
|
1697
1767
|
/** The material last set, so a lit program rebuilt for glass can be given it. */
|
|
1698
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;
|
|
1699
1785
|
/**
|
|
1700
1786
|
* The lit stage's switches: what the lit programs were compiled with (`litOn`), and what the
|
|
1701
1787
|
* profile and the content have asked for since (`litWanted`).
|
|
@@ -1823,7 +1909,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1823
1909
|
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1824
1910
|
gl.useProgram(this.depthProgram);
|
|
1825
1911
|
},
|
|
1826
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
1912
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
1827
1913
|
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1828
1914
|
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1829
1915
|
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
@@ -1833,8 +1919,25 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1833
1919
|
}
|
|
1834
1920
|
this.shadowDrawBudget.ask();
|
|
1835
1921
|
const { gl } = this;
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|
+
);
|
|
1838
1941
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1839
1942
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1840
1943
|
/* The peel layer reads the first pass's depths; `beginShadowPass` bound that texture to
|
|
@@ -1844,6 +1947,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1844
1947
|
this.skinPalette.update(gl, palette);
|
|
1845
1948
|
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
1846
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
|
+
}
|
|
1847
1956
|
/* Narrowing is safe for the reason `mesh` above gives: this renderer only ever handed
|
|
1848
1957
|
out its own `Mesh`. */
|
|
1849
1958
|
(mesh as Mesh).draw(gl);
|
|
@@ -1889,7 +1998,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1889
1998
|
if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1890
1999
|
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
1891
2000
|
gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
|
|
1892
|
-
gl.
|
|
2001
|
+
gl.uniform2f(u['uAlphaCutout'] ?? null, cutout.cutoff, cutout.dithered);
|
|
1893
2002
|
/* Narrowed for the reason the sink's own `mesh` gives: this renderer only hands out its own. */
|
|
1894
2003
|
(cutout.albedo as SurfaceTexture).bind(gl, SURFACE_TEXTURE_UNIT);
|
|
1895
2004
|
}
|
|
@@ -2802,11 +2911,22 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2802
2911
|
this.quality.discardResolvedAttachments,
|
|
2803
2912
|
)
|
|
2804
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;
|
|
2805
2924
|
/*
|
|
2806
2925
|
* Said once at init, because the number a caller asked for and the number the part gave
|
|
2807
2926
|
* them are not always the same, and a silently clamped sample count is a quality setting
|
|
2808
2927
|
* that does not apply — the same failure mode `capabilityClamp` exists to avoid.
|
|
2809
2928
|
*/
|
|
2929
|
+
this.canvasSamples = Math.max(1, (gl.getParameter(gl.SAMPLES) as number | null) ?? 1);
|
|
2810
2930
|
const got = this.sceneTarget?.sampleCount ?? 1;
|
|
2811
2931
|
if (this.quality.sceneSamples > 1 && got !== this.quality.sceneSamples) {
|
|
2812
2932
|
console.warn(
|
|
@@ -3020,6 +3140,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3020
3140
|
this.disposed = true;
|
|
3021
3141
|
|
|
3022
3142
|
const { gl } = this;
|
|
3143
|
+
if (!this.contextLost) this.skinScatter?.dispose();
|
|
3023
3144
|
this.detachContextLoss();
|
|
3024
3145
|
this.contextLostListeners.length = 0;
|
|
3025
3146
|
this.contextRestoredListeners.length = 0;
|
|
@@ -3060,10 +3181,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3060
3181
|
this.flatSkinnedProgram,
|
|
3061
3182
|
this.flatMorphedProgram,
|
|
3062
3183
|
this.flatBothProgram,
|
|
3184
|
+
this.flatSkinned8Program,
|
|
3185
|
+
this.flatBoth8Program,
|
|
3186
|
+
this.depthSkinned8Program,
|
|
3063
3187
|
this.flatInstancedProgram,
|
|
3064
3188
|
]) {
|
|
3065
3189
|
if (extra !== null) gl.deleteProgram(extra);
|
|
3066
3190
|
}
|
|
3191
|
+
for (const entry of [...this.clothFlat.values(), ...this.clothDepth.values()]) {
|
|
3192
|
+
gl.deleteProgram(entry.program);
|
|
3193
|
+
}
|
|
3067
3194
|
for (const program of [
|
|
3068
3195
|
this.flatProgram,
|
|
3069
3196
|
this.panelProgram,
|
|
@@ -3160,12 +3287,27 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3160
3287
|
*/
|
|
3161
3288
|
private warmVariants(data: MeshData): void {
|
|
3162
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;
|
|
3163
3292
|
const morphable = data.morphTargets !== undefined;
|
|
3164
3293
|
for (const skinned of skinnable ? [false, true] : [false]) {
|
|
3165
3294
|
for (const morphed of morphable ? [false, true] : [false]) {
|
|
3166
|
-
if (skinned || morphed) this.ensureVariant(skinned, morphed);
|
|
3295
|
+
if (skinned || morphed) this.ensureVariant(skinned, morphed, skinned && eight);
|
|
3167
3296
|
}
|
|
3168
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
|
+
}
|
|
3169
3311
|
}
|
|
3170
3312
|
|
|
3171
3313
|
/**
|
|
@@ -3210,25 +3352,36 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3210
3352
|
* `bindMeshPass` would otherwise carry every uniform at zero, and zero is a real value for most
|
|
3211
3353
|
* of them — the failure this file records for `uGrain` and `uAmbientGround`.
|
|
3212
3354
|
*/
|
|
3213
|
-
private ensureVariant(skinned: boolean, morphed: boolean): void {
|
|
3355
|
+
private ensureVariant(skinned: boolean, morphed: boolean, eight = false): void {
|
|
3214
3356
|
if (this.contextLost) return;
|
|
3215
|
-
if (skinned &&
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
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
|
+
}
|
|
3219
3365
|
|
|
3220
3366
|
const { gl } = this;
|
|
3221
|
-
const label = `flat.${morphed ? 'morphed' : ''}${skinned ? 'skinned' : ''}`;
|
|
3367
|
+
const label = `flat.${morphed ? 'morphed' : ''}${skinned ? (eight ? 'skinned8' : 'skinned') : ''}`;
|
|
3222
3368
|
/* `flatFragSource`, not a fresh `flatFrag`: every variant shades through one fragment stage
|
|
3223
3369
|
at one light budget, and the uploads are written for that budget. */
|
|
3370
|
+
const vertex = flatVert({ skinned, morphed, instanced: false });
|
|
3224
3371
|
const program = compileProgram(
|
|
3225
3372
|
gl,
|
|
3226
|
-
|
|
3373
|
+
skinned && eight ? skinEight(vertex) : vertex,
|
|
3227
3374
|
this.flatFragSource,
|
|
3228
3375
|
label,
|
|
3229
3376
|
);
|
|
3230
3377
|
const uniforms = uniformLocations(gl, program, label);
|
|
3231
|
-
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) {
|
|
3232
3385
|
this.flatBothProgram = program;
|
|
3233
3386
|
this.flatBothUniforms = uniforms;
|
|
3234
3387
|
} else if (skinned) {
|
|
@@ -3245,6 +3398,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3245
3398
|
if (camera !== null && env !== null) {
|
|
3246
3399
|
gl.useProgram(program);
|
|
3247
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);
|
|
3248
3403
|
this.useFlatProgram();
|
|
3249
3404
|
}
|
|
3250
3405
|
}
|
|
@@ -3257,11 +3412,24 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3257
3412
|
* a canvas drawn procedurally at runtime, a decoded bitmap, a video frame — stays
|
|
3258
3413
|
* the consumer's decision. What it gains is a GPU object with sane sampler state.
|
|
3259
3414
|
*/
|
|
3260
|
-
createSurfaceTexture(
|
|
3261
|
-
source
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
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;
|
|
3265
3433
|
}
|
|
3266
3434
|
|
|
3267
3435
|
/**
|
|
@@ -3271,10 +3439,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3271
3439
|
* refusal names the first that is not.
|
|
3272
3440
|
*/
|
|
3273
3441
|
createSurfaceTextureArray(
|
|
3274
|
-
sources: readonly
|
|
3442
|
+
sources: readonly SurfaceSource[],
|
|
3275
3443
|
options: SurfaceTextureOptions = {},
|
|
3276
3444
|
): SurfaceTexture {
|
|
3277
|
-
return new SurfaceTexture(this.gl, sources, options);
|
|
3445
|
+
return new SurfaceTexture(this.gl, sources, options, this.compressedFormats);
|
|
3278
3446
|
}
|
|
3279
3447
|
|
|
3280
3448
|
/**
|
|
@@ -3332,6 +3500,176 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3332
3500
|
}
|
|
3333
3501
|
this.skinPalette.update(this.gl, palette);
|
|
3334
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);
|
|
3335
3673
|
}
|
|
3336
3674
|
|
|
3337
3675
|
/**
|
|
@@ -3353,6 +3691,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3353
3691
|
this.materialDoubleSided = material?.doubleSided === true;
|
|
3354
3692
|
this.materialHasMaps = materialHasMaps(material);
|
|
3355
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
|
+
}
|
|
3356
3704
|
/*
|
|
3357
3705
|
* Written to every flat program, because material state persists across draws and a skinned
|
|
3358
3706
|
* draw is entitled to the material the caller set before it. A second program holding none of
|
|
@@ -3370,6 +3718,30 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3370
3718
|
this.useFlatProgram();
|
|
3371
3719
|
}
|
|
3372
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
|
+
|
|
3373
3745
|
/**
|
|
3374
3746
|
* One flat program's view of the current material.
|
|
3375
3747
|
*
|
|
@@ -3420,7 +3792,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3420
3792
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3421
3793
|
if (orm !== null) orm.bind(gl, ORM_TEXTURE_UNIT);
|
|
3422
3794
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
3423
|
-
|
|
3795
|
+
this.materialFlags[1] = orm === null ? 0 : 1;
|
|
3424
3796
|
gl.uniform3f(
|
|
3425
3797
|
u['uOrmScale'] ?? null,
|
|
3426
3798
|
material?.occlusionStrength ?? 1,
|
|
@@ -3434,7 +3806,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3434
3806
|
*
|
|
3435
3807
|
* **The stand-in is black, so the gate is what keeps a glow on.** The shader multiplies by
|
|
3436
3808
|
* this map, and `emptyTexture2D` is opaque black rather than the multiplicative identity, so an
|
|
3437
|
-
* 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
|
|
3438
3810
|
* and the read never happens. What would make that wrong is a read taken outside that branch:
|
|
3439
3811
|
* every glow on a material without a map would go out.
|
|
3440
3812
|
*/
|
|
@@ -3443,7 +3815,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3443
3815
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3444
3816
|
if (emissiveMap !== null) emissiveMap.bind(gl, EMISSIVE_TEXTURE_UNIT);
|
|
3445
3817
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
3446
|
-
|
|
3818
|
+
this.materialFlags[2] = emissiveMap === null ? 0 : 1;
|
|
3447
3819
|
const emissiveScale = material?.emissiveScale ?? null;
|
|
3448
3820
|
gl.uniform3f(
|
|
3449
3821
|
u['uEmissiveScale'] ?? null,
|
|
@@ -3461,7 +3833,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3461
3833
|
*/
|
|
3462
3834
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
3463
3835
|
gl.uniform2f(u['uUvOffset'] ?? null, material?.uOffset ?? 0, material?.vOffset ?? 0);
|
|
3464
|
-
|
|
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);
|
|
3465
3850
|
|
|
3466
3851
|
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
3467
3852
|
return, so a material with no albedo still leaves a complete texture on the unit. */
|
|
@@ -3479,12 +3864,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3479
3864
|
array target, and with nothing there it is incomplete. */
|
|
3480
3865
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
3481
3866
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3482
|
-
|
|
3867
|
+
this.materialFlags[0] = 0;
|
|
3868
|
+
gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
|
|
3483
3869
|
return;
|
|
3484
3870
|
}
|
|
3485
3871
|
albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
3486
|
-
gl.
|
|
3487
|
-
|
|
3872
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(material, false));
|
|
3873
|
+
this.materialFlags[0] = 1;
|
|
3874
|
+
gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
|
|
3488
3875
|
}
|
|
3489
3876
|
|
|
3490
3877
|
/**
|
|
@@ -3503,7 +3890,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3503
3890
|
* Rebind `SURFACE_TEXTURE_UNIT` to whatever `setSurfaceTexture` last put there.
|
|
3504
3891
|
*
|
|
3505
3892
|
* Only `drawSdfText` calls this, after the atlas has finished borrowing the same unit.
|
|
3506
|
-
* 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
|
|
3507
3894
|
* touched by the text draw and are still correct, since that draw runs its own separate
|
|
3508
3895
|
* program. What moved is unit 15's actual `WebGLTexture` binding, which is GL state, not
|
|
3509
3896
|
* program state, and this puts it back to what `currentSurfaceTexture` says it should be.
|
|
@@ -3793,6 +4180,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3793
4180
|
if (camera !== null && env !== null) {
|
|
3794
4181
|
gl.useProgram(program);
|
|
3795
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);
|
|
3796
4185
|
this.useFlatProgram();
|
|
3797
4186
|
}
|
|
3798
4187
|
}
|
|
@@ -3834,6 +4223,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3834
4223
|
opacity: number,
|
|
3835
4224
|
options: TranslucentMeshOptions = {},
|
|
3836
4225
|
): void {
|
|
4226
|
+
this.scatterSkin();
|
|
3837
4227
|
this.submitInstanced(batch, data, opacity, true, options);
|
|
3838
4228
|
}
|
|
3839
4229
|
|
|
@@ -3876,8 +4266,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3876
4266
|
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
3877
4267
|
this.drawBudget.ask();
|
|
3878
4268
|
this.ensureInstancedProgram();
|
|
3879
|
-
|
|
3880
|
-
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;
|
|
3881
4274
|
if (program === null || u === null) return;
|
|
3882
4275
|
|
|
3883
4276
|
const { gl } = this;
|
|
@@ -3935,8 +4328,19 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3935
4328
|
/* Both sides of a double-sided material, as every mesh path does and WebGPU's pipeline does
|
|
3936
4329
|
for both: without it a batch's far faces were culled here alone. */
|
|
3937
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));
|
|
3938
4339
|
if (culling && cullsInstances(count, mesh.indexCount)) batch.drawCulled(gl, data, this.frustum);
|
|
3939
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));
|
|
3940
4344
|
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
3941
4345
|
|
|
3942
4346
|
if (layer > 0) {
|
|
@@ -4107,6 +4511,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4107
4511
|
tint: Vec3,
|
|
4108
4512
|
env: Environment,
|
|
4109
4513
|
): void {
|
|
4514
|
+
this.scatterSkin();
|
|
4110
4515
|
// The engine decides, not the caller: every pass already knows where the
|
|
4111
4516
|
// waterline is, and leaving it to each call site is how one of them ends up
|
|
4112
4517
|
// raining dust into the sea.
|
|
@@ -4151,6 +4556,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4151
4556
|
style: TextStyle,
|
|
4152
4557
|
timeSec: number,
|
|
4153
4558
|
): void {
|
|
4559
|
+
this.scatterSkin();
|
|
4154
4560
|
if (text.draw(viewportWidth, viewportHeight, originX, originY, style, timeSec)) {
|
|
4155
4561
|
this.textBudget.ask();
|
|
4156
4562
|
}
|
|
@@ -4256,6 +4662,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4256
4662
|
}
|
|
4257
4663
|
|
|
4258
4664
|
drawSdfText(handle: SdfTextRenderer, model: Float32Array, color: Vec3, opacity: number): void {
|
|
4665
|
+
this.scatterSkin();
|
|
4259
4666
|
if (this.contextLost) return;
|
|
4260
4667
|
const viewProj = this.frameViewProj;
|
|
4261
4668
|
if (viewProj === null) return;
|
|
@@ -4355,6 +4762,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4355
4762
|
env: Environment,
|
|
4356
4763
|
timeSeconds: number,
|
|
4357
4764
|
): void {
|
|
4765
|
+
this.scatterSkin();
|
|
4358
4766
|
if (data.count === 0) return;
|
|
4359
4767
|
const { gl } = this;
|
|
4360
4768
|
const u = batch.uniforms;
|
|
@@ -4416,6 +4824,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4416
4824
|
coreGain: number,
|
|
4417
4825
|
minWidthPerMetre = 0.004,
|
|
4418
4826
|
): void {
|
|
4827
|
+
this.scatterSkin();
|
|
4419
4828
|
if (data.count === 0) return;
|
|
4420
4829
|
const { gl } = this;
|
|
4421
4830
|
const u = batch.uniforms;
|
|
@@ -4490,6 +4899,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4490
4899
|
minWidthPerMetre = 0,
|
|
4491
4900
|
additive = false,
|
|
4492
4901
|
): void {
|
|
4902
|
+
this.scatterSkin();
|
|
4493
4903
|
if (data.count === 0) return;
|
|
4494
4904
|
const { gl } = this;
|
|
4495
4905
|
const u = lines.uniforms;
|
|
@@ -4735,6 +5145,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4735
5145
|
originY = 0,
|
|
4736
5146
|
originZ = 0,
|
|
4737
5147
|
): void {
|
|
5148
|
+
this.scatterSkin();
|
|
4738
5149
|
plumes.draw(
|
|
4739
5150
|
this.gl,
|
|
4740
5151
|
this.frameViewFor(camera),
|
|
@@ -4793,6 +5204,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4793
5204
|
windZ = 0,
|
|
4794
5205
|
strength = 1,
|
|
4795
5206
|
): void {
|
|
5207
|
+
this.scatterSkin();
|
|
4796
5208
|
if (caustics === null || !this.quality.water) return;
|
|
4797
5209
|
const drew = caustics.draw(
|
|
4798
5210
|
this.gl,
|
|
@@ -5122,6 +5534,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5122
5534
|
this.uploadWind(this.depthUniforms, this.frameWind);
|
|
5123
5535
|
gl.useProgram(this.depthSkinnedProgram);
|
|
5124
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
|
+
}
|
|
5125
5545
|
gl.useProgram(this.depthCutoutProgram);
|
|
5126
5546
|
this.uploadWind(this.depthCutoutUniforms, this.frameWind);
|
|
5127
5547
|
gl.useProgram(this.depthProgram);
|
|
@@ -5148,14 +5568,21 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5148
5568
|
this.bindSceneCasterMaterial(material);
|
|
5149
5569
|
this.drawMesh(mesh as Mesh, model);
|
|
5150
5570
|
},
|
|
5151
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
5571
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
5152
5572
|
/* The opaque handle narrowed to this backend's own, for the reason `casterSink`
|
|
5153
5573
|
gives about the mesh: nothing else ever handed one out. */
|
|
5154
5574
|
this.bindSceneCasterMaterial(material);
|
|
5155
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
|
+
}
|
|
5156
5582
|
this.drawMesh(mesh as Mesh, model);
|
|
5157
5583
|
/* Put back, or the next rigid draw in the replay skins by whatever this one left bound. */
|
|
5158
5584
|
this.setSkinPalette(null);
|
|
5585
|
+
this.setCloth(null);
|
|
5159
5586
|
},
|
|
5160
5587
|
instanced: (batch, data, material) => {
|
|
5161
5588
|
/* The opaque handle narrowed to this backend's own, for the reason `casterSink`
|
|
@@ -5295,7 +5722,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5295
5722
|
if (cutout !== null) {
|
|
5296
5723
|
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
5297
5724
|
gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
|
|
5298
|
-
gl.
|
|
5725
|
+
gl.uniform2f(u['uAlphaCutout'] ?? null, cutout.cutoff, cutout.dithered);
|
|
5299
5726
|
(cutout.albedo as SurfaceTexture).bind(gl, SURFACE_TEXTURE_UNIT);
|
|
5300
5727
|
gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
5301
5728
|
}
|
|
@@ -5434,11 +5861,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5434
5861
|
morphed: boolean,
|
|
5435
5862
|
instanced: boolean,
|
|
5436
5863
|
label: string,
|
|
5864
|
+
eight = false,
|
|
5437
5865
|
): { program: WebGLProgram; uniforms: Record<string, WebGLUniformLocation> } | null => {
|
|
5438
5866
|
if (old === null) return null;
|
|
5867
|
+
const vertex = flatVert({ skinned, morphed, instanced });
|
|
5439
5868
|
const program = compileProgram(
|
|
5440
5869
|
gl,
|
|
5441
|
-
|
|
5870
|
+
eight ? skinEight(vertex) : vertex,
|
|
5442
5871
|
this.flatFragSource,
|
|
5443
5872
|
label,
|
|
5444
5873
|
);
|
|
@@ -5459,14 +5888,50 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5459
5888
|
const both = recompile(this.flatBothProgram, true, true, false, 'flat.morphedskinned');
|
|
5460
5889
|
this.flatBothProgram = both?.program ?? null;
|
|
5461
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;
|
|
5462
5897
|
const instanced = recompile(this.flatInstancedProgram, false, false, true, 'flat.instanced');
|
|
5463
5898
|
this.flatInstancedProgram = instanced?.program ?? null;
|
|
5464
5899
|
this.flatInstancedUniforms = instanced?.uniforms ?? null;
|
|
5465
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
|
+
|
|
5466
5929
|
this.flatTargets.length = 0;
|
|
5467
|
-
for (const target of [plain, skinned, morphed, both, instanced]) {
|
|
5930
|
+
for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced]) {
|
|
5468
5931
|
if (target !== null) this.flatTargets.push(target);
|
|
5469
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);
|
|
5470
5935
|
const camera = this.lastPassCamera;
|
|
5471
5936
|
const env = this.lastPassEnv;
|
|
5472
5937
|
for (const target of this.flatTargets) {
|
|
@@ -6688,12 +7153,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6688
7153
|
|
|
6689
7154
|
beginFrame(clearColor: Vec3): void {
|
|
6690
7155
|
if (this.contextLost) return;
|
|
7156
|
+
this.skinScattered = false;
|
|
7157
|
+
this.skinScatter?.reset();
|
|
6691
7158
|
if (this.litAsked) {
|
|
6692
7159
|
this.litAsked = false;
|
|
6693
7160
|
this.rebuildLit();
|
|
6694
7161
|
}
|
|
6695
7162
|
this.budget.reset();
|
|
6696
7163
|
this.materials.dirty();
|
|
7164
|
+
this.cutoutFrame = (this.cutoutFrame + 1) % 64;
|
|
6697
7165
|
/*
|
|
6698
7166
|
* Said once, for the reason `refuseOitMultisampled` gives: a quality setting that is enabled
|
|
6699
7167
|
* and does nothing is the fault the capability clamp exists to prevent.
|
|
@@ -6770,6 +7238,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6770
7238
|
if (this.contextLost) return;
|
|
6771
7239
|
/* A view model left open would squeeze every pass after the world, the composite included. */
|
|
6772
7240
|
if (this.viewModelActive) this.endViewModel();
|
|
7241
|
+
/* Skin not yet spread, because nothing blended came after it, is spread now. */
|
|
7242
|
+
this.scatterSkin();
|
|
6773
7243
|
|
|
6774
7244
|
/*
|
|
6775
7245
|
* Camera motion blur, built from the camera the frame was *rendered* with.
|
|
@@ -7330,6 +7800,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7330
7800
|
* is a non-idempotent step entering this body; there is none today, and one added would show
|
|
7331
7801
|
* as a skinned game behaving differently from an unskinned one.
|
|
7332
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;
|
|
7333
7813
|
for (const target of this.flatTargets) {
|
|
7334
7814
|
this.gl.useProgram(target.program);
|
|
7335
7815
|
this.writeMeshPassState(target.uniforms, camera, env);
|
|
@@ -7663,7 +8143,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7663
8143
|
* the sort of per-draw cost this renderer avoids everywhere else.
|
|
7664
8144
|
*/
|
|
7665
8145
|
/*
|
|
7666
|
-
* 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`
|
|
7667
8147
|
* is 0 until a caller asks for one, and a world that never asks would otherwise leave
|
|
7668
8148
|
* this sampler incomplete for the whole session.
|
|
7669
8149
|
*/
|
|
@@ -7743,19 +8223,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7743
8223
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
7744
8224
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7745
8225
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
7746
|
-
|
|
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);
|
|
7747
8228
|
/* No albedo, so no effects table: the stand-in, which the shader reads as none. */
|
|
7748
8229
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
7749
8230
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
7750
8231
|
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
7751
8232
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
7752
8233
|
gl.uniform2f(u['uUvOffset'] ?? null, 0, 0);
|
|
7753
|
-
gl.
|
|
7754
|
-
gl.uniform1i(u['uDoubleSided'] ?? null, 0);
|
|
7755
|
-
this.materialDoubleSided = false;
|
|
7756
|
-
this.materialHasMaps = false;
|
|
7757
|
-
this.currentMaterial = null;
|
|
7758
|
-
this.currentSurfaceTexture = null;
|
|
8234
|
+
gl.uniform4f(u['uCutout'] ?? null, 0, 0, 0, 0);
|
|
7759
8235
|
|
|
7760
8236
|
/*
|
|
7761
8237
|
* **And the normal map, which this did not reset and had to.**
|
|
@@ -7783,16 +8259,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7783
8259
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
7784
8260
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7785
8261
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
7786
|
-
gl.uniform1i(u['uOrmEnabled'] ?? null, 0);
|
|
7787
8262
|
gl.uniform3f(u['uOrmScale'] ?? null, 1, 1, 1);
|
|
7788
8263
|
|
|
7789
8264
|
/* And the emissive map. The scale goes back to one, which is the identity it multiplies by. */
|
|
7790
8265
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
7791
8266
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7792
8267
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
7793
|
-
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, 0);
|
|
7794
8268
|
gl.uniform3f(u['uEmissiveScale'] ?? null, 1, 1, 1);
|
|
7795
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
|
+
|
|
7796
8276
|
/*
|
|
7797
8277
|
* White, because a uniform nobody has written is **zero**, and this one multiplies
|
|
7798
8278
|
* vertex colour.
|
|
@@ -7903,6 +8383,66 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7903
8383
|
if (this.quality.cullDraws && this.occluded(mesh.bounds, model)) return;
|
|
7904
8384
|
this.drawBudget.ask();
|
|
7905
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 {
|
|
7906
8446
|
const { gl } = this;
|
|
7907
8447
|
/*
|
|
7908
8448
|
* A rigged mesh takes the skinned program, an unrigged one the plain program.
|
|
@@ -7922,40 +8462,86 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7922
8462
|
* belongs to was never compiled. Deriving one from the other, as the first version did, made a
|
|
7923
8463
|
* missing skinned-only program silently switch morph off as well.
|
|
7924
8464
|
*/
|
|
7925
|
-
|
|
7926
|
-
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);
|
|
7927
8482
|
const morphed = both || (wantMorph && !wantSkin && this.flatMorphedUniforms !== null);
|
|
7928
8483
|
const variant =
|
|
7929
8484
|
skinned && morphed
|
|
7930
|
-
?
|
|
8485
|
+
? bothUniforms
|
|
7931
8486
|
: skinned
|
|
7932
|
-
?
|
|
8487
|
+
? skinnedUniforms
|
|
7933
8488
|
: morphed
|
|
7934
8489
|
? this.flatMorphedUniforms
|
|
7935
8490
|
: null;
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
?
|
|
7940
|
-
: skinned
|
|
7941
|
-
?
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
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
|
+
}
|
|
7945
8529
|
|
|
7946
8530
|
if (program !== null) {
|
|
7947
8531
|
gl.useProgram(program);
|
|
7948
|
-
if (skinned) {
|
|
8532
|
+
if (skinned || clothed !== undefined) {
|
|
7949
8533
|
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
7950
8534
|
gl.uniform1i(u['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
7951
8535
|
}
|
|
7952
|
-
if (morphed && mesh.morph !== null) {
|
|
8536
|
+
if ((morphed || clothed !== undefined) && mesh.morph !== null) {
|
|
7953
8537
|
mesh.morph.bind(gl, MORPH_DELTA_TEXTURE_UNIT);
|
|
7954
8538
|
gl.uniform1i(u['uMorphDeltas'] ?? null, MORPH_DELTA_TEXTURE_UNIT);
|
|
7955
8539
|
gl.uniform1i(u['uMorphTargetCount'] ?? null, mesh.morph.targetCount);
|
|
7956
8540
|
gl.uniform1i(u['uMorphTextureWidth'] ?? null, mesh.morph.width);
|
|
7957
|
-
gl.uniform1fv(u['uMorphWeights'] ?? null, this.morphWeights
|
|
8541
|
+
gl.uniform1fv(u['uMorphWeights'] ?? null, this.morphWeights ?? NO_MORPH_WEIGHTS);
|
|
7958
8542
|
}
|
|
8543
|
+
if (clothed !== undefined && cloth !== null) this.bindCloth(u, cloth);
|
|
8544
|
+
if (modelKind === 'eye') this.writeEyeAxis(u, model);
|
|
7959
8545
|
} else {
|
|
7960
8546
|
this.useFlatProgram();
|
|
7961
8547
|
}
|
|
@@ -7988,11 +8574,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7988
8574
|
* depth 1.0 against a buffer cleared to 1.0, and under `LESS` it would not draw at
|
|
7989
8575
|
* all. Film, caustics and text lie flush on surfaces for the same reason.
|
|
7990
8576
|
*/
|
|
7991
|
-
|
|
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);
|
|
7992
8585
|
if (layer === 0) {
|
|
7993
8586
|
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
7994
8587
|
mesh.draw(gl);
|
|
7995
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);
|
|
7996
8591
|
gl.depthFunc(this.reversedDepth ? gl.GEQUAL : gl.LEQUAL);
|
|
7997
8592
|
if (tint !== null) gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
7998
8593
|
if (program !== null) this.useFlatProgram();
|
|
@@ -8014,6 +8609,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8014
8609
|
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
8015
8610
|
mesh.draw(gl);
|
|
8016
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);
|
|
8017
8614
|
gl.polygonOffset(0, 0);
|
|
8018
8615
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
8019
8616
|
if (program !== null) this.useFlatProgram();
|
|
@@ -8066,6 +8663,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8066
8663
|
opacity: number,
|
|
8067
8664
|
options: TranslucentMeshOptions = {},
|
|
8068
8665
|
): void {
|
|
8666
|
+
this.scatterSkin();
|
|
8069
8667
|
if (this.contextLost) return;
|
|
8070
8668
|
/* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
|
|
8071
8669
|
and `Mesh.indexCount`. */
|
|
@@ -8088,10 +8686,19 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8088
8686
|
this.drawBudget.ask();
|
|
8089
8687
|
|
|
8090
8688
|
const { gl } = this;
|
|
8091
|
-
|
|
8092
|
-
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);
|
|
8093
8696
|
gl.uniformMatrix4fv(u['uModel'] ?? null, false, model);
|
|
8094
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));
|
|
8095
8702
|
/*
|
|
8096
8703
|
* Both default to what `bindMeshPass` already put there — lit and fogged — so the
|
|
8097
8704
|
* common call with no fourth argument writes the same two values it always implicitly
|
|
@@ -8169,6 +8776,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8169
8776
|
if (!depthWrite && ownsState) gl.depthMask(true);
|
|
8170
8777
|
if (ownsState) gl.disable(gl.BLEND);
|
|
8171
8778
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
8779
|
+
if (ditheredHere)
|
|
8780
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, false));
|
|
8172
8781
|
if (tint !== null) gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
8173
8782
|
if (refracting) this.unbindSeeThrough(u);
|
|
8174
8783
|
/* Put back for whatever is drawn next, exactly as `uOpacity` and `uTint` are — a pass
|
|
@@ -8640,6 +9249,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8640
9249
|
sheen: number,
|
|
8641
9250
|
options: FilmOptions = {},
|
|
8642
9251
|
): void {
|
|
9252
|
+
this.scatterSkin();
|
|
8643
9253
|
const { gl } = this;
|
|
8644
9254
|
const u = this.filmUniforms;
|
|
8645
9255
|
gl.useProgram(this.filmProgram);
|
|
@@ -8802,3 +9412,22 @@ function angularDiscExponent(angularRadius: number): number {
|
|
|
8802
9412
|
* name to prefer. It goes when something else forces a major, and not before.
|
|
8803
9413
|
*/
|
|
8804
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
|
+
}
|