@driftengine/core 4.8.6 → 4.9.0
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 +132 -0
- package/README.md +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +8 -1
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +35 -0
- package/dist/render/areaLights.js +35 -0
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +41 -3
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/webgl2/renderer.d.ts +140 -16
- package/dist/render/backend/webgl2/renderer.js +436 -44
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
- package/dist/render/backend/webgpu/flatPass.js +58 -26
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +166 -3
- package/dist/render/backend/webgpu/renderer.js +738 -83
- 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/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/clusteredLights.d.ts +23 -5
- package/dist/render/clusteredLights.js +35 -8
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- 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/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +5 -0
- package/dist/render/instances.d.ts +19 -5
- package/dist/render/instances.js +9 -3
- package/dist/render/lightBudget.d.ts +12 -1
- package/dist/render/lightBudget.js +23 -3
- package/dist/render/lightmap.d.ts +23 -9
- package/dist/render/lightmap.js +40 -18
- package/dist/render/particlePool.d.ts +21 -0
- package/dist/render/particlePool.js +7 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +47 -15
- package/dist/render/renderQuality.js +4 -0
- 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 +24 -1
- package/dist/render/screenSpaceReflection.d.ts +32 -1
- package/dist/render/screenSpaceReflection.js +14 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- 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 +1 -1
- package/dist/render/shaders/flat/areaLight.js +46 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +5 -1
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +22 -2
- 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 +85 -22
- package/dist/render/shaders/flat/models.d.ts +5 -2
- package/dist/render/shaders/flat/models.js +100 -16
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +1 -1
- package/dist/render/shaders/flat/preamble.js +10 -3
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- 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/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.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/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +192 -16
- package/dist/render/shaders/generated/flat.wgsl.js +230 -54
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +32 -7
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +32 -7
- 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/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- 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/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +46 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +107 -1
- package/dist/render/surfaceModel.d.ts +29 -8
- package/dist/render/surfaceModel.js +33 -9
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +44 -1
- package/dist/render/surfaceTexture.js +96 -5
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +22 -2
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +58 -0
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +58 -2
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/webgl2/renderer.ts +548 -50
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/flatPass.ts +63 -26
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +927 -100
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
- package/src/render/clusteredLights.ts +41 -8
- package/src/render/displayRange.ts +61 -0
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +4 -0
- package/src/render/instances.ts +26 -6
- package/src/render/lightBudget.ts +40 -2
- package/src/render/lightmap.ts +64 -23
- package/src/render/particlePool.ts +23 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +52 -15
- 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 +30 -2
- package/src/render/screenSpaceReflection.ts +38 -1
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +46 -0
- package/src/render/shaders/flat/glassTint.ts +13 -2
- package/src/render/shaders/flat/index.ts +37 -2
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +85 -22
- package/src/render/shaders/flat/models.ts +100 -16
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +1 -1
- package/src/render/shaders/flat/preamble.ts +10 -3
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +230 -54
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +32 -7
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +46 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +139 -1
- package/src/render/surfaceModel.ts +47 -10
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +136 -9
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
package/src/render/fog.ts
CHANGED
|
@@ -40,7 +40,10 @@ export interface FogOptions {
|
|
|
40
40
|
readonly underwaterFactor: number;
|
|
41
41
|
/** `MEDIUM_FOG` integrates a medium; `LINEAR_FOG` ramps between near and far. */
|
|
42
42
|
readonly mode: number;
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Where haze begins: the ramp's start in linear mode, and in the medium the distance along the
|
|
45
|
+
* ray it is integrated from (`Atmosphere.fogStart`), 0 at the camera.
|
|
46
|
+
*/
|
|
44
47
|
readonly near: number;
|
|
45
48
|
readonly far: number;
|
|
46
49
|
}
|
|
@@ -88,13 +91,26 @@ export function mediumFog(fog: FogOptions, dist: number, pointY: number): number
|
|
|
88
91
|
return mix(ramp, 1 - Math.pow(2, -wetLinear * wetLinear * 1.442695), fog.underwaterFactor);
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
/*
|
|
95
|
+
* **Where the medium begins along the ray**, `near` — the linear mode's slot, which this mode
|
|
96
|
+
* never read before `Atmosphere.fogStart`. The ray is integrated from that point on: the density
|
|
97
|
+
* there, carried over what is left of the ray. At a start of 0 every term below is the one it
|
|
98
|
+
* always was, so a world that sets none draws the same bits.
|
|
99
|
+
*/
|
|
100
|
+
const start = Math.min(fog.near, dist);
|
|
101
|
+
const before = dist > 0 ? start / dist : 0;
|
|
91
102
|
const t = (pointY - fog.eyeY) * fog.heightFalloff;
|
|
103
|
+
const rest = t * (1 - before);
|
|
92
104
|
/*
|
|
93
105
|
* `(1 - e^-t)/t`, which tends to 1 as the ray levels out. **Guarded rather than branched**: at
|
|
94
106
|
* `t = 0` the quotient is 0/0, and 1e-4 of a scale height is 8 mm.
|
|
95
107
|
*/
|
|
96
|
-
const denom = Math.abs(
|
|
97
|
-
const air =
|
|
108
|
+
const denom = Math.abs(rest) < 1e-4 ? 1e-4 : rest;
|
|
109
|
+
const air =
|
|
110
|
+
1 -
|
|
111
|
+
Math.exp(
|
|
112
|
+
(-fog.density * Math.exp(-t * before) * (dist - start) * (1 - Math.exp(-denom))) / denom,
|
|
113
|
+
);
|
|
98
114
|
if (fog.underwaterFactor <= 0) return air;
|
|
99
115
|
/* Water is its own medium and keeps its own curve, so crossing the surface changes nothing
|
|
100
116
|
about how the water column reads. */
|
|
@@ -30,6 +30,12 @@ export const FRAME_RESOURCES = [
|
|
|
30
30
|
'aoTarget',
|
|
31
31
|
'depthSnapshot',
|
|
32
32
|
'inset',
|
|
33
|
+
/*
|
|
34
|
+
* The frame's colour as a registered pass reads it: a copy taken at that pass's draw, so reading
|
|
35
|
+
* it constrains no attachment's store, as `depthSnapshot` does not. Last, so no bit before it
|
|
36
|
+
* moved when it arrived.
|
|
37
|
+
*/
|
|
38
|
+
'colorSnapshot',
|
|
33
39
|
] as const;
|
|
34
40
|
|
|
35
41
|
export type FrameResource = (typeof FRAME_RESOURCES)[number];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reflections traced through the frame's own materials: `RenderQuality.screenSpaceReflections`.
|
|
3
|
+
*
|
|
4
|
+
* **Every opaque lit surface reflects the frame by its own material**, where `ReflectiveSurface`
|
|
5
|
+
* reflects it by a box's: the lit stage leaves, per pixel, how much of the environment it showed and
|
|
6
|
+
* at what roughness (`shaders/flat/reflectionSurface.ts`), and a ray marched against the frame's
|
|
7
|
+
* depth swaps that share for what it finds — so a glossy panel mirrors the figures on it and the
|
|
8
|
+
* matte panel beside it barely does, in one mesh with one material map. Past `maxRoughness` a
|
|
9
|
+
* surface keeps the probes' reflection alone, which is what a march cannot do better than: a rough
|
|
10
|
+
* lobe wants many rays, and one ray blurred is only a fair stand-in for a narrow one.
|
|
11
|
+
*
|
|
12
|
+
* **What a surface reflects is what the lit stage already reflected**: a metal by its metalness, a
|
|
13
|
+
* dielectric where `setSurfaceReflectivity` or its material asks for one. A surface that reflects
|
|
14
|
+
* no environment reflects no frame either, which is what keeps the two from disagreeing.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** How the frame's materials reflect it. Every field optional; see `DEFAULT_FRAME_REFLECTIONS`. */
|
|
18
|
+
export interface FrameReflections {
|
|
19
|
+
/** The roughness past which a surface reflects the probes alone, faded over the last quarter. */
|
|
20
|
+
readonly maxRoughness?: number;
|
|
21
|
+
/** How far a ray travels before it gives up, in metres. */
|
|
22
|
+
readonly reachM?: number;
|
|
23
|
+
/** How deep a depth sample is believed to be, in metres. See `ScreenSpaceMarch.thicknessM`. */
|
|
24
|
+
readonly thicknessM?: number;
|
|
25
|
+
/** Samples along a ray, at most 32. */
|
|
26
|
+
readonly steps?: number;
|
|
27
|
+
/** How far a fully rough reflection is smeared, as a share of the frame's height. */
|
|
28
|
+
readonly blur?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ResolvedFrameReflections = Required<FrameReflections>;
|
|
32
|
+
|
|
33
|
+
/** The march `ReflectiveSurface` defaults to, a ceiling at satin, and a smear of 3% of the frame. */
|
|
34
|
+
export const DEFAULT_FRAME_REFLECTIONS: ResolvedFrameReflections = Object.freeze({
|
|
35
|
+
maxRoughness: 0.6,
|
|
36
|
+
reachM: 8,
|
|
37
|
+
thicknessM: 0.25,
|
|
38
|
+
steps: 24,
|
|
39
|
+
blur: 0.03,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const finite = (value: number | undefined, fallback: number): number =>
|
|
43
|
+
value !== undefined && Number.isFinite(value) ? value : fallback;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The option as the renderers read it: null where it is off, and every number held where the
|
|
47
|
+
* shader can use it — a ceiling above zero, a reach and a thickness above zero, a whole number of
|
|
48
|
+
* steps from 1 to 32, and a blur from none to a fifth of the frame. Clamped rather than refused,
|
|
49
|
+
* since a quality option is read at construction and a renderer that threw there would draw nothing.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveFrameReflections(
|
|
52
|
+
asked: boolean | FrameReflections | undefined,
|
|
53
|
+
): ResolvedFrameReflections | null {
|
|
54
|
+
if (asked === undefined || asked === false) return null;
|
|
55
|
+
const given = asked === true ? {} : asked;
|
|
56
|
+
const d = DEFAULT_FRAME_REFLECTIONS;
|
|
57
|
+
return {
|
|
58
|
+
maxRoughness: Math.min(Math.max(finite(given.maxRoughness, d.maxRoughness), 0.01), 1),
|
|
59
|
+
reachM: Math.max(finite(given.reachM, d.reachM), 0.01),
|
|
60
|
+
thicknessM: Math.max(finite(given.thicknessM, d.thicknessM), 0.001),
|
|
61
|
+
steps: Math.min(Math.max(Math.round(finite(given.steps, d.steps)), 1), 32),
|
|
62
|
+
blur: Math.min(Math.max(finite(given.blur, d.blur), 0), 0.2),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** What a renderer says once where the frame cannot carry these reflections, and why. */
|
|
67
|
+
export function frameReflectionsRefused(reason: string): string {
|
|
68
|
+
return (
|
|
69
|
+
`screenSpaceReflections is not drawn: ${reason}. Surfaces keep the probes' reflection; ` +
|
|
70
|
+
'the trace needs `screenEffects` and `hdrScene`, and a frame of one sample.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -23,15 +23,20 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import type { PointLightSet } from '../lightBudget.ts';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
POINT_LIGHT_COS_INNER,
|
|
28
|
+
POINT_LIGHT_COS_OUTER,
|
|
29
|
+
lightFalloffExponentOf,
|
|
30
|
+
} from '../clusteredLights.ts';
|
|
27
31
|
|
|
28
32
|
/** Lights the probe bake shades a hit with. */
|
|
29
33
|
export const MAX_BOUNCE_LIGHTS = 32;
|
|
30
34
|
/**
|
|
31
35
|
* Floats a light takes: where it is and how far it reaches; its colour at its weight and the cosine
|
|
32
|
-
* of its inner cone; the way it points and the cosine of its outer cone
|
|
36
|
+
* of its inner cone; the way it points and the cosine of its outer cone; and its own falloff
|
|
37
|
+
* exponent, 0 for the frame's, in a fourth row of its own (`PointLightSource.falloffExponent`).
|
|
33
38
|
*/
|
|
34
|
-
export const BOUNCE_LIGHT_FLOATS =
|
|
39
|
+
export const BOUNCE_LIGHT_FLOATS = 16;
|
|
35
40
|
|
|
36
41
|
export function createBounceLights(): Float32Array {
|
|
37
42
|
return new Float32Array(MAX_BOUNCE_LIGHTS * BOUNCE_LIGHT_FLOATS);
|
|
@@ -62,6 +67,10 @@ export function resolveBounceLights(lights: PointLightSet | null, out: Float32Ar
|
|
|
62
67
|
out[at + 9] = lights.lightDirections?.[i * 3 + 1] ?? 0;
|
|
63
68
|
out[at + 10] = lights.lightDirections?.[i * 3 + 2] ?? 0;
|
|
64
69
|
out[at + 11] = lights.lightConeCos?.[i * 2 + 1] ?? POINT_LIGHT_COS_OUTER;
|
|
70
|
+
out[at + 12] = lightFalloffExponentOf(lights.lightFalloffExponents?.[i]);
|
|
71
|
+
out[at + 13] = 0;
|
|
72
|
+
out[at + 14] = 0;
|
|
73
|
+
out[at + 15] = 0;
|
|
65
74
|
count++;
|
|
66
75
|
}
|
|
67
76
|
return count;
|
|
@@ -76,6 +76,10 @@ export function cullInstances(
|
|
|
76
76
|
if (regions !== undefined && out.lightmapRegions !== undefined) {
|
|
77
77
|
for (let k = 0; k < 4; k++) out.lightmapRegions[kept * 4 + k] = regions[i * 4 + k] as number;
|
|
78
78
|
}
|
|
79
|
+
const cells = data.uvRegions;
|
|
80
|
+
if (cells !== undefined && out.uvRegions !== undefined) {
|
|
81
|
+
for (let k = 0; k < 4; k++) out.uvRegions[kept * 4 + k] = cells[i * 4 + k] as number;
|
|
82
|
+
}
|
|
79
83
|
kept++;
|
|
80
84
|
}
|
|
81
85
|
out.count = kept;
|
package/src/render/instances.ts
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* **What it gives up** is a matrix per instance where a scatter spends five floats: 80 bytes
|
|
14
14
|
* against 44, and no wind. **What would make it wrong** is a caller wanting per-instance
|
|
15
|
-
* anything beyond an opacity — a morph weight,
|
|
16
|
-
* and the sixteen WebGL2 guarantees are already spent. The opacity rides the float the
|
|
17
|
-
* padded with, which is why
|
|
15
|
+
* anything beyond an opacity and a texture cell — a morph weight, say — which wants another
|
|
16
|
+
* attribute, and the sixteen WebGL2 guarantees are already spent. The opacity rides the float the
|
|
17
|
+
* stride was padded with and the cell the matrix's bottom row, which is why neither costs a byte.
|
|
18
18
|
*/
|
|
19
19
|
export interface MeshInstances {
|
|
20
20
|
/**
|
|
@@ -51,6 +51,20 @@ export interface MeshInstances {
|
|
|
51
51
|
* either mismatch is said once on the console.
|
|
52
52
|
*/
|
|
53
53
|
readonly lightmapRegions?: Float32Array;
|
|
54
|
+
/**
|
|
55
|
+
* Four floats each, the instance's cell of its texture: `[scaleU, scaleV, offsetU, offsetV]`,
|
|
56
|
+
* applied to the mesh's coordinates before the material's own scale and offset, in every pass
|
|
57
|
+
* that reads them — so one draw carries particles in different cells of a flipbook, or props
|
|
58
|
+
* wearing different tiles of one atlas. **Absent, or past `count`, is the whole texture**,
|
|
59
|
+
* `[1, 1, 0, 0]`, which is what every instance read before 4.8.7.
|
|
60
|
+
*
|
|
61
|
+
* **It rides the matrix's bottom row**, which an instance's placement — a turn, a scale and a
|
|
62
|
+
* move — leaves at `[0, 0, 0, 1]`, and which every stage drawing an instance rebuilds as that.
|
|
63
|
+
* So a placement must be affine, as every placement of a solid object is; a projective one is
|
|
64
|
+
* drawn as its affine part. A culled batch (`cullInstances`) carries the cells only into a
|
|
65
|
+
* target that has the array.
|
|
66
|
+
*/
|
|
67
|
+
readonly uvRegions?: Float32Array;
|
|
54
68
|
/** How many instances the arrays hold. Fixed at creation; the buffers are sized from it. */
|
|
55
69
|
readonly capacity: number;
|
|
56
70
|
/** How many are live. The rest of the buffer is neither uploaded nor drawn. */
|
|
@@ -75,8 +89,8 @@ export function createMeshInstances(capacity: number): MeshInstances {
|
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
/**
|
|
78
|
-
* Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix,
|
|
79
|
-
* tint, and the opacity.
|
|
92
|
+
* Floats one instance occupies in the interleaved vertex buffer: sixteen of matrix, the bottom row
|
|
93
|
+
* of which carries the texture cell, three of tint, and the opacity.
|
|
80
94
|
*
|
|
81
95
|
* **Twenty so the stride is 80 bytes rather than 76.** A vertex buffer's stride must be a multiple
|
|
82
96
|
* of four on both backends, which 76 already is — the twentieth float was padding for the
|
|
@@ -101,12 +115,18 @@ export const INSTANCE_STRIDE = INSTANCE_FLOATS * 4;
|
|
|
101
115
|
* Writes nothing beyond `count`, and allocates nothing: `out` is the caller's staging array.
|
|
102
116
|
*/
|
|
103
117
|
export function packInstances(instances: MeshInstances, out: Float32Array): void {
|
|
104
|
-
const { models, tints, alphas, lightmapRegions } = instances;
|
|
118
|
+
const { models, tints, alphas, lightmapRegions, uvRegions } = instances;
|
|
105
119
|
const count = Math.min(instances.count, instances.capacity);
|
|
106
120
|
for (let i = 0; i < count; i += 1) {
|
|
107
121
|
const at = i * INSTANCE_FLOATS;
|
|
108
122
|
const m = i * 16;
|
|
109
123
|
for (let c = 0; c < 16; c += 1) out[at + c] = models[m + c] as number;
|
|
124
|
+
/* The texture cell in the bottom row an affine placement leaves free. See `uvRegions`. */
|
|
125
|
+
const cell = i * 4;
|
|
126
|
+
out[at + 3] = uvRegions?.[cell] ?? 1;
|
|
127
|
+
out[at + 7] = uvRegions?.[cell + 1] ?? 1;
|
|
128
|
+
out[at + 11] = uvRegions?.[cell + 2] ?? 0;
|
|
129
|
+
out[at + 15] = uvRegions?.[cell + 3] ?? 0;
|
|
110
130
|
if (lightmapRegions !== undefined) {
|
|
111
131
|
/* The region in the tint's and the opacity's place. See `lightmapRegions`. */
|
|
112
132
|
for (let c = 0; c < 4; c += 1) out[at + 16 + c] = lightmapRegions[i * 4 + c] as number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Fixed point-light budget shared by CPU selection and the flat shader. */
|
|
2
|
+
import { MAX_AREA_LIGHTS, packAreaSizesAndDoors } from './areaLights.ts';
|
|
2
3
|
import type { AreaLightBuffer } from './areaLights.ts';
|
|
3
4
|
import type { ResolvedAreaShadows } from './areaShadowSet.ts';
|
|
4
5
|
import {
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
lampSourceRadius,
|
|
8
9
|
lampWeight,
|
|
9
10
|
lightChannelsOf,
|
|
11
|
+
lightFalloffExponentOf,
|
|
10
12
|
} from './clusteredLights.ts';
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -401,11 +403,20 @@ export interface PointLightSet {
|
|
|
401
403
|
* character's own key and rim lights, which the floor around the character must not take.
|
|
402
404
|
*/
|
|
403
405
|
readonly lightChannels?: Float32Array;
|
|
406
|
+
/**
|
|
407
|
+
* One per light: its own falloff exponent, `(1 - (d/R)^2)^n` with no distance term, or 0 for the
|
|
408
|
+
* frame's falloff. **Absent, or a short array, is 0 for every light**, so a scene that names none
|
|
409
|
+
* falls off as it did. See `PointLightSource.falloffExponent`.
|
|
410
|
+
*/
|
|
411
|
+
readonly lightFalloffExponents?: Float32Array;
|
|
404
412
|
}
|
|
405
413
|
|
|
406
414
|
/** Weights are optional per consumer; a short array means every light is fully present. */
|
|
407
415
|
const FULLY_PRESENT: Float32Array = new Float32Array(MAX_POINT_LIGHTS).fill(1);
|
|
408
416
|
|
|
417
|
+
/** The fixed arm's rectangles as `uAreaLightSize` holds them; refilled per bind, allocated once. */
|
|
418
|
+
const AREA_SIZES_AND_DOORS = new Float32Array(MAX_AREA_LIGHTS * 4);
|
|
419
|
+
|
|
409
420
|
/** No size at all, which is the behaviour every scene had before a light could have one. */
|
|
410
421
|
const POINT_SOURCES: Float32Array = new Float32Array(MAX_POINT_LIGHTS);
|
|
411
422
|
/**
|
|
@@ -504,7 +515,8 @@ export function bindPointLights(
|
|
|
504
515
|
* every lamp switched off.
|
|
505
516
|
*/
|
|
506
517
|
gl.uniform3fv(uniforms['uLightDir[0]'] ?? null, r.directions);
|
|
507
|
-
|
|
518
|
+
/* The cone and, in z, the light's own falloff exponent. See `conesAndFalloffs`. */
|
|
519
|
+
gl.uniform4fv(uniforms['uLightCone[0]'] ?? null, r.conesAndFalloffs);
|
|
508
520
|
gl.uniform1fv(uniforms['uLightIesProfile[0]'] ?? null, r.iesProfiles);
|
|
509
521
|
/* The azimuth and, in w, the channels. See `axesAndChannels`. */
|
|
510
522
|
gl.uniform4fv(uniforms['uLightIesAxis[0]'] ?? null, r.axesAndChannels);
|
|
@@ -535,7 +547,9 @@ export function bindAreaLights(
|
|
|
535
547
|
gl.uniform3fv(uniforms['uAreaLightColor[0]'] ?? null, lights.colors);
|
|
536
548
|
gl.uniform3fv(uniforms['uAreaLightRight[0]'] ?? null, lights.right);
|
|
537
549
|
gl.uniform3fv(uniforms['uAreaLightUp[0]'] ?? null, lights.up);
|
|
538
|
-
|
|
550
|
+
/* The half extents and, in zw, the barn doors. See `packAreaSizesAndDoors`. */
|
|
551
|
+
packAreaSizesAndDoors(lights, AREA_SIZES_AND_DOORS);
|
|
552
|
+
gl.uniform4fv(uniforms['uAreaLightSize[0]'] ?? null, AREA_SIZES_AND_DOORS);
|
|
539
553
|
gl.uniform1fv(uniforms['uAreaLightTwoSided[0]'] ?? null, lights.twoSided);
|
|
540
554
|
}
|
|
541
555
|
|
|
@@ -589,6 +603,12 @@ export interface ResolvedPointLights {
|
|
|
589
603
|
* array of their own, which would cost a row a light of a budget that has none to give.
|
|
590
604
|
*/
|
|
591
605
|
readonly axesAndChannels: Float32Array;
|
|
606
|
+
/**
|
|
607
|
+
* Owned, four a light: the cone's two cosines, then the light's own falloff exponent, as
|
|
608
|
+
* `uLightCone` carries them — a `vec2` array costs a whole row a light on WebGL2 anyway, so the
|
|
609
|
+
* exponent rides the half it left rather than a row of its own.
|
|
610
|
+
*/
|
|
611
|
+
readonly conesAndFalloffs: Float32Array;
|
|
592
612
|
/**
|
|
593
613
|
* Owned, `MAX_POINT_LIGHTS` long: the emitter sizes and weights as the froxel record carries
|
|
594
614
|
* them, at half precision. `sourceRadii` and `weights` point here once a light set resolves, so
|
|
@@ -614,6 +634,7 @@ const glLights: ResolvedPointLights = {
|
|
|
614
634
|
iesAxes: NO_IES_AXES,
|
|
615
635
|
cookies: NO_COOKIES,
|
|
616
636
|
axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
637
|
+
conesAndFalloffs: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
617
638
|
heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
|
|
618
639
|
heldWeights: new Float32Array(MAX_POINT_LIGHTS),
|
|
619
640
|
};
|
|
@@ -649,6 +670,7 @@ export function resolvePointLights(
|
|
|
649
670
|
out.iesAxes = NO_IES_AXES;
|
|
650
671
|
out.cookies = NO_COOKIES;
|
|
651
672
|
packAxesAndChannels(NO_IES_AXES, undefined, out.axesAndChannels);
|
|
673
|
+
packConesAndFalloffs(OPEN_CONES, undefined, out.conesAndFalloffs);
|
|
652
674
|
return out;
|
|
653
675
|
}
|
|
654
676
|
/*
|
|
@@ -742,9 +764,25 @@ export function resolvePointLights(
|
|
|
742
764
|
out.iesAxes = iesAxes;
|
|
743
765
|
out.cookies = cookies;
|
|
744
766
|
packAxesAndChannels(iesAxes, lights.lightChannels, out.axesAndChannels);
|
|
767
|
+
packConesAndFalloffs(coneCos, lights.lightFalloffExponents, out.conesAndFalloffs);
|
|
745
768
|
return out;
|
|
746
769
|
}
|
|
747
770
|
|
|
771
|
+
/** The cone and the falloff exponent, four a light, as `uLightCone` holds them. */
|
|
772
|
+
function packConesAndFalloffs(
|
|
773
|
+
cones: Float32Array,
|
|
774
|
+
exponents: Float32Array | undefined,
|
|
775
|
+
out: Float32Array,
|
|
776
|
+
): void {
|
|
777
|
+
const named = (exponents?.length ?? 0) >= MAX_POINT_LIGHTS ? exponents : undefined;
|
|
778
|
+
for (let light = 0; light < MAX_POINT_LIGHTS; light++) {
|
|
779
|
+
out[light * 4] = cones[light * 2] ?? POINT_LIGHT_COS_INNER;
|
|
780
|
+
out[light * 4 + 1] = cones[light * 2 + 1] ?? POINT_LIGHT_COS_OUTER;
|
|
781
|
+
out[light * 4 + 2] = lightFalloffExponentOf(named?.[light]);
|
|
782
|
+
out[light * 4 + 3] = 0;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
748
786
|
/** The azimuth and the channels, four a light, as `uLightIesAxis` holds them. */
|
|
749
787
|
function packAxesAndChannels(
|
|
750
788
|
axes: Float32Array,
|
package/src/render/lightmap.ts
CHANGED
|
@@ -30,17 +30,19 @@
|
|
|
30
30
|
* regions no tint. **What it does not do**: the GPU-driven pipeline and splats read no page.
|
|
31
31
|
*/
|
|
32
32
|
import type { MeshData } from '@driftengine/drft';
|
|
33
|
-
import {
|
|
33
|
+
import { packRgb9e5 } from './rgb9e5.ts';
|
|
34
34
|
|
|
35
35
|
/** One page as a consumer decoded it. */
|
|
36
36
|
export interface LightmapPage {
|
|
37
37
|
readonly width: number;
|
|
38
38
|
readonly height: number;
|
|
39
39
|
/**
|
|
40
|
-
* Linear irradiance,
|
|
41
|
-
*
|
|
40
|
+
* Linear irradiance, row-major from the top, in the same units every other light here is in — a
|
|
41
|
+
* bake from another engine scaled by that engine's exposure, as its lights are. Either three
|
|
42
|
+
* floats a texel, or one word a texel already packed as rgb9e5 (`rgb9e5.ts`), which is how such a
|
|
43
|
+
* bake usually arrives and which uploads with no conversion at all.
|
|
42
44
|
*/
|
|
43
|
-
readonly irradiance: Float32Array;
|
|
45
|
+
readonly irradiance: Float32Array | Uint32Array;
|
|
44
46
|
/**
|
|
45
47
|
* The direction the light arrives from, four bytes a texel: a first-order spherical harmonic in
|
|
46
48
|
* this engine's world axes, each value stored as `v * 0.5 + 0.5` — xyz the axis, w the constant.
|
|
@@ -60,13 +62,25 @@ export type LightmapRegion = readonly [number, number, number, number];
|
|
|
60
62
|
/** The whole page. */
|
|
61
63
|
export const WHOLE_PAGE: LightmapRegion = [1, 1, 0, 0];
|
|
62
64
|
|
|
63
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* A page as both backends upload it: two layers of four bytes a texel, row-major from the top. The
|
|
67
|
+
* first is the irradiance's rgb9e5 word, byte by byte from its lowest; the second is the direction.
|
|
68
|
+
*
|
|
69
|
+
* **Eight bytes a texel, and the shader filters it.** Half floats were sixteen: a stage of 49 pages
|
|
70
|
+
* and 21.8 million texels held 348 MB, where this holds 174. The price is that the irradiance cannot
|
|
71
|
+
* be filtered as it is stored — a bilinear blend of two words with different exponents is not the
|
|
72
|
+
* blend of the colours they hold — so the lit stage fetches a texel's four neighbours, decodes each
|
|
73
|
+
* and blends the colours (`modelBaked` in `shaders/flat/models.ts`): four fetches a layer where one
|
|
74
|
+
* filtered sample was, on lightmapped surfaces alone. **What would make it wrong** is every device
|
|
75
|
+
* a page reaches sampling `rgb9e5ufloat`, which WebGL2 does as `RGB9_E5` too; it would then be a
|
|
76
|
+
* texture of its own, filtered by the device, and a binding the lit stage has no room for.
|
|
77
|
+
*/
|
|
64
78
|
export interface LightmapTexels {
|
|
65
79
|
readonly kind: 'lightmap-texels';
|
|
66
80
|
readonly width: number;
|
|
67
81
|
readonly height: number;
|
|
68
|
-
|
|
69
|
-
readonly
|
|
82
|
+
/** Each layer's bytes, in layer order: the irradiance, then the direction. */
|
|
83
|
+
readonly layers: readonly Uint8Array[];
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
/** Whether a surface texture's source is a page's texels rather than an image or blocks. */
|
|
@@ -79,9 +93,9 @@ export function isLightmapTexels(source: unknown): source is LightmapTexels {
|
|
|
79
93
|
}
|
|
80
94
|
|
|
81
95
|
/**
|
|
82
|
-
* A page as both backends upload it
|
|
83
|
-
*
|
|
84
|
-
*
|
|
96
|
+
* A page as both backends upload it (see `LightmapTexels`). Refuses a page whose arrays are not its
|
|
97
|
+
* size, naming the one that is short. Packed irradiance is read where it lies, with no copy; float
|
|
98
|
+
* irradiance is packed once, here.
|
|
85
99
|
*/
|
|
86
100
|
export function lightmapTexels(page: LightmapPage): LightmapTexels {
|
|
87
101
|
const { width, height } = page;
|
|
@@ -89,10 +103,14 @@ export function lightmapTexels(page: LightmapPage): LightmapTexels {
|
|
|
89
103
|
throw new Error(`createLightmap: a ${width} by ${height} page has nothing in it`);
|
|
90
104
|
}
|
|
91
105
|
const texels = width * height;
|
|
92
|
-
|
|
106
|
+
const irradiance = page.irradiance;
|
|
107
|
+
const packed = irradiance instanceof Uint32Array;
|
|
108
|
+
const wanted = packed ? texels : texels * 3;
|
|
109
|
+
if (irradiance.length !== wanted) {
|
|
93
110
|
throw new Error(
|
|
94
|
-
`createLightmap: ${
|
|
95
|
-
|
|
111
|
+
`createLightmap: ${irradiance.length} irradiance ${packed ? 'words' : 'floats'} for a ` +
|
|
112
|
+
`${width} by ${height} page, which wants ${wanted}: ` +
|
|
113
|
+
(packed ? 'one rgb9e5 word a texel' : 'three a texel'),
|
|
96
114
|
);
|
|
97
115
|
}
|
|
98
116
|
if (page.direction.length !== texels * 4) {
|
|
@@ -101,24 +119,47 @@ export function lightmapTexels(page: LightmapPage): LightmapTexels {
|
|
|
101
119
|
`page, which wants ${texels * 4}: four a texel`,
|
|
102
120
|
);
|
|
103
121
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
let words: Uint32Array;
|
|
123
|
+
if (packed) {
|
|
124
|
+
words = irradiance;
|
|
125
|
+
} else {
|
|
126
|
+
words = new Uint32Array(texels);
|
|
127
|
+
for (let t = 0; t < texels; t++) {
|
|
128
|
+
words[t] = packRgb9e5(
|
|
129
|
+
irradiance[t * 3] as number,
|
|
130
|
+
irradiance[t * 3 + 1] as number,
|
|
131
|
+
irradiance[t * 3 + 2] as number,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
112
134
|
}
|
|
113
135
|
return {
|
|
114
136
|
kind: 'lightmap-texels',
|
|
115
137
|
width,
|
|
116
138
|
height,
|
|
117
|
-
layers:
|
|
118
|
-
texels: toHalfFloats(floats),
|
|
139
|
+
layers: [littleEndianBytes(words), page.direction],
|
|
119
140
|
};
|
|
120
141
|
}
|
|
121
142
|
|
|
143
|
+
/** Whether this platform lays a word out lowest byte first, which the shader's decode assumes. */
|
|
144
|
+
const LITTLE_ENDIAN = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* A page's words as bytes, lowest first: a view of the same memory on every little-endian platform,
|
|
148
|
+
* which is every one a browser runs on, and a reordered copy anywhere else.
|
|
149
|
+
*/
|
|
150
|
+
function littleEndianBytes(words: Uint32Array): Uint8Array {
|
|
151
|
+
const bytes = new Uint8Array(words.buffer, words.byteOffset, words.length * 4);
|
|
152
|
+
if (LITTLE_ENDIAN) return bytes;
|
|
153
|
+
const swapped = new Uint8Array(bytes.length);
|
|
154
|
+
for (let i = 0; i < bytes.length; i += 4) {
|
|
155
|
+
swapped[i] = bytes[i + 3] as number;
|
|
156
|
+
swapped[i + 1] = bytes[i + 2] as number;
|
|
157
|
+
swapped[i + 2] = bytes[i + 1] as number;
|
|
158
|
+
swapped[i + 3] = bytes[i] as number;
|
|
159
|
+
}
|
|
160
|
+
return swapped;
|
|
161
|
+
}
|
|
162
|
+
|
|
122
163
|
/**
|
|
123
164
|
* A mesh with its second coordinates moved onto the grain and relief streams they ride, or the
|
|
124
165
|
* mesh itself where it has none. Called by both backends' `createMesh`, before anything records
|
|
@@ -53,6 +53,29 @@ export interface ParticleInstances {
|
|
|
53
53
|
capacity: number;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Floats one particle takes in a buffer a caller's own compute writes (`DeviceParticles`), in this
|
|
58
|
+
* order: position 3, half-width 1, roll 1, colour 3, opacity 1, age 1, seed 1, velocity 3, a
|
|
59
|
+
* sprite's frame 1 and its half-height 1 — `ParticleInstances`' streams, interleaved as the pool's
|
|
60
|
+
* own upload interleaves them. 64 bytes a particle.
|
|
61
|
+
*/
|
|
62
|
+
export const DEVICE_PARTICLE_FLOATS = 16;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Particles a caller's own compute shader wrote into a buffer of its own (`registerCompute`), drawn
|
|
66
|
+
* where they lie by `drawDeviceParticles` rather than read back and uploaded again. WebGPU only.
|
|
67
|
+
*
|
|
68
|
+
* **Not sorted**, as a pool's are where its batch asks: the device holds them, so an alpha-blended
|
|
69
|
+
* set that must be drawn far to near sorts itself. A slot with a half-width of 0 draws nothing, which
|
|
70
|
+
* is how a fixed-size buffer holds fewer live particles than it has room for.
|
|
71
|
+
*/
|
|
72
|
+
export interface DeviceParticles {
|
|
73
|
+
/** A buffer with `VERTEX` usage, `DEVICE_PARTICLE_FLOATS` floats a particle. */
|
|
74
|
+
readonly buffer: GPUBuffer;
|
|
75
|
+
/** How many particles from its start to draw; no more than the buffer holds. */
|
|
76
|
+
readonly count: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
56
79
|
function createParticleInstances(capacity: number): ParticleInstances {
|
|
57
80
|
return {
|
|
58
81
|
positions: new Float32Array(capacity * 3),
|
package/src/render/pass.ts
CHANGED
|
@@ -119,7 +119,23 @@ export type PassContext = {
|
|
|
119
119
|
/** The exposure that goes with it. 1 where the composite will apply the real one. */
|
|
120
120
|
readonly outputExposure: number;
|
|
121
121
|
} & (
|
|
122
|
-
| {
|
|
122
|
+
| {
|
|
123
|
+
readonly backend: 'webgl2';
|
|
124
|
+
readonly gl: WebGL2RenderingContext;
|
|
125
|
+
/**
|
|
126
|
+
* The frame's colour with everything drawn before this pass, for a pass declaring
|
|
127
|
+
* `reads: ['colorSnapshot']`, and null for one that does not or where the frame keeps no
|
|
128
|
+
* scene target (`screenEffects` off). A copy, never the attachment being drawn into, which
|
|
129
|
+
* WebGL2 cannot sample; taken afresh at each such pass's draw. WebGPU hands the same copy
|
|
130
|
+
* over in `PrepareContext.sceneColor`, where a bind group has to be built before the pass.
|
|
131
|
+
*/
|
|
132
|
+
readonly sceneColor: WebGLTexture | null;
|
|
133
|
+
/**
|
|
134
|
+
* The frame's depth as it stands, for a pass declaring `reads: ['depthSnapshot']`: a copy, at
|
|
135
|
+
* the buffer's own values and reversed as it is (1 near, 0 far). Null as `sceneColor` is.
|
|
136
|
+
*/
|
|
137
|
+
readonly sceneDepth: WebGLTexture | null;
|
|
138
|
+
}
|
|
123
139
|
| {
|
|
124
140
|
readonly backend: 'webgpu';
|
|
125
141
|
readonly pass: GPURenderPassEncoder;
|
|
@@ -217,6 +233,42 @@ export type PrepareContext =
|
|
|
217
233
|
* should not apply it either; the renderer's own verbs do not jitter a mirror.
|
|
218
234
|
*/
|
|
219
235
|
readonly jitter: Float32Array;
|
|
236
|
+
/**
|
|
237
|
+
* The frame's single-sample copy of its depth, or null where the frame keeps none (no
|
|
238
|
+
* composite: `screenEffects` off).
|
|
239
|
+
*
|
|
240
|
+
* One `r32float` texel a pixel holding the depth buffer's own value, reversed as the
|
|
241
|
+
* buffer is (`REVERSED_DEPTH`: 1 at the near plane, 0 at the far one). Unfilterable, so a
|
|
242
|
+
* pass binds it as `'unfilterable-float'` and reads it with a non-filtering sampler or
|
|
243
|
+
* `textureLoad`.
|
|
244
|
+
*
|
|
245
|
+
* **Handed over here, filled at the draw.** The view is the one this frame's copy will be
|
|
246
|
+
* written into, so a pass builds its bind groups with it before the frame's pass opens; the
|
|
247
|
+
* copy itself is taken when a pass that declares `reads: ['depthSnapshot']` is drawn, with
|
|
248
|
+
* everything drawn before it, which is what the soft edge of a translucent particle
|
|
249
|
+
* needs. A pass that does not declare that read finds whatever the frame last wrote there.
|
|
250
|
+
*/
|
|
251
|
+
readonly sceneDepth: GPUTextureView | null;
|
|
252
|
+
/**
|
|
253
|
+
* The frame's colour, for a pass that declares `reads: ['colorSnapshot']`, or null where no
|
|
254
|
+
* registered pass does or the frame keeps no composite (`screenEffects` off).
|
|
255
|
+
*
|
|
256
|
+
* At the scene's own size and format — under a reconstruction, the render's rather than the
|
|
257
|
+
* output's, since that is the picture a registered pass draws into — and filterable.
|
|
258
|
+
* **Handed over here, refilled at the draw**: the view is the texture each reading pass's
|
|
259
|
+
* draw copies the frame into, with everything drawn before it, so a distortion samples the
|
|
260
|
+
* scene behind it and a full-screen effect drawn last reads the whole frame and writes over
|
|
261
|
+
* it, in scene light, before the tone curve. A copy a reading pass, where `sceneDepth` is one
|
|
262
|
+
* a frame.
|
|
263
|
+
*
|
|
264
|
+
* **Under a reconstruction a pass that reads it is drawn after the upscale**, over the
|
|
265
|
+
* reconstructed picture and after every blended draw, whatever order it was asked in — at the
|
|
266
|
+
* output's size and the reconstruction's format, which `PassContext.format` names at that
|
|
267
|
+
* draw, so a pass drawn there keeps a pipeline for it. The view is then the output-size copy.
|
|
268
|
+
* `sceneDepth` stays the render's opaque depth: at a ratio above 1 its texels are coarser by
|
|
269
|
+
* the ratio, and a blended draw is in neither depth, as it never is.
|
|
270
|
+
*/
|
|
271
|
+
readonly sceneColor: GPUTextureView | null;
|
|
220
272
|
};
|
|
221
273
|
|
|
222
274
|
/**
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/** Allocation-free selection and packing of point lights for a fixed shader budget. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
POINT_LIGHT_COS_INNER,
|
|
5
|
+
POINT_LIGHT_COS_OUTER,
|
|
6
|
+
lightFalloffExponentOf,
|
|
7
|
+
} from './clusteredLights.ts';
|
|
4
8
|
import { MAX_POINT_LIGHTS, POINT_SHADOW_POOL } from './lightBudget.ts';
|
|
5
9
|
import { gatherLights } from './lightGrid.ts';
|
|
6
10
|
import type { LightGrid } from './lightGrid.ts';
|
|
@@ -198,6 +202,14 @@ export interface PointLightSource {
|
|
|
198
202
|
* whose uniform budget has none to spare.
|
|
199
203
|
*/
|
|
200
204
|
inLightField?: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* How the light fades with distance when it names its own way: `(1 - (d/R)^2)^n` with no distance
|
|
207
|
+
* term, `R` its radius and `n` this. Unreal's falloff for a light without inverse-square falloff,
|
|
208
|
+
* so a rig authored there — a fill of brightness 160 across 164 m, a key at exponent 8 — lights
|
|
209
|
+
* the space it was authored for rather than one that ends a few metres out. Absent or 0, the
|
|
210
|
+
* frame's own falloff (`pointLightFalloff`), which is every light before 4.8.7.
|
|
211
|
+
*/
|
|
212
|
+
falloffExponent?: number;
|
|
201
213
|
}
|
|
202
214
|
|
|
203
215
|
export interface PointLightBuffer {
|
|
@@ -256,6 +268,11 @@ export interface PointLightBuffer {
|
|
|
256
268
|
readonly directions: Float32Array;
|
|
257
269
|
/** Two per slot: the cosine of the inner cone angle, then of the outer. */
|
|
258
270
|
readonly coneCos: Float32Array;
|
|
271
|
+
/**
|
|
272
|
+
* One per slot: the light's own falloff exponent, or 0 for the frame's falloff. See
|
|
273
|
+
* `PointLightSource.falloffExponent`.
|
|
274
|
+
*/
|
|
275
|
+
readonly falloffExponents: Float32Array;
|
|
259
276
|
/** One per slot: a row of the photometric atlas, or −1 for a light with no profile. */
|
|
260
277
|
readonly iesProfiles: Float32Array;
|
|
261
278
|
/** Internal insertion-sort storage; exposed only to keep one flat object. */
|
|
@@ -307,6 +324,7 @@ export function createPointLightBuffer(capacity: number = MAX_POINT_LIGHTS): Poi
|
|
|
307
324
|
weights: new Float32Array(shaded),
|
|
308
325
|
directions: new Float32Array(shaded * 3),
|
|
309
326
|
coneCos: new Float32Array(shaded * 2),
|
|
327
|
+
falloffExponents: new Float32Array(shaded),
|
|
310
328
|
/* −1 rather than 0, because row 0 is a real fixture and a light that asked for none must not
|
|
311
329
|
be shaped by whichever profile happened to load first. */
|
|
312
330
|
iesProfiles: new Float32Array(shaded).fill(-1),
|
|
@@ -587,6 +605,7 @@ function choose(
|
|
|
587
605
|
* Restricting it to spots would be a smaller feature for no saving.
|
|
588
606
|
*/
|
|
589
607
|
out.iesProfiles[slot] = light.iesProfile ?? -1;
|
|
608
|
+
out.falloffExponents[slot] = lightFalloffExponentOf(light.falloffExponent);
|
|
590
609
|
}
|
|
591
610
|
out.count = chosen;
|
|
592
611
|
|