@driftengine/core 4.8.5 → 4.8.7
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 +4392 -4250
- package/README.md +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -2
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +54 -0
- package/dist/render/areaLights.js +55 -0
- package/dist/render/backend/api.d.ts +43 -2
- package/dist/render/backend/webgl2/instanced.d.ts +2 -0
- package/dist/render/backend/webgl2/instanced.js +11 -2
- package/dist/render/backend/webgl2/renderer.d.ts +120 -11
- package/dist/render/backend/webgl2/renderer.js +356 -53
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
- package/dist/render/backend/webgl2/skinScatter.js +88 -26
- package/dist/render/backend/webgpu/buffers.js +2 -1
- package/dist/render/backend/webgpu/clusterBinner.js +3 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
- package/dist/render/backend/webgpu/flatPass.js +15 -13
- package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
- package/dist/render/backend/webgpu/particlePass.js +81 -12
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/postPass.js +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +93 -8
- package/dist/render/backend/webgpu/renderer.js +483 -93
- package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
- package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
- package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
- package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +45 -5
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/bloomChain.d.ts +44 -0
- package/dist/render/bloomChain.js +28 -0
- package/dist/render/bloomPass.d.ts +3 -1
- package/dist/render/bloomPass.js +16 -2
- package/dist/render/clusteredLights.d.ts +78 -6
- package/dist/render/clusteredLights.js +214 -13
- package/dist/render/driftLight/bake.d.ts +2 -0
- package/dist/render/driftLight/bake.js +1 -1
- package/dist/render/driftLight/falloff.d.ts +4 -2
- package/dist/render/driftLight/falloff.js +8 -2
- package/dist/render/fixtureAtlas.js +1 -1
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +12 -0
- package/dist/render/instances.d.ts +47 -11
- package/dist/render/instances.js +24 -9
- package/dist/render/lightBudget.d.ts +38 -2
- package/dist/render/lightBudget.js +48 -5
- package/dist/render/lightmap.d.ts +116 -0
- package/dist/render/lightmap.js +140 -0
- package/dist/render/mesh.js +2 -1
- package/dist/render/particleBatch.d.ts +59 -3
- package/dist/render/particleBatch.js +53 -2
- package/dist/render/particleMaterial.d.ts +6 -1
- package/dist/render/particleMaterial.js +10 -2
- package/dist/render/particlePool.d.ts +32 -0
- package/dist/render/particlePool.js +9 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/renderQuality.d.ts +8 -7
- package/dist/render/rendererHarness.js +3 -0
- package/dist/render/rgb9e5.d.ts +22 -0
- package/dist/render/rgb9e5.js +47 -0
- package/dist/render/sceneCapture.d.ts +29 -0
- package/dist/render/sceneCapture.js +36 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +8 -1
- package/dist/render/screenSpaceReflection.d.ts +27 -1
- package/dist/render/screenSpaceReflection.js +9 -0
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.js +37 -3
- package/dist/render/shaders/flat/areaLight.d.ts +10 -0
- package/dist/render/shaders/flat/areaLight.js +142 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +3 -1
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +46 -6
- package/dist/render/shaders/flat/lobes.d.ts +6 -0
- package/dist/render/shaders/flat/lobes.js +49 -43
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +158 -121
- package/dist/render/shaders/flat/models.d.ts +21 -6
- package/dist/render/shaders/flat/models.js +161 -17
- package/dist/render/shaders/flat/normalMap.d.ts +10 -0
- package/dist/render/shaders/flat/normalMap.js +25 -0
- package/dist/render/shaders/flat/preamble.js +84 -40
- package/dist/render/shaders/flat/skin.js +6 -5
- package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
- package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1647 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1685 -2082
- package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
- package/dist/render/shaders/generated/particle.wgsl.js +115 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
- package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
- package/dist/render/shaders/generated/water.wgsl.js +23 -16
- package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
- package/dist/render/shaders/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +13 -2
- package/dist/render/shaders/skinBlur.js +25 -3
- package/dist/render/shaders/ssrTrace.js +20 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.js +1 -0
- package/dist/render/surfaceModel.d.ts +49 -8
- package/dist/render/surfaceModel.js +54 -8
- package/dist/render/surfaceTexture.d.ts +38 -1
- package/dist/render/surfaceTexture.js +37 -2
- package/package.json +6 -6
- package/src/index.ts +26 -3
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +97 -0
- package/src/render/backend/api.ts +61 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +470 -59
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgl2/skinScatter.ts +120 -43
- package/src/render/backend/webgpu/buffers.ts +2 -1
- package/src/render/backend/webgpu/clusterBinner.ts +3 -1
- package/src/render/backend/webgpu/flatPass.ts +18 -13
- package/src/render/backend/webgpu/particlePass.ts +119 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +589 -113
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
- package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
- package/src/render/backend/webgpu/surfaceTexturePass.ts +57 -6
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +259 -12
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +9 -0
- package/src/render/instances.ts +61 -12
- package/src/render/lightBudget.ts +88 -4
- package/src/render/lightmap.ts +243 -0
- package/src/render/mesh.ts +2 -1
- package/src/render/particleBatch.ts +95 -4
- package/src/render/particleMaterial.ts +11 -1
- package/src/render/particlePool.ts +36 -0
- package/src/render/particleSort.ts +133 -0
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/renderQuality.ts +8 -7
- package/src/render/rendererHarness.ts +3 -0
- package/src/render/rgb9e5.ts +52 -0
- package/src/render/sceneCapture.ts +56 -0
- package/src/render/sceneTarget.ts +18 -1
- package/src/render/screenSpaceReflection.ts +33 -1
- package/src/render/shaders/bloom.ts +20 -3
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +142 -0
- package/src/render/shaders/flat/glassTint.ts +9 -2
- package/src/render/shaders/flat/index.ts +51 -6
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +158 -121
- package/src/render/shaders/flat/models.ts +170 -21
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +84 -40
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/flat.wgsl.ts +1685 -2082
- package/src/render/shaders/generated/particle.wgsl.ts +116 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/particle.ts +134 -1
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/shaders/ssrTrace.ts +20 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +1 -0
- package/src/render/surfaceModel.ts +82 -10
- package/src/render/surfaceTexture.ts +101 -3
package/src/render/instances.ts
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* plant. This one carries a full transform, because a vehicle pitches and rolls on its suspension
|
|
11
11
|
* and a yaw cannot say so.
|
|
12
12
|
*
|
|
13
|
-
* **What it gives up** is a matrix per instance where a scatter spends five floats:
|
|
13
|
+
* **What it gives up** is a matrix per instance where a scatter spends five floats: 80 bytes
|
|
14
14
|
* against 44, and no wind. **What would make it wrong** is a caller wanting per-instance
|
|
15
|
-
* anything
|
|
16
|
-
* WebGL2 guarantees are already spent.
|
|
15
|
+
* anything beyond an opacity and a texture cell — a morph weight, say — which wants another
|
|
16
|
+
* attribute, and the sixteen WebGL2 guarantees are already spent. The opacity rides the float the
|
|
17
|
+
* stride was padded with and the cell the matrix's bottom row, which is why neither costs a byte.
|
|
17
18
|
*/
|
|
18
19
|
export interface MeshInstances {
|
|
19
20
|
/**
|
|
@@ -30,6 +31,40 @@ export interface MeshInstances {
|
|
|
30
31
|
* — the alternative is a second batch per colour, which is the thing this exists to avoid.
|
|
31
32
|
*/
|
|
32
33
|
readonly tints: Float32Array;
|
|
34
|
+
/**
|
|
35
|
+
* One float each, the instance's opacity from 0 to 1, which a blended draw
|
|
36
|
+
* (`drawTranslucentInstanced`) multiplies into the batch's own. **Absent, or an instance past its
|
|
37
|
+
* end, is 1** — wholly opaque, which is what every instance was before 4.8.6.
|
|
38
|
+
*
|
|
39
|
+
* What lets one draw carry particles fading at different rates: without it a consumer drew a
|
|
40
|
+
* batch per opacity level, each a draw and a material of its own. It is the instance's twin of
|
|
41
|
+
* the per-vertex alpha lane (`MeshData.channel`), multiplied in where that lane is.
|
|
42
|
+
*/
|
|
43
|
+
readonly alphas?: Float32Array;
|
|
44
|
+
/**
|
|
45
|
+
* Four floats each, the instance's region of its lightmap page: `[scaleU, scaleV, biasU, biasV]`,
|
|
46
|
+
* read by a draw whose material is a `lightmapModel` — instances of one mesh in one material sit
|
|
47
|
+
* in different regions of one page, each applied before the material's own region. **They ride
|
|
48
|
+
* the tint and the opacity**, which a lightmapped instance gives up (see `lightmap.ts`): where
|
|
49
|
+
* these are given, those are not read. **A lightmapped batch needs them**, since a bake is
|
|
50
|
+
* different light at each instance; one drawn without them reads its tints as its regions, and
|
|
51
|
+
* either mismatch is said once on the console.
|
|
52
|
+
*/
|
|
53
|
+
readonly lightmapRegions?: Float32Array;
|
|
54
|
+
/**
|
|
55
|
+
* Four floats each, the instance's cell of its texture: `[scaleU, scaleV, offsetU, offsetV]`,
|
|
56
|
+
* applied to the mesh's coordinates before the material's own scale and offset, in every pass
|
|
57
|
+
* that reads them — so one draw carries particles in different cells of a flipbook, or props
|
|
58
|
+
* wearing different tiles of one atlas. **Absent, or past `count`, is the whole texture**,
|
|
59
|
+
* `[1, 1, 0, 0]`, which is what every instance read before 4.8.7.
|
|
60
|
+
*
|
|
61
|
+
* **It rides the matrix's bottom row**, which an instance's placement — a turn, a scale and a
|
|
62
|
+
* move — leaves at `[0, 0, 0, 1]`, and which every stage drawing an instance rebuilds as that.
|
|
63
|
+
* So a placement must be affine, as every placement of a solid object is; a projective one is
|
|
64
|
+
* drawn as its affine part. A culled batch (`cullInstances`) carries the cells only into a
|
|
65
|
+
* target that has the array.
|
|
66
|
+
*/
|
|
67
|
+
readonly uvRegions?: Float32Array;
|
|
33
68
|
/** How many instances the arrays hold. Fixed at creation; the buffers are sized from it. */
|
|
34
69
|
readonly capacity: number;
|
|
35
70
|
/** How many are live. The rest of the buffer is neither uploaded nor drawn. */
|
|
@@ -47,20 +82,22 @@ export function createMeshInstances(capacity: number): MeshInstances {
|
|
|
47
82
|
return {
|
|
48
83
|
models: new Float32Array(capacity * 16),
|
|
49
84
|
tints: new Float32Array(capacity * 3),
|
|
85
|
+
alphas: new Float32Array(capacity).fill(1),
|
|
50
86
|
capacity,
|
|
51
87
|
count: 0,
|
|
52
88
|
};
|
|
53
89
|
}
|
|
54
90
|
|
|
55
91
|
/**
|
|
56
|
-
* Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix,
|
|
57
|
-
* tint, and
|
|
92
|
+
* Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix, the bottom row
|
|
93
|
+
* of which carries the texture cell, three of tint, and the opacity.
|
|
58
94
|
*
|
|
59
|
-
* **
|
|
60
|
-
*
|
|
61
|
-
* offsets: the tint sits at byte 64 and a three-float attribute ending at 76 leaves the
|
|
62
|
-
* instance's first column starting there, which is legal but puts every second instance on an
|
|
63
|
-
* offset no driver aligns well.
|
|
95
|
+
* **Twenty so the stride is 80 bytes rather than 76.** A vertex buffer's stride must be a multiple
|
|
96
|
+
* of four on both backends, which 76 already is — the twentieth float was padding for the
|
|
97
|
+
* *attribute* offsets: the tint sits at byte 64 and a three-float attribute ending at 76 leaves the
|
|
98
|
+
* next instance's first column starting there, which is legal but puts every second instance on an
|
|
99
|
+
* offset no driver aligns well. Since 4.8.6 it carries the instance's opacity, read with the tint
|
|
100
|
+
* as one four-float attribute, so the opacity cost no attribute and no byte.
|
|
64
101
|
*/
|
|
65
102
|
export const INSTANCE_FLOATS = 20;
|
|
66
103
|
|
|
@@ -68,7 +105,7 @@ export const INSTANCE_FLOATS = 20;
|
|
|
68
105
|
export const INSTANCE_STRIDE = INSTANCE_FLOATS * 4;
|
|
69
106
|
|
|
70
107
|
/**
|
|
71
|
-
* Interleave `count` instances into `out`, matrix
|
|
108
|
+
* Interleave `count` instances into `out`, matrix, tint and opacity, at `INSTANCE_FLOATS` apart.
|
|
72
109
|
*
|
|
73
110
|
* **Interleaved rather than two buffers** so a batch's upload is one `writeBuffer` and an
|
|
74
111
|
* instance's colour sits beside the matrix that places it. Shared by both backends, because
|
|
@@ -78,16 +115,28 @@ export const INSTANCE_STRIDE = INSTANCE_FLOATS * 4;
|
|
|
78
115
|
* Writes nothing beyond `count`, and allocates nothing: `out` is the caller's staging array.
|
|
79
116
|
*/
|
|
80
117
|
export function packInstances(instances: MeshInstances, out: Float32Array): void {
|
|
81
|
-
const { models, tints } = instances;
|
|
118
|
+
const { models, tints, alphas, lightmapRegions, uvRegions } = instances;
|
|
82
119
|
const count = Math.min(instances.count, instances.capacity);
|
|
83
120
|
for (let i = 0; i < count; i += 1) {
|
|
84
121
|
const at = i * INSTANCE_FLOATS;
|
|
85
122
|
const m = i * 16;
|
|
86
123
|
for (let c = 0; c < 16; c += 1) out[at + c] = models[m + c] as number;
|
|
124
|
+
/* The texture cell in the bottom row an affine placement leaves free. See `uvRegions`. */
|
|
125
|
+
const cell = i * 4;
|
|
126
|
+
out[at + 3] = uvRegions?.[cell] ?? 1;
|
|
127
|
+
out[at + 7] = uvRegions?.[cell + 1] ?? 1;
|
|
128
|
+
out[at + 11] = uvRegions?.[cell + 2] ?? 0;
|
|
129
|
+
out[at + 15] = uvRegions?.[cell + 3] ?? 0;
|
|
130
|
+
if (lightmapRegions !== undefined) {
|
|
131
|
+
/* The region in the tint's and the opacity's place. See `lightmapRegions`. */
|
|
132
|
+
for (let c = 0; c < 4; c += 1) out[at + 16 + c] = lightmapRegions[i * 4 + c] as number;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
87
135
|
const t = i * 3;
|
|
88
136
|
out[at + 16] = tints[t] as number;
|
|
89
137
|
out[at + 17] = tints[t + 1] as number;
|
|
90
138
|
out[at + 18] = tints[t + 2] as number;
|
|
139
|
+
out[at + 19] = alphas?.[i] ?? 1;
|
|
91
140
|
}
|
|
92
141
|
}
|
|
93
142
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Fixed point-light budget shared by CPU selection and the flat shader. */
|
|
2
|
+
import { MAX_AREA_LIGHTS, packAreaSizesAndDoors } from './areaLights.ts';
|
|
2
3
|
import type { AreaLightBuffer } from './areaLights.ts';
|
|
3
4
|
import type { ResolvedAreaShadows } from './areaShadowSet.ts';
|
|
4
5
|
import {
|
|
@@ -6,6 +7,8 @@ import {
|
|
|
6
7
|
POINT_LIGHT_COS_OUTER,
|
|
7
8
|
lampSourceRadius,
|
|
8
9
|
lampWeight,
|
|
10
|
+
lightChannelsOf,
|
|
11
|
+
lightFalloffExponentOf,
|
|
9
12
|
} from './clusteredLights.ts';
|
|
10
13
|
|
|
11
14
|
/**
|
|
@@ -256,12 +259,22 @@ export const CLOTH_REST_TEXTURE_UNIT = CLOTH_PARTICLES_TEXTURE_UNIT + 1;
|
|
|
256
259
|
export const MODEL_MAP_TEXTURE_UNIT = CLOTH_REST_TEXTURE_UNIT + 1;
|
|
257
260
|
|
|
258
261
|
/**
|
|
259
|
-
* The
|
|
262
|
+
* The units skin's screen-space blur reads on WebGL2 — its target and the frame's depth — past
|
|
260
263
|
* every unit a lit program binds, so spreading skin in the middle of a frame unbinds nothing the
|
|
261
264
|
* draws after it sample. WebGL2 guarantees thirty-two.
|
|
262
265
|
*/
|
|
263
266
|
export const SKIN_BLUR_TEXTURE_UNIT = MODEL_MAP_TEXTURE_UNIT + 1;
|
|
264
267
|
export const SKIN_BLUR_DEPTH_UNIT = SKIN_BLUR_TEXTURE_UNIT + 1;
|
|
268
|
+
/** And the skin's colour, which the blur's second axis multiplies the spread light by. */
|
|
269
|
+
export const SKIN_BLUR_ALBEDO_UNIT = SKIN_BLUR_DEPTH_UNIT + 1;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* A sprite particle's image and the frame's depth copy its soft edge reads, on WebGL2: past every
|
|
273
|
+
* unit a lit program binds, for the skin blur's reason — a particle is drawn in the middle of a
|
|
274
|
+
* frame, and the draws after it must find their textures where they left them.
|
|
275
|
+
*/
|
|
276
|
+
export const PARTICLE_SPRITE_UNIT = SKIN_BLUR_ALBEDO_UNIT + 1;
|
|
277
|
+
export const PARTICLE_DEPTH_UNIT = PARTICLE_SPRITE_UNIT + 1;
|
|
265
278
|
|
|
266
279
|
/**
|
|
267
280
|
* A cookie's tile, in texels a side.
|
|
@@ -380,11 +393,30 @@ export interface PointLightSet {
|
|
|
380
393
|
* drawn for one spot works in another with a different angle.
|
|
381
394
|
*/
|
|
382
395
|
readonly lightCookies?: Float32Array;
|
|
396
|
+
/**
|
|
397
|
+
* One per light: the lighting channels it lights, as a bitmask in a float — 1, 2, 3 … 255.
|
|
398
|
+
* **Absent, or a short array, is channel 1 for every light**, which is every surface's default
|
|
399
|
+
* too, so a scene that never names a channel lights everything with everything as it did.
|
|
400
|
+
*
|
|
401
|
+
* A light shades a surface only where its mask and the surface's `SurfaceMaterial.lightChannels`
|
|
402
|
+
* share a bit. What it is for: a rig of lights that belongs to some draws and not others — a
|
|
403
|
+
* character's own key and rim lights, which the floor around the character must not take.
|
|
404
|
+
*/
|
|
405
|
+
readonly lightChannels?: Float32Array;
|
|
406
|
+
/**
|
|
407
|
+
* One per light: its own falloff exponent, `(1 - (d/R)^2)^n` with no distance term, or 0 for the
|
|
408
|
+
* frame's falloff. **Absent, or a short array, is 0 for every light**, so a scene that names none
|
|
409
|
+
* falls off as it did. See `PointLightSource.falloffExponent`.
|
|
410
|
+
*/
|
|
411
|
+
readonly lightFalloffExponents?: Float32Array;
|
|
383
412
|
}
|
|
384
413
|
|
|
385
414
|
/** Weights are optional per consumer; a short array means every light is fully present. */
|
|
386
415
|
const FULLY_PRESENT: Float32Array = new Float32Array(MAX_POINT_LIGHTS).fill(1);
|
|
387
416
|
|
|
417
|
+
/** The fixed arm's rectangles as `uAreaLightSize` holds them; refilled per bind, allocated once. */
|
|
418
|
+
const AREA_SIZES_AND_DOORS = new Float32Array(MAX_AREA_LIGHTS * 4);
|
|
419
|
+
|
|
388
420
|
/** No size at all, which is the behaviour every scene had before a light could have one. */
|
|
389
421
|
const POINT_SOURCES: Float32Array = new Float32Array(MAX_POINT_LIGHTS);
|
|
390
422
|
/**
|
|
@@ -483,9 +515,11 @@ export function bindPointLights(
|
|
|
483
515
|
* every lamp switched off.
|
|
484
516
|
*/
|
|
485
517
|
gl.uniform3fv(uniforms['uLightDir[0]'] ?? null, r.directions);
|
|
486
|
-
|
|
518
|
+
/* The cone and, in z, the light's own falloff exponent. See `conesAndFalloffs`. */
|
|
519
|
+
gl.uniform4fv(uniforms['uLightCone[0]'] ?? null, r.conesAndFalloffs);
|
|
487
520
|
gl.uniform1fv(uniforms['uLightIesProfile[0]'] ?? null, r.iesProfiles);
|
|
488
|
-
|
|
521
|
+
/* The azimuth and, in w, the channels. See `axesAndChannels`. */
|
|
522
|
+
gl.uniform4fv(uniforms['uLightIesAxis[0]'] ?? null, r.axesAndChannels);
|
|
489
523
|
gl.uniform1fv(uniforms['uLightCookie[0]'] ?? null, r.cookies);
|
|
490
524
|
}
|
|
491
525
|
|
|
@@ -513,7 +547,9 @@ export function bindAreaLights(
|
|
|
513
547
|
gl.uniform3fv(uniforms['uAreaLightColor[0]'] ?? null, lights.colors);
|
|
514
548
|
gl.uniform3fv(uniforms['uAreaLightRight[0]'] ?? null, lights.right);
|
|
515
549
|
gl.uniform3fv(uniforms['uAreaLightUp[0]'] ?? null, lights.up);
|
|
516
|
-
|
|
550
|
+
/* The half extents and, in zw, the barn doors. See `packAreaSizesAndDoors`. */
|
|
551
|
+
packAreaSizesAndDoors(lights, AREA_SIZES_AND_DOORS);
|
|
552
|
+
gl.uniform4fv(uniforms['uAreaLightSize[0]'] ?? null, AREA_SIZES_AND_DOORS);
|
|
517
553
|
gl.uniform1fv(uniforms['uAreaLightTwoSided[0]'] ?? null, lights.twoSided);
|
|
518
554
|
}
|
|
519
555
|
|
|
@@ -561,6 +597,18 @@ export interface ResolvedPointLights {
|
|
|
561
597
|
iesProfiles: Float32Array;
|
|
562
598
|
iesAxes: Float32Array;
|
|
563
599
|
cookies: Float32Array;
|
|
600
|
+
/**
|
|
601
|
+
* Owned, four a light: the photometric azimuth in xyz and the light's channels in w, which is how
|
|
602
|
+
* `uLightIesAxis` carries them — the channels in a lane the axis left, rather than in a uniform
|
|
603
|
+
* array of their own, which would cost a row a light of a budget that has none to give.
|
|
604
|
+
*/
|
|
605
|
+
readonly axesAndChannels: Float32Array;
|
|
606
|
+
/**
|
|
607
|
+
* Owned, four a light: the cone's two cosines, then the light's own falloff exponent, as
|
|
608
|
+
* `uLightCone` carries them — a `vec2` array costs a whole row a light on WebGL2 anyway, so the
|
|
609
|
+
* exponent rides the half it left rather than a row of its own.
|
|
610
|
+
*/
|
|
611
|
+
readonly conesAndFalloffs: Float32Array;
|
|
564
612
|
/**
|
|
565
613
|
* Owned, `MAX_POINT_LIGHTS` long: the emitter sizes and weights as the froxel record carries
|
|
566
614
|
* them, at half precision. `sourceRadii` and `weights` point here once a light set resolves, so
|
|
@@ -585,6 +633,8 @@ const glLights: ResolvedPointLights = {
|
|
|
585
633
|
iesProfiles: NO_PROFILES,
|
|
586
634
|
iesAxes: NO_IES_AXES,
|
|
587
635
|
cookies: NO_COOKIES,
|
|
636
|
+
axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
637
|
+
conesAndFalloffs: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
588
638
|
heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
|
|
589
639
|
heldWeights: new Float32Array(MAX_POINT_LIGHTS),
|
|
590
640
|
};
|
|
@@ -619,6 +669,8 @@ export function resolvePointLights(
|
|
|
619
669
|
out.iesProfiles = NO_PROFILES;
|
|
620
670
|
out.iesAxes = NO_IES_AXES;
|
|
621
671
|
out.cookies = NO_COOKIES;
|
|
672
|
+
packAxesAndChannels(NO_IES_AXES, undefined, out.axesAndChannels);
|
|
673
|
+
packConesAndFalloffs(OPEN_CONES, undefined, out.conesAndFalloffs);
|
|
622
674
|
return out;
|
|
623
675
|
}
|
|
624
676
|
/*
|
|
@@ -711,5 +763,37 @@ export function resolvePointLights(
|
|
|
711
763
|
out.iesProfiles = iesProfiles;
|
|
712
764
|
out.iesAxes = iesAxes;
|
|
713
765
|
out.cookies = cookies;
|
|
766
|
+
packAxesAndChannels(iesAxes, lights.lightChannels, out.axesAndChannels);
|
|
767
|
+
packConesAndFalloffs(coneCos, lights.lightFalloffExponents, out.conesAndFalloffs);
|
|
714
768
|
return out;
|
|
715
769
|
}
|
|
770
|
+
|
|
771
|
+
/** The cone and the falloff exponent, four a light, as `uLightCone` holds them. */
|
|
772
|
+
function packConesAndFalloffs(
|
|
773
|
+
cones: Float32Array,
|
|
774
|
+
exponents: Float32Array | undefined,
|
|
775
|
+
out: Float32Array,
|
|
776
|
+
): void {
|
|
777
|
+
const named = (exponents?.length ?? 0) >= MAX_POINT_LIGHTS ? exponents : undefined;
|
|
778
|
+
for (let light = 0; light < MAX_POINT_LIGHTS; light++) {
|
|
779
|
+
out[light * 4] = cones[light * 2] ?? POINT_LIGHT_COS_INNER;
|
|
780
|
+
out[light * 4 + 1] = cones[light * 2 + 1] ?? POINT_LIGHT_COS_OUTER;
|
|
781
|
+
out[light * 4 + 2] = lightFalloffExponentOf(named?.[light]);
|
|
782
|
+
out[light * 4 + 3] = 0;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** The azimuth and the channels, four a light, as `uLightIesAxis` holds them. */
|
|
787
|
+
function packAxesAndChannels(
|
|
788
|
+
axes: Float32Array,
|
|
789
|
+
channels: Float32Array | undefined,
|
|
790
|
+
out: Float32Array,
|
|
791
|
+
): void {
|
|
792
|
+
const named = (channels?.length ?? 0) >= MAX_POINT_LIGHTS ? channels : undefined;
|
|
793
|
+
for (let light = 0; light < MAX_POINT_LIGHTS; light++) {
|
|
794
|
+
out[light * 4] = axes[light * 3] ?? 0;
|
|
795
|
+
out[light * 4 + 1] = axes[light * 3 + 1] ?? 0;
|
|
796
|
+
out[light * 4 + 2] = axes[light * 3 + 2] ?? 0;
|
|
797
|
+
out[light * 4 + 3] = lightChannelsOf(named?.[light]);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A baked lightmap: what a consumer hands over, how both backends lay it out, and what a surface
|
|
3
|
+
* reads from it — the backend-neutral half of `createLightmap` and `lightmapModel`, by the
|
|
4
|
+
* 2026-08-13 rule.
|
|
5
|
+
*
|
|
6
|
+
* **What it is for.** A stage built in another engine carries its static light baked: the bounce
|
|
7
|
+
* off its walls, the soft occlusion in its corners, most of the colour a reference render shows.
|
|
8
|
+
* The dynamic lights alone leave everything they do not reach flat. A consumer decodes the bake
|
|
9
|
+
* into pages — one irradiance image and one direction image each — and every lightmapped surface
|
|
10
|
+
* reads its own region of its page, at its second texture coordinates scaled and offset into it.
|
|
11
|
+
*
|
|
12
|
+
* **What a surface adds**, for its shading normal `n` (the normal map's, where it has one):
|
|
13
|
+
*
|
|
14
|
+
* d = direction * 2 - 1
|
|
15
|
+
* light = irradiance(uv) * max(0, dot(d.xyz, n) + d.w)
|
|
16
|
+
* diffuse += albedo * (1 - metal) * light
|
|
17
|
+
*
|
|
18
|
+
* Unreal's high-quality lightmap read, `GetLightMapColorHQ`, once its own vectors have decoded the
|
|
19
|
+
* page — the first-order spherical harmonic its direction carries, in this engine's axes. **Added
|
|
20
|
+
* to what the dynamic lights give**, and to the ambient: a consumer whose bake already holds the
|
|
21
|
+
* sky's light zeroes the ambient for those draws itself, with `setAmbientSH` and nine zeros.
|
|
22
|
+
*
|
|
23
|
+
* **A shading model, because everything it would otherwise take is spent.** The page binds where a
|
|
24
|
+
* model's map does and the region is the model's numbers (`lightmapModel`), so a lightmapped
|
|
25
|
+
* material is a pipeline constant of its own and nothing else pays for it; the surface is otherwise
|
|
26
|
+
* the standard model, unchanged. The second coordinates ride the grain and relief attributes
|
|
27
|
+
* (`MeshData.lightmapUvs`, stored below zero by `withLightmapUvs`), and an instance's region rides
|
|
28
|
+
* its tint and opacity (`MeshInstances.lightmapRegions`). **What it gives up**: a lightmapped
|
|
29
|
+
* surface has no procedural grain or relief, no other shading model, and an instanced batch with
|
|
30
|
+
* regions no tint. **What it does not do**: the GPU-driven pipeline and splats read no page.
|
|
31
|
+
*/
|
|
32
|
+
import type { MeshData } from '@driftengine/drft';
|
|
33
|
+
import { packRgb9e5 } from './rgb9e5.ts';
|
|
34
|
+
|
|
35
|
+
/** One page as a consumer decoded it. */
|
|
36
|
+
export interface LightmapPage {
|
|
37
|
+
readonly width: number;
|
|
38
|
+
readonly height: number;
|
|
39
|
+
/**
|
|
40
|
+
* Linear irradiance, row-major from the top, in the same units every other light here is in — a
|
|
41
|
+
* bake from another engine scaled by that engine's exposure, as its lights are. Either three
|
|
42
|
+
* floats a texel, or one word a texel already packed as rgb9e5 (`rgb9e5.ts`), which is how such a
|
|
43
|
+
* bake usually arrives and which uploads with no conversion at all.
|
|
44
|
+
*/
|
|
45
|
+
readonly irradiance: Float32Array | Uint32Array;
|
|
46
|
+
/**
|
|
47
|
+
* The direction the light arrives from, four bytes a texel: a first-order spherical harmonic in
|
|
48
|
+
* this engine's world axes, each value stored as `v * 0.5 + 0.5` — xyz the axis, w the constant.
|
|
49
|
+
*/
|
|
50
|
+
readonly direction: Uint8Array;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Layers a page is uploaded as: the irradiance, then the direction. */
|
|
54
|
+
export const LIGHTMAP_LAYERS = 2;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Where a surface's region of its page is: `lightmapUv = uv2 * scale + bias`, as
|
|
58
|
+
* `[scaleU, scaleV, biasU, biasV]`. The whole page is the identity.
|
|
59
|
+
*/
|
|
60
|
+
export type LightmapRegion = readonly [number, number, number, number];
|
|
61
|
+
|
|
62
|
+
/** The whole page. */
|
|
63
|
+
export const WHOLE_PAGE: LightmapRegion = [1, 1, 0, 0];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A page as both backends upload it: two layers of four bytes a texel, row-major from the top. The
|
|
67
|
+
* first is the irradiance's rgb9e5 word, byte by byte from its lowest; the second is the direction.
|
|
68
|
+
*
|
|
69
|
+
* **Eight bytes a texel, and the shader filters it.** Half floats were sixteen: a stage of 49 pages
|
|
70
|
+
* and 21.8 million texels held 348 MB, where this holds 174. The price is that the irradiance cannot
|
|
71
|
+
* be filtered as it is stored — a bilinear blend of two words with different exponents is not the
|
|
72
|
+
* blend of the colours they hold — so the lit stage fetches a texel's four neighbours, decodes each
|
|
73
|
+
* and blends the colours (`modelBaked` in `shaders/flat/models.ts`): four fetches a layer where one
|
|
74
|
+
* filtered sample was, on lightmapped surfaces alone. **What would make it wrong** is every device
|
|
75
|
+
* a page reaches sampling `rgb9e5ufloat`, which WebGL2 does as `RGB9_E5` too; it would then be a
|
|
76
|
+
* texture of its own, filtered by the device, and a binding the lit stage has no room for.
|
|
77
|
+
*/
|
|
78
|
+
export interface LightmapTexels {
|
|
79
|
+
readonly kind: 'lightmap-texels';
|
|
80
|
+
readonly width: number;
|
|
81
|
+
readonly height: number;
|
|
82
|
+
/** Each layer's bytes, in layer order: the irradiance, then the direction. */
|
|
83
|
+
readonly layers: readonly Uint8Array[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Whether a surface texture's source is a page's texels rather than an image or blocks. */
|
|
87
|
+
export function isLightmapTexels(source: unknown): source is LightmapTexels {
|
|
88
|
+
return (
|
|
89
|
+
typeof source === 'object' &&
|
|
90
|
+
source !== null &&
|
|
91
|
+
(source as { kind?: unknown }).kind === 'lightmap-texels'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A page as both backends upload it (see `LightmapTexels`). Refuses a page whose arrays are not its
|
|
97
|
+
* size, naming the one that is short. Packed irradiance is read where it lies, with no copy; float
|
|
98
|
+
* irradiance is packed once, here.
|
|
99
|
+
*/
|
|
100
|
+
export function lightmapTexels(page: LightmapPage): LightmapTexels {
|
|
101
|
+
const { width, height } = page;
|
|
102
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) {
|
|
103
|
+
throw new Error(`createLightmap: a ${width} by ${height} page has nothing in it`);
|
|
104
|
+
}
|
|
105
|
+
const texels = width * height;
|
|
106
|
+
const irradiance = page.irradiance;
|
|
107
|
+
const packed = irradiance instanceof Uint32Array;
|
|
108
|
+
const wanted = packed ? texels : texels * 3;
|
|
109
|
+
if (irradiance.length !== wanted) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`createLightmap: ${irradiance.length} irradiance ${packed ? 'words' : 'floats'} for a ` +
|
|
112
|
+
`${width} by ${height} page, which wants ${wanted}: ` +
|
|
113
|
+
(packed ? 'one rgb9e5 word a texel' : 'three a texel'),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (page.direction.length !== texels * 4) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`createLightmap: ${page.direction.length} direction bytes for a ${width} by ${height} ` +
|
|
119
|
+
`page, which wants ${texels * 4}: four a texel`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
let words: Uint32Array;
|
|
123
|
+
if (packed) {
|
|
124
|
+
words = irradiance;
|
|
125
|
+
} else {
|
|
126
|
+
words = new Uint32Array(texels);
|
|
127
|
+
for (let t = 0; t < texels; t++) {
|
|
128
|
+
words[t] = packRgb9e5(
|
|
129
|
+
irradiance[t * 3] as number,
|
|
130
|
+
irradiance[t * 3 + 1] as number,
|
|
131
|
+
irradiance[t * 3 + 2] as number,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
kind: 'lightmap-texels',
|
|
137
|
+
width,
|
|
138
|
+
height,
|
|
139
|
+
layers: [littleEndianBytes(words), page.direction],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Whether this platform lays a word out lowest byte first, which the shader's decode assumes. */
|
|
144
|
+
const LITTLE_ENDIAN = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* A page's words as bytes, lowest first: a view of the same memory on every little-endian platform,
|
|
148
|
+
* which is every one a browser runs on, and a reordered copy anywhere else.
|
|
149
|
+
*/
|
|
150
|
+
function littleEndianBytes(words: Uint32Array): Uint8Array {
|
|
151
|
+
const bytes = new Uint8Array(words.buffer, words.byteOffset, words.length * 4);
|
|
152
|
+
if (LITTLE_ENDIAN) return bytes;
|
|
153
|
+
const swapped = new Uint8Array(bytes.length);
|
|
154
|
+
for (let i = 0; i < bytes.length; i += 4) {
|
|
155
|
+
swapped[i] = bytes[i + 3] as number;
|
|
156
|
+
swapped[i + 1] = bytes[i + 2] as number;
|
|
157
|
+
swapped[i + 2] = bytes[i + 1] as number;
|
|
158
|
+
swapped[i + 3] = bytes[i] as number;
|
|
159
|
+
}
|
|
160
|
+
return swapped;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A mesh with its second coordinates moved onto the grain and relief streams they ride, or the
|
|
165
|
+
* mesh itself where it has none. Called by both backends' `createMesh`, before anything records
|
|
166
|
+
* which streams the mesh has.
|
|
167
|
+
*
|
|
168
|
+
* **Stored as `-1 - uv`, below zero, and that is what keeps the lanes safe to share.** Grain takes
|
|
169
|
+
* effect only above zero and relief is read through `max(relief, 0)`, so the same mesh drawn by a
|
|
170
|
+
* material that is not lightmapped is a mesh with no grain and no relief — rather than one mottled
|
|
171
|
+
* and bumped by its own coordinates, which is what storing them as they are drew. **What it gives
|
|
172
|
+
* up** is coordinates at or below −1, which read as nothing; a page's coordinates are from 0 to 1.
|
|
173
|
+
*/
|
|
174
|
+
export function withLightmapUvs(data: MeshData): MeshData {
|
|
175
|
+
const uvs = data.lightmapUvs;
|
|
176
|
+
if (uvs === undefined) return data;
|
|
177
|
+
const vertices = uvs.length / 2;
|
|
178
|
+
const grain = new Float32Array(vertices);
|
|
179
|
+
const relief = new Float32Array(vertices);
|
|
180
|
+
for (let v = 0; v < vertices; v++) {
|
|
181
|
+
grain[v] = -1 - (uvs[v * 2] as number);
|
|
182
|
+
relief[v] = -1 - (uvs[v * 2 + 1] as number);
|
|
183
|
+
}
|
|
184
|
+
return { ...data, grain, relief, lightmapUvs: undefined };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** A material's region, checked: four finite numbers, the whole page where none was named. */
|
|
188
|
+
export function lightmapRegionOf(region: LightmapRegion | undefined): LightmapRegion {
|
|
189
|
+
if (region === undefined) return WHOLE_PAGE;
|
|
190
|
+
let finite = region.length === 4;
|
|
191
|
+
for (let i = 0; i < region.length; i++) finite &&= Number.isFinite(region[i]);
|
|
192
|
+
if (!finite) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
'lightmapModel: a region is four numbers, [scaleU, scaleV, biasU, biasV], ' +
|
|
195
|
+
`and was given ${String(region)}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return region;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let warnedUnpaged = false;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Says once that a lightmapped material was set with no page. The lit stage adds nothing for one,
|
|
205
|
+
* which is the defined state; this is what keeps it from also being a silent one.
|
|
206
|
+
*/
|
|
207
|
+
export function warnUnpagedLightmap(
|
|
208
|
+
model: { readonly kind: string } | null | undefined,
|
|
209
|
+
map: unknown,
|
|
210
|
+
): void {
|
|
211
|
+
if (warnedUnpaged || model?.kind !== 'lightmap' || (map !== null && map !== undefined)) return;
|
|
212
|
+
warnedUnpaged = true;
|
|
213
|
+
console.warn(
|
|
214
|
+
'setMaterial: a lightmapModel material with no modelMap reads no page and adds no light. ' +
|
|
215
|
+
'Pass the page createLightmap returned as its modelMap.',
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let warnedRegions = false;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Says once that an instanced batch and its material disagree about lightmap regions: a batch with
|
|
223
|
+
* regions drawn by a material that is not lightmapped, or a lightmapped one drawn without them. The
|
|
224
|
+
* regions ride the lane a tint and an opacity do (`MeshInstances.lightmapRegions`), so either draw
|
|
225
|
+
* reads one as the other: drawn, and wrong, which is why it is said rather than left.
|
|
226
|
+
*/
|
|
227
|
+
export function warnRegionsWithoutPage(
|
|
228
|
+
model: { readonly kind: string } | null | undefined,
|
|
229
|
+
regions: Float32Array | undefined,
|
|
230
|
+
): void {
|
|
231
|
+
if (warnedRegions) return;
|
|
232
|
+
const lightmapped = model?.kind === 'lightmap';
|
|
233
|
+
if (lightmapped === (regions !== undefined)) return;
|
|
234
|
+
warnedRegions = true;
|
|
235
|
+
console.warn(
|
|
236
|
+
lightmapped
|
|
237
|
+
? 'drawInstanced: a lightmapModel material drawing a batch with no lightmapRegions reads ' +
|
|
238
|
+
'its tints as regions. Give each instance its region of the page.'
|
|
239
|
+
: 'drawInstanced: a batch with lightmapRegions drawn by a material that is not a ' +
|
|
240
|
+
'lightmapModel reads its regions as its tints and opacities. Draw it with its page, or ' +
|
|
241
|
+
'without regions.',
|
|
242
|
+
);
|
|
243
|
+
}
|
package/src/render/mesh.ts
CHANGED
|
@@ -400,7 +400,8 @@ export class Mesh {
|
|
|
400
400
|
gl.vertexAttribDivisor(location, 1);
|
|
401
401
|
}
|
|
402
402
|
gl.enableVertexAttribArray(15);
|
|
403
|
-
|
|
403
|
+
/* The tint and, in w, the opacity: one attribute for both. See `INSTANCE_FLOATS`. */
|
|
404
|
+
gl.vertexAttribPointer(15, 4, gl.FLOAT, false, stride, 64);
|
|
404
405
|
gl.vertexAttribDivisor(15, 1);
|
|
405
406
|
gl.bindVertexArray(null);
|
|
406
407
|
return vao;
|