@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { PARTICLE_MOTE_FRAG, PARTICLE_SMOKE_FRAG, PARTICLE_SPARK_FRAG, PARTICLE_VERT, } from './shaders/particle.js';
|
|
2
|
-
import { PARTICLE_BINDINGS, PARTICLE_MOTE_FRAG_WGSL, PARTICLE_SMOKE_FRAG_WGSL, PARTICLE_SPARK_FRAG_WGSL, PARTICLE_VERT_WGSL, } from './shaders/generated/particle.wgsl.js';
|
|
1
|
+
import { PARTICLE_MOTE_FRAG, PARTICLE_SMOKE_FRAG, PARTICLE_SPARK_FRAG, PARTICLE_SPRITE_FRAG, PARTICLE_VERT, } from './shaders/particle.js';
|
|
2
|
+
import { PARTICLE_BINDINGS, PARTICLE_MOTE_FRAG_WGSL, PARTICLE_SMOKE_FRAG_WGSL, PARTICLE_SPARK_FRAG_WGSL, PARTICLE_SPRITE_FRAG_WGSL, PARTICLE_VERT_WGSL, } from './shaders/generated/particle.wgsl.js';
|
|
3
3
|
/**
|
|
4
4
|
* Every particle material this engine ships.
|
|
5
5
|
*
|
|
@@ -28,6 +28,14 @@ const MATERIALS = {
|
|
|
28
28
|
vertexWgsl: PARTICLE_VERT_WGSL,
|
|
29
29
|
fragmentWgsl: PARTICLE_MOTE_FRAG_WGSL,
|
|
30
30
|
},
|
|
31
|
+
/* An image a consumer supplies, not a shape this engine draws: see `PARTICLE_SPRITE_FRAG`. */
|
|
32
|
+
sprite: {
|
|
33
|
+
label: 'particle:sprite',
|
|
34
|
+
vertexSource: PARTICLE_VERT,
|
|
35
|
+
fragmentSource: PARTICLE_SPRITE_FRAG,
|
|
36
|
+
vertexWgsl: PARTICLE_VERT_WGSL,
|
|
37
|
+
fragmentWgsl: PARTICLE_SPRITE_FRAG_WGSL,
|
|
38
|
+
},
|
|
31
39
|
};
|
|
32
40
|
/** What the generator recorded about the shared vertex stage. */
|
|
33
41
|
export const PARTICLE_VERT_BINDINGS = PARTICLE_BINDINGS.PARTICLE_VERT;
|
|
@@ -36,9 +36,41 @@ 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
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Floats one particle takes in a buffer a caller's own compute writes (`DeviceParticles`), in this
|
|
55
|
+
* order: position 3, half-width 1, roll 1, colour 3, opacity 1, age 1, seed 1, velocity 3, a
|
|
56
|
+
* sprite's frame 1 and its half-height 1 — `ParticleInstances`' streams, interleaved as the pool's
|
|
57
|
+
* own upload interleaves them. 64 bytes a particle.
|
|
58
|
+
*/
|
|
59
|
+
export declare const DEVICE_PARTICLE_FLOATS = 16;
|
|
60
|
+
/**
|
|
61
|
+
* Particles a caller's own compute shader wrote into a buffer of its own (`registerCompute`), drawn
|
|
62
|
+
* where they lie by `drawDeviceParticles` rather than read back and uploaded again. WebGPU only.
|
|
63
|
+
*
|
|
64
|
+
* **Not sorted**, as a pool's are where its batch asks: the device holds them, so an alpha-blended
|
|
65
|
+
* set that must be drawn far to near sorts itself. A slot with a half-width of 0 draws nothing, which
|
|
66
|
+
* is how a fixed-size buffer holds fewer live particles than it has room for.
|
|
67
|
+
*/
|
|
68
|
+
export interface DeviceParticles {
|
|
69
|
+
/** A buffer with `VERTEX` usage, `DEVICE_PARTICLE_FLOATS` floats a particle. */
|
|
70
|
+
readonly buffer: GPUBuffer;
|
|
71
|
+
/** How many particles from its start to draw; no more than the buffer holds. */
|
|
72
|
+
readonly count: number;
|
|
73
|
+
}
|
|
42
74
|
/**
|
|
43
75
|
* A pool of short-lived particles, simulated on the CPU and drawn as instances.
|
|
44
76
|
*
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { createInstanceData, writeInstance } from './instancedMesh.js';
|
|
2
|
+
/**
|
|
3
|
+
* Floats one particle takes in a buffer a caller's own compute writes (`DeviceParticles`), in this
|
|
4
|
+
* order: position 3, half-width 1, roll 1, colour 3, opacity 1, age 1, seed 1, velocity 3, a
|
|
5
|
+
* sprite's frame 1 and its half-height 1 — `ParticleInstances`' streams, interleaved as the pool's
|
|
6
|
+
* own upload interleaves them. 64 bytes a particle.
|
|
7
|
+
*/
|
|
8
|
+
export const DEVICE_PARTICLE_FLOATS = 16;
|
|
2
9
|
function createParticleInstances(capacity) {
|
|
3
10
|
return {
|
|
4
11
|
positions: new Float32Array(capacity * 3),
|
|
@@ -9,6 +16,8 @@ function createParticleInstances(capacity) {
|
|
|
9
16
|
ages: new Float32Array(capacity),
|
|
10
17
|
seeds: new Float32Array(capacity),
|
|
11
18
|
velocities: new Float32Array(capacity * 3),
|
|
19
|
+
frames: new Float32Array(capacity),
|
|
20
|
+
heights: new Float32Array(capacity),
|
|
12
21
|
count: 0,
|
|
13
22
|
capacity,
|
|
14
23
|
};
|
|
@@ -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
|
+
}
|
|
@@ -69,6 +69,14 @@ export interface PointLightSource {
|
|
|
69
69
|
* whose uniform budget has none to spare.
|
|
70
70
|
*/
|
|
71
71
|
inLightField?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* How the light fades with distance when it names its own way: `(1 - (d/R)^2)^n` with no distance
|
|
74
|
+
* term, `R` its radius and `n` this. Unreal's falloff for a light without inverse-square falloff,
|
|
75
|
+
* so a rig authored there — a fill of brightness 160 across 164 m, a key at exponent 8 — lights
|
|
76
|
+
* the space it was authored for rather than one that ends a few metres out. Absent or 0, the
|
|
77
|
+
* frame's own falloff (`pointLightFalloff`), which is every light before 4.8.7.
|
|
78
|
+
*/
|
|
79
|
+
falloffExponent?: number;
|
|
72
80
|
}
|
|
73
81
|
export interface PointLightBuffer {
|
|
74
82
|
readonly positions: Float32Array;
|
|
@@ -126,6 +134,11 @@ export interface PointLightBuffer {
|
|
|
126
134
|
readonly directions: Float32Array;
|
|
127
135
|
/** Two per slot: the cosine of the inner cone angle, then of the outer. */
|
|
128
136
|
readonly coneCos: Float32Array;
|
|
137
|
+
/**
|
|
138
|
+
* One per slot: the light's own falloff exponent, or 0 for the frame's falloff. See
|
|
139
|
+
* `PointLightSource.falloffExponent`.
|
|
140
|
+
*/
|
|
141
|
+
readonly falloffExponents: Float32Array;
|
|
129
142
|
/** One per slot: a row of the photometric atlas, or −1 for a light with no profile. */
|
|
130
143
|
readonly iesProfiles: Float32Array;
|
|
131
144
|
/** Internal insertion-sort storage; exposed only to keep one flat object. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Allocation-free selection and packing of point lights for a fixed shader budget. */
|
|
2
|
-
import { POINT_LIGHT_COS_INNER, POINT_LIGHT_COS_OUTER } from './clusteredLights.js';
|
|
2
|
+
import { POINT_LIGHT_COS_INNER, POINT_LIGHT_COS_OUTER, lightFalloffExponentOf, } from './clusteredLights.js';
|
|
3
3
|
import { MAX_POINT_LIGHTS, POINT_SHADOW_POOL } from './lightBudget.js';
|
|
4
4
|
import { gatherLights } from './lightGrid.js';
|
|
5
5
|
export const DEFAULT_POINT_LIGHT_VIEW_RANGE = 180;
|
|
@@ -150,6 +150,7 @@ export function createPointLightBuffer(capacity = MAX_POINT_LIGHTS) {
|
|
|
150
150
|
weights: new Float32Array(shaded),
|
|
151
151
|
directions: new Float32Array(shaded * 3),
|
|
152
152
|
coneCos: new Float32Array(shaded * 2),
|
|
153
|
+
falloffExponents: new Float32Array(shaded),
|
|
153
154
|
/* −1 rather than 0, because row 0 is a real fixture and a light that asked for none must not
|
|
154
155
|
be shaped by whichever profile happened to load first. */
|
|
155
156
|
iesProfiles: new Float32Array(shaded).fill(-1),
|
|
@@ -382,6 +383,7 @@ function choose(sources, visit, visitCount, x, y, z, out, timeSeconds, viewRange
|
|
|
382
383
|
* Restricting it to spots would be a smaller feature for no saving.
|
|
383
384
|
*/
|
|
384
385
|
out.iesProfiles[slot] = light.iesProfile ?? -1;
|
|
386
|
+
out.falloffExponents[slot] = lightFalloffExponentOf(light.falloffExponent);
|
|
385
387
|
}
|
|
386
388
|
out.count = chosen;
|
|
387
389
|
/*
|
|
@@ -209,13 +209,14 @@ export interface RenderQuality {
|
|
|
209
209
|
* **`'pre-integrated'`, the default**: the lit stage's fit alone — a terminator softened and
|
|
210
210
|
* reddened by the surface's curvature, light through thin parts, a penumbra each channel crosses
|
|
211
211
|
* as far as it travels — at no cost beyond the skin pipeline itself. **`'screen-space'`** adds
|
|
212
|
-
* Burley's blur in the picture as well: a skin draw writes its diffuse
|
|
213
|
-
* is drawn
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
212
|
+
* Burley's blur in the picture as well: a skin draw writes the light its diffuse is made of to a
|
|
213
|
+
* target of its own and its colour to another (it is drawn three times to do so), and the light is
|
|
214
|
+
* spread across the pixels its scatter distance covers, multiplied by the colour and added back
|
|
215
|
+
* before anything blended is drawn, so light crosses a shadow's edge and a nostril's rim in the
|
|
216
|
+
* picture rather than in a fit. See `skinBlur.ts`.
|
|
217
|
+
*
|
|
218
|
+
* Construction-time: three half-float targets the size of the frame, and two multisampled ones
|
|
219
|
+
* under multisampling, made at the first skin draw. It needs the composite (`screenEffects`), which is
|
|
219
220
|
* where the frame is still linear light to add to; without one, skin is pre-integrated and the
|
|
220
221
|
* renderer says so once. What would make the default wrong is a close-up face that reads as wax
|
|
221
222
|
* at a shadow's edge — that is what the blur is for.
|
|
@@ -76,6 +76,9 @@ export function recordingGl(options = {}) {
|
|
|
76
76
|
ONE: 1,
|
|
77
77
|
ONE_MINUS_SRC_ALPHA: 0x0303,
|
|
78
78
|
TRIANGLES: 0x0004,
|
|
79
|
+
/* The winding a pass names, so a test can tell which one it chose. */
|
|
80
|
+
CW: 0x0900,
|
|
81
|
+
CCW: 0x0901,
|
|
79
82
|
TEXTURE0: 0x84c0,
|
|
80
83
|
DEPTH_BUFFER_BIT: 0x00000100,
|
|
81
84
|
DEPTH_TEST: 0x0b71,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rgb9e5: three 9-bit mantissas over one shared 5-bit exponent, an unsigned float colour in four
|
|
3
|
+
* bytes. `EXT_texture_shared_exponent`'s encoding, the one a GPU's `rgb9e5ufloat` reads, written
|
|
4
|
+
* out so a lightmap page can hold its irradiance in four bytes a texel against a half float's eight.
|
|
5
|
+
*
|
|
6
|
+
* **What it gives up**: precision in the smaller channels, which share the largest one's exponent —
|
|
7
|
+
* a channel a thousandth of its neighbour keeps about one bit — and anything negative or past
|
|
8
|
+
* 65,408, which it clamps. Light is neither, and a bake's colours rarely span more than a few
|
|
9
|
+
* octaves within one texel, so a lightmap loses nothing visible; a value that needs each channel
|
|
10
|
+
* exact wants a format of its own.
|
|
11
|
+
*
|
|
12
|
+
* The bits, low to high: red in 0–8, green in 9–17, blue in 18–26, the exponent in 27–31, biased by
|
|
13
|
+
* 15, and a value is `mantissa * 2^(exponent - 15 - 9)`.
|
|
14
|
+
*/
|
|
15
|
+
/** The largest value the format holds: 511/512 of 2^16. */
|
|
16
|
+
export declare const RGB9E5_MAX: number;
|
|
17
|
+
/** One colour packed into its 32 bits, rounding to nearest; NaN and negatives are zero. */
|
|
18
|
+
export declare function packRgb9e5(red: number, green: number, blue: number): number;
|
|
19
|
+
/** A packed colour's three channels, into `out[at…at+2]`. */
|
|
20
|
+
export declare function unpackRgb9e5(bits: number, out: {
|
|
21
|
+
[index: number]: number;
|
|
22
|
+
}, at?: number): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rgb9e5: three 9-bit mantissas over one shared 5-bit exponent, an unsigned float colour in four
|
|
3
|
+
* bytes. `EXT_texture_shared_exponent`'s encoding, the one a GPU's `rgb9e5ufloat` reads, written
|
|
4
|
+
* out so a lightmap page can hold its irradiance in four bytes a texel against a half float's eight.
|
|
5
|
+
*
|
|
6
|
+
* **What it gives up**: precision in the smaller channels, which share the largest one's exponent —
|
|
7
|
+
* a channel a thousandth of its neighbour keeps about one bit — and anything negative or past
|
|
8
|
+
* 65,408, which it clamps. Light is neither, and a bake's colours rarely span more than a few
|
|
9
|
+
* octaves within one texel, so a lightmap loses nothing visible; a value that needs each channel
|
|
10
|
+
* exact wants a format of its own.
|
|
11
|
+
*
|
|
12
|
+
* The bits, low to high: red in 0–8, green in 9–17, blue in 18–26, the exponent in 27–31, biased by
|
|
13
|
+
* 15, and a value is `mantissa * 2^(exponent - 15 - 9)`.
|
|
14
|
+
*/
|
|
15
|
+
const BIAS = 15;
|
|
16
|
+
const MANTISSA_BITS = 9;
|
|
17
|
+
const MANTISSA_VALUES = 1 << MANTISSA_BITS;
|
|
18
|
+
const MAX_EXPONENT = 31;
|
|
19
|
+
/** The largest value the format holds: 511/512 of 2^16. */
|
|
20
|
+
export const RGB9E5_MAX = ((MANTISSA_VALUES - 1) / MANTISSA_VALUES) * 2 ** (MAX_EXPONENT - BIAS);
|
|
21
|
+
const clampChannel = (v) => (v > 0 ? Math.min(v, RGB9E5_MAX) : 0);
|
|
22
|
+
/** One colour packed into its 32 bits, rounding to nearest; NaN and negatives are zero. */
|
|
23
|
+
export function packRgb9e5(red, green, blue) {
|
|
24
|
+
const r = clampChannel(red);
|
|
25
|
+
const g = clampChannel(green);
|
|
26
|
+
const b = clampChannel(blue);
|
|
27
|
+
const largest = Math.max(r, g, b);
|
|
28
|
+
let exponent = Math.max(-BIAS - 1, Math.floor(Math.log2(largest))) + 1 + BIAS;
|
|
29
|
+
let scale = 2 ** (exponent - BIAS - MANTISSA_BITS);
|
|
30
|
+
/* Rounding the largest channel up to 512 needs one more exponent, which the spec allows for. */
|
|
31
|
+
if (Math.floor(largest / scale + 0.5) === MANTISSA_VALUES) {
|
|
32
|
+
exponent += 1;
|
|
33
|
+
scale *= 2;
|
|
34
|
+
}
|
|
35
|
+
/* Inline rather than a helper: a page packs millions of texels, and a closure a call is one each. */
|
|
36
|
+
const mr = Math.min(MANTISSA_VALUES - 1, Math.floor(r / scale + 0.5));
|
|
37
|
+
const mg = Math.min(MANTISSA_VALUES - 1, Math.floor(g / scale + 0.5));
|
|
38
|
+
const mb = Math.min(MANTISSA_VALUES - 1, Math.floor(b / scale + 0.5));
|
|
39
|
+
return (mr | (mg << 9) | (mb << 18) | (exponent << 27)) >>> 0;
|
|
40
|
+
}
|
|
41
|
+
/** A packed colour's three channels, into `out[at…at+2]`. */
|
|
42
|
+
export function unpackRgb9e5(bits, out, at = 0) {
|
|
43
|
+
const scale = 2 ** ((bits >>> 27) - BIAS - MANTISSA_BITS);
|
|
44
|
+
out[at] = (bits & 511) * scale;
|
|
45
|
+
out[at + 1] = ((bits >>> 9) & 511) * scale;
|
|
46
|
+
out[at + 2] = ((bits >>> 18) & 511) * scale;
|
|
47
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scene capture: a texture the world is drawn into from a camera of the caller's, which a material
|
|
3
|
+
* then shows like any image — a screen in the world showing what a camera elsewhere sees, a monitor,
|
|
4
|
+
* a security feed. The planar reflection's pass, for a caller's camera rather than a mirrored one.
|
|
5
|
+
*
|
|
6
|
+
* **It holds radiance, not display pixels**, as a probe does: drawn with the output transform held
|
|
7
|
+
* off, at the colour format a probe takes — half floats under `hdrScene`, bytes otherwise — and
|
|
8
|
+
* sampled linear, so a material showing it as its `emissive` emits the light the capture saw and the
|
|
9
|
+
* frame's grade then applies once, to the screen as to everything around it.
|
|
10
|
+
*
|
|
11
|
+
* **What it gives up**: it is the mesh pass alone, as a probe bake is — the sky, water, particles and
|
|
12
|
+
* anything else a scene draws after its world are in the capture only if the callback draws them;
|
|
13
|
+
* no temporal resolve, bloom or other effect after the world runs on it; and it has no mip chain, so
|
|
14
|
+
* a screen seen small and far shimmers where a mipmapped image would not.
|
|
15
|
+
*/
|
|
16
|
+
/** What a capture's texture is made from, in place of an image: its size and colour format. */
|
|
17
|
+
export interface SceneCaptureTexels {
|
|
18
|
+
readonly kind: 'scene-capture';
|
|
19
|
+
readonly width: number;
|
|
20
|
+
readonly height: number;
|
|
21
|
+
/** Half floats, which hold light past one; bytes otherwise. */
|
|
22
|
+
readonly float: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The largest side a capture may have, the size every WebGL2 device must take. */
|
|
25
|
+
export declare const MAX_SCENE_CAPTURE_SIZE = 4096;
|
|
26
|
+
/** A capture's description, refused by name where a side is not a whole number of pixels it can be. */
|
|
27
|
+
export declare function sceneCaptureTexels(width: number, height: number, float: boolean): SceneCaptureTexels;
|
|
28
|
+
/** Whether a texture source is a capture's description rather than an image. */
|
|
29
|
+
export declare function isSceneCaptureTexels(source: unknown): source is SceneCaptureTexels;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scene capture: a texture the world is drawn into from a camera of the caller's, which a material
|
|
3
|
+
* then shows like any image — a screen in the world showing what a camera elsewhere sees, a monitor,
|
|
4
|
+
* a security feed. The planar reflection's pass, for a caller's camera rather than a mirrored one.
|
|
5
|
+
*
|
|
6
|
+
* **It holds radiance, not display pixels**, as a probe does: drawn with the output transform held
|
|
7
|
+
* off, at the colour format a probe takes — half floats under `hdrScene`, bytes otherwise — and
|
|
8
|
+
* sampled linear, so a material showing it as its `emissive` emits the light the capture saw and the
|
|
9
|
+
* frame's grade then applies once, to the screen as to everything around it.
|
|
10
|
+
*
|
|
11
|
+
* **What it gives up**: it is the mesh pass alone, as a probe bake is — the sky, water, particles and
|
|
12
|
+
* anything else a scene draws after its world are in the capture only if the callback draws them;
|
|
13
|
+
* no temporal resolve, bloom or other effect after the world runs on it; and it has no mip chain, so
|
|
14
|
+
* a screen seen small and far shimmers where a mipmapped image would not.
|
|
15
|
+
*/
|
|
16
|
+
/** The largest side a capture may have, the size every WebGL2 device must take. */
|
|
17
|
+
export const MAX_SCENE_CAPTURE_SIZE = 4096;
|
|
18
|
+
/** A capture's description, refused by name where a side is not a whole number of pixels it can be. */
|
|
19
|
+
export function sceneCaptureTexels(width, height, float) {
|
|
20
|
+
for (const [name, side] of [
|
|
21
|
+
['width', width],
|
|
22
|
+
['height', height],
|
|
23
|
+
]) {
|
|
24
|
+
if (!Number.isInteger(side) || side < 1 || side > MAX_SCENE_CAPTURE_SIZE) {
|
|
25
|
+
throw new Error(`createSceneCapture: ${name} must be a whole number from 1 to ${MAX_SCENE_CAPTURE_SIZE}, ` +
|
|
26
|
+
`not ${String(side)}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { kind: 'scene-capture', width, height, float };
|
|
30
|
+
}
|
|
31
|
+
/** Whether a texture source is a capture's description rather than an image. */
|
|
32
|
+
export function isSceneCaptureTexels(source) {
|
|
33
|
+
return (typeof source === 'object' &&
|
|
34
|
+
source !== null &&
|
|
35
|
+
source.kind === 'scene-capture');
|
|
36
|
+
}
|
|
@@ -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
|
|
@@ -159,9 +159,30 @@ export declare function edgeFade(margin: number, band: number): number;
|
|
|
159
159
|
* nothing at one running directly into it.
|
|
160
160
|
*/
|
|
161
161
|
export declare function towardCameraFade(direction: ReadonlyVec3, viewDirection: ReadonlyVec3): number;
|
|
162
|
+
/**
|
|
163
|
+
* What a `ReflectiveSurface` is made with: its box, which way the surfaces inside it face, and how
|
|
164
|
+
* much and how far they reflect. Every number but the box has a default; see each field.
|
|
165
|
+
*/
|
|
162
166
|
export interface ReflectiveSurfaceOptions extends DecalBox {
|
|
163
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* How much of what the ray finds lands on the surface, 0 to 1: one share over the box, or with
|
|
169
|
+
* `fresnel` the surface's reflectance head-on.
|
|
170
|
+
*/
|
|
164
171
|
readonly strength?: number;
|
|
172
|
+
/**
|
|
173
|
+
* Whether `strength` is the surface's reflectance head-on, F0, rather than one share over the
|
|
174
|
+
* box. Each pixel then returns `F0 · A + B` of what its ray finds, the split-sum environment BRDF
|
|
175
|
+
* at its own view and `roughness` — Karis's fit, the one the lit stage reflects its environment
|
|
176
|
+
* by — so a floor returns more toward the horizon than underfoot, and the share follows the
|
|
177
|
+
* camera as Unreal weighs its reflections. **False by default**, the one share as before.
|
|
178
|
+
*
|
|
179
|
+
* **What it gives up**: the pass has no material buffer, so the roughness and F0 are the box's,
|
|
180
|
+
* not each pixel's, and nothing occludes the reflection where the lit stage's ambient occlusion
|
|
181
|
+
* would. A floor of one material is what it is for.
|
|
182
|
+
*/
|
|
183
|
+
readonly fresnel?: boolean;
|
|
184
|
+
/** The surface's roughness, 0 to 1, that `fresnel` weighs by. 0, a polished surface, if absent. */
|
|
185
|
+
readonly roughness?: number;
|
|
165
186
|
/** How far a ray may travel, world metres. */
|
|
166
187
|
readonly reachM?: number;
|
|
167
188
|
/** How deep a depth sample is treated as being. See `ScreenSpaceMarch.thicknessM`. */
|
|
@@ -189,6 +210,9 @@ export declare class ReflectiveSurface {
|
|
|
189
210
|
readonly axis: Float32Array<ArrayBuffer>;
|
|
190
211
|
readonly tint: Float32Array<ArrayBuffer>;
|
|
191
212
|
strength: number;
|
|
213
|
+
/** See `ReflectiveSurfaceOptions.fresnel`. */
|
|
214
|
+
fresnel: boolean;
|
|
215
|
+
roughness: number;
|
|
192
216
|
reachM: number;
|
|
193
217
|
thicknessM: number;
|
|
194
218
|
steps: number;
|
|
@@ -226,6 +250,8 @@ export interface RecordedReflection {
|
|
|
226
250
|
readonly axis: Float32Array;
|
|
227
251
|
readonly tint: Float32Array;
|
|
228
252
|
strength: number;
|
|
253
|
+
fresnel: boolean;
|
|
254
|
+
roughness: number;
|
|
229
255
|
reachM: number;
|
|
230
256
|
thicknessM: number;
|
|
231
257
|
steps: number;
|
|
@@ -292,6 +292,9 @@ export class ReflectiveSurface {
|
|
|
292
292
|
axis = new Float32Array(3);
|
|
293
293
|
tint = new Float32Array(3);
|
|
294
294
|
strength;
|
|
295
|
+
/** See `ReflectiveSurfaceOptions.fresnel`. */
|
|
296
|
+
fresnel;
|
|
297
|
+
roughness;
|
|
295
298
|
reachM;
|
|
296
299
|
thicknessM;
|
|
297
300
|
steps;
|
|
@@ -308,6 +311,8 @@ export class ReflectiveSurface {
|
|
|
308
311
|
/* Half, because a mirror is the rare case: a wet floor, polished stone and a car bonnet all
|
|
309
312
|
return a fraction of what lands on them, and a consumer who wants a mirror asks for one. */
|
|
310
313
|
this.strength = options.strength ?? 0.5;
|
|
314
|
+
this.fresnel = options.fresnel ?? false;
|
|
315
|
+
this.roughness = options.roughness ?? 0;
|
|
311
316
|
/* Eight metres covers a room and a street corner. Further costs steps rather than accuracy,
|
|
312
317
|
since the step is the reach divided by them. */
|
|
313
318
|
this.reachM = options.reachM ?? 8;
|
|
@@ -366,6 +371,8 @@ function newRecord() {
|
|
|
366
371
|
axis: new Float32Array(3),
|
|
367
372
|
tint: new Float32Array(3),
|
|
368
373
|
strength: 0.5,
|
|
374
|
+
fresnel: false,
|
|
375
|
+
roughness: 0,
|
|
369
376
|
reachM: 8,
|
|
370
377
|
thicknessM: 0.25,
|
|
371
378
|
steps: 24,
|
|
@@ -414,6 +421,8 @@ export class ReflectionQueue {
|
|
|
414
421
|
record.axis.set(surface.axis);
|
|
415
422
|
record.tint.set(surface.tint);
|
|
416
423
|
record.strength = Math.min(1, Math.max(0, surface.strength));
|
|
424
|
+
record.fresnel = surface.fresnel;
|
|
425
|
+
record.roughness = Math.min(1, Math.max(0, surface.roughness));
|
|
417
426
|
record.reachM = Math.max(0, surface.reachM);
|
|
418
427
|
record.thicknessM = Math.max(0, surface.thicknessM);
|
|
419
428
|
record.steps = Math.min(32, Math.max(1, Math.round(surface.steps)));
|
|
@@ -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";
|