@driftengine/core 4.8.5 → 4.8.6
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 +4321 -4250
- package/dist/index.d.ts +10 -2
- package/dist/index.js +11 -1
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/areaLights.d.ts +19 -0
- package/dist/render/areaLights.js +20 -0
- package/dist/render/backend/api.d.ts +13 -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 +74 -1
- package/dist/render/backend/webgl2/renderer.js +229 -37
- 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 +80 -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 +58 -7
- package/dist/render/backend/webgpu/renderer.js +329 -78
- 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 +2 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +18 -4
- 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 +55 -1
- package/dist/render/clusteredLights.js +182 -8
- package/dist/render/fixtureAtlas.js +1 -1
- package/dist/render/instanceCull.js +7 -0
- package/dist/render/instances.d.ts +32 -10
- package/dist/render/instances.js +17 -8
- package/dist/render/lightBudget.d.ts +26 -1
- package/dist/render/lightBudget.js +26 -3
- package/dist/render/lightmap.d.ts +102 -0
- package/dist/render/lightmap.js +118 -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 +11 -0
- package/dist/render/particlePool.js +2 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +8 -1
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- package/dist/render/shaders/flat/areaLight.d.ts +10 -0
- package/dist/render/shaders/flat/areaLight.js +96 -0
- package/dist/render/shaders/flat/index.js +30 -4
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +107 -105
- package/dist/render/shaders/flat/models.d.ts +16 -4
- package/dist/render/shaders/flat/models.js +64 -4
- 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 +74 -37
- 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/flat.wgsl.d.ts +1631 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1669 -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/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- package/dist/render/shaders/skinBlur.js +25 -3
- package/dist/render/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- package/dist/render/surfaceModel.d.ts +21 -1
- package/dist/render/surfaceModel.js +22 -0
- package/dist/render/surfaceTexture.d.ts +14 -1
- package/dist/render/surfaceTexture.js +15 -2
- package/package.json +6 -6
- package/src/index.ts +20 -1
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/areaLights.ts +39 -0
- package/src/render/backend/api.ts +14 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +292 -35
- 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 +118 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +396 -95
- 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 +26 -5
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +221 -7
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/instanceCull.ts +5 -0
- package/src/render/instances.ts +40 -11
- package/src/render/lightBudget.ts +48 -2
- package/src/render/lightmap.ts +202 -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 +13 -0
- package/src/render/particleSort.ts +133 -0
- package/src/render/sceneTarget.ts +18 -1
- package/src/render/shaders/bloom.ts +20 -3
- package/src/render/shaders/flat/areaLight.ts +96 -0
- package/src/render/shaders/flat/index.ts +30 -4
- package/src/render/shaders/flat/main.ts +107 -105
- package/src/render/shaders/flat/models.ts +73 -8
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +74 -37
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
- package/src/render/shaders/generated/flat.wgsl.ts +1669 -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/particle.ts +134 -1
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/surfaceModel.ts +36 -1
- package/src/render/surfaceTexture.ts +41 -3
|
@@ -36,6 +36,17 @@ export interface ParticleInstances {
|
|
|
36
36
|
seeds: Float32Array;
|
|
37
37
|
/** Current velocity, three floats each: what a streak is stretched along. */
|
|
38
38
|
velocities: Float32Array;
|
|
39
|
+
/**
|
|
40
|
+
* A sprite's flipbook frame, one float each: its whole part names a cell of the image, counted
|
|
41
|
+
* across then down from the top-left, and its fraction is how far toward the next a batch that
|
|
42
|
+
* blends cells has come. Read by the `'sprite'` material alone. Absent is frame 0.
|
|
43
|
+
*/
|
|
44
|
+
frames?: Float32Array;
|
|
45
|
+
/**
|
|
46
|
+
* A sprite's half-height in metres, one float each, beside `sizes`' half-width — a sprite need
|
|
47
|
+
* not be square. Read by the `'sprite'` material alone; absent or 0 is as tall as it is wide.
|
|
48
|
+
*/
|
|
49
|
+
heights?: Float32Array;
|
|
39
50
|
count: number;
|
|
40
51
|
capacity: number;
|
|
41
52
|
}
|
|
@@ -9,6 +9,8 @@ function createParticleInstances(capacity) {
|
|
|
9
9
|
ages: new Float32Array(capacity),
|
|
10
10
|
seeds: new Float32Array(capacity),
|
|
11
11
|
velocities: new Float32Array(capacity * 3),
|
|
12
|
+
frames: new Float32Array(capacity),
|
|
13
|
+
heights: new Float32Array(capacity),
|
|
12
14
|
count: 0,
|
|
13
15
|
capacity,
|
|
14
16
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pool's particles, farthest from the eye first, for a blended batch that asks to be sorted.
|
|
3
|
+
*
|
|
4
|
+
* **Why a blended batch wants it.** An alpha blend is order-dependent: a near puff drawn before a
|
|
5
|
+
* far one is covered by it, and a cloud of them drawn in the order they were born flickers as
|
|
6
|
+
* that order and the camera disagree. Back to front is the order the blend assumes. Additive
|
|
7
|
+
* blending is a sum and needs none of it, which is why sorting is the batch's choice.
|
|
8
|
+
*
|
|
9
|
+
* **Backend-neutral, as a copy**: the sorted particles are written into a second set of streams the
|
|
10
|
+
* batch owns, and each backend uploads that set exactly as it uploads an unsorted one — so the
|
|
11
|
+
* order is one decision, not two, by the 2026-08-13 rule. A merge sort into preallocated scratch,
|
|
12
|
+
* so nothing is allocated per frame however many particles there are; stable, so two particles at
|
|
13
|
+
* one distance keep their order and do not trade places from frame to frame.
|
|
14
|
+
*
|
|
15
|
+
* **What it gives up** is order between batches and within one particle: two sorted batches still
|
|
16
|
+
* draw one after the other, and two crossing cards cut through each other. **What would make it
|
|
17
|
+
* wrong** is a field of particles large enough that sorting it on the CPU costs a frame; the GPU
|
|
18
|
+
* sorts it then.
|
|
19
|
+
*/
|
|
20
|
+
import type { ParticleInstances } from './particlePool.ts';
|
|
21
|
+
/** What sorting a batch keeps between frames: the order, its keys and the sorted copy. */
|
|
22
|
+
export interface ParticleSort {
|
|
23
|
+
readonly order: Uint32Array;
|
|
24
|
+
readonly scratch: Uint32Array;
|
|
25
|
+
readonly keys: Float32Array;
|
|
26
|
+
readonly sorted: ParticleInstances;
|
|
27
|
+
}
|
|
28
|
+
/** Room to sort `capacity` particles, allocated once with the batch. */
|
|
29
|
+
export declare function createParticleSort(capacity: number): ParticleSort;
|
|
30
|
+
/**
|
|
31
|
+
* `data`'s live particles into `sort.sorted`, farthest from `eye` first, and that copy returned.
|
|
32
|
+
* At most the sort's capacity are kept.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sortBackToFront(data: ParticleInstances, eye: ArrayLike<number>, sort: ParticleSort): ParticleInstances;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Room to sort `capacity` particles, allocated once with the batch. */
|
|
2
|
+
export function createParticleSort(capacity) {
|
|
3
|
+
return {
|
|
4
|
+
order: new Uint32Array(capacity),
|
|
5
|
+
scratch: new Uint32Array(capacity),
|
|
6
|
+
keys: new Float32Array(capacity),
|
|
7
|
+
sorted: {
|
|
8
|
+
positions: new Float32Array(capacity * 3),
|
|
9
|
+
sizes: new Float32Array(capacity),
|
|
10
|
+
spins: new Float32Array(capacity),
|
|
11
|
+
colors: new Float32Array(capacity * 3),
|
|
12
|
+
alphas: new Float32Array(capacity),
|
|
13
|
+
ages: new Float32Array(capacity),
|
|
14
|
+
seeds: new Float32Array(capacity),
|
|
15
|
+
velocities: new Float32Array(capacity * 3),
|
|
16
|
+
frames: new Float32Array(capacity),
|
|
17
|
+
heights: new Float32Array(capacity),
|
|
18
|
+
count: 0,
|
|
19
|
+
capacity,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `data`'s live particles into `sort.sorted`, farthest from `eye` first, and that copy returned.
|
|
25
|
+
* At most the sort's capacity are kept.
|
|
26
|
+
*/
|
|
27
|
+
export function sortBackToFront(data, eye, sort) {
|
|
28
|
+
const count = Math.min(data.count, sort.order.length);
|
|
29
|
+
const { order, keys } = sort;
|
|
30
|
+
const ex = eye[0] ?? 0;
|
|
31
|
+
const ey = eye[1] ?? 0;
|
|
32
|
+
const ez = eye[2] ?? 0;
|
|
33
|
+
for (let i = 0; i < count; i++) {
|
|
34
|
+
const dx = data.positions[i * 3] - ex;
|
|
35
|
+
const dy = data.positions[i * 3 + 1] - ey;
|
|
36
|
+
const dz = data.positions[i * 3 + 2] - ez;
|
|
37
|
+
keys[i] = dx * dx + dy * dy + dz * dz;
|
|
38
|
+
order[i] = i;
|
|
39
|
+
}
|
|
40
|
+
mergeSortFarFirst(order, sort.scratch, keys, count);
|
|
41
|
+
const out = sort.sorted;
|
|
42
|
+
for (let k = 0; k < count; k++) {
|
|
43
|
+
const i = order[k];
|
|
44
|
+
copy3(data.positions, i, out.positions, k);
|
|
45
|
+
copy3(data.colors, i, out.colors, k);
|
|
46
|
+
copy3(data.velocities, i, out.velocities, k);
|
|
47
|
+
out.sizes[k] = data.sizes[i];
|
|
48
|
+
out.spins[k] = data.spins[i];
|
|
49
|
+
out.alphas[k] = data.alphas[i];
|
|
50
|
+
out.ages[k] = data.ages[i];
|
|
51
|
+
out.seeds[k] = data.seeds[i];
|
|
52
|
+
out.frames[k] = data.frames?.[i] ?? 0;
|
|
53
|
+
out.heights[k] = data.heights?.[i] ?? 0;
|
|
54
|
+
}
|
|
55
|
+
out.count = count;
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function copy3(from, i, to, k) {
|
|
59
|
+
to[k * 3] = from[i * 3];
|
|
60
|
+
to[k * 3 + 1] = from[i * 3 + 1];
|
|
61
|
+
to[k * 3 + 2] = from[i * 3 + 2];
|
|
62
|
+
}
|
|
63
|
+
/** A bottom-up merge sort of `order` by `keys`, largest first, stable, through `scratch`. */
|
|
64
|
+
function mergeSortFarFirst(order, scratch, keys, count) {
|
|
65
|
+
let from = order;
|
|
66
|
+
let to = scratch;
|
|
67
|
+
for (let width = 1; width < count; width *= 2) {
|
|
68
|
+
for (let start = 0; start < count; start += 2 * width) {
|
|
69
|
+
const middle = Math.min(start + width, count);
|
|
70
|
+
const end = Math.min(start + 2 * width, count);
|
|
71
|
+
let a = start;
|
|
72
|
+
let b = middle;
|
|
73
|
+
let at = start;
|
|
74
|
+
while (a < middle && b < end) {
|
|
75
|
+
/* `>=` keeps the left run's particle first on a tie, which is what makes this stable. */
|
|
76
|
+
if (keys[from[a]] >= keys[from[b]]) {
|
|
77
|
+
to[at++] = from[a++];
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
to[at++] = from[b++];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
while (a < middle)
|
|
84
|
+
to[at++] = from[a++];
|
|
85
|
+
while (b < end)
|
|
86
|
+
to[at++] = from[b++];
|
|
87
|
+
}
|
|
88
|
+
const swap = from;
|
|
89
|
+
from = to;
|
|
90
|
+
to = swap;
|
|
91
|
+
}
|
|
92
|
+
/* Copied by hand: a subarray would be a view allocated every frame. */
|
|
93
|
+
if (from !== order)
|
|
94
|
+
for (let i = 0; i < count; i++)
|
|
95
|
+
order[i] = from[i];
|
|
96
|
+
}
|
|
@@ -195,6 +195,9 @@ export declare class SceneTarget {
|
|
|
195
195
|
* must reject it in the same place or the accumulation and the revealage cover different
|
|
196
196
|
* fragments.
|
|
197
197
|
*/
|
|
198
|
+
/** The frame's size in pixels, as its targets are allocated. */
|
|
199
|
+
get frameWidth(): number;
|
|
200
|
+
get frameHeight(): number;
|
|
198
201
|
/**
|
|
199
202
|
* The depth the frame draws against, for a second framebuffer to share: skin's diffuse half,
|
|
200
203
|
* which tests against it and writes none. The multisampled renderbuffer where the frame is
|
|
@@ -311,6 +314,8 @@ export declare class SceneTarget {
|
|
|
311
314
|
readonly strength: number;
|
|
312
315
|
/** In scene units, which is why this wants `keepsRange`. See `BloomPass`. */
|
|
313
316
|
readonly threshold: number;
|
|
317
|
+
/** The ramp and each level's tint, as `resolveBloomResponse` writes them. */
|
|
318
|
+
readonly response: Float32Array;
|
|
314
319
|
}, dof?: {
|
|
315
320
|
/** Where the lens is focused, in metres. */
|
|
316
321
|
readonly distance: number;
|
|
@@ -432,6 +432,13 @@ export class SceneTarget {
|
|
|
432
432
|
* must reject it in the same place or the accumulation and the revealage cover different
|
|
433
433
|
* fragments.
|
|
434
434
|
*/
|
|
435
|
+
/** The frame's size in pixels, as its targets are allocated. */
|
|
436
|
+
get frameWidth() {
|
|
437
|
+
return this.width;
|
|
438
|
+
}
|
|
439
|
+
get frameHeight() {
|
|
440
|
+
return this.height;
|
|
441
|
+
}
|
|
435
442
|
/**
|
|
436
443
|
* The depth the frame draws against, for a second framebuffer to share: skin's diffuse half,
|
|
437
444
|
* which tests against it and writes none. The multisampled renderbuffer where the frame is
|
|
@@ -776,7 +783,7 @@ export class SceneTarget {
|
|
|
776
783
|
let bloomTexture = null;
|
|
777
784
|
if (bloom !== undefined && scene !== null) {
|
|
778
785
|
this.bloom ??= new BloomPass(gl);
|
|
779
|
-
bloomTexture = this.bloom.run(scene, this.width, this.height, bloom.threshold);
|
|
786
|
+
bloomTexture = this.bloom.run(scene, this.width, this.height, bloom.threshold, bloom.response);
|
|
780
787
|
}
|
|
781
788
|
/*
|
|
782
789
|
* Eye adaptation, metered from the same resolved scene bloom reads, and after it for the same
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
* 3. **Upsample**, additively back up the chain through a tent filter.
|
|
28
28
|
*
|
|
29
29
|
* Deliberately absent, because they were asked against by name: lens dirt, anamorphic streaks
|
|
30
|
-
* and ghosting. One threshold, one radius, one strength.
|
|
30
|
+
* and ghosting. One threshold, one radius, one strength — and, since 4.8.6, how a colour comes in
|
|
31
|
+
* past the threshold and a tint for each octave, which is how another engine's stage is matched
|
|
32
|
+
* rather than how this one looks: see `BloomResponse` in `bloomChain.ts`.
|
|
31
33
|
*/
|
|
32
34
|
/**
|
|
33
35
|
* Stage one: threshold the scene and halve it.
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
* first stage because it is not energy-preserving, and running it down the whole chain darkens
|
|
47
49
|
* every large bright area rather than only taming isolated ones.
|
|
48
50
|
*/
|
|
49
|
-
export declare const BLOOM_PREFILTER_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\n\nuniform sampler2D uSource;\n/** One source texel in UV. The thirteen taps are laid out in source texels. */\nuniform vec2 uTexel;\n/** In scene units. 1.0 is \"brighter than white\", which is the useful place to start. */\nuniform float uThreshold;\n\nout vec4 fragColor;\n\n/**\n * A ceiling on what any one pixel may contribute, well above anything a scene means.\n *\n * Not a look control. A half-float target can hold an infinity, and an infinity through the\n * threshold below is inf/inf, which is a NaN that the chain then spreads across the whole\n * frame \u2014 the one failure mode where a post pass destroys the picture rather than degrading\n * it. Anything above this is indistinguishable after the tone curve anyway.\n */\nconst float BLOOM_CEILING = 256.0;\n\nfloat brightness(vec3 c) {\n return max(c.r, max(c.g, c.b));\n}\n\nvec3 fetch(vec2 uv) {\n vec3 c = min(textureLod(uSource, uv, 0.0).rgb, vec3(BLOOM_CEILING));\n float bright = brightness(c);\n return c * (max(bright - uThreshold, 0.0) / max(bright, 1e-4));\n}\n\n/** How much a box counts for. A runaway pixel drags its box's brightness up and its weight down. */\nfloat firefly(vec3 c) {\n return 1.0 / (1.0 + brightness(c));\n}\n\nvoid main() {\n\n vec2 t = uTexel;\n vec3 a = fetch(vUv + vec2(-2.0, 2.0) * t);\n vec3 b = fetch(vUv + vec2( 0.0, 2.0) * t);\n vec3 c = fetch(vUv + vec2( 2.0, 2.0) * t);\n vec3 d = fetch(vUv + vec2(-2.0, 0.0) * t);\n vec3 e = fetch(vUv);\n vec3 f = fetch(vUv + vec2( 2.0, 0.0) * t);\n vec3 g = fetch(vUv + vec2(-2.0, -2.0) * t);\n vec3 h = fetch(vUv + vec2( 0.0, -2.0) * t);\n vec3 i = fetch(vUv + vec2( 2.0, -2.0) * t);\n vec3 j = fetch(vUv + vec2(-1.0, 1.0) * t);\n vec3 k = fetch(vUv + vec2( 1.0, 1.0) * t);\n vec3 l = fetch(vUv + vec2(-1.0, -1.0) * t);\n vec3 m = fetch(vUv + vec2( 1.0, -1.0) * t);\n\n vec3 box0 = (a + b + d + e) * 0.25;\n vec3 box1 = (b + c + e + f) * 0.25;\n vec3 box2 = (d + e + g + h) * 0.25;\n vec3 box3 = (e + f + h + i) * 0.25;\n vec3 box4 = (j + k + l + m) * 0.25;\n\n float w0 = firefly(box0) * 0.125;\n float w1 = firefly(box1) * 0.125;\n float w2 = firefly(box2) * 0.125;\n float w3 = firefly(box3) * 0.125;\n float w4 = firefly(box4) * 0.5;\n vec3 sum = box0 * w0 + box1 * w1 + box2 * w2 + box3 * w3 + box4 * w4;\n fragColor = vec4(sum / max(w0 + w1 + w2 + w3 + w4, 1e-4), 1.0);\n}\n";
|
|
51
|
+
export declare const BLOOM_PREFILTER_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\n\nuniform sampler2D uSource;\n/** One source texel in UV. The thirteen taps are laid out in source texels. */\nuniform vec2 uTexel;\n/** In scene units. 1.0 is \"brighter than white\", which is the useful place to start. */\nuniform float uThreshold;\n/**\n * Scene units over which a colour comes in past the threshold, or 0 to subtract the threshold\n * instead. See `BloomResponse.ramp`.\n */\nuniform float uRamp;\n\nout vec4 fragColor;\n\n/**\n * A ceiling on what any one pixel may contribute, well above anything a scene means.\n *\n * Not a look control. A half-float target can hold an infinity, and an infinity through the\n * threshold below is inf/inf, which is a NaN that the chain then spreads across the whole\n * frame \u2014 the one failure mode where a post pass destroys the picture rather than degrading\n * it. Anything above this is indistinguishable after the tone curve anyway.\n */\nconst float BLOOM_CEILING = 256.0;\n\nfloat brightness(vec3 c) {\n return max(c.r, max(c.g, c.b));\n}\n\nvec3 fetch(vec2 uv) {\n vec3 c = min(textureLod(uSource, uv, 0.0).rgb, vec3(BLOOM_CEILING));\n float bright = brightness(c);\n /* A ramp keeps a share of the colour, all of it a ramp's width past the line. */\n if (uRamp > 0.0) return c * clamp((bright - uThreshold) / uRamp, 0.0, 1.0);\n return c * (max(bright - uThreshold, 0.0) / max(bright, 1e-4));\n}\n\n/** How much a box counts for. A runaway pixel drags its box's brightness up and its weight down. */\nfloat firefly(vec3 c) {\n return 1.0 / (1.0 + brightness(c));\n}\n\nvoid main() {\n\n vec2 t = uTexel;\n vec3 a = fetch(vUv + vec2(-2.0, 2.0) * t);\n vec3 b = fetch(vUv + vec2( 0.0, 2.0) * t);\n vec3 c = fetch(vUv + vec2( 2.0, 2.0) * t);\n vec3 d = fetch(vUv + vec2(-2.0, 0.0) * t);\n vec3 e = fetch(vUv);\n vec3 f = fetch(vUv + vec2( 2.0, 0.0) * t);\n vec3 g = fetch(vUv + vec2(-2.0, -2.0) * t);\n vec3 h = fetch(vUv + vec2( 0.0, -2.0) * t);\n vec3 i = fetch(vUv + vec2( 2.0, -2.0) * t);\n vec3 j = fetch(vUv + vec2(-1.0, 1.0) * t);\n vec3 k = fetch(vUv + vec2( 1.0, 1.0) * t);\n vec3 l = fetch(vUv + vec2(-1.0, -1.0) * t);\n vec3 m = fetch(vUv + vec2( 1.0, -1.0) * t);\n\n vec3 box0 = (a + b + d + e) * 0.25;\n vec3 box1 = (b + c + e + f) * 0.25;\n vec3 box2 = (d + e + g + h) * 0.25;\n vec3 box3 = (e + f + h + i) * 0.25;\n vec3 box4 = (j + k + l + m) * 0.25;\n\n float w0 = firefly(box0) * 0.125;\n float w1 = firefly(box1) * 0.125;\n float w2 = firefly(box2) * 0.125;\n float w3 = firefly(box3) * 0.125;\n float w4 = firefly(box4) * 0.5;\n vec3 sum = box0 * w0 + box1 * w1 + box2 * w2 + box3 * w3 + box4 * w4;\n fragColor = vec4(sum / max(w0 + w1 + w2 + w3 + w4, 1e-4), 1.0);\n}\n";
|
|
50
52
|
/**
|
|
51
53
|
* Stage two: halve what is already thresholded, once per level.
|
|
52
54
|
*
|
|
@@ -58,7 +60,9 @@ export declare const BLOOM_DOWNSAMPLE_FRAG = "#version 300 es\nprecision highp f
|
|
|
58
60
|
/**
|
|
59
61
|
* Stage three: a 3x3 tent, added into the level above.
|
|
60
62
|
*
|
|
61
|
-
* **Additive rather than a mix, and each octave counts the same
|
|
63
|
+
* **Additive rather than a mix, and each octave counts the same** unless a tint says otherwise:
|
|
64
|
+
* the level being added onto is multiplied by its own tint as it is, by the blend, and the deepest
|
|
65
|
+
* by `uScale` on the first draw. White everywhere is exactly the sum. A lone bright pixel arrives
|
|
62
66
|
* at level n divided by four to the n, spread over four to the n pixels, so summing the levels
|
|
63
67
|
* lifts its own pixel by about a third and lays a long shallow tail around it. That falloff is
|
|
64
68
|
* what a real glare looks like; a single gaussian gives a disc with an end to it.
|
|
@@ -67,4 +71,4 @@ export declare const BLOOM_DOWNSAMPLE_FRAG = "#version 300 es\nprecision highp f
|
|
|
67
71
|
* level and on every display. A tent rather than a box because the seams between the four
|
|
68
72
|
* texels a bilinear upsample reads are otherwise visible as a grid on a wide, faint halo.
|
|
69
73
|
*/
|
|
70
|
-
export declare const BLOOM_UPSAMPLE_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\n\nuniform sampler2D uSource;\n/** How far the tent reaches, in UV. One number for the whole chain. */\nuniform float uRadius;\n\nout vec4 fragColor;\n\nvoid main() {\n float r = uRadius;\n vec3 a = textureLod(uSource, vUv + vec2(-r, r), 0.0).rgb;\n vec3 b = textureLod(uSource, vUv + vec2(0.0, r), 0.0).rgb;\n vec3 c = textureLod(uSource, vUv + vec2( r, r), 0.0).rgb;\n vec3 d = textureLod(uSource, vUv + vec2(-r, 0.0), 0.0).rgb;\n vec3 e = textureLod(uSource, vUv, 0.0).rgb;\n vec3 f = textureLod(uSource, vUv + vec2( r, 0.0), 0.0).rgb;\n vec3 g = textureLod(uSource, vUv + vec2(-r, -r), 0.0).rgb;\n vec3 h = textureLod(uSource, vUv + vec2(0.0, -r), 0.0).rgb;\n vec3 i = textureLod(uSource, vUv + vec2( r, -r), 0.0).rgb;\n\n vec3 sum = e * 4.0 + (b + d + f + h) * 2.0 + (a + c + g + i);\n fragColor = vec4(sum * 0.0625, 1.0);\n}\n";
|
|
74
|
+
export declare const BLOOM_UPSAMPLE_FRAG = "#version 300 es\nprecision highp float;\n\nin vec2 vUv;\n\nuniform sampler2D uSource;\n/**\n * What the level being read is multiplied by: its tint, on the first draw up from the deepest\n * level, and white on every other \u2014 each other level's tint is applied to the level below as it\n * is added onto, by the blend. See `BloomResponse.tints`.\n */\nuniform vec3 uScale;\n/** How far the tent reaches, in UV. One number for the whole chain. */\nuniform float uRadius;\n\nout vec4 fragColor;\n\nvoid main() {\n float r = uRadius;\n vec3 a = textureLod(uSource, vUv + vec2(-r, r), 0.0).rgb;\n vec3 b = textureLod(uSource, vUv + vec2(0.0, r), 0.0).rgb;\n vec3 c = textureLod(uSource, vUv + vec2( r, r), 0.0).rgb;\n vec3 d = textureLod(uSource, vUv + vec2(-r, 0.0), 0.0).rgb;\n vec3 e = textureLod(uSource, vUv, 0.0).rgb;\n vec3 f = textureLod(uSource, vUv + vec2( r, 0.0), 0.0).rgb;\n vec3 g = textureLod(uSource, vUv + vec2(-r, -r), 0.0).rgb;\n vec3 h = textureLod(uSource, vUv + vec2(0.0, -r), 0.0).rgb;\n vec3 i = textureLod(uSource, vUv + vec2( r, -r), 0.0).rgb;\n\n vec3 sum = e * 4.0 + (b + d + f + h) * 2.0 + (a + c + g + i);\n fragColor = vec4(sum * 0.0625 * uScale, 1.0);\n}\n";
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
* 3. **Upsample**, additively back up the chain through a tent filter.
|
|
28
28
|
*
|
|
29
29
|
* Deliberately absent, because they were asked against by name: lens dirt, anamorphic streaks
|
|
30
|
-
* and ghosting. One threshold, one radius, one strength.
|
|
30
|
+
* and ghosting. One threshold, one radius, one strength — and, since 4.8.6, how a colour comes in
|
|
31
|
+
* past the threshold and a tint for each octave, which is how another engine's stage is matched
|
|
32
|
+
* rather than how this one looks: see `BloomResponse` in `bloomChain.ts`.
|
|
31
33
|
*/
|
|
32
34
|
/**
|
|
33
35
|
* The thirteen taps every downsampling stage shares, decomposed into five overlapping boxes.
|
|
@@ -125,6 +127,11 @@ uniform sampler2D uSource;
|
|
|
125
127
|
uniform vec2 uTexel;
|
|
126
128
|
/** In scene units. 1.0 is "brighter than white", which is the useful place to start. */
|
|
127
129
|
uniform float uThreshold;
|
|
130
|
+
/**
|
|
131
|
+
* Scene units over which a colour comes in past the threshold, or 0 to subtract the threshold
|
|
132
|
+
* instead. See \`BloomResponse.ramp\`.
|
|
133
|
+
*/
|
|
134
|
+
uniform float uRamp;
|
|
128
135
|
|
|
129
136
|
out vec4 fragColor;
|
|
130
137
|
|
|
@@ -141,6 +148,8 @@ ${BLOOM_BRIGHTNESS}
|
|
|
141
148
|
vec3 fetch(vec2 uv) {
|
|
142
149
|
vec3 c = min(textureLod(uSource, uv, 0.0).rgb, vec3(BLOOM_CEILING));
|
|
143
150
|
float bright = brightness(c);
|
|
151
|
+
/* A ramp keeps a share of the colour, all of it a ramp's width past the line. */
|
|
152
|
+
if (uRamp > 0.0) return c * clamp((bright - uThreshold) / uRamp, 0.0, 1.0);
|
|
144
153
|
return c * (max(bright - uThreshold, 0.0) / max(bright, 1e-4));
|
|
145
154
|
}
|
|
146
155
|
|
|
@@ -189,7 +198,9 @@ ${BLOOM_BOXES}
|
|
|
189
198
|
/**
|
|
190
199
|
* Stage three: a 3x3 tent, added into the level above.
|
|
191
200
|
*
|
|
192
|
-
* **Additive rather than a mix, and each octave counts the same
|
|
201
|
+
* **Additive rather than a mix, and each octave counts the same** unless a tint says otherwise:
|
|
202
|
+
* the level being added onto is multiplied by its own tint as it is, by the blend, and the deepest
|
|
203
|
+
* by `uScale` on the first draw. White everywhere is exactly the sum. A lone bright pixel arrives
|
|
193
204
|
* at level n divided by four to the n, spread over four to the n pixels, so summing the levels
|
|
194
205
|
* lifts its own pixel by about a third and lays a long shallow tail around it. That falloff is
|
|
195
206
|
* what a real glare looks like; a single gaussian gives a disc with an end to it.
|
|
@@ -204,6 +215,12 @@ precision highp float;
|
|
|
204
215
|
in vec2 vUv;
|
|
205
216
|
|
|
206
217
|
uniform sampler2D uSource;
|
|
218
|
+
/**
|
|
219
|
+
* What the level being read is multiplied by: its tint, on the first draw up from the deepest
|
|
220
|
+
* level, and white on every other — each other level's tint is applied to the level below as it
|
|
221
|
+
* is added onto, by the blend. See \`BloomResponse.tints\`.
|
|
222
|
+
*/
|
|
223
|
+
uniform vec3 uScale;
|
|
207
224
|
/** How far the tent reaches, in UV. One number for the whole chain. */
|
|
208
225
|
uniform float uRadius;
|
|
209
226
|
|
|
@@ -222,6 +239,6 @@ void main() {
|
|
|
222
239
|
vec3 i = textureLod(uSource, vUv + vec2( r, -r), 0.0).rgb;
|
|
223
240
|
|
|
224
241
|
vec3 sum = e * 4.0 + (b + d + f + h) * 2.0 + (a + c + g + i);
|
|
225
|
-
fragColor = vec4(sum * 0.0625, 1.0);
|
|
242
|
+
fragColor = vec4(sum * 0.0625 * uScale, 1.0);
|
|
226
243
|
}
|
|
227
244
|
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One rectangle's light on a fragment: its form factor, and what it adds once its occlusion is
|
|
3
|
+
* known. **The one statement of it**, called by the fixed loop over the first `maxAreaLights`
|
|
4
|
+
* rectangles and by the froxel table's for the rest, so the two arms shade a rectangle with one
|
|
5
|
+
* body rather than two transcriptions of it — the zero-pixel gate between them means something
|
|
6
|
+
* only if that is so.
|
|
7
|
+
*
|
|
8
|
+
* Depth zero and after the models, whose `modelLight` it calls; see `flatFrag`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AREA_LIGHT_GLSL = "\n/**\n * The rectangle's signed form factor at this fragment: its corners **wound so a surface on the\n * emitting side sees them counter-clockwise**, which is what makes it positive there. The natural\n * order \u2014 minus, plus, plus, minus \u2014 winds the other way, and every one-sided light then clamped to\n * zero and the frame was black. The sign is the sidedness: a one-sided rectangle takes the positive\n * part and a two-sided one the magnitude.\n */\nfloat areaSignedForm(vec3 n, vec3 centre, vec3 right, vec3 up, vec2 halfSize) {\n vec3 c0 = centre - right * halfSize.x + up * halfSize.y;\n vec3 c1 = centre + right * halfSize.x + up * halfSize.y;\n vec3 c2 = centre + right * halfSize.x - up * halfSize.y;\n vec3 c3 = centre - right * halfSize.x - up * halfSize.y;\n return quadFormFactor(n, vWorldPos, c0, c1, c2, c3);\n}\n\n/**\n * What one rectangle of colour `color` and form factor `form` adds to the lamps' two sums, open\n * and shadowed by `occl` and `glass`.\n *\n * **A model answers a rectangle as it answers a lamp at its centre**, carried at the rectangle's\n * own energy: the diffuse by the exact form factor over the centre's cosine, the specular by the\n * form factor. An approximation, stated: a model's lobe is not integrated over the rectangle as the\n * standard lobe is, so a glossy model under a large softbox shows a lobe from its centre rather than\n * its shape.\n *\n * **The standard lobe is integrated over the rectangle**, rather than sampled at one point on it.\n * What stood here once handed the closest point on the rectangle to `sphereLobe` and scaled it by\n * `form`, the *diffuse* form factor. Measured against a brute-force integral by\n * `scripts/areaSpecular.mjs`, that lost the reflection rather than blurring it: on polished metal\n * facing a softbox it returned 0.000233 where the integral is 0.9207, and head-on on a rough surface\n * it was 3.7 times too bright. Worst 287% either way; this is 40.1% where the light is what the\n * surface reflects and 79.0% for one overhead. `quadCoverage` says what fraction of the specular\n * lobe the rectangle covers and the environment BRDF says how much the surface returns, the split\n * the probe path uses. **`dfg.x + dfg.y` and not `envSpecularEnergy`**, deliberately: the\n * compensation returns the multiply-scattered share, and every direct lobe in this shader is\n * single-scattering, so adding it here returns energy the light never had. Measured at 211%\n * against 40.1%.\n */\nvoid areaLightAdd(\n vec3 centre,\n vec3 right,\n vec3 up,\n vec2 halfSize,\n vec3 color,\n float form,\n float occl,\n vec3 glass,\n vec3 n,\n vec3 albedo,\n float metal,\n vec3 specColor,\n float roughness,\n bool modelled,\n inout vec3 lampOpen,\n inout vec3 lampShadowed\n) {\n if (modelled) {\n vec3 areaL = normalize(centre - vWorldPos);\n modelLight(areaL, 0.0, 0.0);\n vec3 areaModel = (mDiffuse * (form / max(dot(n, areaL), 1e-3)) + mSpecular * form) * color;\n lampOpen += areaModel;\n lampShadowed += areaModel * occl * glass;\n return;\n }\n /* Held back with the point lamps', and shadowed by the same term it always was. */\n vec3 areaDiffuse = albedo * color * form * (1.0 - metal);\n lampOpen += areaDiffuse;\n lampShadowed += areaDiffuse * occl * glass;\n\n if (vSpecular > 0.0 || metal > 0.0) {\n vec3 c0 = centre - right * halfSize.x + up * halfSize.y;\n vec3 c1 = centre + right * halfSize.x + up * halfSize.y;\n vec3 c2 = centre + right * halfSize.x - up * halfSize.y;\n vec3 c3 = centre - right * halfSize.x - up * halfSize.y;\n vec3 toEyeArea = normalize(uCameraPos - vWorldPos);\n float coverage = quadCoverage(n, vWorldPos, toEyeArea, roughness, c0, c1, c2, c3);\n if (coverage > 0.0) {\n vec2 areaDfg = envBrdfApprox(max(dot(n, toEyeArea), 0.0), roughness);\n vec3 areaSpec = specColor * areaDfg.x + vec3(areaDfg.y);\n vec3 areaHighlight = areaSpec * color * coverage;\n lampOpen += areaHighlight;\n lampShadowed += areaHighlight * occl * glass;\n }\n }\n}\n";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One rectangle's light on a fragment: its form factor, and what it adds once its occlusion is
|
|
3
|
+
* known. **The one statement of it**, called by the fixed loop over the first `maxAreaLights`
|
|
4
|
+
* rectangles and by the froxel table's for the rest, so the two arms shade a rectangle with one
|
|
5
|
+
* body rather than two transcriptions of it — the zero-pixel gate between them means something
|
|
6
|
+
* only if that is so.
|
|
7
|
+
*
|
|
8
|
+
* Depth zero and after the models, whose `modelLight` it calls; see `flatFrag`.
|
|
9
|
+
*/
|
|
10
|
+
export const AREA_LIGHT_GLSL = /* glsl */ `
|
|
11
|
+
/**
|
|
12
|
+
* The rectangle's signed form factor at this fragment: its corners **wound so a surface on the
|
|
13
|
+
* emitting side sees them counter-clockwise**, which is what makes it positive there. The natural
|
|
14
|
+
* order — minus, plus, plus, minus — winds the other way, and every one-sided light then clamped to
|
|
15
|
+
* zero and the frame was black. The sign is the sidedness: a one-sided rectangle takes the positive
|
|
16
|
+
* part and a two-sided one the magnitude.
|
|
17
|
+
*/
|
|
18
|
+
float areaSignedForm(vec3 n, vec3 centre, vec3 right, vec3 up, vec2 halfSize) {
|
|
19
|
+
vec3 c0 = centre - right * halfSize.x + up * halfSize.y;
|
|
20
|
+
vec3 c1 = centre + right * halfSize.x + up * halfSize.y;
|
|
21
|
+
vec3 c2 = centre + right * halfSize.x - up * halfSize.y;
|
|
22
|
+
vec3 c3 = centre - right * halfSize.x - up * halfSize.y;
|
|
23
|
+
return quadFormFactor(n, vWorldPos, c0, c1, c2, c3);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* What one rectangle of colour \`color\` and form factor \`form\` adds to the lamps' two sums, open
|
|
28
|
+
* and shadowed by \`occl\` and \`glass\`.
|
|
29
|
+
*
|
|
30
|
+
* **A model answers a rectangle as it answers a lamp at its centre**, carried at the rectangle's
|
|
31
|
+
* own energy: the diffuse by the exact form factor over the centre's cosine, the specular by the
|
|
32
|
+
* form factor. An approximation, stated: a model's lobe is not integrated over the rectangle as the
|
|
33
|
+
* standard lobe is, so a glossy model under a large softbox shows a lobe from its centre rather than
|
|
34
|
+
* its shape.
|
|
35
|
+
*
|
|
36
|
+
* **The standard lobe is integrated over the rectangle**, rather than sampled at one point on it.
|
|
37
|
+
* What stood here once handed the closest point on the rectangle to \`sphereLobe\` and scaled it by
|
|
38
|
+
* \`form\`, the *diffuse* form factor. Measured against a brute-force integral by
|
|
39
|
+
* \`scripts/areaSpecular.mjs\`, that lost the reflection rather than blurring it: on polished metal
|
|
40
|
+
* facing a softbox it returned 0.000233 where the integral is 0.9207, and head-on on a rough surface
|
|
41
|
+
* it was 3.7 times too bright. Worst 287% either way; this is 40.1% where the light is what the
|
|
42
|
+
* surface reflects and 79.0% for one overhead. \`quadCoverage\` says what fraction of the specular
|
|
43
|
+
* lobe the rectangle covers and the environment BRDF says how much the surface returns, the split
|
|
44
|
+
* the probe path uses. **\`dfg.x + dfg.y\` and not \`envSpecularEnergy\`**, deliberately: the
|
|
45
|
+
* compensation returns the multiply-scattered share, and every direct lobe in this shader is
|
|
46
|
+
* single-scattering, so adding it here returns energy the light never had. Measured at 211%
|
|
47
|
+
* against 40.1%.
|
|
48
|
+
*/
|
|
49
|
+
void areaLightAdd(
|
|
50
|
+
vec3 centre,
|
|
51
|
+
vec3 right,
|
|
52
|
+
vec3 up,
|
|
53
|
+
vec2 halfSize,
|
|
54
|
+
vec3 color,
|
|
55
|
+
float form,
|
|
56
|
+
float occl,
|
|
57
|
+
vec3 glass,
|
|
58
|
+
vec3 n,
|
|
59
|
+
vec3 albedo,
|
|
60
|
+
float metal,
|
|
61
|
+
vec3 specColor,
|
|
62
|
+
float roughness,
|
|
63
|
+
bool modelled,
|
|
64
|
+
inout vec3 lampOpen,
|
|
65
|
+
inout vec3 lampShadowed
|
|
66
|
+
) {
|
|
67
|
+
if (modelled) {
|
|
68
|
+
vec3 areaL = normalize(centre - vWorldPos);
|
|
69
|
+
modelLight(areaL, 0.0, 0.0);
|
|
70
|
+
vec3 areaModel = (mDiffuse * (form / max(dot(n, areaL), 1e-3)) + mSpecular * form) * color;
|
|
71
|
+
lampOpen += areaModel;
|
|
72
|
+
lampShadowed += areaModel * occl * glass;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
/* Held back with the point lamps', and shadowed by the same term it always was. */
|
|
76
|
+
vec3 areaDiffuse = albedo * color * form * (1.0 - metal);
|
|
77
|
+
lampOpen += areaDiffuse;
|
|
78
|
+
lampShadowed += areaDiffuse * occl * glass;
|
|
79
|
+
|
|
80
|
+
if (vSpecular > 0.0 || metal > 0.0) {
|
|
81
|
+
vec3 c0 = centre - right * halfSize.x + up * halfSize.y;
|
|
82
|
+
vec3 c1 = centre + right * halfSize.x + up * halfSize.y;
|
|
83
|
+
vec3 c2 = centre + right * halfSize.x - up * halfSize.y;
|
|
84
|
+
vec3 c3 = centre - right * halfSize.x - up * halfSize.y;
|
|
85
|
+
vec3 toEyeArea = normalize(uCameraPos - vWorldPos);
|
|
86
|
+
float coverage = quadCoverage(n, vWorldPos, toEyeArea, roughness, c0, c1, c2, c3);
|
|
87
|
+
if (coverage > 0.0) {
|
|
88
|
+
vec2 areaDfg = envBrdfApprox(max(dot(n, toEyeArea), 0.0), roughness);
|
|
89
|
+
vec3 areaSpec = specColor * areaDfg.x + vec3(areaDfg.y);
|
|
90
|
+
vec3 areaHighlight = areaSpec * color * coverage;
|
|
91
|
+
lampOpen += areaHighlight;
|
|
92
|
+
lampShadowed += areaHighlight * occl * glass;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
@@ -15,6 +15,8 @@ import { SKINNING_GLSL } from '../skinning.js';
|
|
|
15
15
|
import { SURFACE_GLSL } from './surface.js';
|
|
16
16
|
import { MAIN_GLSL } from './main.js';
|
|
17
17
|
import { MODELS_GLSL } from './models.js';
|
|
18
|
+
import { AREA_LIGHT_GLSL } from './areaLight.js';
|
|
19
|
+
import { NORMAL_MAP_GLSL } from './normalMap.js';
|
|
18
20
|
import { DRIFT_LIGHT_GLSL } from './driftLight.js';
|
|
19
21
|
import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.js';
|
|
20
22
|
import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from '../vertexChannel.js';
|
|
@@ -106,7 +108,17 @@ layout(location = 11) in vec4 aInstanceModel0;
|
|
|
106
108
|
layout(location = 12) in vec4 aInstanceModel1;
|
|
107
109
|
layout(location = 13) in vec4 aInstanceModel2;
|
|
108
110
|
layout(location = 14) in vec4 aInstanceModel3;
|
|
109
|
-
|
|
111
|
+
/**
|
|
112
|
+
* The instance's tint, and its opacity in w: \`MeshInstances.alphas\`, 1 where none was given — or,
|
|
113
|
+
* in a lightmapped batch's pipeline, its region of the page (\`MeshInstances.lightmapRegions\`).
|
|
114
|
+
*/
|
|
115
|
+
layout(location = 15) in vec4 aInstanceTint;
|
|
116
|
+
/*
|
|
117
|
+
* On in the pipeline a lightmapped material draws an instanced batch with, where the lane above is
|
|
118
|
+
* a region and not a tint: the second coordinates leave scaled and offset by it, under a tint and an
|
|
119
|
+
* opacity of one. See lightmap.ts.
|
|
120
|
+
*/
|
|
121
|
+
const bool LIGHTMAP_REGIONS = false; // wgsl:override
|
|
110
122
|
#else
|
|
111
123
|
uniform mat4 uModel;
|
|
112
124
|
/**
|
|
@@ -232,7 +244,8 @@ void main() {
|
|
|
232
244
|
#endif
|
|
233
245
|
#if INSTANCED
|
|
234
246
|
mat4 model = mat4(aInstanceModel0, aInstanceModel1, aInstanceModel2, aInstanceModel3);
|
|
235
|
-
vec3 tint = aInstanceTint;
|
|
247
|
+
vec3 tint = aInstanceTint.rgb;
|
|
248
|
+
if (LIGHTMAP_REGIONS) tint = vec3(1.0);
|
|
236
249
|
#else
|
|
237
250
|
mat4 model = uModel;
|
|
238
251
|
vec3 tint = uTint;
|
|
@@ -286,10 +299,19 @@ void main() {
|
|
|
286
299
|
vRelief = aRelief;
|
|
287
300
|
vTangent = vec4(worldTangent, aTangent.w);
|
|
288
301
|
#if INSTANCED
|
|
289
|
-
/*
|
|
302
|
+
/*
|
|
303
|
+
* No channel on this path; the neutral values are what every mesh read before it existed — but
|
|
304
|
+
* for the opacity, which an instance carries of its own, 1 where none was given.
|
|
305
|
+
*/
|
|
290
306
|
vSkyDirect = 1.0;
|
|
291
|
-
vAlpha =
|
|
307
|
+
vAlpha = aInstanceTint.w;
|
|
292
308
|
vThickness = 1.0;
|
|
309
|
+
if (LIGHTMAP_REGIONS) {
|
|
310
|
+
/* Out of the lane's \`-1 - uv\`, through the region, and back into it. See lightmap.ts. */
|
|
311
|
+
vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x + aInstanceTint.z);
|
|
312
|
+
vRelief = -1.0 - ((-1.0 - aRelief) * aInstanceTint.y + aInstanceTint.w);
|
|
313
|
+
vAlpha = 1.0;
|
|
314
|
+
}
|
|
293
315
|
#else
|
|
294
316
|
vSkyDirect = aChannel.y;
|
|
295
317
|
vAlpha = aChannel.z;
|
|
@@ -426,6 +448,8 @@ export function flatFrag(options) {
|
|
|
426
448
|
* and `main` is unconditional by construction.
|
|
427
449
|
*/
|
|
428
450
|
TANGENT_FRAME_GLSL,
|
|
451
|
+
/* After the frame it calls, unconditional for the same reason. See normalMap.ts. */
|
|
452
|
+
NORMAL_MAP_GLSL,
|
|
429
453
|
/* Unconditional for the same reason, and called only where a cutoff was asked for. */
|
|
430
454
|
CUTOUT_COVERAGE_GLSL,
|
|
431
455
|
CUTOUT_DITHER_GLSL,
|
|
@@ -433,6 +457,8 @@ export function flatFrag(options) {
|
|
|
433
457
|
SURFACE_EFFECTS_GLSL,
|
|
434
458
|
/* Depth zero and after the tangent frame and the lobes it calls; see models.ts. */
|
|
435
459
|
MODELS_GLSL,
|
|
460
|
+
/* After the models, whose answer to a rectangle it asks for. See areaLight.ts. */
|
|
461
|
+
AREA_LIGHT_GLSL,
|
|
436
462
|
MAIN_GLSL,
|
|
437
463
|
].join('\n'), {
|
|
438
464
|
POINT_SHADOWS: options.pointShadows,
|