@driftengine/core 4.5.0 → 4.6.1
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 +62 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.js +13 -1
- package/dist/render/backend/api.d.ts +17 -3
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +122 -8
- package/dist/render/backend/webgl2/renderer.js +249 -128
- package/dist/render/backend/webgpu/buffers.d.ts +17 -0
- package/dist/render/backend/webgpu/buffers.js +34 -3
- package/dist/render/backend/webgpu/depthPass.js +5 -1
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +4 -1
- package/dist/render/backend/webgpu/flatPass.js +38 -9
- package/dist/render/backend/webgpu/glassTintPass.js +2 -1
- package/dist/render/backend/webgpu/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +11 -1
- package/dist/render/backend/webgpu/instanced.js +21 -3
- package/dist/render/backend/webgpu/latePass.d.ts +6 -2
- package/dist/render/backend/webgpu/latePass.js +11 -4
- package/dist/render/backend/webgpu/motionPass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -10
- package/dist/render/backend/webgpu/renderer.js +478 -214
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +17 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +77 -38
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- package/dist/render/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +22 -33
- package/dist/render/lightBudget.js +24 -35
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +23 -24
- package/dist/render/mesh.js +56 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- package/dist/render/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -0
- package/dist/render/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +2 -0
- package/dist/render/shaders/depth.js +12 -9
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- package/dist/render/shaders/flat/index.js +5 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +75 -23
- package/dist/render/shaders/flat/preamble.js +75 -20
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +8 -8
- package/dist/render/shaders/generated/depth.wgsl.js +8 -8
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1320 -1320
- package/dist/render/shaders/generated/flat.wgsl.js +1359 -1345
- package/dist/render/shaders/generated/lightVolume.wgsl.js +18 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- package/dist/render/shaders/recon/lateDepth.wgsl.js +38 -13
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- package/dist/render/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +51 -1
- package/src/render/backend/api.ts +21 -2
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +311 -150
- package/src/render/backend/webgpu/buffers.ts +49 -3
- package/src/render/backend/webgpu/depthPass.ts +5 -1
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +41 -11
- package/src/render/backend/webgpu/glassTintPass.ts +2 -1
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +21 -3
- package/src/render/backend/webgpu/latePass.ts +12 -3
- package/src/render/backend/webgpu/motionPass.ts +2 -1
- package/src/render/backend/webgpu/renderer.ts +556 -239
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +90 -50
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- package/src/render/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +25 -36
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +64 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +4 -0
- package/src/render/shaders/depth.ts +12 -9
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/index.ts +5 -3
- package/src/render/shaders/flat/main.ts +75 -23
- package/src/render/shaders/flat/preamble.ts +75 -20
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +8 -8
- package/src/render/shaders/generated/flat.wgsl.ts +1361 -1345
- package/src/render/shaders/generated/lightVolume.wgsl.ts +20 -4
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- package/src/render/shaders/recon/lateDepth.wgsl.ts +38 -13
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FrameBudget } from '../budget.ts';
|
|
2
2
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.ts';
|
|
3
|
+
import { FOG_RECEDE, fogModeOf } from '../drawFog.ts';
|
|
3
4
|
import { mat4 } from 'gl-matrix';
|
|
4
5
|
import {
|
|
5
6
|
GL_DEPTH_REMAP,
|
|
@@ -49,6 +50,7 @@ import { TranslucentQueue } from '../../translucentQueue.ts';
|
|
|
49
50
|
import { createFrustum, frustumFromViewProjection } from '../../../math/frustum.ts';
|
|
50
51
|
import type { Frustum } from '../../../math/frustum.ts';
|
|
51
52
|
import type { Bounds } from '../../../math/bounds.ts';
|
|
53
|
+
import { batchBoxVisible, cullsInstances } from '../../instanceCull.ts';
|
|
52
54
|
import { OcclusionBuffer } from '../../occlusion.ts';
|
|
53
55
|
import { boundsVisible } from '../../visibility.ts';
|
|
54
56
|
import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.ts';
|
|
@@ -93,6 +95,10 @@ import {
|
|
|
93
95
|
} from '../../emptyTexture.ts';
|
|
94
96
|
import { LightField } from '../../driftLight/lightField.ts';
|
|
95
97
|
import type { LightFieldOptions, LightFieldSource } from '../../driftLight/lightField.ts';
|
|
98
|
+
import type { DriftLightVolumes } from '../../driftLight/presence.ts';
|
|
99
|
+
import type { DenseLightVolume } from '../../driftLight/denseField.ts';
|
|
100
|
+
import { WorldLightField } from '../../driftLight/worldLightField.ts';
|
|
101
|
+
import type { WorldLightFieldOptions } from '../../driftLight/worldLightField.ts';
|
|
96
102
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.ts';
|
|
97
103
|
|
|
98
104
|
/**
|
|
@@ -129,6 +135,8 @@ import { ProbeSweeps } from '../../probeSweeps.ts';
|
|
|
129
135
|
import { octahedralEdgeFor } from '../../prefilterEnvMap.ts';
|
|
130
136
|
import { EnvironmentPrefilterPass } from '../../prefilterPass.ts';
|
|
131
137
|
import { packIesAtlas } from '../../iesProfile.ts';
|
|
138
|
+
import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.ts';
|
|
139
|
+
import { resolveSurfaceScene } from '../../surfaceScene.ts';
|
|
132
140
|
import type { AreaLightBuffer, AreaLightSource } from '../../areaLights.ts';
|
|
133
141
|
import { MAX_AREA_LIGHTS } from '../../areaLights.ts';
|
|
134
142
|
import {
|
|
@@ -182,12 +190,12 @@ import {
|
|
|
182
190
|
ENVIRONMENT_TEXTURE_UNIT,
|
|
183
191
|
SURFACE_TEXTURE_UNIT,
|
|
184
192
|
bindPointLights,
|
|
185
|
-
COOKIE_ATLAS_TEXTURE_UNIT,
|
|
186
193
|
REFRACT_SCENE_TEXTURE_UNIT,
|
|
187
194
|
DRIFT_LIGHT_ATLAS_TEXTURE_UNIT,
|
|
188
195
|
DRIFT_LIGHT_INDEX_TEXTURE_UNIT,
|
|
189
196
|
COOKIE_TILE,
|
|
190
|
-
|
|
197
|
+
FIXTURE_ATLAS_TEXTURE_UNIT,
|
|
198
|
+
SURFACE_EFFECTS_TEXTURE_UNIT,
|
|
191
199
|
bindAreaLights,
|
|
192
200
|
bindAreaShadows,
|
|
193
201
|
openCones,
|
|
@@ -244,7 +252,7 @@ import type { WindStreakOptions } from '../../windStreakRenderer.ts';
|
|
|
244
252
|
import type { WindField } from '../../windField.ts';
|
|
245
253
|
import type { FlockParams } from '../../flockRenderer.ts';
|
|
246
254
|
import { InstancedBatch } from './instanced.ts';
|
|
247
|
-
import type { MeshInstances } from '../../instances.ts';
|
|
255
|
+
import type { InstancedOptions, MeshInstances } from '../../instances.ts';
|
|
248
256
|
import { cutoutOf } from '../../cutoutCaster.ts';
|
|
249
257
|
import type { CutoutCaster } from '../../cutoutCaster.ts';
|
|
250
258
|
import type { SurfaceTextureHandle } from '../api.ts';
|
|
@@ -296,6 +304,29 @@ export interface Environment extends Atmosphere {
|
|
|
296
304
|
* how strongly.
|
|
297
305
|
*/
|
|
298
306
|
nightFactor: number;
|
|
307
|
+
/**
|
|
308
|
+
* Seconds on the caller's clock, for the surfaces that animate — a sign's pulse, a ticker's
|
|
309
|
+
* scroll, a flipbook. The caller's rather than a clock read here, so a held clock holds every
|
|
310
|
+
* surface still and a replay replays them. 0 unless stated.
|
|
311
|
+
*/
|
|
312
|
+
surfaceTime?: number;
|
|
313
|
+
/**
|
|
314
|
+
* How wet the world is, 0 to 1: upward surfaces darken and polish, except materials that say
|
|
315
|
+
* they stay dry. The scene-wide counterpart of `drawFilm`, which stays for a puddle. 0 unless
|
|
316
|
+
* stated.
|
|
317
|
+
*/
|
|
318
|
+
wetness?: number;
|
|
319
|
+
/**
|
|
320
|
+
* The share of facade windows lit, 0 to 1, which a scene sets from its time of day. Windows switch
|
|
321
|
+
* on in steps by a per-window hash, so a rising share lights more windows rather than brightening
|
|
322
|
+
* the ones already lit. 0 unless stated.
|
|
323
|
+
*/
|
|
324
|
+
litWindows?: number;
|
|
325
|
+
/**
|
|
326
|
+
* The share of windows that stay lit however late it gets, 0 to 1, so the small hours are not a
|
|
327
|
+
* dark city. 0 unless stated.
|
|
328
|
+
*/
|
|
329
|
+
lateWindows?: number;
|
|
299
330
|
/**
|
|
300
331
|
* Emission that only shows where the dominant light does not reach, 0 and off by default.
|
|
301
332
|
*
|
|
@@ -556,6 +587,8 @@ export { MAX_DEPTH_LAYER } from '../../depthConvention.ts';
|
|
|
556
587
|
|
|
557
588
|
/** The identity tint, handed back after any draw that asked for one. See `drawMesh`. */
|
|
558
589
|
const WHITE_TINT = new Float32Array([1, 1, 1]);
|
|
590
|
+
/** The lit stage's clip plane when there is no mirror: zero, which clips nothing. */
|
|
591
|
+
const NO_CLIP_PLANE = new Float32Array(4);
|
|
559
592
|
|
|
560
593
|
/**
|
|
561
594
|
* What a film should do beyond shimmering, which until now was nothing.
|
|
@@ -707,6 +740,31 @@ export interface LightVolumeDrawOptions {
|
|
|
707
740
|
* boolean is refused everywhere else in this file.
|
|
708
741
|
*/
|
|
709
742
|
export interface TranslucentMeshOptions {
|
|
743
|
+
/**
|
|
744
|
+
* Add this surface's light to what is behind it instead of covering it: a glow, a beam, a pool
|
|
745
|
+
* of light under a lamp. Its colour times its opacity is added and nothing is darkened, so black
|
|
746
|
+
* is invisible and overlapping glows brighten — which is why such surfaces are authored nearly
|
|
747
|
+
* black where they fade. **Order does not matter** to a sum, so an additive draw is never held
|
|
748
|
+
* for the order-independent pass; it is drawn where it is asked for. Write depth off with it
|
|
749
|
+
* (`depthWrite: false`) or a glow hides the glows behind it.
|
|
750
|
+
*/
|
|
751
|
+
/**
|
|
752
|
+
* Drawn into the picture a reconstruction resolves, rather than after it. For a surface that
|
|
753
|
+
* moves with what it lies on — a glow on a wall, a pane in a window — whose one motion is that
|
|
754
|
+
* surface's, so the resolve can carry it with the surface.
|
|
755
|
+
*
|
|
756
|
+
* **Late is the default, and it is right for anything else.** A translucent surface writes no
|
|
757
|
+
* depth the motion pass could test and may move apart from what shows through it — a caption
|
|
758
|
+
* over a moving car — so a history can only smear it; after the upscale it is sharp and has none.
|
|
759
|
+
* **What late gives up** is the edge: drawn unjittered against a depth the jittered render
|
|
760
|
+
* covered, it meets a silhouette the render drew differently every frame, and a glow along an
|
|
761
|
+
* edge is let through on one frame and not the next. Reconstructed, it is resolved with the edge.
|
|
762
|
+
*
|
|
763
|
+
* Only a reconstructing frame draws anything late, so this changes nothing on WebGL2 or with
|
|
764
|
+
* `reconstruction` off. An order-independent blended set replays where it replays.
|
|
765
|
+
*/
|
|
766
|
+
readonly reconstructed?: boolean;
|
|
767
|
+
readonly additive?: boolean;
|
|
710
768
|
/**
|
|
711
769
|
* `false` draws the mesh exactly its own vertex colour times its own texture, with no
|
|
712
770
|
* ambient, sun, point lights, specular, reflectivity, grain or emissive touching it —
|
|
@@ -1136,7 +1194,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1136
1194
|
private readonly emptyDriftIndex: WebGLTexture;
|
|
1137
1195
|
private readonly emptyDriftAtlas: WebGLTexture;
|
|
1138
1196
|
/** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
|
|
1139
|
-
private lightField:
|
|
1197
|
+
private lightField: DriftLightVolumes | null = null;
|
|
1140
1198
|
private driftIndex: WebGLTexture | null = null;
|
|
1141
1199
|
private driftAtlas: WebGLTexture | null = null;
|
|
1142
1200
|
private readonly driftUniforms = createDriftLightUniforms();
|
|
@@ -1183,6 +1241,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1183
1241
|
private oitActive = false;
|
|
1184
1242
|
/** True while the queue is being replayed, so a replayed draw does not record itself again. */
|
|
1185
1243
|
private oitReplaying = false;
|
|
1244
|
+
/** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
|
|
1245
|
+
private readonly surfaceScene = new Float32Array(4);
|
|
1246
|
+
/** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
|
|
1247
|
+
private oitWeight = 0;
|
|
1248
|
+
private ditherFade = 0;
|
|
1186
1249
|
/** Said once, not per frame, where the context cannot give the float target the sum needs. */
|
|
1187
1250
|
private oitRefused = false;
|
|
1188
1251
|
/** Said once for the whole renderer. See `refuseOitMultisampled`. */
|
|
@@ -1288,6 +1351,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1288
1351
|
private readonly taaDepthToViewZ = Float32Array.of(0, 0, 0, 1);
|
|
1289
1352
|
private readonly aoInvProjection = mat4.create();
|
|
1290
1353
|
private readonly aoProjScale = new Float32Array(2);
|
|
1354
|
+
/** Where this frame's jitter put the occlusion, in the composite's uv. See `uAoOffset`. */
|
|
1355
|
+
private readonly aoOffset = new Float32Array(2);
|
|
1291
1356
|
/**
|
|
1292
1357
|
* Whether this frame has already been resolved to the canvas.
|
|
1293
1358
|
*
|
|
@@ -1379,12 +1444,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1379
1444
|
* world. See `prepareStaticPointShadows`.
|
|
1380
1445
|
*/
|
|
1381
1446
|
private pointShadowArray: PointShadowArray | null = null;
|
|
1382
|
-
/** The
|
|
1383
|
-
private
|
|
1384
|
-
/**
|
|
1447
|
+
/** The fixture atlas — cookies over profiles — allocated on first use. See `fixtureAtlas.ts`. */
|
|
1448
|
+
private fixtureTexture: WebGLTexture | null = null;
|
|
1449
|
+
/** The profiles as packed, kept so a new set of cookies can rebuild the atlas around them. */
|
|
1450
|
+
private iesAtlas = packIesAtlas([]);
|
|
1451
|
+
/** The cookie images, kept so a new set of profiles can rebuild the atlas around them. */
|
|
1452
|
+
private cookieImages: readonly TexImageSource[] = [];
|
|
1453
|
+
/** Rows of profile in the atlas. One until a consumer loads a profile, which is the row of ones. */
|
|
1385
1454
|
private iesRows = 1;
|
|
1386
|
-
|
|
1387
|
-
/** Tiles in the cookie atlas. 0 is the white placeholder and the whole off path. */
|
|
1455
|
+
/** Cookie tiles in the atlas. 0 is none, and the whole off path. */
|
|
1388
1456
|
private cookieTiles = 0;
|
|
1389
1457
|
/** Horizontal planes each profile occupies. 1 unless something asymmetric was loaded. */
|
|
1390
1458
|
private iesPlanes = 1;
|
|
@@ -1701,7 +1769,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1701
1769
|
if (cutout !== null) {
|
|
1702
1770
|
gl.useProgram(this.depthInstancedCutoutProgram);
|
|
1703
1771
|
this.bindCutoutDepth(this.depthInstancedCutoutUniforms, cutout);
|
|
1704
|
-
gpuBatch.
|
|
1772
|
+
gpuBatch.draw(gl, count);
|
|
1705
1773
|
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1706
1774
|
gl.useProgram(this.depthProgram);
|
|
1707
1775
|
this.restoreSurfaceTextureUnit();
|
|
@@ -1712,7 +1780,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1712
1780
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1713
1781
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1714
1782
|
if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1715
|
-
gpuBatch.
|
|
1783
|
+
gpuBatch.draw(gl, count);
|
|
1716
1784
|
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1717
1785
|
gl.useProgram(this.depthProgram);
|
|
1718
1786
|
},
|
|
@@ -1991,6 +2059,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1991
2059
|
return this.occlusion?.occluded(bounds, model) ?? false;
|
|
1992
2060
|
}
|
|
1993
2061
|
|
|
2062
|
+
/**
|
|
2063
|
+
* Whether a world-space box is wholly behind the declared occluders — the tighter question for
|
|
2064
|
+
* something that is a box, such as a region. False with no occlusion buffer. See `occluded`.
|
|
2065
|
+
*/
|
|
2066
|
+
occludedBox(min: ArrayLike<number>, max: ArrayLike<number>): boolean {
|
|
2067
|
+
return this.occlusion?.occludedBox(min, max) ?? false;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1994
2070
|
/* -- Registered passes ---------------------------------------------------------------- */
|
|
1995
2071
|
|
|
1996
2072
|
private readonly passes: PassRegistry = createPassRegistry();
|
|
@@ -2181,106 +2257,67 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2181
2257
|
* `createSurfaceTexture` and the material maps take.
|
|
2182
2258
|
*/
|
|
2183
2259
|
setSpotCookies(images: readonly TexImageSource[]): void {
|
|
2184
|
-
|
|
2185
|
-
this.
|
|
2186
|
-
if (this.cookieTexture === null) this.cookieTexture = gl.createTexture();
|
|
2187
|
-
gl.activeTexture(gl.TEXTURE0 + COOKIE_ATLAS_TEXTURE_UNIT);
|
|
2188
|
-
gl.bindTexture(gl.TEXTURE_2D, this.cookieTexture);
|
|
2189
|
-
if (images.length === 0) {
|
|
2190
|
-
/* White, not black: this multiplies a light's colour. */
|
|
2191
|
-
gl.texImage2D(
|
|
2192
|
-
gl.TEXTURE_2D,
|
|
2193
|
-
0,
|
|
2194
|
-
gl.RGBA8,
|
|
2195
|
-
1,
|
|
2196
|
-
1,
|
|
2197
|
-
0,
|
|
2198
|
-
gl.RGBA,
|
|
2199
|
-
gl.UNSIGNED_BYTE,
|
|
2200
|
-
new Uint8Array([255, 255, 255, 255]),
|
|
2201
|
-
);
|
|
2202
|
-
} else {
|
|
2203
|
-
gl.texImage2D(
|
|
2204
|
-
gl.TEXTURE_2D,
|
|
2205
|
-
0,
|
|
2206
|
-
gl.RGBA8,
|
|
2207
|
-
COOKIE_TILE * images.length,
|
|
2208
|
-
COOKIE_TILE,
|
|
2209
|
-
0,
|
|
2210
|
-
gl.RGBA,
|
|
2211
|
-
gl.UNSIGNED_BYTE,
|
|
2212
|
-
null,
|
|
2213
|
-
);
|
|
2214
|
-
for (let tile = 0; tile < images.length; tile++) {
|
|
2215
|
-
const image = images[tile];
|
|
2216
|
-
if (image === undefined) continue;
|
|
2217
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, tile * COOKIE_TILE, 0, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
/* Linear and clamped: a cookie is an image, and the shader's own inset is what keeps a tile
|
|
2221
|
-
from reaching its neighbour — clamping alone cannot, the seam being interior to the row. */
|
|
2222
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2223
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2224
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2225
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2226
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2260
|
+
this.cookieImages = images.slice();
|
|
2261
|
+
this.buildFixtureAtlas();
|
|
2227
2262
|
}
|
|
2228
2263
|
|
|
2229
2264
|
setIesProfiles(profiles: readonly PhotometricProfile[]): void {
|
|
2265
|
+
this.iesAtlas = packIesAtlas(profiles);
|
|
2266
|
+
this.buildFixtureAtlas();
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
/**
|
|
2270
|
+
* The fixture atlas from the profiles and cookies in hand: the profiles' rows at the bottom, the
|
|
2271
|
+
* cookies' tiles on top, one `RGBA16F` texture. See `fixtureAtlas.ts` for the layout.
|
|
2272
|
+
*
|
|
2273
|
+
* **Half float, matching what the other backend must use**: a 32-bit float texture is not
|
|
2274
|
+
* filterable in core WebGPU, and both tables are curves and images that want linear filtering.
|
|
2275
|
+
* Linear and clamped: a profile is sampled between two angles, and a cookie's own inset is what
|
|
2276
|
+
* keeps a tile from its neighbour — clamping alone cannot, the seam being interior to the row.
|
|
2277
|
+
*/
|
|
2278
|
+
private buildFixtureAtlas(): void {
|
|
2230
2279
|
const { gl } = this;
|
|
2231
|
-
const
|
|
2232
|
-
if (this.
|
|
2233
|
-
gl.activeTexture(gl.TEXTURE0 +
|
|
2234
|
-
gl.bindTexture(gl.TEXTURE_2D, this.
|
|
2235
|
-
/*
|
|
2236
|
-
* `LINEAR` across a row and clamped, unlike the froxel table's `NEAREST`. This one *is* a
|
|
2237
|
-
* curve rather than data addressed by index: interpolating between two columns is sampling
|
|
2238
|
-
* the distribution between two angles, which is exactly what is wanted, and clamping stops a
|
|
2239
|
-
* row bleeding into its neighbour at the edges.
|
|
2240
|
-
*/
|
|
2241
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2242
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2243
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2244
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2245
|
-
/*
|
|
2246
|
-
* One channel, because a row holds one number per angle and the other three would be three
|
|
2247
|
-
* quarters of the upload spent on nothing — and **half float rather than full**, matching what
|
|
2248
|
-
* the other backend must use: a 32-bit float texture is not filterable in core WebGPU, and this
|
|
2249
|
-
* row is a curve that wants linear filtering. Keeping the two formats the same is the
|
|
2250
|
-
* 2026-08-13 rule rather than tidiness; the driver converts the float data on the way in.
|
|
2251
|
-
*/
|
|
2280
|
+
const layout = fixtureAtlasLayout(this.iesAtlas.height, this.cookieImages.length);
|
|
2281
|
+
if (this.fixtureTexture === null) this.fixtureTexture = gl.createTexture();
|
|
2282
|
+
gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
2283
|
+
gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
|
|
2252
2284
|
gl.texImage2D(
|
|
2253
2285
|
gl.TEXTURE_2D,
|
|
2254
2286
|
0,
|
|
2255
|
-
gl.
|
|
2256
|
-
|
|
2257
|
-
|
|
2287
|
+
gl.RGBA16F,
|
|
2288
|
+
layout.width,
|
|
2289
|
+
layout.height,
|
|
2258
2290
|
0,
|
|
2259
|
-
gl.
|
|
2291
|
+
gl.RGBA,
|
|
2260
2292
|
gl.FLOAT,
|
|
2261
|
-
|
|
2293
|
+
fixtureAtlasTexels(layout, this.iesAtlas),
|
|
2262
2294
|
);
|
|
2263
|
-
|
|
2264
|
-
|
|
2295
|
+
for (let tile = 0; tile < this.cookieImages.length; tile++) {
|
|
2296
|
+
const image = this.cookieImages[tile];
|
|
2297
|
+
if (image === undefined) continue;
|
|
2298
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, tile * COOKIE_TILE, 0, gl.RGBA, gl.HALF_FLOAT, image);
|
|
2299
|
+
}
|
|
2300
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2301
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2302
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2303
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2304
|
+
this.iesRows = this.iesAtlas.height;
|
|
2305
|
+
this.iesPlanes = this.iesAtlas.planes;
|
|
2306
|
+
this.cookieTiles = this.cookieImages.length;
|
|
2265
2307
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
2308
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2266
2309
|
}
|
|
2267
2310
|
|
|
2268
|
-
/** Bind the atlas and say
|
|
2311
|
+
/** Bind the fixture atlas and say what is in it. Always, for the reason `bindClusters` gives. */
|
|
2269
2312
|
private bindIesAtlas(u: Record<string, WebGLUniformLocation>): void {
|
|
2270
2313
|
const { gl } = this;
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
gl.
|
|
2274
|
-
gl.
|
|
2314
|
+
/* Built on demand rather than at construction, for the reason the grade's placeholder is. */
|
|
2315
|
+
if (this.fixtureTexture === null) this.buildFixtureAtlas();
|
|
2316
|
+
gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
2317
|
+
gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
|
|
2318
|
+
gl.uniform1i(u['uFixtureAtlas'] ?? null, FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
2275
2319
|
gl.uniform1f(u['uIesAtlasRows'] ?? null, this.iesRows);
|
|
2276
2320
|
gl.uniform1f(u['uIesPlaneCount'] ?? null, this.iesPlanes);
|
|
2277
|
-
/* Built on demand rather than at construction, for the reason the grade's placeholder is: a
|
|
2278
|
-
scene that never projects a cookie should not carry a texture, and a declared sampler still
|
|
2279
|
-
needs a complete one. */
|
|
2280
|
-
if (this.cookieTexture === null) this.setSpotCookies([]);
|
|
2281
|
-
gl.activeTexture(gl.TEXTURE0 + COOKIE_ATLAS_TEXTURE_UNIT);
|
|
2282
|
-
gl.bindTexture(gl.TEXTURE_2D, this.cookieTexture);
|
|
2283
|
-
gl.uniform1i(u['uCookieAtlas'] ?? null, COOKIE_ATLAS_TEXTURE_UNIT);
|
|
2284
2321
|
gl.uniform1f(u['uCookieTiles'] ?? null, this.cookieTiles);
|
|
2285
2322
|
gl.activeTexture(gl.TEXTURE0);
|
|
2286
2323
|
}
|
|
@@ -3035,8 +3072,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3035
3072
|
gl.deleteVertexArray(this.panelVao);
|
|
3036
3073
|
gl.deleteVertexArray(this.skyVao);
|
|
3037
3074
|
if (this.panelBuffer !== null) gl.deleteBuffer(this.panelBuffer);
|
|
3038
|
-
if (this.
|
|
3039
|
-
if (this.iesTexture !== null) gl.deleteTexture(this.iesTexture);
|
|
3075
|
+
if (this.fixtureTexture !== null) gl.deleteTexture(this.fixtureTexture);
|
|
3040
3076
|
if (this.clusterTexture !== null) gl.deleteTexture(this.clusterTexture);
|
|
3041
3077
|
}
|
|
3042
3078
|
|
|
@@ -3193,6 +3229,19 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3193
3229
|
return new SurfaceTexture(this.gl, source, options);
|
|
3194
3230
|
}
|
|
3195
3231
|
|
|
3232
|
+
/**
|
|
3233
|
+
* Upload several images of one size as the layers of one array, for a merged mesh that names a
|
|
3234
|
+
* layer per vertex (`MeshData.layers`). One material binding then covers every image, which is
|
|
3235
|
+
* what makes a block wearing forty facades one draw. Every layer must be the same size, and the
|
|
3236
|
+
* refusal names the first that is not.
|
|
3237
|
+
*/
|
|
3238
|
+
createSurfaceTextureArray(
|
|
3239
|
+
sources: readonly TexImageSource[],
|
|
3240
|
+
options: SurfaceTextureOptions = {},
|
|
3241
|
+
): SurfaceTexture {
|
|
3242
|
+
return new SurfaceTexture(this.gl, sources, options);
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3196
3245
|
/**
|
|
3197
3246
|
* Replace a surface texture's pixels, keeping the GPU object and its sampler state.
|
|
3198
3247
|
*
|
|
@@ -3310,7 +3359,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3310
3359
|
* page-faulted an RDNA4 card and wedged the device. See `emptyTexture.ts`.
|
|
3311
3360
|
*/
|
|
3312
3361
|
gl.activeTexture(gl.TEXTURE0 + NORMAL_TEXTURE_UNIT);
|
|
3313
|
-
gl.bindTexture(gl.
|
|
3362
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3314
3363
|
if (normal !== null) normal.bind(gl, NORMAL_TEXTURE_UNIT);
|
|
3315
3364
|
gl.uniform1i(u['uNormalMap'] ?? null, NORMAL_TEXTURE_UNIT);
|
|
3316
3365
|
gl.uniform1f(
|
|
@@ -3331,7 +3380,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3331
3380
|
*/
|
|
3332
3381
|
const orm = material?.orm ?? null;
|
|
3333
3382
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
3334
|
-
gl.bindTexture(gl.
|
|
3383
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3335
3384
|
if (orm !== null) orm.bind(gl, ORM_TEXTURE_UNIT);
|
|
3336
3385
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
3337
3386
|
gl.uniform1i(u['uOrmEnabled'] ?? null, orm === null ? 0 : 1);
|
|
@@ -3354,7 +3403,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3354
3403
|
*/
|
|
3355
3404
|
const emissiveMap = material?.emissive ?? null;
|
|
3356
3405
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
3357
|
-
gl.bindTexture(gl.
|
|
3406
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3358
3407
|
if (emissiveMap !== null) emissiveMap.bind(gl, EMISSIVE_TEXTURE_UNIT);
|
|
3359
3408
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
3360
3409
|
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, emissiveMap === null ? 0 : 1);
|
|
@@ -3376,7 +3425,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3376
3425
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
3377
3426
|
gl.uniform1i(u['uDoubleSided'] ?? null, material?.doubleSided === true ? 1 : 0);
|
|
3378
3427
|
|
|
3428
|
+
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
3429
|
+
return, so a material with no albedo still leaves a complete texture on the unit. */
|
|
3430
|
+
if (albedo === null) {
|
|
3431
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
3432
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
3433
|
+
} else {
|
|
3434
|
+
albedo.bindEffects(gl, SURFACE_EFFECTS_TEXTURE_UNIT, this.emptyTexture2D);
|
|
3435
|
+
}
|
|
3436
|
+
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
3379
3437
|
if (albedo === null) {
|
|
3438
|
+
/* The array stand-in, for the reason the maps above take one: a sampler reads its unit's
|
|
3439
|
+
array target, and with nothing there it is incomplete. */
|
|
3440
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
3441
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3380
3442
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
3381
3443
|
return;
|
|
3382
3444
|
}
|
|
@@ -3413,7 +3475,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3413
3475
|
return;
|
|
3414
3476
|
}
|
|
3415
3477
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
3416
|
-
gl.bindTexture(gl.
|
|
3478
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3417
3479
|
}
|
|
3418
3480
|
|
|
3419
3481
|
/**
|
|
@@ -3441,8 +3503,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3441
3503
|
setSurfaceReflectivity(amount: number): void {
|
|
3442
3504
|
if (this.contextLost) return;
|
|
3443
3505
|
this.materials.dirty();
|
|
3444
|
-
this.
|
|
3445
|
-
this.gl.uniform1f(this.flatUniforms['uReflectivity'] ?? null, Math.min(1, Math.max(0, amount)));
|
|
3506
|
+
this.writeFlatFloat('uReflectivity', Math.min(1, Math.max(0, amount)));
|
|
3446
3507
|
}
|
|
3447
3508
|
|
|
3448
3509
|
/**
|
|
@@ -3460,8 +3521,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3460
3521
|
setEnvironmentGain(gain: number): void {
|
|
3461
3522
|
if (this.contextLost) return;
|
|
3462
3523
|
this.materials.dirty();
|
|
3463
|
-
this.
|
|
3464
|
-
this.gl.uniform1f(this.flatUniforms['uEnvironmentGain'] ?? null, Math.max(0, gain));
|
|
3524
|
+
this.writeFlatFloat('uEnvironmentGain', Math.max(0, gain));
|
|
3465
3525
|
}
|
|
3466
3526
|
|
|
3467
3527
|
/**
|
|
@@ -3488,10 +3548,55 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3488
3548
|
}
|
|
3489
3549
|
|
|
3490
3550
|
setSurfaceGrain(amount: number): void {
|
|
3551
|
+
if (this.contextLost) return;
|
|
3552
|
+
this.materials.dirty();
|
|
3553
|
+
this.writeFlatFloat('uGrain', Math.min(1, Math.max(0, amount)));
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
/**
|
|
3557
|
+
* One float of material state, into every flat program.
|
|
3558
|
+
*
|
|
3559
|
+
* **Every one, for `setMaterial`'s reason**: an instanced or skinned draw runs a program of its
|
|
3560
|
+
* own and is entitled to the material its caller set before it. These setters wrote the plain
|
|
3561
|
+
* program alone until 2026-09-30, so on this backend a batch drew with the pass defaults — grain
|
|
3562
|
+
* full, no reflection, no relief — while the other backend, whose material block every variant
|
|
3563
|
+
* reads, drew what was asked.
|
|
3564
|
+
*/
|
|
3565
|
+
private writeFlatFloat(name: string, value: number): void {
|
|
3566
|
+
for (const target of this.flatTargets) {
|
|
3567
|
+
this.gl.useProgram(target.program);
|
|
3568
|
+
this.gl.uniform1f(target.uniforms[name] ?? null, value);
|
|
3569
|
+
}
|
|
3570
|
+
this.useFlatProgram();
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
/**
|
|
3574
|
+
* How much of the surfaces drawn next survives a screen-door dither, for crossfading two levels
|
|
3575
|
+
* of detail of one thing.
|
|
3576
|
+
*
|
|
3577
|
+
* 0 is off and is what `bindMeshPass` restores. A positive amount keeps that share of the
|
|
3578
|
+
* pattern's cells, a negative one keeps the complement, so the level fading in drawn at `t` and
|
|
3579
|
+
* the one fading out at `-t` cover every pixel exactly once — no hole, no pixel drawn twice.
|
|
3580
|
+
* Material state like `setSurfaceGrain`; instanced draws read it too, and shadow casters, which
|
|
3581
|
+
* are a pass of their own, do not. See `uDither` in `flat/preamble.ts` for what it costs.
|
|
3582
|
+
*/
|
|
3583
|
+
setDitherFade(amount: number): void {
|
|
3491
3584
|
if (this.contextLost) return;
|
|
3492
3585
|
this.materials.dirty();
|
|
3493
3586
|
this.useFlatProgram();
|
|
3494
|
-
this.
|
|
3587
|
+
this.ditherFade = Math.min(1, Math.max(-1, amount));
|
|
3588
|
+
/* Every flat program, as `setMaterial` writes them: an instanced or skinned draw is entitled
|
|
3589
|
+
to the fade the caller set before it, and a second program holding none draws whole. */
|
|
3590
|
+
for (const target of this.flatTargets) {
|
|
3591
|
+
this.gl.useProgram(target.program);
|
|
3592
|
+
this.writeMode(target.uniforms);
|
|
3593
|
+
}
|
|
3594
|
+
this.useFlatProgram();
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
/** `uWriteMode`: the order-independent weight and the dither, which share one uniform row. */
|
|
3598
|
+
private writeMode(u: Record<string, WebGLUniformLocation>): void {
|
|
3599
|
+
this.gl.uniform2f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade);
|
|
3495
3600
|
}
|
|
3496
3601
|
|
|
3497
3602
|
/**
|
|
@@ -3520,10 +3625,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3520
3625
|
setSurfaceRelief(amount: number, cyclesPerMetre = 60): void {
|
|
3521
3626
|
if (this.contextLost) return;
|
|
3522
3627
|
this.materials.dirty();
|
|
3523
|
-
this.
|
|
3524
|
-
|
|
3525
|
-
this.gl.uniform1f(u['uRelief'] ?? null, Math.min(1, Math.max(0, amount)));
|
|
3526
|
-
this.gl.uniform1f(u['uReliefCycles'] ?? null, Math.max(0.01, cyclesPerMetre));
|
|
3628
|
+
this.writeFlatFloat('uRelief', Math.min(1, Math.max(0, amount)));
|
|
3629
|
+
this.writeFlatFloat('uReliefCycles', Math.max(0.01, cyclesPerMetre));
|
|
3527
3630
|
}
|
|
3528
3631
|
|
|
3529
3632
|
/**
|
|
@@ -3554,11 +3657,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3554
3657
|
setSurfaceTextureRelief(scale: number): void {
|
|
3555
3658
|
if (this.contextLost) return;
|
|
3556
3659
|
this.materials.dirty();
|
|
3557
|
-
this.
|
|
3558
|
-
this.gl.uniform1f(
|
|
3559
|
-
this.flatUniforms['uTextureRelief'] ?? null,
|
|
3560
|
-
Number.isFinite(scale) ? scale : 0,
|
|
3561
|
-
);
|
|
3660
|
+
this.writeFlatFloat('uTextureRelief', Number.isFinite(scale) ? scale : 0);
|
|
3562
3661
|
}
|
|
3563
3662
|
|
|
3564
3663
|
/**
|
|
@@ -3585,8 +3684,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3585
3684
|
setEmissiveGain(gain: number): void {
|
|
3586
3685
|
if (this.contextLost) return;
|
|
3587
3686
|
this.materials.dirty();
|
|
3588
|
-
this.
|
|
3589
|
-
this.gl.uniform1f(this.flatUniforms['uEmissiveGain'] ?? null, gain);
|
|
3687
|
+
this.writeFlatFloat('uEmissiveGain', gain);
|
|
3590
3688
|
}
|
|
3591
3689
|
|
|
3592
3690
|
/* -- Instanced meshes --------------------------------------------------------------- */
|
|
@@ -3634,7 +3732,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3634
3732
|
* One batch per mesh: the attributes bind to the mesh's own vertex array, of which a mesh has
|
|
3635
3733
|
* one. `Mesh.attachInstances` is what refuses a second, and says why.
|
|
3636
3734
|
*/
|
|
3637
|
-
createInstanced(mesh: Mesh, capacity: number): InstancedBatch {
|
|
3735
|
+
createInstanced(mesh: Mesh, capacity: number, options: InstancedOptions = {}): InstancedBatch {
|
|
3638
3736
|
/* The same refusal as the other backend, for the same reason: the instanced attributes take
|
|
3639
3737
|
the two locations a skinned mesh's joints and weights already occupy. */
|
|
3640
3738
|
if (mesh.isSkinned) {
|
|
@@ -3644,7 +3742,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3644
3742
|
);
|
|
3645
3743
|
}
|
|
3646
3744
|
this.ensureInstancedProgram();
|
|
3647
|
-
return new InstancedBatch(this.gl, mesh, capacity);
|
|
3745
|
+
return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false);
|
|
3648
3746
|
}
|
|
3649
3747
|
|
|
3650
3748
|
/** Push placement and colour. Only the live prefix. */
|
|
@@ -3696,9 +3794,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3696
3794
|
): void {
|
|
3697
3795
|
if (this.contextLost) return;
|
|
3698
3796
|
const mesh = batch.mesh;
|
|
3699
|
-
|
|
3797
|
+
/* Nor a batch of a mesh with nothing in it: see `Mesh.indexCount`. */
|
|
3798
|
+
if (!mesh.complete || mesh.indexCount === 0) return;
|
|
3700
3799
|
const count = Math.min(data.count, batch.capacity);
|
|
3701
3800
|
if (count === 0) return;
|
|
3801
|
+
/* A culling batch's camera draw: the whole batch against the view and the occluders first,
|
|
3802
|
+
then instance by instance where it survives. Blended draws keep every instance and their
|
|
3803
|
+
order. See `InstancedOptions.cull`. */
|
|
3804
|
+
const culling = !blend && batch.cull;
|
|
3805
|
+
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
3702
3806
|
this.drawBudget.ask();
|
|
3703
3807
|
this.ensureInstancedProgram();
|
|
3704
3808
|
const program = this.flatInstancedProgram;
|
|
@@ -3710,7 +3814,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3710
3814
|
gl.uniform1i(u['uHasTangents'] ?? null, mesh.hasTangents ? 1 : 0);
|
|
3711
3815
|
|
|
3712
3816
|
const lit = options.lit ?? true;
|
|
3713
|
-
|
|
3817
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
3818
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
3714
3819
|
const toneMapped = options.toneMapped ?? true;
|
|
3715
3820
|
/* A blended batch shows what is behind it — refraction or glass — as a single draw does, and
|
|
3716
3821
|
counts a material of its own for it by the rule both backends share (`materialChanges.ts`). */
|
|
@@ -3739,12 +3844,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3739
3844
|
* part of what the count asks.
|
|
3740
3845
|
*/
|
|
3741
3846
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
3742
|
-
if (
|
|
3847
|
+
if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
3743
3848
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, Math.min(this.gradeCode(), 1));
|
|
3744
3849
|
|
|
3745
3850
|
if (blend) {
|
|
3746
3851
|
gl.enable(gl.BLEND);
|
|
3747
|
-
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.
|
|
3852
|
+
if (options.additive === true) gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
|
|
3853
|
+
else
|
|
3854
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
3748
3855
|
if (!depthWrite) gl.depthMask(false);
|
|
3749
3856
|
}
|
|
3750
3857
|
/* The sign follows the compare, which `depthOffsetForLayer` owns — see `drawMesh`, where the
|
|
@@ -3758,7 +3865,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3758
3865
|
/* Both sides of a double-sided material, as every mesh path does and WebGPU's pipeline does
|
|
3759
3866
|
for both: without it a batch's far faces were culled here alone. */
|
|
3760
3867
|
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
3761
|
-
mesh.
|
|
3868
|
+
if (culling && cullsInstances(count, mesh.indexCount)) batch.drawCulled(gl, data, this.frustum);
|
|
3869
|
+
else batch.draw(gl, count);
|
|
3762
3870
|
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
3763
3871
|
|
|
3764
3872
|
if (layer > 0) {
|
|
@@ -3776,7 +3884,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3776
3884
|
outlives its frame, so a snapshot left on unit 14 is held against the next frame's copy. */
|
|
3777
3885
|
if (refracting) this.unbindSeeThrough(u);
|
|
3778
3886
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
3779
|
-
if (
|
|
3887
|
+
if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
|
|
3780
3888
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
3781
3889
|
if (own) this.materials.dirty();
|
|
3782
3890
|
this.useFlatProgram();
|
|
@@ -5159,7 +5267,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5159
5267
|
cutout,
|
|
5160
5268
|
);
|
|
5161
5269
|
gl.disable(gl.CULL_FACE);
|
|
5162
|
-
gpuBatch.
|
|
5270
|
+
gpuBatch.draw(gl, count);
|
|
5163
5271
|
if (this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
5164
5272
|
if (cutout !== null) this.restoreSurfaceTextureUnit();
|
|
5165
5273
|
},
|
|
@@ -6574,6 +6682,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6574
6682
|
radius: number;
|
|
6575
6683
|
projScale: Float32Array;
|
|
6576
6684
|
invProjection: Float32Array;
|
|
6685
|
+
offset: Float32Array;
|
|
6577
6686
|
}
|
|
6578
6687
|
| undefined;
|
|
6579
6688
|
if (aoStrength > 0 && projection !== null) {
|
|
@@ -6583,11 +6692,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6583
6692
|
metres into a radius in UV. */
|
|
6584
6693
|
this.aoProjScale[0] = projection[0] ?? 1;
|
|
6585
6694
|
this.aoProjScale[1] = projection[5] ?? 1;
|
|
6695
|
+
/* The occlusion read where this frame's jitter put it (`uAoOffset`): a texel stands at its
|
|
6696
|
+
index plus a half less the jitter, in the drawing buffer the jitter was spread over. */
|
|
6697
|
+
this.aoOffset[0] = this.temporalJittering
|
|
6698
|
+
? this.temporalJitterX / Math.max(1, this.gl.drawingBufferWidth)
|
|
6699
|
+
: 0;
|
|
6700
|
+
this.aoOffset[1] = this.temporalJittering
|
|
6701
|
+
? this.temporalJitterY / Math.max(1, this.gl.drawingBufferHeight)
|
|
6702
|
+
: 0;
|
|
6586
6703
|
ao = {
|
|
6587
6704
|
strength: aoStrength,
|
|
6588
6705
|
radius: this.quality.ambientOcclusionRadius,
|
|
6589
6706
|
projScale: this.aoProjScale,
|
|
6590
6707
|
invProjection: this.aoInvProjection as Float32Array,
|
|
6708
|
+
offset: this.aoOffset,
|
|
6591
6709
|
};
|
|
6592
6710
|
}
|
|
6593
6711
|
|
|
@@ -6739,7 +6857,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6739
6857
|
this.oit.accumulateOit(this.canvas.width, this.canvas.height, oitDepth, (weighted) => {
|
|
6740
6858
|
this.oitReplaying = true;
|
|
6741
6859
|
this.useFlatProgram();
|
|
6742
|
-
this.
|
|
6860
|
+
this.oitWeight = weighted ? 1 : 0;
|
|
6861
|
+
this.writeMode(flat);
|
|
6743
6862
|
this.translucentQueue.replay((draw) => {
|
|
6744
6863
|
options.lit = draw.lit;
|
|
6745
6864
|
options.fog = draw.fog;
|
|
@@ -6756,7 +6875,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6756
6875
|
this.drawTranslucentMesh(draw.mesh as Mesh, draw.model, draw.opacity, options);
|
|
6757
6876
|
});
|
|
6758
6877
|
/* Back to zero for every other draw in the frame, exactly as `uOpacity` and `uTint` are. */
|
|
6759
|
-
this.
|
|
6878
|
+
this.oitWeight = 0;
|
|
6879
|
+
this.writeMode(flat);
|
|
6760
6880
|
this.oitReplaying = false;
|
|
6761
6881
|
});
|
|
6762
6882
|
this.sceneTarget?.bind();
|
|
@@ -6942,7 +7062,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6942
7062
|
* A reflection pass binds its own camera and is skipped: blurring toward a mirror's
|
|
6943
7063
|
* previous view is not what a viewer moved through.
|
|
6944
7064
|
*/
|
|
6945
|
-
|
|
7065
|
+
/*
|
|
7066
|
+
* **Not after the present**: a mesh pass bound then is the interface's, over a picture already
|
|
7067
|
+
* resolved. Deciding the frame again drew the interface jittered with nothing to resolve it,
|
|
7068
|
+
* and opened a second temporal frame, so the sequence stepped twice a frame, the world saw every
|
|
7069
|
+
* other position, and the anti-flicker's period never started.
|
|
7070
|
+
*/
|
|
7071
|
+
if (this.framePresented) this.temporalJittering = false;
|
|
7072
|
+
if (!this.reflectionPassActive && !this.probePassActive && !this.framePresented) {
|
|
6946
7073
|
this.frameViewProj = camera.viewProjection;
|
|
6947
7074
|
/* Copied rather than referenced: a caller moves its camera in place, and the decal pass
|
|
6948
7075
|
reads this at the end of the frame. */
|
|
@@ -7056,6 +7183,23 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7056
7183
|
return field;
|
|
7057
7184
|
}
|
|
7058
7185
|
|
|
7186
|
+
/**
|
|
7187
|
+
* Hand a world's DriftLight volume to the renderer: every fixed light of a city, summed offline by
|
|
7188
|
+
* `bakeDenseField`, standing in past the frame's exact choice. Replaces any field made before, of
|
|
7189
|
+
* either kind; the scene calls `field.follow(buffer.complete, ...)` each frame as it would for
|
|
7190
|
+
* `createLightField`, and marks the lights the volume summed `inLightField` so none is counted
|
|
7191
|
+
* twice. See `driftLight/worldLightField.ts`.
|
|
7192
|
+
*/
|
|
7193
|
+
createWorldLightField(
|
|
7194
|
+
volume: DenseLightVolume,
|
|
7195
|
+
options: WorldLightFieldOptions = {},
|
|
7196
|
+
): WorldLightField {
|
|
7197
|
+
this.forgetLightFieldVolumes();
|
|
7198
|
+
const field = new WorldLightField(volume, options);
|
|
7199
|
+
this.lightField = field;
|
|
7200
|
+
return field;
|
|
7201
|
+
}
|
|
7202
|
+
|
|
7059
7203
|
/** Let go of the field and its volumes; the scene's lights are all exact again. */
|
|
7060
7204
|
disposeLightField(): void {
|
|
7061
7205
|
this.forgetLightFieldVolumes();
|
|
@@ -7075,11 +7219,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7075
7219
|
*/
|
|
7076
7220
|
private uploadLightField(): void {
|
|
7077
7221
|
const field = this.lightField;
|
|
7078
|
-
if (field === null || this.driftAtlas !== null || !field.ready || field.
|
|
7222
|
+
if (field === null || this.driftAtlas !== null || !field.ready || field.empty) {
|
|
7079
7223
|
return;
|
|
7080
7224
|
}
|
|
7081
7225
|
const { gl } = this;
|
|
7082
|
-
const [ix, iy, iz] = field.
|
|
7226
|
+
const [ix, iy, iz] = field.indexDims;
|
|
7083
7227
|
const index = gl.createTexture();
|
|
7084
7228
|
gl.bindTexture(gl.TEXTURE_3D, index);
|
|
7085
7229
|
gl.texImage3D(
|
|
@@ -7092,7 +7236,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7092
7236
|
0,
|
|
7093
7237
|
gl.RED_INTEGER,
|
|
7094
7238
|
gl.UNSIGNED_INT,
|
|
7095
|
-
field.
|
|
7239
|
+
field.index,
|
|
7096
7240
|
);
|
|
7097
7241
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
7098
7242
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
@@ -7232,6 +7376,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7232
7376
|
gl.uniform1i(u['uFogEnabled'] ?? null, 1);
|
|
7233
7377
|
/* Grain on by default, so every scene written before it was a choice looks unchanged. */
|
|
7234
7378
|
gl.uniform1f(u['uGrain'] ?? null, 1);
|
|
7379
|
+
/* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
|
|
7380
|
+
this.oitWeight = 0;
|
|
7381
|
+
this.ditherFade = 0;
|
|
7382
|
+
gl.uniform2f(u['uWriteMode'] ?? null, 0, 0);
|
|
7235
7383
|
/* No environment reflection unless a caller asks, so existing scenes are unchanged. */
|
|
7236
7384
|
gl.uniform1f(u['uReflectivity'] ?? null, 0);
|
|
7237
7385
|
/* 1, not 0: this one is a multiplier and its identity is one. See `setEnvironmentGain`. */
|
|
@@ -7241,11 +7389,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7241
7389
|
gl.uniform1f(u['uReliefCycles'] ?? null, 60);
|
|
7242
7390
|
/* And no relief read off a surface texture either, for the same reason. */
|
|
7243
7391
|
gl.uniform1f(u['uTextureRelief'] ?? null, 0);
|
|
7244
|
-
|
|
7392
|
+
/* The mirror's plane in a reflection pass, and zero — which clips nothing — in every other. */
|
|
7245
7393
|
const reflection = this.planarReflection;
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7394
|
+
gl.uniform4fv(
|
|
7395
|
+
u['uClipPlane'] ?? null,
|
|
7396
|
+
this.reflectionPassActive && reflection !== null ? reflection.clipPlane : NO_CLIP_PLANE,
|
|
7397
|
+
);
|
|
7398
|
+
resolveSurfaceScene(env, this.surfaceScene);
|
|
7399
|
+
gl.uniform4fv(u['uSurfaceScene'] ?? null, this.surfaceScene);
|
|
7249
7400
|
bindAtmosphere(
|
|
7250
7401
|
gl,
|
|
7251
7402
|
u,
|
|
@@ -7414,9 +7565,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7414
7565
|
this.bindDriftLight(u, camera.position);
|
|
7415
7566
|
|
|
7416
7567
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
7417
|
-
gl.bindTexture(gl.
|
|
7568
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7418
7569
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
7419
7570
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
7571
|
+
/* No albedo, so no effects table: the stand-in, which the shader reads as none. */
|
|
7572
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
7573
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
7574
|
+
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
7420
7575
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
7421
7576
|
gl.uniform1f(u['uAlbedoCutout'] ?? null, 0);
|
|
7422
7577
|
gl.uniform1i(u['uDoubleSided'] ?? null, 0);
|
|
@@ -7441,20 +7596,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7441
7596
|
* fetch a sampler's descriptor before it evaluates the branch that would have skipped the read.
|
|
7442
7597
|
*/
|
|
7443
7598
|
gl.activeTexture(gl.TEXTURE0 + NORMAL_TEXTURE_UNIT);
|
|
7444
|
-
gl.bindTexture(gl.
|
|
7599
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7445
7600
|
gl.uniform1i(u['uNormalMap'] ?? null, NORMAL_TEXTURE_UNIT);
|
|
7446
7601
|
gl.uniform1f(u['uNormalStrength'] ?? null, 0);
|
|
7447
7602
|
|
|
7448
7603
|
/* And the ORM map, for the reason directly above and the one above that. */
|
|
7449
7604
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
7450
|
-
gl.bindTexture(gl.
|
|
7605
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7451
7606
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
7452
7607
|
gl.uniform1i(u['uOrmEnabled'] ?? null, 0);
|
|
7453
7608
|
gl.uniform3f(u['uOrmScale'] ?? null, 1, 1, 1);
|
|
7454
7609
|
|
|
7455
7610
|
/* And the emissive map. The scale goes back to one, which is the identity it multiplies by. */
|
|
7456
7611
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
7457
|
-
gl.bindTexture(gl.
|
|
7612
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7458
7613
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
7459
7614
|
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, 0);
|
|
7460
7615
|
gl.uniform3f(u['uEmissiveScale'] ?? null, 1, 1, 1);
|
|
@@ -7544,8 +7699,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7544
7699
|
_previousModel: ReadonlyMat4 | Mover | null = null,
|
|
7545
7700
|
): void {
|
|
7546
7701
|
if (this.contextLost) return;
|
|
7547
|
-
/* Geometry that has not all arrived is not drawn. See `Mesh.complete
|
|
7548
|
-
|
|
7702
|
+
/* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
|
|
7703
|
+
and `Mesh.indexCount`. */
|
|
7704
|
+
if (!mesh.complete || mesh.indexCount === 0) return;
|
|
7549
7705
|
|
|
7550
7706
|
/*
|
|
7551
7707
|
* Skipped where the flag asks and the bounds say so.
|
|
@@ -7731,8 +7887,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7731
7887
|
options: TranslucentMeshOptions = {},
|
|
7732
7888
|
): void {
|
|
7733
7889
|
if (this.contextLost) return;
|
|
7734
|
-
/* Geometry that has not all arrived is not drawn. See `Mesh.complete
|
|
7735
|
-
|
|
7890
|
+
/* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
|
|
7891
|
+
and `Mesh.indexCount`. */
|
|
7892
|
+
if (!mesh.complete || mesh.indexCount === 0) return;
|
|
7736
7893
|
|
|
7737
7894
|
if (opacity <= 0) return;
|
|
7738
7895
|
|
|
@@ -7741,7 +7898,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7741
7898
|
* The two buffers are two blend states over the same geometry, so the set has to survive until
|
|
7742
7899
|
* the end of the pass. Everything is copied — see `TranslucentQueue`.
|
|
7743
7900
|
*/
|
|
7744
|
-
if (this.oitActive && !this.oitReplaying) {
|
|
7901
|
+
if (this.oitActive && !this.oitReplaying && options.additive !== true) {
|
|
7745
7902
|
this.translucentQueue.record(mesh, model, opacity, options);
|
|
7746
7903
|
return;
|
|
7747
7904
|
}
|
|
@@ -7760,10 +7917,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7760
7917
|
* drew with, and only a caller that asks pays for the branch that skips them.
|
|
7761
7918
|
*/
|
|
7762
7919
|
const lit = options.lit ?? true;
|
|
7763
|
-
|
|
7920
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
7921
|
+
const fog = fogModeOf(options.fog ?? true, options.additive === true);
|
|
7764
7922
|
const toneMapped = options.toneMapped ?? true;
|
|
7765
7923
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
7766
|
-
if (
|
|
7924
|
+
if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
7767
7925
|
/* `1` is sRGB alone: the conversion without the curve, which is what dropping the tone map
|
|
7768
7926
|
means rather than dropping the whole transform. `Math.min` rather than a literal, so a
|
|
7769
7927
|
renderer asked for `none` stays at none and one asked for `srgb` is already there. */
|
|
@@ -7801,7 +7959,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7801
7959
|
const ownsState = !this.oitReplaying;
|
|
7802
7960
|
if (ownsState) {
|
|
7803
7961
|
gl.enable(gl.BLEND);
|
|
7804
|
-
|
|
7962
|
+
/* Added, where asked: colour times alpha onto what is there, and the target's alpha kept. */
|
|
7963
|
+
if (options.additive === true) gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
|
|
7964
|
+
else gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
7805
7965
|
}
|
|
7806
7966
|
/*
|
|
7807
7967
|
* The two depth options, both scoped to this draw and handed straight back — the same
|
|
@@ -7833,7 +7993,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7833
7993
|
/* Put back for whatever is drawn next, exactly as `uOpacity` and `uTint` are — a pass
|
|
7834
7994
|
cannot inherit a material from the draw before it. */
|
|
7835
7995
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
7836
|
-
if (
|
|
7996
|
+
if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
|
|
7837
7997
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
7838
7998
|
if (own) this.materials.dirty();
|
|
7839
7999
|
}
|
|
@@ -8155,8 +8315,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8155
8315
|
options: LightVolumeDrawOptions = {},
|
|
8156
8316
|
): void {
|
|
8157
8317
|
if (this.contextLost) return;
|
|
8158
|
-
/* Geometry that has not all arrived is not drawn. See `Mesh.complete
|
|
8159
|
-
|
|
8318
|
+
/* Geometry that has not all arrived is not drawn, nor a mesh with none. See `Mesh.complete`
|
|
8319
|
+
and `Mesh.indexCount`. */
|
|
8320
|
+
if (!mesh.complete || mesh.indexCount === 0) return;
|
|
8160
8321
|
const shown =
|
|
8161
8322
|
options.medium === undefined
|
|
8162
8323
|
? strength
|