@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
|
@@ -38,8 +38,19 @@ import { LightField } from '../../driftLight/lightField.js';
|
|
|
38
38
|
import { WorldLightField } from '../../driftLight/worldLightField.js';
|
|
39
39
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.js';
|
|
40
40
|
import { flatFrag, flatVert } from '../../shaders/flat/index.js';
|
|
41
|
-
import {
|
|
41
|
+
import { skinEight } from '../../shaders/skinning.js';
|
|
42
|
+
import { MODEL_MAP_TEXTURE_UNIT, MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT, } from '../../lightBudget.js';
|
|
43
|
+
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel } from '../../surfaceModel.js';
|
|
44
|
+
import { modelBound, skinHalfBound } from '../../shaders/flat/models.js';
|
|
45
|
+
import { GlSkinScatter } from './skinScatter.js';
|
|
46
|
+
import { CLOTH_BINDING_TEXTURE_UNIT, CLOTH_PARTICLES_TEXTURE_UNIT, CLOTH_REST_TEXTURE_UNIT, } from '../../lightBudget.js';
|
|
42
47
|
import { SkinPaletteTexture } from './skinPaletteTexture.js';
|
|
48
|
+
import { clothBound } from '../../shaders/clothBinding.js';
|
|
49
|
+
import { validateClothBinding } from '../../clothBindingData.js';
|
|
50
|
+
import { GlClothBinding, GlClothParticles } from './clothTextures.js';
|
|
51
|
+
import { MAX_MORPH_TARGETS } from '../../skinPalette.js';
|
|
52
|
+
/** The weights a cloth-bound draw of a morphing mesh deforms by when none were set: none. */
|
|
53
|
+
const NO_MORPH_WEIGHTS = new Float32Array(MAX_MORPH_TARGETS);
|
|
43
54
|
/** Shader-side encoding of `RenderQuality.outputTransform`. */
|
|
44
55
|
/* The codes the shader reads, stated once in `vertexDefaults.ts` for both backends. */
|
|
45
56
|
import { LIGHT_VOLUME_VERT, lightVolumeFrag } from '../../shaders/lightVolume.js';
|
|
@@ -73,6 +84,7 @@ import { resolveRenderQuality } from '../../renderQuality.js';
|
|
|
73
84
|
import { drawingBufferSize } from '../../drawingBuffer.js';
|
|
74
85
|
import { Mesh, createMeshIncremental } from '../../mesh.js';
|
|
75
86
|
import { SurfaceTexture } from '../../surfaceTexture.js';
|
|
87
|
+
import { glCompressedFormats } from '../../glCompressed.js';
|
|
76
88
|
import { PlumeRenderer } from '../../plumeRenderer.js';
|
|
77
89
|
import { createScatterDeform, resolveScatterDeform } from '../../scatterDeform.js';
|
|
78
90
|
import { DEPTH_01_TO_CLIP, createResolvedLightVolume, resolveLightVolume, volumeMediumGain, } from '../../lightVolumeDraw.js';
|
|
@@ -94,6 +106,7 @@ import { deviceSnappedCellSize } from '../../textLayout.js';
|
|
|
94
106
|
import { WindStreakRenderer } from '../../windStreakRenderer.js';
|
|
95
107
|
import { InstancedBatch } from './instanced.js';
|
|
96
108
|
import { cutoutOf } from '../../cutoutCaster.js';
|
|
109
|
+
import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.js';
|
|
97
110
|
import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
|
|
98
111
|
import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js';
|
|
99
112
|
import { noLitSwitches } from '../../shaders/flat/glassTint.js';
|
|
@@ -448,6 +461,17 @@ export class WebGL2Renderer {
|
|
|
448
461
|
flatMorphedUniforms = null;
|
|
449
462
|
flatBothProgram = null;
|
|
450
463
|
flatBothUniforms = null;
|
|
464
|
+
/**
|
|
465
|
+
* The two skinned variants built with `SKIN_EIGHT` on, for a rig whose vertices name eight
|
|
466
|
+
* joints: compiled the first time such a mesh is created, and only then. See `skinning.ts`.
|
|
467
|
+
*/
|
|
468
|
+
flatSkinned8Program = null;
|
|
469
|
+
flatSkinned8Uniforms = null;
|
|
470
|
+
flatBoth8Program = null;
|
|
471
|
+
flatBoth8Uniforms = null;
|
|
472
|
+
/** And its shadow caster, so the shadow parts from nothing the body does. */
|
|
473
|
+
depthSkinned8Program = null;
|
|
474
|
+
depthSkinned8Uniforms = null;
|
|
451
475
|
/** The morph weights the following draws use, or null for none. */
|
|
452
476
|
morphWeights = null;
|
|
453
477
|
/**
|
|
@@ -481,6 +505,14 @@ export class WebGL2Renderer {
|
|
|
481
505
|
* before this existed.
|
|
482
506
|
*/
|
|
483
507
|
sceneTarget;
|
|
508
|
+
/**
|
|
509
|
+
* Skin's screen-space scattering, made where the profile asks for it and has a composite to add
|
|
510
|
+
* into, and null otherwise — which is every profile by default. See `GlSkinScatter`.
|
|
511
|
+
*/
|
|
512
|
+
skinScatter;
|
|
513
|
+
/** Whether this frame has spread its skin: once, at its first blended draw or at its end. */
|
|
514
|
+
skinScattered = false;
|
|
515
|
+
skinInverseProjection = mat4.create();
|
|
484
516
|
/** So the refusal below is once per renderer rather than once per frame. See `setColourGrade`. */
|
|
485
517
|
warnedGradeWithoutComposite = false;
|
|
486
518
|
/** Scratch for a volume's `uDepthToLocal`. Built per draw and never allocated in the loop. */
|
|
@@ -503,6 +535,21 @@ export class WebGL2Renderer {
|
|
|
503
535
|
skinPalette = new SkinPaletteTexture();
|
|
504
536
|
/** Whether a palette is currently chosen. Distinct from whether one was ever uploaded. */
|
|
505
537
|
skinPaletteSet = false;
|
|
538
|
+
/**
|
|
539
|
+
* Cloth-bound skinned lit programs, keyed `eight|morphed`, compiled when a binding is made for a
|
|
540
|
+
* mesh of that kind; and their casters, keyed by `eight`. See `ensureClothPrograms`.
|
|
541
|
+
*/
|
|
542
|
+
clothFlat = new Map();
|
|
543
|
+
clothDepth = new Map();
|
|
544
|
+
/** The cloth the next skinned draws are placed by, or null. See `setCloth`. */
|
|
545
|
+
cloth = null;
|
|
546
|
+
/**
|
|
547
|
+
* Each shading model's lit programs, keyed `variant|model` by the vertex variant they pair with,
|
|
548
|
+
* compiled the first time a draw asks for one. See `modelProgram`.
|
|
549
|
+
*/
|
|
550
|
+
modelFlat = new Map();
|
|
551
|
+
/** The last palette chosen, which an eye's axis is turned by. See `setSkinPalette`. */
|
|
552
|
+
paletteData = null;
|
|
506
553
|
emptyTexture2D;
|
|
507
554
|
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
508
555
|
glassScratch = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
@@ -974,6 +1021,22 @@ export class WebGL2Renderer {
|
|
|
974
1021
|
materialHasMaps = false;
|
|
975
1022
|
/** The material last set, so a lit program rebuilt for glass can be given it. */
|
|
976
1023
|
currentMaterial = null;
|
|
1024
|
+
/**
|
|
1025
|
+
* The frame a dithered cutout's pattern is offset by, advanced at `beginFrame` and wrapped at 64
|
|
1026
|
+
* so the noise's argument stays small. See `cutoutDither.ts`.
|
|
1027
|
+
*/
|
|
1028
|
+
cutoutFrame = 0;
|
|
1029
|
+
/** Scratch for `uCutout`, so a material write allocates nothing. */
|
|
1030
|
+
cutoutVector = new Float32Array(4);
|
|
1031
|
+
/** The material's model numbers, `uModelParams`, packed by `packModel`. */
|
|
1032
|
+
modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
1033
|
+
/** The material's four switches, `uMaterialFlags`: albedo, ORM map, emissive map, two-sided. */
|
|
1034
|
+
materialFlags = new Int32Array(4);
|
|
1035
|
+
/**
|
|
1036
|
+
* The default framebuffer's sample count, read once, for a frame drawn with no scene target: a
|
|
1037
|
+
* dithered cutout there takes alpha-to-coverage when the canvas itself is multisampled.
|
|
1038
|
+
*/
|
|
1039
|
+
canvasSamples;
|
|
977
1040
|
/**
|
|
978
1041
|
* The lit stage's switches: what the lit programs were compiled with (`litOn`), and what the
|
|
979
1042
|
* profile and the content have asked for since (`litWanted`).
|
|
@@ -1110,7 +1173,7 @@ export class WebGL2Renderer {
|
|
|
1110
1173
|
gl.enable(gl.CULL_FACE);
|
|
1111
1174
|
gl.useProgram(this.depthProgram);
|
|
1112
1175
|
},
|
|
1113
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
1176
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
1114
1177
|
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1115
1178
|
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1116
1179
|
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
@@ -1120,8 +1183,20 @@ export class WebGL2Renderer {
|
|
|
1120
1183
|
}
|
|
1121
1184
|
this.shadowDrawBudget.ask();
|
|
1122
1185
|
const { gl } = this;
|
|
1123
|
-
|
|
1124
|
-
|
|
1186
|
+
/* A rig of eight casts through the caster built with SKIN_EIGHT on, as it draws. */
|
|
1187
|
+
const eightCaster = mesh.isSkinnedEight &&
|
|
1188
|
+
this.depthSkinned8Program !== null &&
|
|
1189
|
+
this.depthSkinned8Uniforms !== null;
|
|
1190
|
+
/* A garment casts through the cloth-bound caster of its rig's kind, as it draws. */
|
|
1191
|
+
const clothCaster = cloth === undefined
|
|
1192
|
+
? undefined
|
|
1193
|
+
: this.clothDepth.get(mesh.isSkinnedEight ? '8' : '4');
|
|
1194
|
+
const u = clothCaster?.uniforms ??
|
|
1195
|
+
(eightCaster
|
|
1196
|
+
? this.depthSkinned8Uniforms
|
|
1197
|
+
: this.depthSkinnedUniforms);
|
|
1198
|
+
gl.useProgram(clothCaster?.program ??
|
|
1199
|
+
(eightCaster ? this.depthSkinned8Program : this.depthSkinnedProgram));
|
|
1125
1200
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1126
1201
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1127
1202
|
/* The peel layer reads the first pass's depths; `beginShadowPass` bound that texture to
|
|
@@ -1132,6 +1207,12 @@ export class WebGL2Renderer {
|
|
|
1132
1207
|
this.skinPalette.update(gl, palette);
|
|
1133
1208
|
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
1134
1209
|
gl.uniform1i(u['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
1210
|
+
if (clothCaster !== undefined && cloth !== undefined) {
|
|
1211
|
+
this.bindCloth(u, {
|
|
1212
|
+
binding: cloth.binding,
|
|
1213
|
+
particles: cloth.particles,
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1135
1216
|
/* Narrowing is safe for the reason `mesh` above gives: this renderer only ever handed
|
|
1136
1217
|
out its own `Mesh`. */
|
|
1137
1218
|
mesh.draw(gl);
|
|
@@ -1176,7 +1257,7 @@ export class WebGL2Renderer {
|
|
|
1176
1257
|
gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1177
1258
|
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
1178
1259
|
gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
|
|
1179
|
-
gl.
|
|
1260
|
+
gl.uniform2f(u['uAlphaCutout'] ?? null, cutout.cutoff, cutout.dithered);
|
|
1180
1261
|
/* Narrowed for the reason the sink's own `mesh` gives: this renderer only hands out its own. */
|
|
1181
1262
|
cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
1182
1263
|
}
|
|
@@ -1971,11 +2052,20 @@ export class WebGL2Renderer {
|
|
|
1971
2052
|
this.sceneTarget = this.quality.screenEffects
|
|
1972
2053
|
? new SceneTarget(gl, this.quality.sceneSamples, this.quality.hdrScene, this.quality.discardResolvedAttachments)
|
|
1973
2054
|
: null;
|
|
2055
|
+
/* Screen-space skin needs the composite to add into; without one it is pre-integrated, said once. */
|
|
2056
|
+
if (this.quality.skinScattering === 'screen-space' && this.sceneTarget === null) {
|
|
2057
|
+
console.warn("WebGL2: skinScattering 'screen-space' needs screenEffects to add into; skin is pre-integrated");
|
|
2058
|
+
}
|
|
2059
|
+
this.skinScatter =
|
|
2060
|
+
this.quality.skinScattering === 'screen-space' && this.sceneTarget !== null
|
|
2061
|
+
? new GlSkinScatter(gl)
|
|
2062
|
+
: null;
|
|
1974
2063
|
/*
|
|
1975
2064
|
* Said once at init, because the number a caller asked for and the number the part gave
|
|
1976
2065
|
* them are not always the same, and a silently clamped sample count is a quality setting
|
|
1977
2066
|
* that does not apply — the same failure mode `capabilityClamp` exists to avoid.
|
|
1978
2067
|
*/
|
|
2068
|
+
this.canvasSamples = Math.max(1, gl.getParameter(gl.SAMPLES) ?? 1);
|
|
1979
2069
|
const got = this.sceneTarget?.sampleCount ?? 1;
|
|
1980
2070
|
if (this.quality.sceneSamples > 1 && got !== this.quality.sceneSamples) {
|
|
1981
2071
|
console.warn(`Renderer: ${this.quality.sceneSamples}x scene multisampling was requested and this ` +
|
|
@@ -2136,6 +2226,8 @@ export class WebGL2Renderer {
|
|
|
2136
2226
|
return;
|
|
2137
2227
|
this.disposed = true;
|
|
2138
2228
|
const { gl } = this;
|
|
2229
|
+
if (!this.contextLost)
|
|
2230
|
+
this.skinScatter?.dispose();
|
|
2139
2231
|
this.detachContextLoss();
|
|
2140
2232
|
this.contextLostListeners.length = 0;
|
|
2141
2233
|
this.contextRestoredListeners.length = 0;
|
|
@@ -2175,11 +2267,17 @@ export class WebGL2Renderer {
|
|
|
2175
2267
|
this.flatSkinnedProgram,
|
|
2176
2268
|
this.flatMorphedProgram,
|
|
2177
2269
|
this.flatBothProgram,
|
|
2270
|
+
this.flatSkinned8Program,
|
|
2271
|
+
this.flatBoth8Program,
|
|
2272
|
+
this.depthSkinned8Program,
|
|
2178
2273
|
this.flatInstancedProgram,
|
|
2179
2274
|
]) {
|
|
2180
2275
|
if (extra !== null)
|
|
2181
2276
|
gl.deleteProgram(extra);
|
|
2182
2277
|
}
|
|
2278
|
+
for (const entry of [...this.clothFlat.values(), ...this.clothDepth.values()]) {
|
|
2279
|
+
gl.deleteProgram(entry.program);
|
|
2280
|
+
}
|
|
2183
2281
|
for (const program of [
|
|
2184
2282
|
this.flatProgram,
|
|
2185
2283
|
this.panelProgram,
|
|
@@ -2276,13 +2374,19 @@ export class WebGL2Renderer {
|
|
|
2276
2374
|
*/
|
|
2277
2375
|
warmVariants(data) {
|
|
2278
2376
|
const skinnable = data.joints !== undefined;
|
|
2377
|
+
/* A rig of eight takes the variants built with SKIN_EIGHT on, and a caster of its own. */
|
|
2378
|
+
const eight = skinnable && data.joints2 !== undefined;
|
|
2279
2379
|
const morphable = data.morphTargets !== undefined;
|
|
2280
2380
|
for (const skinned of skinnable ? [false, true] : [false]) {
|
|
2281
2381
|
for (const morphed of morphable ? [false, true] : [false]) {
|
|
2282
2382
|
if (skinned || morphed)
|
|
2283
|
-
this.ensureVariant(skinned, morphed);
|
|
2383
|
+
this.ensureVariant(skinned, morphed, skinned && eight);
|
|
2284
2384
|
}
|
|
2285
2385
|
}
|
|
2386
|
+
if (eight && this.depthSkinned8Program === null && !this.contextLost) {
|
|
2387
|
+
this.depthSkinned8Program = compileProgram(this.gl, skinEight(DEPTH_SKINNED_VERT), DEPTH_FRAG, 'depth-skinned8');
|
|
2388
|
+
this.depthSkinned8Uniforms = uniformLocations(this.gl, this.depthSkinned8Program, 'depth-skinned8');
|
|
2389
|
+
}
|
|
2286
2390
|
}
|
|
2287
2391
|
/**
|
|
2288
2392
|
* Rewrite a mesh's positions, and its normals where the caller has them.
|
|
@@ -2324,24 +2428,39 @@ export class WebGL2Renderer {
|
|
|
2324
2428
|
* `bindMeshPass` would otherwise carry every uniform at zero, and zero is a real value for most
|
|
2325
2429
|
* of them — the failure this file records for `uGrain` and `uAmbientGround`.
|
|
2326
2430
|
*/
|
|
2327
|
-
ensureVariant(skinned, morphed) {
|
|
2431
|
+
ensureVariant(skinned, morphed, eight = false) {
|
|
2328
2432
|
if (this.contextLost)
|
|
2329
2433
|
return;
|
|
2330
|
-
if (skinned &&
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2434
|
+
if (skinned && eight) {
|
|
2435
|
+
if (morphed ? this.flatBoth8Program !== null : this.flatSkinned8Program !== null)
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
else {
|
|
2439
|
+
if (skinned && morphed && this.flatBothProgram !== null)
|
|
2440
|
+
return;
|
|
2441
|
+
if (skinned && !morphed && this.flatSkinnedProgram !== null)
|
|
2442
|
+
return;
|
|
2443
|
+
if (!skinned && morphed && this.flatMorphedProgram !== null)
|
|
2444
|
+
return;
|
|
2445
|
+
if (!skinned && !morphed)
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2338
2448
|
const { gl } = this;
|
|
2339
|
-
const label = `flat.${morphed ? 'morphed' : ''}${skinned ? 'skinned' : ''}`;
|
|
2449
|
+
const label = `flat.${morphed ? 'morphed' : ''}${skinned ? (eight ? 'skinned8' : 'skinned') : ''}`;
|
|
2340
2450
|
/* `flatFragSource`, not a fresh `flatFrag`: every variant shades through one fragment stage
|
|
2341
2451
|
at one light budget, and the uploads are written for that budget. */
|
|
2342
|
-
const
|
|
2452
|
+
const vertex = flatVert({ skinned, morphed, instanced: false });
|
|
2453
|
+
const program = compileProgram(gl, skinned && eight ? skinEight(vertex) : vertex, this.flatFragSource, label);
|
|
2343
2454
|
const uniforms = uniformLocations(gl, program, label);
|
|
2344
|
-
if (skinned && morphed) {
|
|
2455
|
+
if (skinned && eight && morphed) {
|
|
2456
|
+
this.flatBoth8Program = program;
|
|
2457
|
+
this.flatBoth8Uniforms = uniforms;
|
|
2458
|
+
}
|
|
2459
|
+
else if (skinned && eight) {
|
|
2460
|
+
this.flatSkinned8Program = program;
|
|
2461
|
+
this.flatSkinned8Uniforms = uniforms;
|
|
2462
|
+
}
|
|
2463
|
+
else if (skinned && morphed) {
|
|
2345
2464
|
this.flatBothProgram = program;
|
|
2346
2465
|
this.flatBothUniforms = uniforms;
|
|
2347
2466
|
}
|
|
@@ -2359,6 +2478,8 @@ export class WebGL2Renderer {
|
|
|
2359
2478
|
if (camera !== null && env !== null) {
|
|
2360
2479
|
gl.useProgram(program);
|
|
2361
2480
|
this.writeMeshPassState(uniforms, camera, env);
|
|
2481
|
+
/* And the material the caller has set, which a program born mid-pass must carry too. */
|
|
2482
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
2362
2483
|
this.useFlatProgram();
|
|
2363
2484
|
}
|
|
2364
2485
|
}
|
|
@@ -2371,7 +2492,21 @@ export class WebGL2Renderer {
|
|
|
2371
2492
|
* the consumer's decision. What it gains is a GPU object with sane sampler state.
|
|
2372
2493
|
*/
|
|
2373
2494
|
createSurfaceTexture(source, options = {}) {
|
|
2374
|
-
return new SurfaceTexture(this.gl, source, options);
|
|
2495
|
+
return new SurfaceTexture(this.gl, source, options, this.compressedFormats);
|
|
2496
|
+
}
|
|
2497
|
+
compressedCache = null;
|
|
2498
|
+
/**
|
|
2499
|
+
* The BC formats this device takes as blocks, each named with its colour space (`'bc7-srgb'`).
|
|
2500
|
+
*
|
|
2501
|
+
* **Ask before choosing**, because a phone usually answers none: a BC texture handed to
|
|
2502
|
+
* `createSurfaceTexture` where its format is not here is refused by name rather than decoded,
|
|
2503
|
+
* since core ships no decoder. `@driftengine/assets`' loader asks this for every BC texture in a
|
|
2504
|
+
* container and decodes at load where the answer is no. Read from the four extensions WebGL2
|
|
2505
|
+
* spreads BC across; see `glCompressed.ts`.
|
|
2506
|
+
*/
|
|
2507
|
+
get compressedFormats() {
|
|
2508
|
+
this.compressedCache ??= glCompressedFormats(this.gl);
|
|
2509
|
+
return this.compressedCache;
|
|
2375
2510
|
}
|
|
2376
2511
|
/**
|
|
2377
2512
|
* Upload several images of one size as the layers of one array, for a merged mesh that names a
|
|
@@ -2380,7 +2515,7 @@ export class WebGL2Renderer {
|
|
|
2380
2515
|
* refusal names the first that is not.
|
|
2381
2516
|
*/
|
|
2382
2517
|
createSurfaceTextureArray(sources, options = {}) {
|
|
2383
|
-
return new SurfaceTexture(this.gl, sources, options);
|
|
2518
|
+
return new SurfaceTexture(this.gl, sources, options, this.compressedFormats);
|
|
2384
2519
|
}
|
|
2385
2520
|
/**
|
|
2386
2521
|
* Replace a surface texture's pixels, keeping the GPU object and its sampler state.
|
|
@@ -2438,6 +2573,148 @@ export class WebGL2Renderer {
|
|
|
2438
2573
|
}
|
|
2439
2574
|
this.skinPalette.update(this.gl, palette);
|
|
2440
2575
|
this.skinPaletteSet = true;
|
|
2576
|
+
this.paletteData = palette;
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* The lit program for one vertex variant shading by a model: the standard program's vertex
|
|
2580
|
+
* stage, and the fragment stage with the model's switch on (`modelBound`). Compiled the first
|
|
2581
|
+
* time a draw asks — a scene that names no model compiles none — and fed the pass and the
|
|
2582
|
+
* material at once, as `ensureClothPrograms` feeds its own.
|
|
2583
|
+
*/
|
|
2584
|
+
modelProgram(variant, kind,
|
|
2585
|
+
/** Which half of a skin it draws under the screen-space blur. See `SkinHalf`. */
|
|
2586
|
+
half = 'whole') {
|
|
2587
|
+
const key = `${variant}|${kind}|${half}`;
|
|
2588
|
+
const held = this.modelFlat.get(key);
|
|
2589
|
+
if (held !== undefined)
|
|
2590
|
+
return held;
|
|
2591
|
+
const { gl } = this;
|
|
2592
|
+
const label = `flat.${variant}.${kind}.${half}`;
|
|
2593
|
+
const program = compileProgram(gl, litVertexOf(variant), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
|
|
2594
|
+
const made = { program, uniforms: uniformLocations(gl, program, label) };
|
|
2595
|
+
this.modelFlat.set(key, made);
|
|
2596
|
+
this.flatTargets.push(made);
|
|
2597
|
+
gl.useProgram(program);
|
|
2598
|
+
const camera = this.lastPassCamera;
|
|
2599
|
+
const env = this.lastPassEnv;
|
|
2600
|
+
if (camera !== null && env !== null)
|
|
2601
|
+
this.writeMeshPassState(made.uniforms, camera, env);
|
|
2602
|
+
this.writeMaterialState(made.uniforms, this.currentMaterial);
|
|
2603
|
+
return made;
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* An eye's axis in the world for this draw, into its program's `uModelParams`: the material's
|
|
2607
|
+
* own numbers with the axis turned by the draw's model matrix and the eye's joint. See
|
|
2608
|
+
* `eyeAxisInWorld`.
|
|
2609
|
+
*/
|
|
2610
|
+
writeEyeAxis(u, model) {
|
|
2611
|
+
const eye = this.currentMaterial?.model;
|
|
2612
|
+
if (eye?.kind !== 'eye')
|
|
2613
|
+
return;
|
|
2614
|
+
eyeAxisInWorld(eye, model, this.skinPaletteSet ? this.paletteData : null, this.modelParams, 4);
|
|
2615
|
+
this.gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
|
|
2616
|
+
}
|
|
2617
|
+
/**
|
|
2618
|
+
* A mesh's cloth binding: which simulation triangle each of its vertices follows, where on it,
|
|
2619
|
+
* and by how much. See `ClothBindingData`.
|
|
2620
|
+
*
|
|
2621
|
+
* Refused by name for a mesh with no rig — a binding blends a vertex between its skinning and the
|
|
2622
|
+
* cloth — and for one that disagrees with the mesh. Compiles the programs that draw and cast it
|
|
2623
|
+
* here, at creation, so no frame ever waits on a compile.
|
|
2624
|
+
*/
|
|
2625
|
+
createClothBinding(mesh, data) {
|
|
2626
|
+
const target = mesh;
|
|
2627
|
+
if (!target.isSkinned) {
|
|
2628
|
+
throw new Error('createClothBinding: this mesh has no rig, and a binding blends each vertex between its ' +
|
|
2629
|
+
'skinning and the cloth');
|
|
2630
|
+
}
|
|
2631
|
+
validateClothBinding(data, target.vertexCount);
|
|
2632
|
+
this.ensureClothPrograms(target.isSkinnedEight, target.morph !== null);
|
|
2633
|
+
return new GlClothBinding(this.gl, data);
|
|
2634
|
+
}
|
|
2635
|
+
/** One character's particles, `count` of them, to be updated every frame. */
|
|
2636
|
+
createClothParticles(count) {
|
|
2637
|
+
return new GlClothParticles(this.gl, count);
|
|
2638
|
+
}
|
|
2639
|
+
/**
|
|
2640
|
+
* This frame's particles, three floats each, world space; last frame's are kept for motion. A
|
|
2641
|
+
* simulation's output, as it is.
|
|
2642
|
+
*/
|
|
2643
|
+
updateClothParticles(particles, positions) {
|
|
2644
|
+
if (this.contextLost)
|
|
2645
|
+
return;
|
|
2646
|
+
particles.update(this.gl, positions);
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* Place the following skinned draws by a cloth: `binding` the mesh's, `particles` the
|
|
2650
|
+
* character's. Null for none, which is every draw until this is called. Both or neither, and
|
|
2651
|
+
* the particles must be as many as the binding names.
|
|
2652
|
+
*/
|
|
2653
|
+
setCloth(binding, particles = null) {
|
|
2654
|
+
if (binding === null || particles === null) {
|
|
2655
|
+
this.cloth = null;
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
if (binding.particles !== particles.count) {
|
|
2659
|
+
throw new Error(`setCloth: the binding names ${binding.particles} particles and these are ${particles.count}`);
|
|
2660
|
+
}
|
|
2661
|
+
this.cloth = { binding, particles };
|
|
2662
|
+
}
|
|
2663
|
+
disposeClothBinding(binding) {
|
|
2664
|
+
if (this.contextLost)
|
|
2665
|
+
return;
|
|
2666
|
+
binding.dispose(this.gl);
|
|
2667
|
+
}
|
|
2668
|
+
disposeClothParticles(particles) {
|
|
2669
|
+
if (this.contextLost)
|
|
2670
|
+
return;
|
|
2671
|
+
particles.dispose(this.gl);
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* The cloth-bound lit program and caster for a rig of this kind, once. Fed at once if a pass is
|
|
2675
|
+
* bound, for the reason `ensureVariant` gives: a program compiled after `bindMeshPass` would
|
|
2676
|
+
* otherwise carry every uniform at zero.
|
|
2677
|
+
*/
|
|
2678
|
+
ensureClothPrograms(eight, morphed) {
|
|
2679
|
+
if (this.contextLost)
|
|
2680
|
+
return;
|
|
2681
|
+
const { gl } = this;
|
|
2682
|
+
const key = `${eight ? 8 : 4}|${morphed ? 'm' : '-'}`;
|
|
2683
|
+
if (!this.clothFlat.has(key)) {
|
|
2684
|
+
const label = `flat.cloth${eight ? '8' : ''}${morphed ? '.morphed' : ''}`;
|
|
2685
|
+
const vertex = flatVert({ skinned: true, morphed, instanced: false });
|
|
2686
|
+
const program = compileProgram(gl, clothBound(eight ? skinEight(vertex) : vertex), this.flatFragSource, label);
|
|
2687
|
+
const uniforms = uniformLocations(gl, program, label);
|
|
2688
|
+
this.clothFlat.set(key, { program, uniforms });
|
|
2689
|
+
this.flatTargets.push({ program, uniforms });
|
|
2690
|
+
const camera = this.lastPassCamera;
|
|
2691
|
+
const env = this.lastPassEnv;
|
|
2692
|
+
if (camera !== null && env !== null) {
|
|
2693
|
+
gl.useProgram(program);
|
|
2694
|
+
this.writeMeshPassState(uniforms, camera, env);
|
|
2695
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
2696
|
+
this.useFlatProgram();
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
const depthKey = eight ? '8' : '4';
|
|
2700
|
+
if (!this.clothDepth.has(depthKey)) {
|
|
2701
|
+
const label = `depth-cloth${eight ? '8' : ''}`;
|
|
2702
|
+
const program = compileProgram(gl, clothBound(eight ? skinEight(DEPTH_SKINNED_VERT) : DEPTH_SKINNED_VERT), DEPTH_FRAG, label);
|
|
2703
|
+
this.clothDepth.set(depthKey, { program, uniforms: uniformLocations(gl, program, label) });
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
/** Bind the cloth's three textures to their units and name them to `u`. */
|
|
2707
|
+
bindCloth(u, cloth) {
|
|
2708
|
+
const { gl } = this;
|
|
2709
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_BINDING_TEXTURE_UNIT);
|
|
2710
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.binding.binding);
|
|
2711
|
+
gl.uniform1i(u['uClothBinding'] ?? null, CLOTH_BINDING_TEXTURE_UNIT);
|
|
2712
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_PARTICLES_TEXTURE_UNIT);
|
|
2713
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.particles.current);
|
|
2714
|
+
gl.uniform1i(u['uClothParticles'] ?? null, CLOTH_PARTICLES_TEXTURE_UNIT);
|
|
2715
|
+
gl.activeTexture(gl.TEXTURE0 + CLOTH_REST_TEXTURE_UNIT);
|
|
2716
|
+
gl.bindTexture(gl.TEXTURE_2D, cloth.binding.rest);
|
|
2717
|
+
gl.uniform1i(u['uClothRest'] ?? null, CLOTH_REST_TEXTURE_UNIT);
|
|
2441
2718
|
}
|
|
2442
2719
|
/**
|
|
2443
2720
|
* Choose the morph weights the following `drawMesh` calls deform by, or null for none.
|
|
@@ -2459,6 +2736,11 @@ export class WebGL2Renderer {
|
|
|
2459
2736
|
this.materialDoubleSided = material?.doubleSided === true;
|
|
2460
2737
|
this.materialHasMaps = materialHasMaps(material);
|
|
2461
2738
|
this.currentMaterial = material;
|
|
2739
|
+
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
2740
|
+
const model = material?.model;
|
|
2741
|
+
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
2742
|
+
this.skinScatter.setProfile(model.profile, model.scatterColor[0] * model.radius, model.scatterColor[1] * model.radius, model.scatterColor[2] * model.radius);
|
|
2743
|
+
}
|
|
2462
2744
|
/*
|
|
2463
2745
|
* Written to every flat program, because material state persists across draws and a skinned
|
|
2464
2746
|
* draw is entitled to the material the caller set before it. A second program holding none of
|
|
@@ -2475,6 +2757,25 @@ export class WebGL2Renderer {
|
|
|
2475
2757
|
}
|
|
2476
2758
|
this.useFlatProgram();
|
|
2477
2759
|
}
|
|
2760
|
+
/**
|
|
2761
|
+
* How a material's cutout edge is drawn this frame: the material's mode against what the frame can
|
|
2762
|
+
* resolve. A translucent draw tests hard whatever the material says, because a blended draw has
|
|
2763
|
+
* nothing to average a pattern with. See `cutoutDither.ts`.
|
|
2764
|
+
*/
|
|
2765
|
+
cutoutResolveNow(material, translucent) {
|
|
2766
|
+
if (translucent || material === null)
|
|
2767
|
+
return 'hard';
|
|
2768
|
+
return resolveCutout(material.cutoutMode ?? 'hard', this.temporalJittering, this.sceneTarget?.sampleCount ?? this.canvasSamples);
|
|
2769
|
+
}
|
|
2770
|
+
/** The lit pass's `uCutout` for a material this frame, into scratch. See `uCutout`. */
|
|
2771
|
+
cutoutUniform(material, translucent) {
|
|
2772
|
+
const v = this.cutoutVector;
|
|
2773
|
+
v[0] = material?.cutout ?? 0;
|
|
2774
|
+
v[1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveNow(material, translucent)];
|
|
2775
|
+
v[2] = this.cutoutFrame;
|
|
2776
|
+
v[3] = 0;
|
|
2777
|
+
return v;
|
|
2778
|
+
}
|
|
2478
2779
|
/**
|
|
2479
2780
|
* One flat program's view of the current material.
|
|
2480
2781
|
*
|
|
@@ -2519,7 +2820,7 @@ export class WebGL2Renderer {
|
|
|
2519
2820
|
if (orm !== null)
|
|
2520
2821
|
orm.bind(gl, ORM_TEXTURE_UNIT);
|
|
2521
2822
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
2522
|
-
|
|
2823
|
+
this.materialFlags[1] = orm === null ? 0 : 1;
|
|
2523
2824
|
gl.uniform3f(u['uOrmScale'] ?? null, material?.occlusionStrength ?? 1, material?.roughnessScale ?? 1, material?.metallicScale ?? 1);
|
|
2524
2825
|
/*
|
|
2525
2826
|
* The emissive map, on exactly the terms the two maps above it are bound: before the albedo
|
|
@@ -2527,7 +2828,7 @@ export class WebGL2Renderer {
|
|
|
2527
2828
|
*
|
|
2528
2829
|
* **The stand-in is black, so the gate is what keeps a glow on.** The shader multiplies by
|
|
2529
2830
|
* this map, and `emptyTexture2D` is opaque black rather than the multiplicative identity, so an
|
|
2530
|
-
* unbound material keeps the arithmetic it had before only because `
|
|
2831
|
+
* unbound material keeps the arithmetic it had before only because its flag in `uMaterialFlags` is 0
|
|
2531
2832
|
* and the read never happens. What would make that wrong is a read taken outside that branch:
|
|
2532
2833
|
* every glow on a material without a map would go out.
|
|
2533
2834
|
*/
|
|
@@ -2537,7 +2838,7 @@ export class WebGL2Renderer {
|
|
|
2537
2838
|
if (emissiveMap !== null)
|
|
2538
2839
|
emissiveMap.bind(gl, EMISSIVE_TEXTURE_UNIT);
|
|
2539
2840
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
2540
|
-
|
|
2841
|
+
this.materialFlags[2] = emissiveMap === null ? 0 : 1;
|
|
2541
2842
|
const emissiveScale = material?.emissiveScale ?? null;
|
|
2542
2843
|
gl.uniform3f(u['uEmissiveScale'] ?? null, emissiveScale?.[0] ?? 1, emissiveScale?.[1] ?? 1, emissiveScale?.[2] ?? 1);
|
|
2543
2844
|
/*
|
|
@@ -2549,7 +2850,20 @@ export class WebGL2Renderer {
|
|
|
2549
2850
|
*/
|
|
2550
2851
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
2551
2852
|
gl.uniform2f(u['uUvOffset'] ?? null, material?.uOffset ?? 0, material?.vOffset ?? 0);
|
|
2552
|
-
|
|
2853
|
+
this.materialFlags[3] = material?.doubleSided === true ? 1 : 0;
|
|
2854
|
+
/*
|
|
2855
|
+
* The material's shading model: its numbers, and its map or the stand-in, before the albedo
|
|
2856
|
+
* early return as every map above is. Written to the standard programs too, which declare both
|
|
2857
|
+
* and read neither — one decision for every program, as `setMaterial` says.
|
|
2858
|
+
*/
|
|
2859
|
+
const modelMap = material?.modelMap ?? null;
|
|
2860
|
+
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
2861
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2862
|
+
if (modelMap !== null)
|
|
2863
|
+
modelMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
|
|
2864
|
+
gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
|
|
2865
|
+
packModel(material?.model ?? null, modelMap !== null, this.modelParams);
|
|
2866
|
+
gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
|
|
2553
2867
|
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
2554
2868
|
return, so a material with no albedo still leaves a complete texture on the unit. */
|
|
2555
2869
|
if (albedo === null) {
|
|
@@ -2568,12 +2882,14 @@ export class WebGL2Renderer {
|
|
|
2568
2882
|
array target, and with nothing there it is incomplete. */
|
|
2569
2883
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
2570
2884
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2571
|
-
|
|
2885
|
+
this.materialFlags[0] = 0;
|
|
2886
|
+
gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
|
|
2572
2887
|
return;
|
|
2573
2888
|
}
|
|
2574
2889
|
albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
2575
|
-
gl.
|
|
2576
|
-
|
|
2890
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(material, false));
|
|
2891
|
+
this.materialFlags[0] = 1;
|
|
2892
|
+
gl.uniform4iv(u['uMaterialFlags'] ?? null, this.materialFlags);
|
|
2577
2893
|
}
|
|
2578
2894
|
/**
|
|
2579
2895
|
* @deprecated Use `setMaterial`.
|
|
@@ -2590,7 +2906,7 @@ export class WebGL2Renderer {
|
|
|
2590
2906
|
* Rebind `SURFACE_TEXTURE_UNIT` to whatever `setSurfaceTexture` last put there.
|
|
2591
2907
|
*
|
|
2592
2908
|
* Only `drawSdfText` calls this, after the atlas has finished borrowing the same unit.
|
|
2593
|
-
* No uniform is written here — the flat program's `uAlbedo
|
|
2909
|
+
* No uniform is written here — the flat program's `uAlbedo` and its flag in `uMaterialFlags` were never
|
|
2594
2910
|
* touched by the text draw and are still correct, since that draw runs its own separate
|
|
2595
2911
|
* program. What moved is unit 15's actual `WebGLTexture` binding, which is GL state, not
|
|
2596
2912
|
* program state, and this puts it back to what `currentSurfaceTexture` says it should be.
|
|
@@ -2867,6 +3183,8 @@ export class WebGL2Renderer {
|
|
|
2867
3183
|
if (camera !== null && env !== null) {
|
|
2868
3184
|
gl.useProgram(program);
|
|
2869
3185
|
this.writeMeshPassState(uniforms, camera, env);
|
|
3186
|
+
/* And the material the caller has set, which a program born mid-pass must carry too. */
|
|
3187
|
+
this.writeMaterialState(uniforms, this.currentMaterial);
|
|
2870
3188
|
this.useFlatProgram();
|
|
2871
3189
|
}
|
|
2872
3190
|
}
|
|
@@ -2898,6 +3216,7 @@ export class WebGL2Renderer {
|
|
|
2898
3216
|
}
|
|
2899
3217
|
/** Draw every live instance, blended. See `drawTranslucentMesh` for what the options mean. */
|
|
2900
3218
|
drawTranslucentInstanced(batch, data, opacity, options = {}) {
|
|
3219
|
+
this.scatterSkin();
|
|
2901
3220
|
this.submitInstanced(batch, data, opacity, true, options);
|
|
2902
3221
|
}
|
|
2903
3222
|
/** Release the placement. The mesh is the caller's and is not released. */
|
|
@@ -2937,8 +3256,11 @@ export class WebGL2Renderer {
|
|
|
2937
3256
|
return;
|
|
2938
3257
|
this.drawBudget.ask();
|
|
2939
3258
|
this.ensureInstancedProgram();
|
|
2940
|
-
|
|
2941
|
-
const
|
|
3259
|
+
/* A material with a model takes that model's instanced program; see `modelProgram`. */
|
|
3260
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
3261
|
+
const modelled = modelKind === null ? null : this.modelProgram('instanced', modelKind);
|
|
3262
|
+
const program = modelled?.program ?? this.flatInstancedProgram;
|
|
3263
|
+
const u = modelled?.uniforms ?? this.flatInstancedUniforms;
|
|
2942
3264
|
if (program === null || u === null)
|
|
2943
3265
|
return;
|
|
2944
3266
|
const { gl } = this;
|
|
@@ -3001,10 +3323,23 @@ export class WebGL2Renderer {
|
|
|
3001
3323
|
for both: without it a batch's far faces were culled here alone. */
|
|
3002
3324
|
if (this.materialDoubleSided)
|
|
3003
3325
|
gl.disable(gl.CULL_FACE);
|
|
3326
|
+
/* A dithered cutout: coverage when this batch is opaque in a multisampled frame, the hard test
|
|
3327
|
+
when it is blended. See `cutoutDither.ts`. */
|
|
3328
|
+
const cutoutResolve = this.cutoutResolveNow(this.currentMaterial, false);
|
|
3329
|
+
const coverage = !blend && cutoutResolve === 'coverage';
|
|
3330
|
+
const ditheredBlend = blend && cutoutResolve !== 'hard';
|
|
3331
|
+
if (coverage)
|
|
3332
|
+
gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
3333
|
+
if (ditheredBlend)
|
|
3334
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
|
|
3004
3335
|
if (culling && cullsInstances(count, mesh.indexCount))
|
|
3005
3336
|
batch.drawCulled(gl, data, this.frustum);
|
|
3006
3337
|
else
|
|
3007
3338
|
batch.draw(gl, count);
|
|
3339
|
+
if (coverage)
|
|
3340
|
+
gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
3341
|
+
if (ditheredBlend)
|
|
3342
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, false));
|
|
3008
3343
|
if (this.materialDoubleSided)
|
|
3009
3344
|
gl.enable(gl.CULL_FACE);
|
|
3010
3345
|
if (layer > 0) {
|
|
@@ -3164,6 +3499,7 @@ export class WebGL2Renderer {
|
|
|
3164
3499
|
}
|
|
3165
3500
|
/** Draw after the opaque scene: streaks are blended and write no depth. */
|
|
3166
3501
|
drawWindStreaks(streaks, camera, wind, timeSeconds, tint, env) {
|
|
3502
|
+
this.scatterSkin();
|
|
3167
3503
|
// The engine decides, not the caller: every pass already knows where the
|
|
3168
3504
|
// waterline is, and leaving it to each call site is how one of them ends up
|
|
3169
3505
|
// raining dust into the sea.
|
|
@@ -3196,6 +3532,7 @@ export class WebGL2Renderer {
|
|
|
3196
3532
|
* whatever box the caller lays out in, and the game's own card is not always the canvas.
|
|
3197
3533
|
*/
|
|
3198
3534
|
drawText(text, viewportWidth, viewportHeight, originX, originY, style, timeSec) {
|
|
3535
|
+
this.scatterSkin();
|
|
3199
3536
|
if (text.draw(viewportWidth, viewportHeight, originX, originY, style, timeSec)) {
|
|
3200
3537
|
this.textBudget.ask();
|
|
3201
3538
|
}
|
|
@@ -3287,6 +3624,7 @@ export class WebGL2Renderer {
|
|
|
3287
3624
|
return this.sceneTarget === null || !this.quality.hdrScene ? this.exposure : 1;
|
|
3288
3625
|
}
|
|
3289
3626
|
drawSdfText(handle, model, color, opacity) {
|
|
3627
|
+
this.scatterSkin();
|
|
3290
3628
|
if (this.contextLost)
|
|
3291
3629
|
return;
|
|
3292
3630
|
const viewProj = this.frameViewProj;
|
|
@@ -3358,6 +3696,7 @@ export class WebGL2Renderer {
|
|
|
3358
3696
|
* whole milestone from a buffer nothing had ever written.
|
|
3359
3697
|
*/
|
|
3360
3698
|
drawParticles(batch, data, camera, env, timeSeconds) {
|
|
3699
|
+
this.scatterSkin();
|
|
3361
3700
|
if (data.count === 0)
|
|
3362
3701
|
return;
|
|
3363
3702
|
const { gl } = this;
|
|
@@ -3403,6 +3742,7 @@ export class WebGL2Renderer {
|
|
|
3403
3742
|
* it strobes as the rasteriser catches it on some frames and not others.
|
|
3404
3743
|
*/
|
|
3405
3744
|
drawBolts(batch, data, camera, env, timeSeconds, core, edge, widthM, coreGain, minWidthPerMetre = 0.004) {
|
|
3745
|
+
this.scatterSkin();
|
|
3406
3746
|
if (data.count === 0)
|
|
3407
3747
|
return;
|
|
3408
3748
|
const { gl } = this;
|
|
@@ -3459,6 +3799,7 @@ export class WebGL2Renderer {
|
|
|
3459
3799
|
* edge inward, as a fraction of the half-width; zero is a clean edge.
|
|
3460
3800
|
*/
|
|
3461
3801
|
drawLines(lines, data, model, camera, env, color, widthM, opacity, softness = 0, minWidthPerMetre = 0, additive = false) {
|
|
3802
|
+
this.scatterSkin();
|
|
3462
3803
|
if (data.count === 0)
|
|
3463
3804
|
return;
|
|
3464
3805
|
const { gl } = this;
|
|
@@ -3642,6 +3983,7 @@ export class WebGL2Renderer {
|
|
|
3642
3983
|
}
|
|
3643
3984
|
// --- Draws that own their context ----------------------------------------
|
|
3644
3985
|
drawPlumes(plumes, camera, timeSeconds, env, windX = 0, windZ = 0, originX = 0, originY = 0, originZ = 0) {
|
|
3986
|
+
this.scatterSkin();
|
|
3645
3987
|
plumes.draw(this.gl, this.frameViewFor(camera), timeSeconds, env, this.quality.underwaterAtmosphere, this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0), this.reflectionPassActive ? (this.planarReflection?.clipPlane ?? null) : null, windX, windZ, originX, originY, originZ);
|
|
3646
3988
|
}
|
|
3647
3989
|
drawWater(water, camera, timeSeconds, settings, env, windX = 0, windZ = 0) {
|
|
@@ -3655,6 +3997,7 @@ export class WebGL2Renderer {
|
|
|
3655
3997
|
* from the water under them.
|
|
3656
3998
|
*/
|
|
3657
3999
|
drawCaustics(caustics, camera, timeSeconds, env, windX = 0, windZ = 0, strength = 1) {
|
|
4000
|
+
this.scatterSkin();
|
|
3658
4001
|
if (caustics === null || !this.quality.water)
|
|
3659
4002
|
return;
|
|
3660
4003
|
const drew = caustics.draw(this.gl, this.frameViewFor(camera), timeSeconds, env.directionalDir, env.directionalColor, env.ambient, env, this.quality.underwaterAtmosphere, windX, windZ, strength);
|
|
@@ -3950,6 +4293,14 @@ export class WebGL2Renderer {
|
|
|
3950
4293
|
this.uploadWind(this.depthUniforms, this.frameWind);
|
|
3951
4294
|
gl.useProgram(this.depthSkinnedProgram);
|
|
3952
4295
|
this.uploadWind(this.depthSkinnedUniforms, this.frameWind);
|
|
4296
|
+
if (this.depthSkinned8Program !== null && this.depthSkinned8Uniforms !== null) {
|
|
4297
|
+
gl.useProgram(this.depthSkinned8Program);
|
|
4298
|
+
this.uploadWind(this.depthSkinned8Uniforms, this.frameWind);
|
|
4299
|
+
}
|
|
4300
|
+
for (const caster of this.clothDepth.values()) {
|
|
4301
|
+
gl.useProgram(caster.program);
|
|
4302
|
+
this.uploadWind(caster.uniforms, this.frameWind);
|
|
4303
|
+
}
|
|
3953
4304
|
gl.useProgram(this.depthCutoutProgram);
|
|
3954
4305
|
this.uploadWind(this.depthCutoutUniforms, this.frameWind);
|
|
3955
4306
|
gl.useProgram(this.depthProgram);
|
|
@@ -3975,14 +4326,18 @@ export class WebGL2Renderer {
|
|
|
3975
4326
|
this.bindSceneCasterMaterial(material);
|
|
3976
4327
|
this.drawMesh(mesh, model);
|
|
3977
4328
|
},
|
|
3978
|
-
skinnedMesh: (mesh, model, palette, material) => {
|
|
4329
|
+
skinnedMesh: (mesh, model, palette, material, cloth) => {
|
|
3979
4330
|
/* The opaque handle narrowed to this backend's own, for the reason `casterSink`
|
|
3980
4331
|
gives about the mesh: nothing else ever handed one out. */
|
|
3981
4332
|
this.bindSceneCasterMaterial(material);
|
|
3982
4333
|
this.setSkinPalette(palette);
|
|
4334
|
+
if (cloth !== undefined) {
|
|
4335
|
+
this.setCloth(cloth.binding, cloth.particles);
|
|
4336
|
+
}
|
|
3983
4337
|
this.drawMesh(mesh, model);
|
|
3984
4338
|
/* Put back, or the next rigid draw in the replay skins by whatever this one left bound. */
|
|
3985
4339
|
this.setSkinPalette(null);
|
|
4340
|
+
this.setCloth(null);
|
|
3986
4341
|
},
|
|
3987
4342
|
instanced: (batch, data, material) => {
|
|
3988
4343
|
/* The opaque handle narrowed to this backend's own, for the reason `casterSink`
|
|
@@ -4107,7 +4462,7 @@ export class WebGL2Renderer {
|
|
|
4107
4462
|
if (cutout !== null) {
|
|
4108
4463
|
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
4109
4464
|
gl.uniform2f(u['uUvOffset'] ?? null, cutout.uOffset, cutout.vOffset);
|
|
4110
|
-
gl.
|
|
4465
|
+
gl.uniform2f(u['uAlphaCutout'] ?? null, cutout.cutoff, cutout.dithered);
|
|
4111
4466
|
cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
4112
4467
|
gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
4113
4468
|
}
|
|
@@ -4245,10 +4600,11 @@ export class WebGL2Renderer {
|
|
|
4245
4600
|
const { gl } = this;
|
|
4246
4601
|
Object.assign(this.litOn, this.litWanted);
|
|
4247
4602
|
this.flatFragSource = this.flatFragAt(this.lightBudget);
|
|
4248
|
-
const recompile = (old, skinned, morphed, instanced, label) => {
|
|
4603
|
+
const recompile = (old, skinned, morphed, instanced, label, eight = false) => {
|
|
4249
4604
|
if (old === null)
|
|
4250
4605
|
return null;
|
|
4251
|
-
const
|
|
4606
|
+
const vertex = flatVert({ skinned, morphed, instanced });
|
|
4607
|
+
const program = compileProgram(gl, eight ? skinEight(vertex) : vertex, this.flatFragSource, label);
|
|
4252
4608
|
gl.deleteProgram(old);
|
|
4253
4609
|
return { program, uniforms: uniformLocations(gl, program, label) };
|
|
4254
4610
|
};
|
|
@@ -4266,14 +4622,40 @@ export class WebGL2Renderer {
|
|
|
4266
4622
|
const both = recompile(this.flatBothProgram, true, true, false, 'flat.morphedskinned');
|
|
4267
4623
|
this.flatBothProgram = both?.program ?? null;
|
|
4268
4624
|
this.flatBothUniforms = both?.uniforms ?? null;
|
|
4625
|
+
const skinned8 = recompile(this.flatSkinned8Program, true, false, false, 'flat.skinned8', true);
|
|
4626
|
+
this.flatSkinned8Program = skinned8?.program ?? null;
|
|
4627
|
+
this.flatSkinned8Uniforms = skinned8?.uniforms ?? null;
|
|
4628
|
+
const both8 = recompile(this.flatBoth8Program, true, true, false, 'flat.morphedskinned8', true);
|
|
4629
|
+
this.flatBoth8Program = both8?.program ?? null;
|
|
4630
|
+
this.flatBoth8Uniforms = both8?.uniforms ?? null;
|
|
4269
4631
|
const instanced = recompile(this.flatInstancedProgram, false, false, true, 'flat.instanced');
|
|
4270
4632
|
this.flatInstancedProgram = instanced?.program ?? null;
|
|
4271
4633
|
this.flatInstancedUniforms = instanced?.uniforms ?? null;
|
|
4634
|
+
for (const [key, entry] of this.clothFlat) {
|
|
4635
|
+
const eight = key.startsWith('8');
|
|
4636
|
+
const withMorph = key.endsWith('m');
|
|
4637
|
+
const label = `flat.cloth${eight ? '8' : ''}${withMorph ? '.morphed' : ''}`;
|
|
4638
|
+
const vertex = flatVert({ skinned: true, morphed: withMorph, instanced: false });
|
|
4639
|
+
const program = compileProgram(gl, clothBound(eight ? skinEight(vertex) : vertex), this.flatFragSource, label);
|
|
4640
|
+
gl.deleteProgram(entry.program);
|
|
4641
|
+
this.clothFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
|
|
4642
|
+
}
|
|
4643
|
+
for (const [key, entry] of this.modelFlat) {
|
|
4644
|
+
const [variant, kind, half] = key.split('|');
|
|
4645
|
+
const label = `flat.${variant}.${kind}.${half}`;
|
|
4646
|
+
const program = compileProgram(gl, litVertexOf(variant), skinHalfBound(modelBound(this.flatFragSource, kind), half), label);
|
|
4647
|
+
gl.deleteProgram(entry.program);
|
|
4648
|
+
this.modelFlat.set(key, { program, uniforms: uniformLocations(gl, program, label) });
|
|
4649
|
+
}
|
|
4272
4650
|
this.flatTargets.length = 0;
|
|
4273
|
-
for (const target of [plain, skinned, morphed, both, instanced]) {
|
|
4651
|
+
for (const target of [plain, skinned, morphed, both, skinned8, both8, instanced]) {
|
|
4274
4652
|
if (target !== null)
|
|
4275
4653
|
this.flatTargets.push(target);
|
|
4276
4654
|
}
|
|
4655
|
+
for (const target of this.clothFlat.values())
|
|
4656
|
+
this.flatTargets.push(target);
|
|
4657
|
+
for (const target of this.modelFlat.values())
|
|
4658
|
+
this.flatTargets.push(target);
|
|
4277
4659
|
const camera = this.lastPassCamera;
|
|
4278
4660
|
const env = this.lastPassEnv;
|
|
4279
4661
|
for (const target of this.flatTargets) {
|
|
@@ -5334,12 +5716,15 @@ export class WebGL2Renderer {
|
|
|
5334
5716
|
beginFrame(clearColor) {
|
|
5335
5717
|
if (this.contextLost)
|
|
5336
5718
|
return;
|
|
5719
|
+
this.skinScattered = false;
|
|
5720
|
+
this.skinScatter?.reset();
|
|
5337
5721
|
if (this.litAsked) {
|
|
5338
5722
|
this.litAsked = false;
|
|
5339
5723
|
this.rebuildLit();
|
|
5340
5724
|
}
|
|
5341
5725
|
this.budget.reset();
|
|
5342
5726
|
this.materials.dirty();
|
|
5727
|
+
this.cutoutFrame = (this.cutoutFrame + 1) % 64;
|
|
5343
5728
|
/*
|
|
5344
5729
|
* Said once, for the reason `refuseOitMultisampled` gives: a quality setting that is enabled
|
|
5345
5730
|
* and does nothing is the fault the capability clamp exists to prevent.
|
|
@@ -5414,6 +5799,8 @@ export class WebGL2Renderer {
|
|
|
5414
5799
|
/* A view model left open would squeeze every pass after the world, the composite included. */
|
|
5415
5800
|
if (this.viewModelActive)
|
|
5416
5801
|
this.endViewModel();
|
|
5802
|
+
/* Skin not yet spread, because nothing blended came after it, is spread now. */
|
|
5803
|
+
this.scatterSkin();
|
|
5417
5804
|
/*
|
|
5418
5805
|
* Camera motion blur, built from the camera the frame was *rendered* with.
|
|
5419
5806
|
*
|
|
@@ -5900,6 +6287,16 @@ export class WebGL2Renderer {
|
|
|
5900
6287
|
* is a non-idempotent step entering this body; there is none today, and one added would show
|
|
5901
6288
|
* as a skinned game behaving differently from an unskinned one.
|
|
5902
6289
|
*/
|
|
6290
|
+
/*
|
|
6291
|
+
* **A pass starts with no material**, so it cannot inherit one from the pass before it. Here
|
|
6292
|
+
* rather than in `writeMeshPassState`, which also feeds a program compiled mid-pass — a model's,
|
|
6293
|
+
* a cloth's, the instanced one — and there forgot the material the caller had set, so the next
|
|
6294
|
+
* draws drew without it until somebody called `setMaterial` again.
|
|
6295
|
+
*/
|
|
6296
|
+
this.materialDoubleSided = false;
|
|
6297
|
+
this.materialHasMaps = false;
|
|
6298
|
+
this.currentMaterial = null;
|
|
6299
|
+
this.currentSurfaceTexture = null;
|
|
5903
6300
|
for (const target of this.flatTargets) {
|
|
5904
6301
|
this.gl.useProgram(target.program);
|
|
5905
6302
|
this.writeMeshPassState(target.uniforms, camera, env);
|
|
@@ -6190,7 +6587,7 @@ export class WebGL2Renderer {
|
|
|
6190
6587
|
* the sort of per-draw cost this renderer avoids everywhere else.
|
|
6191
6588
|
*/
|
|
6192
6589
|
/*
|
|
6193
|
-
* And the surface unit, which starts every pass with no texture on it: `
|
|
6590
|
+
* And the surface unit, which starts every pass with no texture on it: its flag in `uMaterialFlags`
|
|
6194
6591
|
* is 0 until a caller asks for one, and a world that never asks would otherwise leave
|
|
6195
6592
|
* this sampler incomplete for the whole session.
|
|
6196
6593
|
*/
|
|
@@ -6267,19 +6664,15 @@ export class WebGL2Renderer {
|
|
|
6267
6664
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
6268
6665
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
6269
6666
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
6270
|
-
|
|
6667
|
+
/* Every material switch off — the albedo, both maps, two-sided — in their one row. */
|
|
6668
|
+
gl.uniform4i(u['uMaterialFlags'] ?? null, 0, 0, 0, 0);
|
|
6271
6669
|
/* No albedo, so no effects table: the stand-in, which the shader reads as none. */
|
|
6272
6670
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
6273
6671
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
6274
6672
|
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
6275
6673
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
6276
6674
|
gl.uniform2f(u['uUvOffset'] ?? null, 0, 0);
|
|
6277
|
-
gl.
|
|
6278
|
-
gl.uniform1i(u['uDoubleSided'] ?? null, 0);
|
|
6279
|
-
this.materialDoubleSided = false;
|
|
6280
|
-
this.materialHasMaps = false;
|
|
6281
|
-
this.currentMaterial = null;
|
|
6282
|
-
this.currentSurfaceTexture = null;
|
|
6675
|
+
gl.uniform4f(u['uCutout'] ?? null, 0, 0, 0, 0);
|
|
6283
6676
|
/*
|
|
6284
6677
|
* **And the normal map, which this did not reset and had to.**
|
|
6285
6678
|
*
|
|
@@ -6305,14 +6698,17 @@ export class WebGL2Renderer {
|
|
|
6305
6698
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
6306
6699
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
6307
6700
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
6308
|
-
gl.uniform1i(u['uOrmEnabled'] ?? null, 0);
|
|
6309
6701
|
gl.uniform3f(u['uOrmScale'] ?? null, 1, 1, 1);
|
|
6310
6702
|
/* And the emissive map. The scale goes back to one, which is the identity it multiplies by. */
|
|
6311
6703
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
6312
6704
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
6313
6705
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
6314
|
-
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, 0);
|
|
6315
6706
|
gl.uniform3f(u['uEmissiveScale'] ?? null, 1, 1, 1);
|
|
6707
|
+
/* And the shading model: its map back to the stand-in and its numbers to the standard's. */
|
|
6708
|
+
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
6709
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
6710
|
+
gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
|
|
6711
|
+
gl.uniform4fv(u['uModelParams'] ?? null, NO_MODEL_PARAMS);
|
|
6316
6712
|
/*
|
|
6317
6713
|
* White, because a uniform nobody has written is **zero**, and this one multiplies
|
|
6318
6714
|
* vertex colour.
|
|
@@ -6421,6 +6817,58 @@ export class WebGL2Renderer {
|
|
|
6421
6817
|
return;
|
|
6422
6818
|
this.drawBudget.ask();
|
|
6423
6819
|
this.takeMaterial();
|
|
6820
|
+
/* Skin under the screen-space blur draws its two halves: the frame's, then at once its diffuse. */
|
|
6821
|
+
const splits = this.skinSplits();
|
|
6822
|
+
this.drawMeshHalf(mesh, model, depthLayer, tint, splits ? 'scene' : 'whole');
|
|
6823
|
+
if (splits && this.skinScatter !== null) {
|
|
6824
|
+
this.skinScatter.begin();
|
|
6825
|
+
this.drawMeshHalf(mesh, model, depthLayer, tint, 'diffuse');
|
|
6826
|
+
this.sceneTarget?.bind();
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6829
|
+
/**
|
|
6830
|
+
* Whether a skin draw now draws its two halves: the profile asked, the frame has not spread its
|
|
6831
|
+
* skin yet, this is the frame's own picture — not a mirror's or a probe's, nor an overlay after
|
|
6832
|
+
* the frame — and the blur's target can be had.
|
|
6833
|
+
*/
|
|
6834
|
+
skinSplits() {
|
|
6835
|
+
const skin = this.skinScatter;
|
|
6836
|
+
const scene = this.sceneTarget;
|
|
6837
|
+
return (skin !== null &&
|
|
6838
|
+
scene !== null &&
|
|
6839
|
+
this.currentMaterial?.model?.kind === 'skin' &&
|
|
6840
|
+
!this.skinScattered &&
|
|
6841
|
+
!this.reflectionPassActive &&
|
|
6842
|
+
!this.probePassActive &&
|
|
6843
|
+
!this.framePresented &&
|
|
6844
|
+
skin.ready(scene));
|
|
6845
|
+
}
|
|
6846
|
+
/**
|
|
6847
|
+
* Spread the frame's skin and add it back: once, at the first blended draw or at the frame's end.
|
|
6848
|
+
* Called first in every draw WebGPU records as blended — translucent meshes and batches, text,
|
|
6849
|
+
* particles, plumes, wind streaks, bolts, lines, caustics, film — so the two backends add skin at
|
|
6850
|
+
* the same point. The frame's depth is copied for the blur to read, and where it cannot be the
|
|
6851
|
+
* diffuse goes back unspread rather than not at all.
|
|
6852
|
+
*/
|
|
6853
|
+
scatterSkin() {
|
|
6854
|
+
const skin = this.skinScatter;
|
|
6855
|
+
const scene = this.sceneTarget;
|
|
6856
|
+
const projection = this.frameProjection;
|
|
6857
|
+
if (skin === null || scene === null || this.skinScattered)
|
|
6858
|
+
return;
|
|
6859
|
+
/* Latched whether or not skin was drawn: a skin drawn after this point is the whole surface. */
|
|
6860
|
+
this.skinScattered = true;
|
|
6861
|
+
if (!skin.pending)
|
|
6862
|
+
return;
|
|
6863
|
+
if (this.reflectionPassActive || this.probePassActive || projection === null)
|
|
6864
|
+
return;
|
|
6865
|
+
const depth = scene.snapshotDepth(true);
|
|
6866
|
+
mat4.invert(this.skinInverseProjection, projection);
|
|
6867
|
+
skin.spread(scene, depth, this.emptyTexture2D, projection, this.skinInverseProjection);
|
|
6868
|
+
this.useFlatProgram();
|
|
6869
|
+
}
|
|
6870
|
+
/** One draw of a mesh, as `drawMesh` describes, of the half of a skin `half` names. */
|
|
6871
|
+
drawMeshHalf(mesh, model, depthLayer, tint, half) {
|
|
6424
6872
|
const { gl } = this;
|
|
6425
6873
|
/*
|
|
6426
6874
|
* A rigged mesh takes the skinned program, an unrigged one the plain program.
|
|
@@ -6440,37 +6888,80 @@ export class WebGL2Renderer {
|
|
|
6440
6888
|
* belongs to was never compiled. Deriving one from the other, as the first version did, made a
|
|
6441
6889
|
* missing skinned-only program silently switch morph off as well.
|
|
6442
6890
|
*/
|
|
6443
|
-
|
|
6444
|
-
const
|
|
6891
|
+
/* A rig of eight takes the pair built with SKIN_EIGHT on; `warmVariants` compiled them. */
|
|
6892
|
+
const eight = wantSkin && mesh.isSkinnedEight;
|
|
6893
|
+
/*
|
|
6894
|
+
* **A cloth set and a skinned draw take the cloth-bound program of this rig's kind**, compiled
|
|
6895
|
+
* when the binding was made. Its morph half is the mesh's whether weights are set or not: the
|
|
6896
|
+
* program declares the deltas whenever the mesh has them, so they are bound, at zero weight
|
|
6897
|
+
* when none were asked for — an unbound sampler would default to a shadow map's unit.
|
|
6898
|
+
*/
|
|
6899
|
+
const cloth = wantSkin ? this.cloth : null;
|
|
6900
|
+
const clothed = cloth === null
|
|
6901
|
+
? undefined
|
|
6902
|
+
: this.clothFlat.get(`${eight ? 8 : 4}|${mesh.morph !== null ? 'm' : '-'}`);
|
|
6903
|
+
const bothUniforms = eight ? this.flatBoth8Uniforms : this.flatBothUniforms;
|
|
6904
|
+
const skinnedUniforms = eight ? this.flatSkinned8Uniforms : this.flatSkinnedUniforms;
|
|
6905
|
+
const both = wantSkin && wantMorph && bothUniforms !== null;
|
|
6906
|
+
const skinned = both || (wantSkin && skinnedUniforms !== null);
|
|
6445
6907
|
const morphed = both || (wantMorph && !wantSkin && this.flatMorphedUniforms !== null);
|
|
6446
6908
|
const variant = skinned && morphed
|
|
6447
|
-
?
|
|
6909
|
+
? bothUniforms
|
|
6448
6910
|
: skinned
|
|
6449
|
-
?
|
|
6911
|
+
? skinnedUniforms
|
|
6450
6912
|
: morphed
|
|
6451
6913
|
? this.flatMorphedUniforms
|
|
6452
6914
|
: null;
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
?
|
|
6456
|
-
: skinned
|
|
6457
|
-
?
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6915
|
+
let u = clothed?.uniforms ?? variant ?? this.flatUniforms;
|
|
6916
|
+
let program = clothed !== undefined
|
|
6917
|
+
? clothed.program
|
|
6918
|
+
: skinned && morphed
|
|
6919
|
+
? eight
|
|
6920
|
+
? this.flatBoth8Program
|
|
6921
|
+
: this.flatBothProgram
|
|
6922
|
+
: skinned
|
|
6923
|
+
? eight
|
|
6924
|
+
? this.flatSkinned8Program
|
|
6925
|
+
: this.flatSkinnedProgram
|
|
6926
|
+
: morphed
|
|
6927
|
+
? this.flatMorphedProgram
|
|
6928
|
+
: null;
|
|
6929
|
+
/* A material with a model takes that model's program for the same vertex variant. */
|
|
6930
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
6931
|
+
if (modelKind !== null) {
|
|
6932
|
+
const modelled = this.modelProgram(clothed !== undefined
|
|
6933
|
+
? `cloth${eight ? 8 : 4}${mesh.morph !== null ? 'm' : '-'}`
|
|
6934
|
+
: skinned && morphed
|
|
6935
|
+
? eight
|
|
6936
|
+
? 'both8'
|
|
6937
|
+
: 'both'
|
|
6938
|
+
: skinned
|
|
6939
|
+
? eight
|
|
6940
|
+
? 'skinned8'
|
|
6941
|
+
: 'skinned'
|
|
6942
|
+
: morphed
|
|
6943
|
+
? 'morphed'
|
|
6944
|
+
: 'plain', modelKind, half);
|
|
6945
|
+
program = modelled.program;
|
|
6946
|
+
u = modelled.uniforms;
|
|
6947
|
+
}
|
|
6461
6948
|
if (program !== null) {
|
|
6462
6949
|
gl.useProgram(program);
|
|
6463
|
-
if (skinned) {
|
|
6950
|
+
if (skinned || clothed !== undefined) {
|
|
6464
6951
|
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
6465
6952
|
gl.uniform1i(u['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
6466
6953
|
}
|
|
6467
|
-
if (morphed && mesh.morph !== null) {
|
|
6954
|
+
if ((morphed || clothed !== undefined) && mesh.morph !== null) {
|
|
6468
6955
|
mesh.morph.bind(gl, MORPH_DELTA_TEXTURE_UNIT);
|
|
6469
6956
|
gl.uniform1i(u['uMorphDeltas'] ?? null, MORPH_DELTA_TEXTURE_UNIT);
|
|
6470
6957
|
gl.uniform1i(u['uMorphTargetCount'] ?? null, mesh.morph.targetCount);
|
|
6471
6958
|
gl.uniform1i(u['uMorphTextureWidth'] ?? null, mesh.morph.width);
|
|
6472
|
-
gl.uniform1fv(u['uMorphWeights'] ?? null, this.morphWeights);
|
|
6959
|
+
gl.uniform1fv(u['uMorphWeights'] ?? null, this.morphWeights ?? NO_MORPH_WEIGHTS);
|
|
6473
6960
|
}
|
|
6961
|
+
if (clothed !== undefined && cloth !== null)
|
|
6962
|
+
this.bindCloth(u, cloth);
|
|
6963
|
+
if (modelKind === 'eye')
|
|
6964
|
+
this.writeEyeAxis(u, model);
|
|
6474
6965
|
}
|
|
6475
6966
|
else {
|
|
6476
6967
|
this.useFlatProgram();
|
|
@@ -6505,13 +6996,26 @@ export class WebGL2Renderer {
|
|
|
6505
6996
|
* depth 1.0 against a buffer cleared to 1.0, and under `LESS` it would not draw at
|
|
6506
6997
|
* all. Film, caustics and text lie flush on surfaces for the same reason.
|
|
6507
6998
|
*/
|
|
6508
|
-
|
|
6999
|
+
/* A skin's diffuse half is the same surface again: it finds the depth its frame half wrote. */
|
|
7000
|
+
const again = half === 'diffuse';
|
|
7001
|
+
gl.depthFunc(again ? gl.EQUAL : this.reversedDepth ? gl.GREATER : gl.LESS);
|
|
7002
|
+
if (again)
|
|
7003
|
+
gl.depthMask(false);
|
|
7004
|
+
/* A dithered cutout in a multisampled frame with no temporal resolve: its share becomes the
|
|
7005
|
+
pixel's samples. Scoped to this draw, as the depth compare above is. See `cutoutDither.ts`. */
|
|
7006
|
+
const coverage = !again && this.cutoutResolveNow(this.currentMaterial, false) === 'coverage';
|
|
7007
|
+
if (coverage)
|
|
7008
|
+
gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
6509
7009
|
if (layer === 0) {
|
|
6510
7010
|
if (this.materialDoubleSided)
|
|
6511
7011
|
gl.disable(gl.CULL_FACE);
|
|
6512
7012
|
mesh.draw(gl);
|
|
6513
7013
|
if (this.materialDoubleSided)
|
|
6514
7014
|
gl.enable(gl.CULL_FACE);
|
|
7015
|
+
if (coverage)
|
|
7016
|
+
gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
7017
|
+
if (again)
|
|
7018
|
+
gl.depthMask(true);
|
|
6515
7019
|
gl.depthFunc(this.reversedDepth ? gl.GEQUAL : gl.LEQUAL);
|
|
6516
7020
|
if (tint !== null)
|
|
6517
7021
|
gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
@@ -6537,6 +7041,10 @@ export class WebGL2Renderer {
|
|
|
6537
7041
|
mesh.draw(gl);
|
|
6538
7042
|
if (this.materialDoubleSided)
|
|
6539
7043
|
gl.enable(gl.CULL_FACE);
|
|
7044
|
+
if (coverage)
|
|
7045
|
+
gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE);
|
|
7046
|
+
if (again)
|
|
7047
|
+
gl.depthMask(true);
|
|
6540
7048
|
gl.polygonOffset(0, 0);
|
|
6541
7049
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
6542
7050
|
if (program !== null)
|
|
@@ -6585,6 +7093,7 @@ export class WebGL2Renderer {
|
|
|
6585
7093
|
* handed straight back.
|
|
6586
7094
|
*/
|
|
6587
7095
|
drawTranslucentMesh(mesh, model, opacity, options = {}) {
|
|
7096
|
+
this.scatterSkin();
|
|
6588
7097
|
if (this.contextLost)
|
|
6589
7098
|
return;
|
|
6590
7099
|
/* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
|
|
@@ -6607,10 +7116,22 @@ export class WebGL2Renderer {
|
|
|
6607
7116
|
order-independent transparency, and not once more when it was recorded. */
|
|
6608
7117
|
this.drawBudget.ask();
|
|
6609
7118
|
const { gl } = this;
|
|
6610
|
-
|
|
6611
|
-
this.
|
|
7119
|
+
/* A material with a model takes that model's plain program; see `modelProgram`. */
|
|
7120
|
+
const modelKind = this.currentMaterial?.model?.kind ?? null;
|
|
7121
|
+
const modelled = modelKind === null ? null : this.modelProgram('plain', modelKind);
|
|
7122
|
+
const u = modelled?.uniforms ?? this.flatUniforms;
|
|
7123
|
+
if (modelled === null)
|
|
7124
|
+
this.useFlatProgram();
|
|
7125
|
+
else
|
|
7126
|
+
gl.useProgram(modelled.program);
|
|
7127
|
+
if (modelKind === 'eye')
|
|
7128
|
+
this.writeEyeAxis(u, model);
|
|
6612
7129
|
gl.uniformMatrix4fv(u['uModel'] ?? null, false, model);
|
|
6613
7130
|
gl.uniform1f(u['uOpacity'] ?? null, Math.min(opacity, 1));
|
|
7131
|
+
/* A dithered cutout tests hard on a blended draw, and is put back below with the rest. */
|
|
7132
|
+
const ditheredHere = this.cutoutResolveNow(this.currentMaterial, false) !== 'hard';
|
|
7133
|
+
if (ditheredHere)
|
|
7134
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, true));
|
|
6614
7135
|
/*
|
|
6615
7136
|
* Both default to what `bindMeshPass` already put there — lit and fogged — so the
|
|
6616
7137
|
* common call with no fourth argument writes the same two values it always implicitly
|
|
@@ -6700,6 +7221,8 @@ export class WebGL2Renderer {
|
|
|
6700
7221
|
if (ownsState)
|
|
6701
7222
|
gl.disable(gl.BLEND);
|
|
6702
7223
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
7224
|
+
if (ditheredHere)
|
|
7225
|
+
gl.uniform4fv(u['uCutout'] ?? null, this.cutoutUniform(this.currentMaterial, false));
|
|
6703
7226
|
if (tint !== null)
|
|
6704
7227
|
gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
6705
7228
|
if (refracting)
|
|
@@ -7107,6 +7630,7 @@ export class WebGL2Renderer {
|
|
|
7107
7630
|
* `beginPlanarReflection` for when that target exists.
|
|
7108
7631
|
*/
|
|
7109
7632
|
drawFilm(mesh, camera, timeSeconds, env, sheen, options = {}) {
|
|
7633
|
+
this.scatterSkin();
|
|
7110
7634
|
const { gl } = this;
|
|
7111
7635
|
const u = this.filmUniforms;
|
|
7112
7636
|
gl.useProgram(this.filmProgram);
|
|
@@ -7228,3 +7752,20 @@ function angularDiscExponent(angularRadius) {
|
|
|
7228
7752
|
* name to prefer. It goes when something else forces a major, and not before.
|
|
7229
7753
|
*/
|
|
7230
7754
|
export { WebGL2Renderer as Renderer };
|
|
7755
|
+
/** The standard model's numbers: no model, no map. */
|
|
7756
|
+
const NO_MODEL_PARAMS = new Float32Array(MODEL_PARAM_FLOATS);
|
|
7757
|
+
/**
|
|
7758
|
+
* The lit vertex stage a program of this variant compiles: the standard programs' and the cloth's,
|
|
7759
|
+
* which `modelProgram` pairs a model's fragment stage with.
|
|
7760
|
+
*/
|
|
7761
|
+
function litVertexOf(variant) {
|
|
7762
|
+
if (variant.startsWith('cloth')) {
|
|
7763
|
+
const eight = variant[5] === '8';
|
|
7764
|
+
const vertex = flatVert({ skinned: true, morphed: variant.endsWith('m'), instanced: false });
|
|
7765
|
+
return clothBound(eight ? skinEight(vertex) : vertex);
|
|
7766
|
+
}
|
|
7767
|
+
const skinned = variant.startsWith('skinned') || variant.startsWith('both');
|
|
7768
|
+
const morphed = variant === 'morphed' || variant.startsWith('both');
|
|
7769
|
+
const vertex = flatVert({ skinned, morphed, instanced: variant === 'instanced' });
|
|
7770
|
+
return variant.endsWith('8') ? skinEight(vertex) : vertex;
|
|
7771
|
+
}
|