@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/CHANGELOG.json
CHANGED
|
@@ -1,4 +1,136 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "4.9.0",
|
|
4
|
+
"date": "2026-10-07",
|
|
5
|
+
"entries": [
|
|
6
|
+
{
|
|
7
|
+
"kind": "added",
|
|
8
|
+
"short": "`screenSpaceReflections`: every opaque lit surface reflects the frame by its own material, the probe's share swapped for what a ray finds, softened by roughness and left to the probes past a ceiling.",
|
|
9
|
+
"text": "A renderer built with `screenSpaceReflections` reflects the frame in every opaque lit surface by that surface's own material, where `ReflectiveSurface` reflects it by a box's. The lit stage leaves, per pixel, how much of the environment it showed and the tint a reflection lands with; the screen-space trace gains a material mode that marches every such pixel against the frame's depth and swaps the probe's share for what it finds. The swap is exact because the lit stage's blend is a mix: the frame holds the environment times the tint times the weight, and a found reflection belongs there with the same tint and weight, so nothing else in the pixel moves. A rougher surface shows a softer reflection, blurred by its roughness, and past `maxRoughness` it keeps the probes' reflection alone. One floor mesh with one ORM map therefore mirrors the figures standing on its glossy panels and barely shows them on its matte ones. What a surface reflects is what the lit stage already reflected: a metal by its metalness, a dielectric where `setSurfaceReflectivity` or its material asks. `true` takes `DEFAULT_FRAME_REFLECTIONS`; a `FrameReflections` sets the ceiling, the march and the blur. WebGPU writes the two maps by replaying the opaque lit draws without their lamps, since a second attachment on the frame's pass would bind every pipeline in it; WebGL2 writes them beside the colour, enabled around the opaque lit draws alone and behind a lit switch, so a renderer without the option carries nothing for it. It needs `screenEffects`, `hdrScene` and one sample, and says so once where it has none. On `demo/dev/frameReflections.html` the trace moves 30,138 floor pixels and none above them, a ceiling below every stripe matches the frame without the trace to the pixel, and the two backends differ by 41 pixels."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"kind": "added",
|
|
13
|
+
"short": "`setSurfaceOverlay` lays a rim of light, a dissolve with a glowing edge and wrinkle normals by six region weights over a draw's materials, its images regions of one atlas.",
|
|
14
|
+
"text": "`renderer.setSurfaceOverlay(overlay)` lays something over the surfaces of the draws that follow, beyond their materials, so one character's whole set of materials changes together while another's does not; per draw, reset by `bindMeshPass`. A `rim` glows along the silhouette: an edge `(1 - N·V)^falloff` leaning toward normals that face up, raised to a contrast, times a noise laid in screen space and scrolled on the environment's `surfaceTime`, a slow pulse and a mask in the mesh's uv, added to emission and not gated on the night. A `dissolve` cuts the surface away where a noise in its uv falls under a threshold, with a glowing band at the edge and a colour laid through the same noise. A `wrinkle` blends a second normal map into the surface's by six region weights read through two masks. Every image is a region of one atlas (`OverlayRegion`), read in the slot the frame's refraction copy takes, which an opaque surface never reads; a draw that refracts keeps its refraction and reads none of the overlay's images, and says so once. A lit switch, off until an overlay is set. WebGL2 carries the overlay's fifteen uniform vectors where its device has room and refuses it in words where it does not, as on a 256-vector part at the eight-light budget, and reads a compressed atlas as none. On `demo/dev/surfaceOverlay.html` every control moves only the object it names, and the two backends agree to the pixel."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"kind": "added",
|
|
18
|
+
"short": "`outputTransform: 'filmic'` and `setFilmicCurve`: a film curve shaped by five numbers that holds mid grey at mid grey, applied by the composite.",
|
|
19
|
+
"text": "`outputTransform: 'filmic'` is a film curve in the ACEScg working space: a straight segment through mid grey meeting a soft toe below and a shoulder above, shaped by a slope, a toe, a shoulder and two clips, with the ACES reference's glow and red modifier around it. `setFilmicCurve` sets the numbers and a renderer starts with `DEFAULT_FILMIC_CURVE`; a curve that runs backwards or divides by zero is clamped and said once rather than refused. 0.18 of scene light comes out at 0.18, where `'aces'` gives about 0.11: 118 of 255 on both backends, and every one of sixteen grey and colour patches agrees with a consumer's own transcription of the reference. It is the composite's, so it needs `screenEffects` and `hdrScene`; without them every pass grades with `'aces'` and the renderer says so once. Bound to DriftScript as `drift/render.filmic`."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"kind": "added",
|
|
23
|
+
"short": "`highDynamicRange` draws to a display's own range on WebGPU, `displayRange` says which range the frame went out in, and `setDisplayLuminance` lets highlights reach the display's peak.",
|
|
24
|
+
"text": "`highDynamicRange: true` asks for a canvas whose highlights run past paper white as far as the display can show. It is had where the renderer is on WebGPU, the frame keeps its light past white (`screenEffects` and `hdrScene`) and the display reports a high range: the canvas is configured `rgba16float` with extended tone mapping and read back to confirm it. `renderer.displayRange` says which range the frame goes out in, `'high'` or `'standard'`, and `displayRangeReason` says why; WebGL2 draws into an eight-bit canvas and always answers `'standard'`. `setDisplayLuminance(paperWhite, peak)` gives the ratio highlights may reach: paper white stays where it was and so does everything below it, the filmic shoulder rolls off toward the peak, and a colour grade colours a highlight without clipping it back to white. On a standard display it changes nothing. A host without a browser display query answers it with `createRenderer`'s `highDynamicRangeDisplay`."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"kind": "added",
|
|
28
|
+
"short": "`setCameraMotionBlur(scale, maxShare)` blurs each drawn object by its own motion under DriftTR on WebGPU: a mover smears while the still stage behind it stays sharp.",
|
|
29
|
+
"text": "Camera motion blur now reads each pixel's motion from the reconstruction's motion target, so a mover with a previous placement or pose smears along its own path while the stage standing still behind it does not, and `setCameraMotionBlur` takes a maximum as a share of the frame. On `demo/dev/objectBlur.html` a box crossing a still camera's view smears 8 pixels, 1 with the blur off, 5 capped. It is WebGPU's under DriftTR alone, since that is where per-object motion is kept; WebGL2, and WebGPU without reconstruction, blur by the camera's motion alone and say so once when given a mover."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"kind": "added",
|
|
33
|
+
"short": "`setChromaticAberration(intensity, start)` pulls the colour channels apart toward the frame's edges past a start radius, at three wavelengths, on both backends.",
|
|
34
|
+
"text": "`setChromaticAberration(intensity, start)` is a lens fringe in the composite: the three channels sampled apart toward the frame's edges, scaled by their wavelengths, from a start radius outward, so the middle of the frame is untouched. Held until changed; 0 is none. On `demo/dev/toneCurve.html?fringe=` it moves 13,480 pixels, none inside the start, and the two backends agree. It needs `screenEffects`, and says so once."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"kind": "added",
|
|
38
|
+
"short": "`Atmosphere.fogStart` keeps the exponential fog's first metres clear, integrated along each ray's own path in the lit stage and the GPU-driven pipeline alike.",
|
|
39
|
+
"text": "`Atmosphere.fogStart` holds the exponential height fog off for its first metres: the medium begins where the ray has travelled that far, integrated along the ray's own path, so a stage reads clear up close and hazes with distance as a fog start does in a scene authored elsewhere. 0, or absent, is the fog as before. On `demo/dev/fogStart.html` the measured columns match values worked by hand on both backends, and the GPU-driven pipeline's fog takes it too."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"kind": "added",
|
|
43
|
+
"short": "A registered pass reads the frame: `reads: ['colorSnapshot']` hands it the colour drawn so far as `sceneColor`, beside the depth's `sceneDepth`, on both backends.",
|
|
44
|
+
"text": "A pass that declares `reads: ['depthSnapshot']` is handed the frame's depth as `sceneDepth` — in the `PrepareContext` on WebGPU, an `r32float` texture, and in the `PassContext` on WebGL2 — and one that declares `reads: ['colorSnapshot']` the colour with everything drawn before it as `sceneColor`, copied afresh at each such pass's draw. A distortion samples the scene behind it with it; a full-screen pass drawn last reads the whole frame and writes over it in scene light, before the tone curve. Under DriftTR the pass runs after the upscale over a copy of the reconstructed frame. On `demo/dev/framePass.html` a pass inverting the frame reads the same values on both backends, with multisampling too."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"kind": "changed",
|
|
48
|
+
"short": "The worker factories are at declared subpaths, `@driftengine/assets/bcWorkers` and `@driftengine/physics/workers`, where a bundler and Node can both find them.",
|
|
49
|
+
"text": "`spawnBcWorker` and the physics island pool's `createIslandPool` were reached by a path into a package's `src`, which a published package's `exports` does not have to allow and a strict resolver refuses. Each now has a subpath of its own in its package's `exports`, `@driftengine/assets/bcWorkers` and `@driftengine/physics/workers`, built to `dist` like the barrels, and the messages that name them say so. The barrels still do not reach them, which is what keeps a worker out of a game that never names one."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"kind": "fixed",
|
|
53
|
+
"short": "A scene capture or a probe bake inside a frame no longer turns that frame black on WebGPU, nor switches off its skin spread, nor steps the temporal jitter twice.",
|
|
54
|
+
"text": "Three defects at the boundary a capture or a probe bake opens in the middle of a frame. On WebGPU, `captureScene` and `bakeProbe` saved the open pass before flushing the frame graph, which opens the frame's pass, so a capture after any draw restored the wrong pass and the frame came out black. On both backends, the latch that spreads skin once a frame was set before the check that refuses a mirror, a probe or a capture, so a blended draw inside a capture switched the frame's skin spread off. And the temporal history stepped its jitter each time the camera was bound rather than once a frame, so a frame that bound it twice drew two frames' jitter. Each has a test, and `scripts/capture-check.mjs` captures at the start of a frame, among its draws and at its end, against controls taken the same way."
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"kind": "fixed",
|
|
58
|
+
"short": "WebGPU asks a device for the textures and samplers the environment probe's widest lit variant really binds, sixteen and twelve, counted from the generated bindings.",
|
|
59
|
+
"text": "The check that decides whether a device can build the environment probe's permutation compared its limits against seven sampled textures and four samplers, typed when the probe was added. The widest lit variant had since grown to sixteen and twelve with the material maps, glass, the fixture atlas and DriftLight, so a device under those would have been admitted to a permutation it cannot bind. The counts now come from the generated bindings, a shared sampler counted once."
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"version": "4.8.7",
|
|
65
|
+
"date": "2026-10-06",
|
|
66
|
+
"entries": [
|
|
67
|
+
{
|
|
68
|
+
"kind": "changed",
|
|
69
|
+
"short": "A lightmap page is 8 bytes a texel, half what it was: the irradiance is an rgb9e5 word the lit stage decodes and filters, and a bake that arrives packed uploads where it lies.",
|
|
70
|
+
"text": "`createLightmap` stored a page as two half-float layers, 16 bytes a texel, so a stage of 49 pages and 21.8 million texels took 348 MB on the device. The irradiance is now one rgb9e5 word a texel, three 9-bit mantissas over a shared 5-bit exponent in the layout of `EXT_texture_shared_exponent` and WebGPU's `rgb9e5ufloat`, beside the direction's four bytes, in two `rgba8` layers that the lit stage decodes and filters itself: 8 bytes a texel, 174 MB for the same stage. `LightmapPage.irradiance` takes a `Uint32Array` of packed words as a bake hands them over, uploaded where they lie with no float array in between, as well as floats, which are packed once at upload. What it gives up is precision in a texel's smaller channels, which share the largest one's exponent, and anything past 65,408; light has neither to lose. On `demo/dev/lightmap.html` a page of one value is still 0 pixels from the same ambient raised by `setAmbientSH`, and the room is within one level of 4.8.6's on both backends."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"kind": "changed",
|
|
74
|
+
"short": "The BC decode worker is behind its own specifier, so a game importing `@driftengine/assets` no longer has it in its build: name `spawnBcWorker` as `DrftLoader`'s `bcWorker` to decode in it.",
|
|
75
|
+
"text": "`DrftLoader` decoded a BC texture the device cannot take as blocks in a worker it built with `new Worker(new URL('./bcWorker.ts', import.meta.url))` inside the package's barrel, and a bundler writes that worker out at transform time, before tree-shaking, so every game importing anything from `@driftengine/assets` carried the BC decoder's chunk from 4.8.4 on, whether or not it loaded a BC texture. The gate that asserts no barrel names a worker bundled each barrel against the built packages and skipped one that would not bundle, so it saw this one only when another test's `npm pack` had built them first, which is how it passed three releases. The factory is now `spawnBcWorker` at `@driftengine/assets/src/bcWorkers.ts`, as the physics island pool is, and a loader takes it as `bcWorker`; without it BC textures decode on the main thread and the loader says so once, naming the option. The gate bundles from source now. Every scene and page in this repository that loads a model names the worker."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"kind": "added",
|
|
79
|
+
"short": "`PointLightSource.falloffExponent` lights by `(1 - (d/R)^2)^n` with no distance term, Unreal's falloff for a light without inverse square, in both arms and on water, DriftLight and DriftRay.",
|
|
80
|
+
"text": "A point or spot light may name its own falloff: `(1 - (d/R)^2)^n` with `R` its radius and `n` the exponent, and no inverse square, which is how Unreal lights a light that does not use inverse-square falloff. A rig authored there keeps its shape: a fill meant to light a whole hall across a hundred metres does, where the frame's falloff had it gone a few metres out. 0, or absent, is the frame's falloff as before. The selection carries it in `PointLightBuffer.falloffExponents` and the environment in `lightFalloffExponents`; the fixed arm reads it in the third lane of the cone uniform, which a two-float array already paid a whole row for, so the eight-light build spends no uniform row on it, and the froxel table's record grows to six texels. Water, DriftLight's volume and DriftRay's bounce fall off by it too, so a light lands the same way on every surface it reaches. On `demo/dev/clustered.html` one light drawn by the fixed arm and by the table is 0 pixels apart on both backends, and an exponent of 8 moves 42,123 pixels against none."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"kind": "added",
|
|
84
|
+
"short": "`AreaLightSource.barnDoorAngle` and `barnDoorLength` hang barn doors on a rectangle: each fragment is lit by the part the doors leave in view, its highlight included, in both arms.",
|
|
85
|
+
"text": "A rectangle may carry barn doors, a flap hinged at each edge standing `barnDoorAngle` degrees from its normal and `barnDoorLength` metres long, 20 cm when absent, as Unreal's rect lights do. At 90 they fold flat and hide nothing; smaller narrows the light to the opening they leave. Each fragment shades the part of the rectangle no door's tip hides, one axis at a time, so the form factor and the integrated highlight both come from that smaller rectangle, in the fixed arm, which reads the doors in the size uniform's two spare lanes, and in the froxel table's record. Each door is taken as endless along its hinge, so where two meet the corner they hide is square rather than mitred, and a shadowed rectangle's map is still baked from its whole face. Measured from straight above a 1.2 by 0.5 m panel 1.2 m over a floor with 20 degree, 30 cm doors: nothing changes within the 1.04 by 0.69 m box the door tips leave whole, nothing is lit past 4.94 and 2.32 m where they hide all of it, the fixed and clustered arms are 0 pixels apart, and doors at 90 degrees are 0 pixels from none, on both backends."
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"kind": "added",
|
|
89
|
+
"short": "`SurfaceMaterial.physicalSpecular` shades a lamp's and the sun's highlight as GGX's own `pi D Vis F N.L` with the specular attribute as F0, on the standard, lightmap and anisotropic models.",
|
|
90
|
+
"text": "The engine's highlight is a look control: it peaks at the specular attribute times one however rough the surface, so strength and width are set apart. A material with `physicalSpecular: true` takes GGX's own term instead, `pi * D * Vis * F * N.L` with Smith's height-correlated masking, as Unreal shades and as this engine's skin and eye already did. Its peak is `1 / (4 alpha^2)` of the light head-on, so a polished surface's highlight is several times the look's and a rough one's lower and wider; the specular attribute is read as the reflectance at normal incidence, Unreal's `0.08 * Specular`, and Fresnel brightens every surface toward grazing rather than a metal alone. Lamps, widened by their size as before, and the sun take it, on the standard, lightmap and anisotropic models; a rectangle's highlight was already the lobe integrated over it with that F0. The code is a lit switch, compiled into the lit programs the first time a material asks, so a scene that never does carries none of it. Measured with a lamp and an eye mirrored about a floor at roughness 0.5, where the term works out by hand to 4.4376: the physical highlight at 1/4.4377 of the light reads the look's middle pixel to within a level on both backends, from a lamp and from the sun, and the anisotropic model's physical lobe at strength 0 is the standard one to within a level everywhere."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"kind": "added",
|
|
94
|
+
"short": "`ReflectiveSurface.fresnel` reads `strength` as the surface's reflectance head-on and weighs each pixel by the split-sum BRDF at its view and `roughness`, so a reflection follows the camera.",
|
|
95
|
+
"text": "A screen-space reflection's `strength` was one share over its box, so a floor reflected as much underfoot as toward the horizon and a consumer re-matched the share to its camera's angle each frame. With `fresnel: true` the strength is the surface's reflectance head-on and each pixel returns `F0 * A + B` of what its ray finds, the split-sum environment BRDF at its own view and the box's `roughness`: Karis's fit, now one GLSL text that the lit stage and the reflection pass both include. The pass reads no material, so the reflectance and roughness are the box's, and nothing occludes the reflection where the lit stage's ambient occlusion would. Off by default, the one share as before. Measured on `demo/dev/ssr.html`: at a reflectance of 1 and roughness 0 it is 0 pixels from a strength of 1, as the fit says, and at a pixel whose view works out by hand to N.V 0.344, reflectance 0.04 matches the constant strength the fit gives there, 0.153, to within a level on both backends."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"kind": "added",
|
|
99
|
+
"short": "`MeshInstances.uvRegions` gives each instance its own cell of its texture, so mesh particles on different flipbook frames, or props on different tiles of an atlas, are one draw.",
|
|
100
|
+
"text": "Four floats an instance, `[scaleU, scaleV, offsetU, offsetV]`, applied to the mesh's texture coordinates before the material's own scale and offset, in the lit, depth and motion stages alike, so a cutout instance's shadow is cut by its own cell. They ride the bottom row of the instance's matrix, which a placement made of a turn, a scale and a move leaves at `[0, 0, 0, 1]` and which every stage drawing an instance now rebuilds as that, so they cost no attribute and no byte; an instanced placement must be affine, as every placement of a solid object is. Absent, every instance wears the whole texture, and a culled copy carries the cells into a target that has the array. On `demo/dev/instanceCells.html` eight cards each on their own cell of a strip, in one instanced draw, are 0 pixels from eight single draws with the cell in their materials, opaque and blended, on both backends; the published scenes and the instanced shadow, cull and opacity pages are 0 pixels from 4.8.6 on WebGPU."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"kind": "added",
|
|
104
|
+
"short": "`createSceneCapture` and `captureScene` draw the world from a camera of your own into a texture any material shows, a stadium screen's emissive or a monitor's, on both backends.",
|
|
105
|
+
"text": "`createSceneCapture(width, height)` makes a texture the scene can be drawn into, and `captureScene(capture, camera, clearColor, draw)` draws it: the callback is handed the camera, its matrices shaped to the capture, and submits the world exactly as it would to the screen. The texture then goes wherever an image goes, as a material's `emissive` for a screen in the world showing what another camera sees, a monitor or a security feed. It holds radiance, as a reflection probe does: the output transform and exposure are held off for the pass, at the world's colour format and sample count, half floats under `hdrScene`, so the frame grades the screen once with everything around it. It is the probe bake's arrangement and the mesh pass alone: the sky, water and particles are in it only if the callback draws them, nothing after the world runs on it, it has no mip chain, and the frame's camera, jitter, motion, refraction copy and skin spread are left alone while it draws. WebGL2 draws a capture's rows flipped, as it does a probe face's, so it reads as an image does. A second camera's view shown on a quad filling the frame is 0 pixels from that view drawn directly, single-sampled on both backends and at four samples on WebGPU, where WebGL2's multisampled capture is WebGPU's to the pixel; and a frame with a capture in it is 0 pixels from the frame without one outside the screen."
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"kind": "added",
|
|
109
|
+
"short": "`drawDeviceParticles` draws a particle pool's material from a buffer your own compute shader wrote, where it lies, with nothing read back. WebGPU only; WebGL2 says so once.",
|
|
110
|
+
"text": "A simulation can run entirely on the device: a compute shader of the consumer's own, registered with `registerCompute`, writes its particles into a buffer of its own, and `drawDeviceParticles(batch, { buffer, count }, camera, env, time)` draws the pool's material, blend, flipbook, fades and fog from that buffer in place of the one the pool uploads into. Each particle is `DEVICE_PARTICLE_FLOATS`, sixteen floats in a `ParticleInstances`' order, and the buffer needs `VERTEX` usage beside `STORAGE`; a slot of half-width 0 draws nothing, so a fixed-size buffer can hold fewer live particles than it has room for, and a count past what the buffer holds is cut to it. The particles are not sorted. WebGL2 has no compute stage, so there it says so once and draws nothing. On `demo/dev/spriteParticles.html` sixteen sprites written by a compute shader are 0 pixels from the same sixteen uploaded, soft edges included."
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"kind": "fixed",
|
|
114
|
+
"short": "A skin's `radius` is how far its light travels, as documented: the profile is at two fifths of it, so screen-space skin no longer smears a face seen up close over tens of pixels.",
|
|
115
|
+
"text": "`skinModel`'s `radius` was documented as how far light travels beneath the surface before it leaves, and was handed to Burley's profile as its distance `d` itself. That profile carries light 2.5 `d` on average and the blur's taps reach 9 `d`, so light travelled two and a half times as far as the model said, and `skinScattering: 'screen-space'` spread a face at arm's length over sixty pixels: a nose, lips and brows washed into one blur. The profile is now at `radius / 2.5` in both paths, the screen-space blur and the lit stage's pre-integrated fit, transmission and penumbra, so the two still agree. Measured first rather than assumed: on a step of light at 0.75 m and 800 by 600, the blur matched the profile's own masses at the old distance exactly, edges at the predicted taps, so the kernel was right and the distance was not; it now reaches 26 pixels where it reached 65, on both backends. A skin authored against the old meaning and looking right keeps its look with its `radius` raised by 2.5."
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"kind": "fixed",
|
|
119
|
+
"short": "The anisotropic model on a mesh with neither tangents nor texture coordinates drew black, a direction normalised from zero; it takes a direction in the surface now.",
|
|
120
|
+
"text": "A brushed surface takes its direction from the tangent frame, and a mesh carrying neither tangents nor coordinates gives a frame of zeros, which normalised to a NaN and drew the whole surface black. It takes a direction in the surface's plane instead, across the world's up, so the highlight is stretched somewhere. Measured on a floor with no coordinates: the anisotropic model at strength 0 draws the standard highlight to within a level on both backends, where it drew nothing."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"kind": "fixed",
|
|
124
|
+
"short": "Arcs stay finite under multisampling: an edge pixel shaded off the quad took the filament's side coordinate past one and its powers to NaN, which bloom spread into a white disc.",
|
|
125
|
+
"text": "A multisampled target shades a pixel on a quad's edge at its centre, which can lie outside the quad. There an arc's side coordinate ran past one, the filament's `across` went negative, and `pow` of a negative number is NaN on both backends: one dark speck, which bloom then spread into a white disc the size of its blur around every arc. `across` is clamped before its powers. Found by a scene drawing short arcs at four samples."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"kind": "note",
|
|
129
|
+
"short": "The lit stage carries the falloff exponent and barn doors in every permutation: 2.8 to 6.4% more instructions on RADV at a phone's viewport, and the same registers.",
|
|
130
|
+
"text": "What every lit pipeline now compiles was measured on RADV with a phone's viewport, against 4.8.6: the fixed-arm lit pixel shader is 5,778 instructions where it was 5,618 on WebGPU and 5,171 where it was 4,989 on WebGL2, both still 96 registers; the clustered one is 6,831 where it was 6,507 and 6,313 where it was 5,934, still 120 and 108. Registers set how many fragments a phone's GPU keeps in flight, and they did not move. The physical highlight is a lit switch and costs nothing until a material asks for it. Core's gzipped size is 828.1 KB, 29.0 KB over 4.8.6, almost all of it the generated WGSL, whose sixteen lit permutations each carry the new light loop."
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
},
|
|
2
134
|
{
|
|
3
135
|
"version": "4.8.6",
|
|
4
136
|
"date": "2026-10-05",
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ carries the loop, the renderer, cameras, input, the scene graph, geometry, text
|
|
|
6
6
|
and the whole of [`@driftengine/physics`](https://driftengine.dev/docs/simulation/rigid-bodies),
|
|
7
7
|
which it re-exports. The rest of the engine is separate packages you add when you need them.
|
|
8
8
|
|
|
9
|
-
**Cost:
|
|
9
|
+
**Cost: 858.8 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
|
|
10
10
|
which fails the build if the figure drifts by more than 3%.
|
|
11
11
|
|
|
12
12
|
The manual is at **[driftengine.dev/docs](https://driftengine.dev/docs)**, with an example running
|
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,8 @@ export { placeCascade } from './render/gi/globalField.ts';
|
|
|
145
145
|
export type { ReflectionOptions, ReflectiveSample } from './render/gi/reflection.ts';
|
|
146
146
|
export { GRADE_PLACEHOLDER_SIZE, MAX_GRADE_SIZE, identityGradeLut } from './render/colourGrade.ts';
|
|
147
147
|
export type { ColourGradeLut } from './render/colourGrade.ts';
|
|
148
|
+
export { DEFAULT_FILMIC_CURVE } from './render/filmicCurve.ts';
|
|
149
|
+
export type { FilmicCurve } from './render/filmicCurve.ts';
|
|
148
150
|
/**
|
|
149
151
|
* How bloom answers past its threshold — a ramp in place of the subtraction, and a tint for each
|
|
150
152
|
* of the pyramid's `BLOOM_LEVELS` octaves — the third argument of `setBloom`.
|
|
@@ -157,6 +159,9 @@ export type { BoltHandle, CausticsHandle, ClothBindingHandle, ClothParticlesHand
|
|
|
157
159
|
export type { ClothBindingData } from './render/clothBindingData.ts';
|
|
158
160
|
export { anisotropicModel, eyeModel, hairModel, lightmapModel, skinModel, } from './render/surfaceModel.ts';
|
|
159
161
|
export type { AnisotropicModel, EyeModel, HairModel, LightmapModel, SkinModel, SurfaceModel, SurfaceModelKind, } from './render/surfaceModel.ts';
|
|
162
|
+
export { DEFAULT_FRAME_REFLECTIONS } from './render/frameReflections.ts';
|
|
163
|
+
export type { FrameReflections } from './render/frameReflections.ts';
|
|
164
|
+
export type { OverlayRegion, SurfaceDissolve, SurfaceOverlay, SurfaceRim, SurfaceWrinkle, } from './render/surfaceOverlay.ts';
|
|
160
165
|
export { WHOLE_PAGE } from './render/lightmap.ts';
|
|
161
166
|
export type { LightmapPage, LightmapRegion } from './render/lightmap.ts';
|
|
162
167
|
export { createSkinnedCloth } from './render/skinnedClothRun.ts';
|
|
@@ -367,8 +372,8 @@ export { seaStateForAgitation, seaStateForWind } from './render/seaState.ts';
|
|
|
367
372
|
export type { SeaState } from './render/seaState.ts';
|
|
368
373
|
export { InstancedMesh, createInstanceData, writeInstance } from './render/instancedMesh.ts';
|
|
369
374
|
export { createMeshInstances, packInstances, INSTANCE_FLOATS, INSTANCE_STRIDE, } from './render/instances.ts';
|
|
370
|
-
export { ParticlePool } from './render/particlePool.ts';
|
|
371
|
-
export type { ParticlePoolOptions, ParticleInstances } from './render/particlePool.ts';
|
|
375
|
+
export { ParticlePool, DEVICE_PARTICLE_FLOATS } from './render/particlePool.ts';
|
|
376
|
+
export type { DeviceParticles, ParticlePoolOptions, ParticleInstances, } from './render/particlePool.ts';
|
|
372
377
|
export { ParticleBatch } from './render/particleBatch.ts';
|
|
373
378
|
export type { ParticleBatchOptions, ParticleBlend, ParticleFacing, } from './render/particleBatch.ts';
|
|
374
379
|
export { PARTICLE_MOTE_FRAG, PARTICLE_SMOKE_FRAG, PARTICLE_SPARK_FRAG, PARTICLE_VERT, } from './render/shaders/particle.ts';
|
package/dist/index.js
CHANGED
|
@@ -209,6 +209,7 @@ export { placeCascade } from './render/gi/globalField.js';
|
|
|
209
209
|
* take the identity, bend it, hand it back — and because it is what the placeholder is.
|
|
210
210
|
*/
|
|
211
211
|
export { GRADE_PLACEHOLDER_SIZE, MAX_GRADE_SIZE, identityGradeLut } from './render/colourGrade.js';
|
|
212
|
+
export { DEFAULT_FILMIC_CURVE } from './render/filmicCurve.js';
|
|
212
213
|
/**
|
|
213
214
|
* How bloom answers past its threshold — a ramp in place of the subtraction, and a tint for each
|
|
214
215
|
* of the pyramid's `BLOOM_LEVELS` octaves — the third argument of `setBloom`.
|
|
@@ -221,6 +222,12 @@ export { probeDevice } from './render/backend/probe.js';
|
|
|
221
222
|
* its own, compiled the first time a draw asks.
|
|
222
223
|
*/
|
|
223
224
|
export { anisotropicModel, eyeModel, hairModel, lightmapModel, skinModel, } from './render/surfaceModel.js';
|
|
225
|
+
/*
|
|
226
|
+
* What a draw lays over its surface beyond its material — a rim of light, a dissolve, wrinkle
|
|
227
|
+
* normals by region — set per draw with `setSurfaceOverlay`, its images regions of one atlas.
|
|
228
|
+
*/
|
|
229
|
+
/* Every opaque lit surface reflecting the frame by its own material: `screenSpaceReflections`. */
|
|
230
|
+
export { DEFAULT_FRAME_REFLECTIONS } from './render/frameReflections.js';
|
|
224
231
|
/*
|
|
225
232
|
* A baked lightmap: the page a consumer decoded, which `createLightmap` uploads as a lightmapped
|
|
226
233
|
* material's `modelMap`, and the region of it a material or an instance reads.
|
|
@@ -474,7 +481,7 @@ export { InstancedMesh, createInstanceData, writeInstance } from './render/insta
|
|
|
474
481
|
* pitches and rolls and a yaw cannot say so.
|
|
475
482
|
*/
|
|
476
483
|
export { createMeshInstances, packInstances, INSTANCE_FLOATS, INSTANCE_STRIDE, } from './render/instances.js';
|
|
477
|
-
export { ParticlePool } from './render/particlePool.js';
|
|
484
|
+
export { ParticlePool, DEVICE_PARTICLE_FLOATS } from './render/particlePool.js';
|
|
478
485
|
export { ParticleBatch } from './render/particleBatch.js';
|
|
479
486
|
export { PARTICLE_MOTE_FRAG, PARTICLE_SMOKE_FRAG, PARTICLE_SPARK_FRAG, PARTICLE_VERT, } from './render/shaders/particle.js';
|
|
480
487
|
export { BoltPool } from './render/boltPool.js';
|
|
@@ -5,3 +5,13 @@ export declare function anisotropicAlphas(roughness: number, strength: number, o
|
|
|
5
5
|
* are `th`, `bh` and `nh`. 1 at the mirror direction.
|
|
6
6
|
*/
|
|
7
7
|
export declare function anisotropicLobe(th: number, bh: number, nh: number, alphaT: number, alphaB: number): number;
|
|
8
|
+
/** Three components in the lobe's frame: along the direction, across it, along the normal. */
|
|
9
|
+
export type FrameVector = readonly [number, number, number];
|
|
10
|
+
/**
|
|
11
|
+
* The physical form of the same lobe, `π · D · Vis · N·L`, for a surface whose highlight is GGX's
|
|
12
|
+
* own (`SurfaceMaterial.physicalSpecular`): the stretched distribution normalised as a BRDF's, and
|
|
13
|
+
* Smith's height-correlated masking stretched with it (Heitz 2014, as Filament writes it), Fresnel
|
|
14
|
+
* left to the caller. The half vector, the light and the view are each in the lobe's frame. At
|
|
15
|
+
* strength 0 it is isotropic GGX, the term skin's and the eye's highlights are.
|
|
16
|
+
*/
|
|
17
|
+
export declare function anisotropicPhysicalLobe(half: FrameVector, light: FrameVector, view: FrameVector, alphaT: number, alphaB: number): number;
|
|
@@ -31,3 +31,22 @@ export function anisotropicLobe(th, bh, nh, alphaT, alphaB) {
|
|
|
31
31
|
const d = x * x + y * y + nh * nh;
|
|
32
32
|
return 1 / Math.max(d * d, 1e-8);
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The physical form of the same lobe, `π · D · Vis · N·L`, for a surface whose highlight is GGX's
|
|
36
|
+
* own (`SurfaceMaterial.physicalSpecular`): the stretched distribution normalised as a BRDF's, and
|
|
37
|
+
* Smith's height-correlated masking stretched with it (Heitz 2014, as Filament writes it), Fresnel
|
|
38
|
+
* left to the caller. The half vector, the light and the view are each in the lobe's frame. At
|
|
39
|
+
* strength 0 it is isotropic GGX, the term skin's and the eye's highlights are.
|
|
40
|
+
*/
|
|
41
|
+
export function anisotropicPhysicalLobe(half, light, view, alphaT, alphaB) {
|
|
42
|
+
const x = half[0] / alphaT;
|
|
43
|
+
const y = half[1] / alphaB;
|
|
44
|
+
const d = x * x + y * y + half[2] * half[2];
|
|
45
|
+
const distribution = 1 / (Math.PI * alphaT * alphaB * d * d);
|
|
46
|
+
const ndl = light[2];
|
|
47
|
+
const ndv = view[2];
|
|
48
|
+
const lambdaView = ndl * Math.hypot(alphaT * view[0], alphaB * view[1], ndv);
|
|
49
|
+
const lambdaLight = ndv * Math.hypot(alphaT * light[0], alphaB * light[1], ndl);
|
|
50
|
+
const visibility = 0.5 / Math.max(lambdaView + lambdaLight, 1e-5);
|
|
51
|
+
return Math.PI * distribution * visibility * ndl;
|
|
52
|
+
}
|
|
@@ -95,6 +95,22 @@ export interface AreaLightSource {
|
|
|
95
95
|
* of a scene unit, and never inside the rectangle itself. Unreal's `AttenuationRadius`, in metres.
|
|
96
96
|
*/
|
|
97
97
|
range?: number;
|
|
98
|
+
/**
|
|
99
|
+
* Barn doors: a flap hinged at each of the rectangle's four edges, standing this many degrees from
|
|
100
|
+
* its normal. 90, or absent, folds them flat and they hide nothing; smaller closes them, so the
|
|
101
|
+
* light narrows to the opening they leave — a rectangle at 50° throws a beam rather than filling
|
|
102
|
+
* its hemisphere. Unreal's `BarnDoorAngle`, whose default of 88° is nearly flat.
|
|
103
|
+
*
|
|
104
|
+
* What a fragment sees past the doors is the part of the rectangle no door's tip hides, one axis
|
|
105
|
+
* at a time, and the rectangle then shades as that smaller rectangle, its highlight included.
|
|
106
|
+
* Its shadow map is still baked from the whole rectangle's centre. **What it gives up**: each
|
|
107
|
+
* door is taken as endless along its hinge, so the two axes clip apart and the corner where two
|
|
108
|
+
* doors meet hides a square rather than the mitre two real flaps make. One-sided: a two-sided
|
|
109
|
+
* rectangle's back face has none.
|
|
110
|
+
*/
|
|
111
|
+
barnDoorAngle?: number;
|
|
112
|
+
/** The barn doors' length in metres. Absent, 0.2: Unreal's default of 20 cm. */
|
|
113
|
+
barnDoorLength?: number;
|
|
98
114
|
}
|
|
99
115
|
/** The irradiance a derived `range` ends at, in scene units. See `AreaLightSource.range`. */
|
|
100
116
|
export declare const AREA_RANGE_CUTOFF = 0.001;
|
|
@@ -109,8 +125,27 @@ export interface AreaLightBuffer {
|
|
|
109
125
|
readonly twoSided: Float32Array;
|
|
110
126
|
/** One a rectangle: how far its light reaches. See `AreaLightSource.range`. */
|
|
111
127
|
readonly ranges?: Float32Array;
|
|
128
|
+
/**
|
|
129
|
+
* Two a rectangle: the cosine of its barn doors' angle from its normal, then their length in
|
|
130
|
+
* metres — 0 and 0 for none. See `AreaLightSource.barnDoorAngle`.
|
|
131
|
+
*/
|
|
132
|
+
readonly barnDoors?: Float32Array;
|
|
112
133
|
}
|
|
113
134
|
export declare function createAreaLightBuffer(capacity?: number): AreaLightBuffer;
|
|
135
|
+
/** Unreal's default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
|
|
136
|
+
export declare const DEFAULT_BARN_DOOR_LENGTH = 0.2;
|
|
137
|
+
/**
|
|
138
|
+
* A rectangle's barn doors as the shader reads them, into `out[at]` and `out[at + 1]`: the cosine of
|
|
139
|
+
* their angle and their length. An angle not between 0 and 90 degrees, or a length not above zero,
|
|
140
|
+
* is no doors at all, written as 0 and 0 — which the shader reads as "nothing hidden".
|
|
141
|
+
*/
|
|
142
|
+
export declare function barnDoorsOf(light: AreaLightSource, out: Float32Array, at: number): void;
|
|
143
|
+
/**
|
|
144
|
+
* The fixed arm's half extents and barn doors, four a rectangle as `uAreaLightSize` holds them: the
|
|
145
|
+
* two half extents, then the doors' cosine and length. Into a target the binder owns, for the first
|
|
146
|
+
* `MAX_AREA_LIGHTS` alone, which are all the fixed arm reads.
|
|
147
|
+
*/
|
|
148
|
+
export declare function packAreaSizesAndDoors(lights: AreaLightBuffer, out: Float32Array): void;
|
|
114
149
|
/**
|
|
115
150
|
* How far a rectangle's light reaches when it names no range: where its irradiance on its own axis,
|
|
116
151
|
* `L · A / d²` for a rectangle of radiance `L` and area `A` seen from far, falls to
|
|
@@ -20,8 +20,41 @@ export function createAreaLightBuffer(capacity = MAX_AREA_LIGHTS) {
|
|
|
20
20
|
sizes: new Float32Array(capacity * 2),
|
|
21
21
|
twoSided: new Float32Array(capacity),
|
|
22
22
|
ranges: new Float32Array(capacity),
|
|
23
|
+
barnDoors: new Float32Array(capacity * 2),
|
|
23
24
|
};
|
|
24
25
|
}
|
|
26
|
+
/** Unreal's default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
|
|
27
|
+
export const DEFAULT_BARN_DOOR_LENGTH = 0.2;
|
|
28
|
+
/**
|
|
29
|
+
* A rectangle's barn doors as the shader reads them, into `out[at]` and `out[at + 1]`: the cosine of
|
|
30
|
+
* their angle and their length. An angle not between 0 and 90 degrees, or a length not above zero,
|
|
31
|
+
* is no doors at all, written as 0 and 0 — which the shader reads as "nothing hidden".
|
|
32
|
+
*/
|
|
33
|
+
export function barnDoorsOf(light, out, at) {
|
|
34
|
+
const angle = light.barnDoorAngle;
|
|
35
|
+
const length = light.barnDoorLength ?? DEFAULT_BARN_DOOR_LENGTH;
|
|
36
|
+
if (angle === undefined || !(angle >= 0 && angle < 90) || !(length > 0)) {
|
|
37
|
+
out[at] = 0;
|
|
38
|
+
out[at + 1] = 0;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
out[at] = Math.cos((angle * Math.PI) / 180);
|
|
42
|
+
out[at + 1] = length;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The fixed arm's half extents and barn doors, four a rectangle as `uAreaLightSize` holds them: the
|
|
46
|
+
* two half extents, then the doors' cosine and length. Into a target the binder owns, for the first
|
|
47
|
+
* `MAX_AREA_LIGHTS` alone, which are all the fixed arm reads.
|
|
48
|
+
*/
|
|
49
|
+
export function packAreaSizesAndDoors(lights, out) {
|
|
50
|
+
const doors = lights.barnDoors;
|
|
51
|
+
for (let a = 0; a < MAX_AREA_LIGHTS; a++) {
|
|
52
|
+
out[a * 4] = lights.sizes[a * 2] ?? 0;
|
|
53
|
+
out[a * 4 + 1] = lights.sizes[a * 2 + 1] ?? 0;
|
|
54
|
+
out[a * 4 + 2] = doors?.[a * 2] ?? 0;
|
|
55
|
+
out[a * 4 + 3] = doors?.[a * 2 + 1] ?? 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
25
58
|
/**
|
|
26
59
|
* How far a rectangle's light reaches when it names no range: where its irradiance on its own axis,
|
|
27
60
|
* `L · A / d²` for a rectangle of radiance `L` and area `A` seen from far, falls to
|
|
@@ -106,6 +139,8 @@ export function selectAreaLights(lights, out) {
|
|
|
106
139
|
? light.range
|
|
107
140
|
: areaRange(light.r, light.g, light.b, light.halfWidth, light.halfHeight);
|
|
108
141
|
}
|
|
142
|
+
if (out.barnDoors !== undefined)
|
|
143
|
+
barnDoorsOf(light, out.barnDoors, slot * 2);
|
|
109
144
|
}
|
|
110
145
|
return out;
|
|
111
146
|
}
|
|
@@ -45,6 +45,19 @@ export interface Atmosphere {
|
|
|
45
45
|
/** Linear mode only: where haze begins, and where it is total. */
|
|
46
46
|
fogNear?: number;
|
|
47
47
|
fogFar?: number;
|
|
48
|
+
/**
|
|
49
|
+
* The medium only: how far along the view ray it begins, in metres. Absent or 0 is at the
|
|
50
|
+
* camera, as every world before it was drawn.
|
|
51
|
+
*
|
|
52
|
+
* The air nearer than this is perfectly clear, and what lies beyond is fogged by the air past
|
|
53
|
+
* this point alone — so a faint, bright haze can lie over a distant crowd while the figures a
|
|
54
|
+
* few metres from the camera stay sharp, which no density can do when the medium starts at the
|
|
55
|
+
* eye: there it veils the whole frame, lifting the shadows and draining the colour before it
|
|
56
|
+
* reaches anything far enough to want it. Unlike `linear`'s `fogNear`, nothing past it is a
|
|
57
|
+
* ramp: it is the same extinction, integrated from further out. **What it gives up** is the air
|
|
58
|
+
* between the camera and the start, which a real medium has; that is the point of it.
|
|
59
|
+
*/
|
|
60
|
+
fogStart?: number;
|
|
48
61
|
underwater: UnderwaterAtmosphere | null;
|
|
49
62
|
}
|
|
50
63
|
/**
|
|
@@ -46,7 +46,15 @@ export function resolveAtmosphere(atmosphere, cameraY, underwaterEnabled, out) {
|
|
|
46
46
|
out.fogHeightFalloff = atmosphere.fogHeightFalloff;
|
|
47
47
|
out.fogEyeY = cameraY;
|
|
48
48
|
out.fogMode = atmosphere.fogMode === 'linear' ? 1 : 0;
|
|
49
|
-
|
|
49
|
+
/*
|
|
50
|
+
* **One slot, two meanings, and never both**: the ramp's near distance in linear mode, and in
|
|
51
|
+
* the medium where it begins (`fogStart`), which the medium never read before — so a world that
|
|
52
|
+
* left a `fogNear` on an exponential fog starts nothing, and draws exactly as it did.
|
|
53
|
+
*/
|
|
54
|
+
out.fogNear =
|
|
55
|
+
atmosphere.fogMode === 'linear'
|
|
56
|
+
? (atmosphere.fogNear ?? 0)
|
|
57
|
+
: Math.max(0, Number.isFinite(atmosphere.fogStart) ? (atmosphere.fogStart ?? 0) : 0);
|
|
50
58
|
out.fogFar = atmosphere.fogFar ?? 1;
|
|
51
59
|
/*
|
|
52
60
|
* The air medium stands in when nothing is submerged. The shaders multiply by the factor
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { Bounds } from '../../math/bounds.ts';
|
|
2
2
|
import type { Vec3 } from '../../math/color.ts';
|
|
3
|
+
import type { Camera } from '../camera.ts';
|
|
3
4
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
4
5
|
import type { MeshData } from '../mesh.ts';
|
|
5
6
|
import type { ProbeBakeOptions } from '../reflectionProbe.ts';
|
|
6
7
|
import type { Renderer, TranslucentMeshOptions } from './webgl2/renderer.ts';
|
|
7
8
|
import type { InstancedOptions, MeshInstances } from '../instances.ts';
|
|
8
9
|
import type { SurfaceMaterial } from '../surfaceTexture.ts';
|
|
10
|
+
import type { SurfaceOverlay } from '../surfaceOverlay.ts';
|
|
9
11
|
import type { SurfaceSource } from '../compressedSource.ts';
|
|
10
12
|
import type { LightmapPage } from '../lightmap.ts';
|
|
13
|
+
import type { DeviceParticles } from '../particlePool.ts';
|
|
11
14
|
import type { ClothBindingData } from '../clothBindingData.ts';
|
|
12
15
|
import type { TextStyle } from '../textLayout.ts';
|
|
13
16
|
import type { SdfFont } from '../sdfFont.ts';
|
|
@@ -187,7 +190,7 @@ export interface MeshOptions {
|
|
|
187
190
|
}
|
|
188
191
|
export type RendererApi = Omit<{
|
|
189
192
|
[K in keyof Renderer]: Renderer[K];
|
|
190
|
-
}, 'gpuTimer' | 'createMesh' | 'createMeshIncremental' | 'updateMesh' | 'drawMesh' | 'disposeMesh' | 'drawTranslucentMesh' | 'drawLightVolume' | 'drawFilm' | 'createScatter' | 'drawScatter' | 'uploadScatter' | 'disposeScatter' | 'createInstanced' | 'uploadInstanced' | 'drawInstanced' | 'drawTranslucentInstanced' | 'disposeInstanced' | 'createWater' | 'drawWater' | 'disposeWater' | 'createPlumes' | 'drawPlumes' | 'disposePlumes' | 'setPlumeScale' | 'createCaustics' | 'drawCaustics' | 'disposeCaustics' | 'createParticles' | 'drawParticles' | 'disposeParticles' | 'createWindStreaks' | 'drawWindStreaks' | 'disposeWindStreaks' | 'createFlock' | 'drawFlock' | 'disposeFlock' | 'createBolts' | 'drawBolts' | 'disposeBolts' | 'createLines' | 'drawLines' | 'disposeLines' | 'createText' | 'setText' | 'setPlate' | 'drawText' | 'textWidth' | 'disposeText' | 'createSdfText' | 'setSdfText' | 'drawSdfText' | 'disposeSdfText' | 'createSurfaceTexture' | 'createSurfaceTextureArray' | 'createLightmap' | 'updateSurfaceTexture' | 'disposeSurfaceTexture' | 'setSurfaceTexture' | 'setMaterial' | 'createClothBinding' | 'createClothParticles' | 'updateClothParticles' | 'setCloth' | 'disposeClothBinding' | 'disposeClothParticles'> & {
|
|
193
|
+
}, 'gpuTimer' | 'createMesh' | 'createMeshIncremental' | 'updateMesh' | 'drawMesh' | 'disposeMesh' | 'drawTranslucentMesh' | 'drawLightVolume' | 'drawFilm' | 'createScatter' | 'drawScatter' | 'uploadScatter' | 'disposeScatter' | 'createInstanced' | 'uploadInstanced' | 'drawInstanced' | 'drawTranslucentInstanced' | 'disposeInstanced' | 'createWater' | 'drawWater' | 'disposeWater' | 'createPlumes' | 'drawPlumes' | 'disposePlumes' | 'setPlumeScale' | 'createCaustics' | 'drawCaustics' | 'disposeCaustics' | 'createParticles' | 'drawParticles' | 'drawDeviceParticles' | 'disposeParticles' | 'createWindStreaks' | 'drawWindStreaks' | 'disposeWindStreaks' | 'createFlock' | 'drawFlock' | 'disposeFlock' | 'createBolts' | 'drawBolts' | 'disposeBolts' | 'createLines' | 'drawLines' | 'disposeLines' | 'createText' | 'setText' | 'setPlate' | 'drawText' | 'textWidth' | 'disposeText' | 'createSdfText' | 'setSdfText' | 'drawSdfText' | 'disposeSdfText' | 'createSurfaceTexture' | 'createSurfaceTextureArray' | 'createLightmap' | 'createSceneCapture' | 'captureScene' | 'updateSurfaceTexture' | 'disposeSurfaceTexture' | 'setSurfaceTexture' | 'setMaterial' | 'setSurfaceOverlay' | 'createClothBinding' | 'createClothParticles' | 'updateClothParticles' | 'setCloth' | 'disposeClothBinding' | 'disposeClothParticles'> & {
|
|
191
194
|
/**
|
|
192
195
|
* A mesh's cloth binding: which simulation triangle each of its vertices follows, where on it and
|
|
193
196
|
* by how much (`ClothBindingData`). The mesh must be skinned — a binding blends each vertex
|
|
@@ -211,6 +214,13 @@ export type RendererApi = Omit<{
|
|
|
211
214
|
setCloth(binding: ClothBindingHandle | null, particles?: ClothParticlesHandle | null): void;
|
|
212
215
|
disposeClothBinding(binding: ClothBindingHandle): void;
|
|
213
216
|
disposeClothParticles(particles: ClothParticlesHandle): void;
|
|
217
|
+
/**
|
|
218
|
+
* What the following draws lay over their surfaces beyond their materials — a rim of light, a
|
|
219
|
+
* dissolve with a glowing edge, wrinkle normals by region — or null for none, which every pass
|
|
220
|
+
* starts with. Per draw, so one character's whole set of materials changes together. Its images
|
|
221
|
+
* are regions of one atlas (`SurfaceOverlay.maps`). See `surfaceOverlay.ts`.
|
|
222
|
+
*/
|
|
223
|
+
setSurfaceOverlay(overlay: SurfaceOverlay | null): void;
|
|
214
224
|
/**
|
|
215
225
|
* Upload a caller's image, or a BC image's blocks with their stored chain where
|
|
216
226
|
* `compressedFormats` has the format. See `SurfaceTextureHandle` for the boundary this guards.
|
|
@@ -232,12 +242,33 @@ export type RendererApi = Omit<{
|
|
|
232
242
|
createSurfaceTextureArray(sources: readonly SurfaceSource[], options?: Parameters<Renderer['createSurfaceTexture']>[1]): SurfaceTextureHandle;
|
|
233
243
|
/**
|
|
234
244
|
* A baked lightmap page on the device, as the texture a `lightmapModel` material takes for its
|
|
235
|
-
* `modelMap`: two layers of
|
|
236
|
-
*
|
|
245
|
+
* `modelMap`: two layers of four bytes a texel, the irradiance as an rgb9e5 word and the
|
|
246
|
+
* direction, which the lit stage decodes and filters itself, clamped at the page's edges, with no
|
|
247
|
+
* mip chain. A page whose irradiance arrives packed uploads where it lies. Disposed as any texture is, with
|
|
237
248
|
* `disposeSurfaceTexture`; `updateSurfaceTexture` refuses it, as it refuses any array. See
|
|
238
249
|
* `lightmap.ts` for what a surface does with it.
|
|
239
250
|
*/
|
|
240
251
|
createLightmap(page: LightmapPage): SurfaceTextureHandle;
|
|
252
|
+
/**
|
|
253
|
+
* A texture the scene can be drawn into from a camera of the caller's (`captureScene`), which a
|
|
254
|
+
* material then shows like any image — most often as its `emissive`, for a screen in the world
|
|
255
|
+
* showing what a camera elsewhere sees. `width` and `height` in pixels, 1 to 4096. It holds
|
|
256
|
+
* radiance, at the colour format a probe takes: half floats under `hdrScene`, bytes otherwise.
|
|
257
|
+
* Disposed as any texture is; `updateSurfaceTexture` refuses it. See `sceneCapture.ts`.
|
|
258
|
+
*/
|
|
259
|
+
createSceneCapture(width: number, height: number): SurfaceTextureHandle;
|
|
260
|
+
/**
|
|
261
|
+
* Draw the scene into a capture from `camera`, at the moment a caller says, usually once a frame
|
|
262
|
+
* before the frame's own world. `draw` is handed the camera, its matrices updated for the
|
|
263
|
+
* capture's shape, and submits the scene exactly as it would to the screen — `bindMeshPass`,
|
|
264
|
+
* `drawMesh` and the rest; `bindMeshPass` the frame's own camera again afterwards. The output
|
|
265
|
+
* transform is held off, so a screen showing it is graded once, with the frame.
|
|
266
|
+
*
|
|
267
|
+
* **The mesh pass alone**, as a probe bake is: what a scene draws after its world is in the
|
|
268
|
+
* capture only if `draw` draws it, and no temporal resolve or effect after the world runs on it.
|
|
269
|
+
* Returns whether it drew: false for a texture that is not a capture.
|
|
270
|
+
*/
|
|
271
|
+
captureScene(capture: SurfaceTextureHandle, camera: Camera, clearColor: Vec3, draw: (camera: Camera) => void): boolean;
|
|
241
272
|
/**
|
|
242
273
|
* Replace a texture's pixels, keeping the GPU object and its sampler state.
|
|
243
274
|
*
|
|
@@ -444,6 +475,13 @@ export type RendererApi = Omit<{
|
|
|
444
475
|
}): ParticleHandle;
|
|
445
476
|
/** Draw a pool's live particles, after the opaque scene. */
|
|
446
477
|
drawParticles(batch: ParticleHandle, data: Parameters<Renderer['drawParticles']>[1], camera: Parameters<Renderer['drawParticles']>[2], env: Parameters<Renderer['drawParticles']>[3], timeSeconds: number): void;
|
|
478
|
+
/**
|
|
479
|
+
* Draw a pool's particles from a buffer a caller's own compute wrote (`registerCompute`), where
|
|
480
|
+
* they lie: the pool's material, blend, flipbook and fades, its instance stream the caller's
|
|
481
|
+
* buffer. WebGPU only; WebGL2 says so once and draws nothing. Not sorted. See `DeviceParticles`
|
|
482
|
+
* for the layout.
|
|
483
|
+
*/
|
|
484
|
+
drawDeviceParticles(batch: ParticleHandle, particles: DeviceParticles, camera: Parameters<Renderer['drawParticles']>[2], env: Parameters<Renderer['drawParticles']>[3], timeSeconds: number): void;
|
|
447
485
|
/** Release a particle pool. */
|
|
448
486
|
disposeParticles(batch: ParticleHandle): void;
|
|
449
487
|
/** Draw a wet film over geometry, using a handle as the surface it lies on. */
|
|
@@ -132,6 +132,12 @@ export interface CreateRendererOptions {
|
|
|
132
132
|
* badge is looked at on a deployed build.
|
|
133
133
|
*/
|
|
134
134
|
readonly splash?: boolean | SplashOptions;
|
|
135
|
+
/**
|
|
136
|
+
* Whether the display shows a high dynamic range, asked only where `highDynamicRange` was. The
|
|
137
|
+
* browser's `(dynamic-range: high)` query by default; a host with no DOM, or one that knows
|
|
138
|
+
* better about its own display, answers instead. See `displayRange.ts`.
|
|
139
|
+
*/
|
|
140
|
+
readonly highDynamicRangeDisplay?: () => boolean;
|
|
135
141
|
}
|
|
136
142
|
/** What was built, and which path built it. */
|
|
137
143
|
export interface CreatedRenderer {
|
|
@@ -4,6 +4,7 @@ import { BACKEND_TIMEOUT_MS, forcedBackend, selectBackend } from './select.js';
|
|
|
4
4
|
import { TIMED_OUT, withDeadline } from './deadline.js';
|
|
5
5
|
import { mountSplash } from '../../ui/splash.js';
|
|
6
6
|
import { gpuDrivenRefusal, gpuDrivenSupported } from '../gpudriven/pipeline.js';
|
|
7
|
+
import { browserHighDynamicRange, chooseDisplayRange } from '../displayRange.js';
|
|
7
8
|
/**
|
|
8
9
|
* What a caller who says nothing gets.
|
|
9
10
|
*
|
|
@@ -177,7 +178,9 @@ export async function createRenderer(canvas, quality = {}, options = {}) {
|
|
|
177
178
|
const { createGpuSurface } = await import('./webgpu/device.js');
|
|
178
179
|
const { WebGPURenderer } = await import('./webgpu/renderer.js');
|
|
179
180
|
stage = 'creating the surface';
|
|
180
|
-
const
|
|
181
|
+
const profile = resolveRenderQuality(qualityFor('webgpu'));
|
|
182
|
+
const surface = createGpuSurface(canvas, device, chooseDisplayRange(profile.highDynamicRange, profile.screenEffects && profile.hdrScene, profile.highDynamicRange &&
|
|
183
|
+
(options.highDynamicRangeDisplay ?? browserHighDynamicRange)()));
|
|
181
184
|
stage = 'constructing the renderer';
|
|
182
185
|
return { surface, WebGPURenderer };
|
|
183
186
|
})();
|