@driftengine/core 4.10.0 → 4.11.1
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 +107 -0
- package/README.md +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +2 -2
- package/dist/render/ambientOcclusionPass.d.ts +3 -2
- package/dist/render/ambientOcclusionPass.js +4 -2
- package/dist/render/backend/api.d.ts +21 -1
- package/dist/render/backend/webgl2/renderer.d.ts +141 -3
- package/dist/render/backend/webgl2/renderer.js +454 -42
- package/dist/render/backend/webgpu/buffers.d.ts +5 -0
- package/dist/render/backend/webgpu/buffers.js +98 -48
- package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
- package/dist/render/backend/webgpu/clothKernels.js +76 -0
- package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
- package/dist/render/backend/webgpu/clothOps.js +78 -0
- package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
- package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
- package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
- package/dist/render/backend/webgpu/clothSetPack.js +144 -0
- package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
- package/dist/render/backend/webgpu/flatPass.js +2 -1
- package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
- package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
- package/dist/render/backend/webgpu/latePass.js +1 -0
- package/dist/render/backend/webgpu/motionPass.js +15 -6
- package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
- package/dist/render/backend/webgpu/pipelineCache.js +39 -1
- package/dist/render/backend/webgpu/postPass.d.ts +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +100 -1
- package/dist/render/backend/webgpu/renderer.js +423 -95
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
- package/dist/render/backend/webgpu/staticBundles.js +2 -1
- package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
- package/dist/render/backend/webgpu/vertexFormats.js +97 -0
- package/dist/render/globalMediumPass.d.ts +2 -0
- package/dist/render/globalMediumPass.js +1 -0
- package/dist/render/indexWidth.d.ts +17 -0
- package/dist/render/indexWidth.js +23 -0
- package/dist/render/instances.d.ts +13 -1
- package/dist/render/instances.js +10 -0
- package/dist/render/lightMatrix.d.ts +11 -0
- package/dist/render/lightMatrix.js +14 -0
- package/dist/render/mesh.d.ts +9 -0
- package/dist/render/mesh.js +38 -14
- package/dist/render/occlusionFade.d.ts +21 -0
- package/dist/render/occlusionFade.js +27 -0
- package/dist/render/renderQuality.d.ts +30 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.d.ts +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shader.d.ts +15 -0
- package/dist/render/shader.js +41 -0
- package/dist/render/shaders/ambientOcclusion.js +12 -1
- package/dist/render/shaders/flat/directionalShadow.js +84 -10
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +3 -0
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +24 -4
- package/dist/render/shaders/flat/layered.d.ts +27 -0
- package/dist/render/shaders/flat/layered.js +198 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +50 -16
- package/dist/render/shaders/flat/mapReads.d.ts +10 -0
- package/dist/render/shaders/flat/mapReads.js +38 -0
- package/dist/render/shaders/flat/movingSun.d.ts +21 -0
- package/dist/render/shaders/flat/movingSun.js +46 -0
- package/dist/render/shaders/flat/normalMap.d.ts +6 -2
- package/dist/render/shaders/flat/normalMap.js +23 -3
- package/dist/render/shaders/flat/overlay.d.ts +3 -2
- package/dist/render/shaders/flat/overlay.js +41 -19
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/worldUv.d.ts +22 -0
- package/dist/render/shaders/flat/worldUv.js +113 -0
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
- package/dist/render/shaders/generated/flat.wgsl.js +791 -391
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
- package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
- package/dist/render/shaders/globalMedium.js +9 -1
- package/dist/render/shaders/lightVolume.js +9 -1
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +16 -3
- package/dist/render/skinnedClothRun.d.ts +28 -0
- package/dist/render/skinnedClothRun.js +103 -35
- package/dist/render/surfaceLayers.d.ts +107 -0
- package/dist/render/surfaceLayers.js +82 -0
- package/dist/render/surfaceOverlay.d.ts +16 -3
- package/dist/render/surfaceOverlay.js +11 -6
- package/dist/render/surfaceProjection.d.ts +34 -0
- package/dist/render/surfaceProjection.js +30 -0
- package/dist/render/surfaceTexture.d.ts +39 -0
- package/dist/render/transmission.d.ts +21 -0
- package/dist/render/transmission.js +21 -0
- package/dist/render/vertexPacking.d.ts +48 -0
- package/dist/render/vertexPacking.js +68 -0
- package/dist/ui/splash.d.ts +42 -2
- package/dist/ui/splash.js +78 -13
- package/package.json +6 -6
- package/src/index.ts +6 -2
- package/src/render/ambientOcclusionPass.ts +4 -1
- package/src/render/backend/api.ts +31 -0
- package/src/render/backend/webgl2/renderer.ts +519 -56
- package/src/render/backend/webgpu/buffers.ts +153 -54
- package/src/render/backend/webgpu/clothKernels.ts +86 -0
- package/src/render/backend/webgpu/clothOps.ts +86 -0
- package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
- package/src/render/backend/webgpu/clothSetPack.ts +197 -0
- package/src/render/backend/webgpu/drawCommand.ts +3 -0
- package/src/render/backend/webgpu/flatPass.ts +26 -28
- package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
- package/src/render/backend/webgpu/latePass.ts +1 -0
- package/src/render/backend/webgpu/motionPass.ts +18 -7
- package/src/render/backend/webgpu/pipelineCache.ts +46 -7
- package/src/render/backend/webgpu/renderer.ts +622 -164
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
- package/src/render/backend/webgpu/staticBundles.ts +2 -1
- package/src/render/backend/webgpu/vertexFormats.ts +113 -0
- package/src/render/globalMediumPass.ts +7 -0
- package/src/render/indexWidth.ts +27 -0
- package/src/render/instances.ts +18 -1
- package/src/render/lightMatrix.ts +15 -0
- package/src/render/mesh.ts +66 -14
- package/src/render/occlusionFade.ts +29 -0
- package/src/render/renderQuality.ts +38 -0
- package/src/render/rendererHarness.ts +15 -0
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shader.ts +59 -0
- package/src/render/shaders/ambientOcclusion.ts +12 -1
- package/src/render/shaders/flat/directionalShadow.ts +84 -10
- package/src/render/shaders/flat/glassTint.ts +7 -1
- package/src/render/shaders/flat/index.ts +39 -4
- package/src/render/shaders/flat/layered.ts +198 -0
- package/src/render/shaders/flat/main.ts +50 -16
- package/src/render/shaders/flat/mapReads.ts +38 -0
- package/src/render/shaders/flat/movingSun.ts +46 -0
- package/src/render/shaders/flat/normalMap.ts +23 -3
- package/src/render/shaders/flat/overlay.ts +41 -19
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/worldUv.ts +113 -0
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
- package/src/render/shaders/generated/flat.wgsl.ts +791 -391
- package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
- package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
- package/src/render/shaders/globalMedium.ts +9 -1
- package/src/render/shaders/lightVolume.ts +9 -1
- package/src/render/shaders/tangentFrame.ts +16 -3
- package/src/render/skinnedClothRun.ts +138 -38
- package/src/render/surfaceLayers.ts +176 -0
- package/src/render/surfaceOverlay.ts +24 -7
- package/src/render/surfaceProjection.ts +57 -0
- package/src/render/surfaceTexture.ts +39 -0
- package/src/render/transmission.ts +27 -0
- package/src/render/vertexPacking.ts +109 -0
- package/src/ui/splash.ts +83 -14
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
|
@@ -11,74 +11,142 @@
|
|
|
11
11
|
* millisecond a frame on the main thread, and 4,500 are twenty — a garment that size wants the
|
|
12
12
|
* device. The device solver ran 4,500 particles in 0.65 ms a frame, most of it the dispatches
|
|
13
13
|
* rather than the particles (`demo/dev/skinnedCloth.html?bench=1`).
|
|
14
|
+
*
|
|
15
|
+
* **Several garments step as one with `createSkinnedClothSet`**: one pass, one submit and one
|
|
16
|
+
* garment's dispatches a step for all of them, where a garment each was a pass, a submit and its
|
|
17
|
+
* own dispatches each. They must share a step, substeps, iterations and most steps a frame.
|
|
14
18
|
*/
|
|
15
19
|
import { SkinnedCloth } from '@driftengine/physics';
|
|
16
20
|
import { computeHostOf } from './backend/webgpu/computeHost.js';
|
|
17
|
-
import {
|
|
21
|
+
import { GpuClothSet } from './backend/webgpu/gpuClothSet.js';
|
|
18
22
|
/** A garment for `renderer`, from a set-up checked by name. */
|
|
19
23
|
export function createSkinnedCloth(renderer, setup) {
|
|
20
24
|
const host = computeHostOf(renderer);
|
|
21
|
-
|
|
25
|
+
const set = host === null ? new CpuClothSet(renderer, [setup]) : new DeviceClothSet(host, [setup]);
|
|
26
|
+
return new OneGarment(set);
|
|
27
|
+
}
|
|
28
|
+
/** A set of one, as a solver: what `createSkinnedCloth` hands back. */
|
|
29
|
+
class OneGarment {
|
|
30
|
+
set;
|
|
31
|
+
particles;
|
|
32
|
+
runsOn;
|
|
33
|
+
constructor(set) {
|
|
34
|
+
this.set = set;
|
|
35
|
+
this.particles = set.particles[0];
|
|
36
|
+
this.runsOn = set.runsOn;
|
|
37
|
+
}
|
|
38
|
+
step(globals, model, dt) {
|
|
39
|
+
this.set.setPose(0, globals, model);
|
|
40
|
+
this.set.step(dt);
|
|
41
|
+
}
|
|
42
|
+
setWind(x, y, z) {
|
|
43
|
+
this.set.setWind(0, x, y, z);
|
|
44
|
+
}
|
|
45
|
+
reset() {
|
|
46
|
+
this.set.reset(0);
|
|
47
|
+
}
|
|
48
|
+
dispose() {
|
|
49
|
+
this.set.dispose();
|
|
50
|
+
}
|
|
51
|
+
/** The particles where the last step left them: the parity check's door. */
|
|
52
|
+
read() {
|
|
53
|
+
return this.set.read(0);
|
|
54
|
+
}
|
|
22
55
|
}
|
|
23
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Garments for `renderer` stepped together, from set-ups checked by name: one pass, one submit and
|
|
58
|
+
* one garment's dispatches a step on the device, whatever the count. They must agree on `step`,
|
|
59
|
+
* `substeps`, `iterations` and `maxSteps`, and are refused by the first they disagree on.
|
|
60
|
+
*/
|
|
61
|
+
export function createSkinnedClothSet(renderer, setups) {
|
|
62
|
+
const host = computeHostOf(renderer);
|
|
63
|
+
return host === null ? new CpuClothSet(renderer, setups) : new DeviceClothSet(host, setups);
|
|
64
|
+
}
|
|
65
|
+
class DeviceClothSet {
|
|
24
66
|
host;
|
|
25
67
|
runsOn = 'device';
|
|
26
68
|
particles;
|
|
27
|
-
|
|
28
|
-
constructor(host,
|
|
69
|
+
set;
|
|
70
|
+
constructor(host, setups) {
|
|
29
71
|
this.host = host;
|
|
30
|
-
this.
|
|
31
|
-
|
|
72
|
+
this.set = new GpuClothSet(host.device(), setups);
|
|
73
|
+
const particles = [];
|
|
74
|
+
for (let g = 0; g < this.set.size; g++)
|
|
75
|
+
particles.push(this.set.particles(g));
|
|
76
|
+
this.particles = particles;
|
|
32
77
|
}
|
|
33
|
-
|
|
78
|
+
setPose(g, globals, model) {
|
|
34
79
|
if (this.host.lost())
|
|
35
80
|
return;
|
|
36
|
-
this.
|
|
81
|
+
this.set.setPose(g, globals, model);
|
|
37
82
|
}
|
|
38
|
-
setWind(x, y, z) {
|
|
39
|
-
this.
|
|
83
|
+
setWind(g, x, y, z) {
|
|
84
|
+
this.set.setWind(g, x, y, z);
|
|
40
85
|
}
|
|
41
|
-
reset() {
|
|
86
|
+
reset(g) {
|
|
87
|
+
if (this.host.lost())
|
|
88
|
+
return;
|
|
89
|
+
this.set.reset(g);
|
|
90
|
+
}
|
|
91
|
+
step(dt) {
|
|
42
92
|
if (this.host.lost())
|
|
43
93
|
return;
|
|
44
|
-
this.
|
|
94
|
+
this.set.step(dt, this.host.changeFrame());
|
|
45
95
|
}
|
|
46
96
|
/** Its buffers at once — its work is submitted — and its textures with the frame's. */
|
|
47
97
|
dispose() {
|
|
48
|
-
this.
|
|
49
|
-
|
|
98
|
+
this.set.dispose();
|
|
99
|
+
for (let g = 0; g < this.set.size; g++)
|
|
100
|
+
this.host.retire(this.set.particles(g).textures);
|
|
50
101
|
}
|
|
51
|
-
/**
|
|
52
|
-
read() {
|
|
53
|
-
return this.
|
|
102
|
+
/** Garment `g`'s particles where its last step left them: the parity check's, waits on the device. */
|
|
103
|
+
read(g) {
|
|
104
|
+
return this.set.read(g);
|
|
54
105
|
}
|
|
55
106
|
}
|
|
56
|
-
class
|
|
107
|
+
class CpuClothSet {
|
|
57
108
|
renderer;
|
|
58
109
|
runsOn = 'cpu';
|
|
59
110
|
particles;
|
|
60
|
-
|
|
61
|
-
/**
|
|
111
|
+
cloths;
|
|
112
|
+
/** Each garment's particles at `alpha`, before they go up. */
|
|
62
113
|
staging;
|
|
63
|
-
constructor(renderer,
|
|
114
|
+
constructor(renderer, setups) {
|
|
64
115
|
this.renderer = renderer;
|
|
65
|
-
this.
|
|
66
|
-
this.particles = renderer.createClothParticles(
|
|
67
|
-
this.staging = new Float32Array(
|
|
116
|
+
this.cloths = setups.map((setup) => new SkinnedCloth(setup));
|
|
117
|
+
this.particles = this.cloths.map((cloth) => renderer.createClothParticles(cloth.count));
|
|
118
|
+
this.staging = this.cloths.map((cloth) => new Float32Array(cloth.count * 3));
|
|
68
119
|
}
|
|
69
|
-
|
|
70
|
-
this.
|
|
71
|
-
this.cloth.advance(dt);
|
|
72
|
-
this.cloth.interpolate(this.cloth.alpha, this.staging);
|
|
73
|
-
this.renderer.updateClothParticles(this.particles, this.staging);
|
|
120
|
+
setPose(g, globals, model) {
|
|
121
|
+
this.at(g).setPose(globals, model);
|
|
74
122
|
}
|
|
75
|
-
setWind(x, y, z) {
|
|
76
|
-
this.
|
|
123
|
+
setWind(g, x, y, z) {
|
|
124
|
+
this.at(g).setWind(x, y, z);
|
|
77
125
|
}
|
|
78
|
-
reset() {
|
|
79
|
-
this.
|
|
126
|
+
reset(g) {
|
|
127
|
+
this.at(g).reset();
|
|
128
|
+
}
|
|
129
|
+
step(dt) {
|
|
130
|
+
for (let g = 0; g < this.cloths.length; g++) {
|
|
131
|
+
const cloth = this.cloths[g];
|
|
132
|
+
const staging = this.staging[g];
|
|
133
|
+
cloth.advance(dt);
|
|
134
|
+
cloth.interpolate(cloth.alpha, staging);
|
|
135
|
+
this.renderer.updateClothParticles(this.particles[g], staging);
|
|
136
|
+
}
|
|
80
137
|
}
|
|
81
138
|
dispose() {
|
|
82
|
-
|
|
139
|
+
for (const particles of this.particles)
|
|
140
|
+
this.renderer.disposeClothParticles(particles);
|
|
141
|
+
}
|
|
142
|
+
/** Garment `g`'s particles where its last step left them, as the device set reads its own. */
|
|
143
|
+
read(g) {
|
|
144
|
+
return Promise.resolve(new Float32Array(this.at(g).positions));
|
|
145
|
+
}
|
|
146
|
+
at(g) {
|
|
147
|
+
const cloth = this.cloths[g];
|
|
148
|
+
if (cloth === undefined)
|
|
149
|
+
throw new Error(`skinned cloth set: no garment ${g}`);
|
|
150
|
+
return cloth;
|
|
83
151
|
}
|
|
84
152
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material blended from layers by a mask: `SurfaceMaterial.layers`, the four vectors
|
|
3
|
+
* `shaders/flat/layered.ts` reads it from, and the decisions both backends take about it.
|
|
4
|
+
*/
|
|
5
|
+
import type { SurfaceProjection } from './surfaceProjection.ts';
|
|
6
|
+
/** The most layers a material blends: a base and one for each of the mask's four channels. */
|
|
7
|
+
export declare const MAX_SURFACE_LAYERS = 5;
|
|
8
|
+
/**
|
|
9
|
+
* Up to five layers, each its own colour, normal and roughness at its own repeat, blended by a mask.
|
|
10
|
+
*
|
|
11
|
+
* **Layer `i` is layer `i` of the material's arrays**: `albedo`, `normal` and `orm` each an array
|
|
12
|
+
* whose layers are the material's layers in order, read at the mesh's coordinates times
|
|
13
|
+
* `repeats[i]`, so rock can repeat forty times across a cliff while the moss on it repeats three.
|
|
14
|
+
* Where the material has a `projection`, the layers are read at the world's horizontal position
|
|
15
|
+
* instead, so a repeat is so many a metre (with the projection's own scale at 1). **The mask** is
|
|
16
|
+
* read at the mesh's own coordinates and lays each layer over those before it: red lays layer 1
|
|
17
|
+
* over the base, green layer 2 over that, blue layer 3, alpha layer 4. `emissiveLayer` gives the
|
|
18
|
+
* material's emissive map to one layer, glowing where that layer shows and nowhere it is covered;
|
|
19
|
+
* absent, the map glows as on any material.
|
|
20
|
+
*
|
|
21
|
+
* **Maps beyond the layers ride the material's own arrays, after its layers**, so they take no
|
|
22
|
+
* texture binding of their own — the lit stage has none to spare: a mask the ORM array carries
|
|
23
|
+
* (`mask: 'orm'`) is the ORM array's layer just past the layers, an `addMask` the one after that,
|
|
24
|
+
* and `meshNormal` the normal array's layer just past the layers. Each shares its array's size and
|
|
25
|
+
* format, so a four-channel mask wants an array that stores four.
|
|
26
|
+
*
|
|
27
|
+
* **What it gives up**: every layer's maps are read wherever any shows, three reads a layer, so five
|
|
28
|
+
* layers are fifteen reads where one material is three; layers share an array's size and format; a
|
|
29
|
+
* cutout reads the base layer's alpha; a mask read where a model's map goes leaves no room for one,
|
|
30
|
+
* so a lightmapped surface carries its mask in its ORM array or its vertices; a triplanar projection
|
|
31
|
+
* lays layers on the horizontal plane alone; and a mask is sampled linear, so it is uploaded as data
|
|
32
|
+
* (`colorSpace: 'linear'`).
|
|
33
|
+
*/
|
|
34
|
+
export interface SurfaceLayers<Texture> {
|
|
35
|
+
/**
|
|
36
|
+
* Where the weights come from, red to alpha laying layers 1 to 4: a map read where a shading
|
|
37
|
+
* model's map goes; `'orm'`, the ORM array's layer just past the layers; or `'vertex'`, the
|
|
38
|
+
* vertex colour's red, green and blue, which then weights the layers instead of tinting them.
|
|
39
|
+
*/
|
|
40
|
+
readonly mask: Texture | 'orm' | 'vertex';
|
|
41
|
+
/** Each layer's repeats across the mesh's coordinates, base first: one to five of them. */
|
|
42
|
+
readonly repeats: readonly number[];
|
|
43
|
+
/** The layer the emissive map glows on, 0 to 4. Absent: everywhere, as on any material. */
|
|
44
|
+
readonly emissiveLayer?: number;
|
|
45
|
+
/**
|
|
46
|
+
* How the layers combine. `'over'`, the default, lays each over the ones before it by its
|
|
47
|
+
* channel. `'sum'` mixes the base toward the sum of each layer times its channel, by the channels'
|
|
48
|
+
* sum held to 1: two layers at half each are both half there, where laying over would let the
|
|
49
|
+
* second cover half of the first.
|
|
50
|
+
*/
|
|
51
|
+
readonly blend?: 'over' | 'sum';
|
|
52
|
+
/**
|
|
53
|
+
* A second mask added to one layer's weight, read where the layers are (the world, under a
|
|
54
|
+
* `projection`) times `repeat`, its red times `intensity`: sand drifting into the gravel's layer at
|
|
55
|
+
* a scale of its own. The ORM array's layer after the layers and any mask it carries.
|
|
56
|
+
*/
|
|
57
|
+
readonly addMask?: {
|
|
58
|
+
readonly layer: number;
|
|
59
|
+
readonly repeat: number;
|
|
60
|
+
readonly intensity: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* One layer weighted by how much the surface faces up rather than by its mask channel:
|
|
64
|
+
* `saturate(bias + sharpness × (up / 2 + 1/2))`, `up` the world normal's upward share before any
|
|
65
|
+
* normal map. Snow on the tops of rocks, moss on a ledge.
|
|
66
|
+
*/
|
|
67
|
+
readonly facing?: {
|
|
68
|
+
readonly layer: number;
|
|
69
|
+
readonly bias: number;
|
|
70
|
+
readonly sharpness: number;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* A normal map read at the mesh's own coordinates and tangents, the normal array's layer just past
|
|
74
|
+
* the layers, under the layers' blended normal: the large shapes of a rock or a cliff, which a
|
|
75
|
+
* layer repeating forty times across it cannot carry.
|
|
76
|
+
*/
|
|
77
|
+
readonly meshNormal?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/** Floats `packSurfaceLayers` writes: four vectors. */
|
|
80
|
+
export declare const LAYER_FLOATS = 16;
|
|
81
|
+
/**
|
|
82
|
+
* `layers` into `out`, four vectors. The first five floats are the repeats (1 for a layer not
|
|
83
|
+
* given), then how many layers there are, which one glows (−1 for none) and where the mask comes
|
|
84
|
+
* from (0 a map, 1 the ORM array, 2 the vertex colour). Then the blend (0 over, 1 sum), whether a
|
|
85
|
+
* mesh normal is laid under the layers, the added mask's layer (−1 for none) and repeat; its
|
|
86
|
+
* intensity; and the facing layer (−1 for none), its bias and its sharpness. Zeros, no layers, where
|
|
87
|
+
* there are none.
|
|
88
|
+
*
|
|
89
|
+
* A repeat that is not a positive number is 1, so a layer is never laid at a scale that collapses it
|
|
90
|
+
* to a point; a glow, an added mask or a facing layer naming a layer there is not is none, and the
|
|
91
|
+
* base, which no channel lays, is not one an added mask or the facing can name.
|
|
92
|
+
*/
|
|
93
|
+
export declare function packSurfaceLayers<Texture>(layers: SurfaceLayers<Texture> | null | undefined, out: Float32Array): void;
|
|
94
|
+
/** The texture a layered material's mask is, where it is one rather than another array or the vertices. */
|
|
95
|
+
export declare function layerMaskMap<Texture>(layers: SurfaceLayers<Texture> | null | undefined): Texture | null;
|
|
96
|
+
/**
|
|
97
|
+
* Whether a material's layers can be drawn beside its `modelMap`: always, unless the mask is a map,
|
|
98
|
+
* which is read where a model's map goes and so cannot share the slot with one — a lightmap's page
|
|
99
|
+
* among them. The backends draw such a material as one layer and say so once.
|
|
100
|
+
*/
|
|
101
|
+
export declare function layersFitBeside<Texture>(layers: SurfaceLayers<Texture>, modelMap: Texture | null | undefined): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* The projection a layered material is drawn with: a triplanar one is laid on the horizontal plane,
|
|
104
|
+
* at its own scale, since every layer's maps read three times over would be forty-five reads a pixel.
|
|
105
|
+
* Anything else as it was. Whether to say so is the caller's.
|
|
106
|
+
*/
|
|
107
|
+
export declare function layeredProjection<Texture>(projection: SurfaceProjection | null | undefined, layers: SurfaceLayers<Texture> | null | undefined): SurfaceProjection | null;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** The most layers a material blends: a base and one for each of the mask's four channels. */
|
|
2
|
+
export const MAX_SURFACE_LAYERS = 5;
|
|
3
|
+
/** Floats `packSurfaceLayers` writes: four vectors. */
|
|
4
|
+
export const LAYER_FLOATS = 16;
|
|
5
|
+
/** Where the mask is read, as the fourth float of the second vector carries it. */
|
|
6
|
+
const MASK_SOURCE = { map: 0, orm: 1, vertex: 2 };
|
|
7
|
+
/**
|
|
8
|
+
* `layers` into `out`, four vectors. The first five floats are the repeats (1 for a layer not
|
|
9
|
+
* given), then how many layers there are, which one glows (−1 for none) and where the mask comes
|
|
10
|
+
* from (0 a map, 1 the ORM array, 2 the vertex colour). Then the blend (0 over, 1 sum), whether a
|
|
11
|
+
* mesh normal is laid under the layers, the added mask's layer (−1 for none) and repeat; its
|
|
12
|
+
* intensity; and the facing layer (−1 for none), its bias and its sharpness. Zeros, no layers, where
|
|
13
|
+
* there are none.
|
|
14
|
+
*
|
|
15
|
+
* A repeat that is not a positive number is 1, so a layer is never laid at a scale that collapses it
|
|
16
|
+
* to a point; a glow, an added mask or a facing layer naming a layer there is not is none, and the
|
|
17
|
+
* base, which no channel lays, is not one an added mask or the facing can name.
|
|
18
|
+
*/
|
|
19
|
+
export function packSurfaceLayers(layers, out) {
|
|
20
|
+
out.fill(0);
|
|
21
|
+
if (layers === null || layers === undefined || layers.repeats.length === 0)
|
|
22
|
+
return;
|
|
23
|
+
const count = Math.min(layers.repeats.length, MAX_SURFACE_LAYERS);
|
|
24
|
+
for (let i = 0; i < MAX_SURFACE_LAYERS; i++)
|
|
25
|
+
out[i] = positive(i < count ? layers.repeats[i] : 1);
|
|
26
|
+
out[5] = count;
|
|
27
|
+
const glow = layers.emissiveLayer;
|
|
28
|
+
out[6] = glow !== undefined && Number.isInteger(glow) && glow >= 0 && glow < count ? glow : -1;
|
|
29
|
+
out[7] =
|
|
30
|
+
layers.mask === 'orm'
|
|
31
|
+
? MASK_SOURCE.orm
|
|
32
|
+
: layers.mask === 'vertex'
|
|
33
|
+
? MASK_SOURCE.vertex
|
|
34
|
+
: MASK_SOURCE.map;
|
|
35
|
+
out[8] = layers.blend === 'sum' ? 1 : 0;
|
|
36
|
+
out[9] = layers.meshNormal === true ? 1 : 0;
|
|
37
|
+
const added = layers.addMask;
|
|
38
|
+
const addLayer = laidLayer(added?.layer, count);
|
|
39
|
+
out[10] = addLayer;
|
|
40
|
+
out[11] = addLayer < 0 ? 0 : positive(added?.repeat);
|
|
41
|
+
out[12] = addLayer < 0 ? 0 : finite(added?.intensity);
|
|
42
|
+
const facing = layers.facing;
|
|
43
|
+
const faceLayer = laidLayer(facing?.layer, count);
|
|
44
|
+
out[13] = faceLayer;
|
|
45
|
+
out[14] = faceLayer < 0 ? 0 : finite(facing?.bias);
|
|
46
|
+
out[15] = faceLayer < 0 ? 0 : finite(facing?.sharpness);
|
|
47
|
+
}
|
|
48
|
+
/** The texture a layered material's mask is, where it is one rather than another array or the vertices. */
|
|
49
|
+
export function layerMaskMap(layers) {
|
|
50
|
+
const mask = layers?.mask;
|
|
51
|
+
return mask === undefined || mask === 'orm' || mask === 'vertex' ? null : mask;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether a material's layers can be drawn beside its `modelMap`: always, unless the mask is a map,
|
|
55
|
+
* which is read where a model's map goes and so cannot share the slot with one — a lightmap's page
|
|
56
|
+
* among them. The backends draw such a material as one layer and say so once.
|
|
57
|
+
*/
|
|
58
|
+
export function layersFitBeside(layers, modelMap) {
|
|
59
|
+
return modelMap === null || modelMap === undefined || layerMaskMap(layers) === null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The projection a layered material is drawn with: a triplanar one is laid on the horizontal plane,
|
|
63
|
+
* at its own scale, since every layer's maps read three times over would be forty-five reads a pixel.
|
|
64
|
+
* Anything else as it was. Whether to say so is the caller's.
|
|
65
|
+
*/
|
|
66
|
+
export function layeredProjection(projection, layers) {
|
|
67
|
+
if (projection === null || projection === undefined)
|
|
68
|
+
return null;
|
|
69
|
+
if (layers === null || layers === undefined || projection.kind !== 'triplanar')
|
|
70
|
+
return projection;
|
|
71
|
+
return { kind: 'planar', scale: projection.scale };
|
|
72
|
+
}
|
|
73
|
+
/** A layer an added mask or the facing may name: one a channel lays, 1 to `count − 1`, or −1. */
|
|
74
|
+
function laidLayer(layer, count) {
|
|
75
|
+
return layer !== undefined && Number.isInteger(layer) && layer >= 1 && layer < count ? layer : -1;
|
|
76
|
+
}
|
|
77
|
+
function positive(value) {
|
|
78
|
+
return value !== undefined && Number.isFinite(value) && value > 0 ? value : 1;
|
|
79
|
+
}
|
|
80
|
+
function finite(value) {
|
|
81
|
+
return value !== undefined && Number.isFinite(value) ? value : 0;
|
|
82
|
+
}
|
|
@@ -27,12 +27,24 @@ export interface OverlayRegion {
|
|
|
27
27
|
* raised to a contrast, times a noise laid in screen space, a slow pulse and a mask, in a colour.
|
|
28
28
|
* Added to what the surface emits, and not gated on the night as emission is: it is a glow the
|
|
29
29
|
* surface wears, not light it gives off.
|
|
30
|
+
*
|
|
31
|
+
* **Or, with `mode: 'blend'`, a colour the surface is pulled toward**, lit like the rest of it: the
|
|
32
|
+
* base colour mixed toward the rim's by the edge times `alpha`, before any light reads it, so the
|
|
33
|
+
* rim is bright where light falls and dark where none does. The colour is `colour` times
|
|
34
|
+
* `intensity`, each channel held at 1 as a base colour is, so a colour of 8 red is a red of 1 and
|
|
35
|
+
* not a glow; `alpha` is the weight, which may pass 1 to saturate the edge sooner.
|
|
30
36
|
*/
|
|
31
37
|
export interface SurfaceRim {
|
|
32
38
|
readonly colour: Vec3;
|
|
33
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* How bright, 0 for none, multiplied with `alpha`. Where the rim blends it scales `colour`
|
|
41
|
+
* instead, so 0 there is a rim of black, and `alpha` alone says how much: 0 for none.
|
|
42
|
+
*/
|
|
34
43
|
readonly intensity: number;
|
|
44
|
+
/** The rim's weight, 1 unless given. */
|
|
35
45
|
readonly alpha?: number;
|
|
46
|
+
/** `'add'`, the default, lays the rim on as glow; `'blend'` pulls the base colour toward it, lit. */
|
|
47
|
+
readonly mode?: 'add' | 'blend';
|
|
36
48
|
/** The edge's exponent: larger keeps the rim nearer the silhouette. 1.5 unless given. */
|
|
37
49
|
readonly falloff?: number;
|
|
38
50
|
/**
|
|
@@ -118,10 +130,11 @@ export declare const OVERLAY_FLOATS: number;
|
|
|
118
130
|
* no overlay. A region with a zero scale is an image not given, and its term reads as none — a
|
|
119
131
|
* noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
|
|
120
132
|
*
|
|
121
|
-
* - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
|
|
133
|
+
* - 0: rim colour, rim strength (intensity times alpha; 0 is no rim). A blending rim: the colour
|
|
134
|
+
* times intensity, each channel held at 1, and the alpha alone
|
|
122
135
|
* - 1: falloff, upward lean, contrast, mask weight
|
|
123
136
|
* - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
|
|
124
|
-
* - 4: the pulse's low and high (1 and 1 where none)
|
|
137
|
+
* - 4: the pulse's low and high (1 and 1 where none), and the mode: 0 adds, 1 blends
|
|
125
138
|
* - 5: the rim's mask region
|
|
126
139
|
* - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
|
|
127
140
|
* - 8: the edge's light and its strength; 9: the laid-over colour and its amount
|
|
@@ -14,10 +14,11 @@ function region(out, at, r) {
|
|
|
14
14
|
* no overlay. A region with a zero scale is an image not given, and its term reads as none — a
|
|
15
15
|
* noise of 1, a mask of 0, no wrinkle — and every region is one without `maps`. Fifteen vectors:
|
|
16
16
|
*
|
|
17
|
-
* - 0: rim colour, rim strength (intensity times alpha; 0 is no rim)
|
|
17
|
+
* - 0: rim colour, rim strength (intensity times alpha; 0 is no rim). A blending rim: the colour
|
|
18
|
+
* times intensity, each channel held at 1, and the alpha alone
|
|
18
19
|
* - 1: falloff, upward lean, contrast, mask weight
|
|
19
20
|
* - 2: the rim's noise region; 3: its scroll along x and y, its tiling, the pulse's rate
|
|
20
|
-
* - 4: the pulse's low and high (1 and 1 where none)
|
|
21
|
+
* - 4: the pulse's low and high (1 and 1 where none), and the mode: 0 adds, 1 blends
|
|
21
22
|
* - 5: the rim's mask region
|
|
22
23
|
* - 6: the dissolve's noise region (a zero scale is no dissolve); 7: its tiling, threshold, edge
|
|
23
24
|
* - 8: the edge's light and its strength; 9: the laid-over colour and its amount
|
|
@@ -31,10 +32,14 @@ export function packSurfaceOverlay(overlay, out) {
|
|
|
31
32
|
const images = overlay.maps !== undefined;
|
|
32
33
|
const rim = overlay.rim;
|
|
33
34
|
if (rim !== undefined) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const blend = rim.mode === 'blend';
|
|
36
|
+
const scale = blend ? finite(rim.intensity, 0) : 1;
|
|
37
|
+
for (let c = 0; c < 3; c++) {
|
|
38
|
+
const channel = finite(rim.colour[c], 0) * scale;
|
|
39
|
+
out[c] = blend ? Math.min(Math.max(channel, 0), 1) : channel;
|
|
40
|
+
}
|
|
41
|
+
out[3] = Math.max(0, (blend ? 1 : finite(rim.intensity, 0)) * finite(rim.alpha, 1));
|
|
42
|
+
out[18] = blend ? 1 : 0;
|
|
38
43
|
out[4] = Math.max(0, finite(rim.falloff, 1.5));
|
|
39
44
|
out[5] = Math.max(0, finite(rim.upward, 0.4));
|
|
40
45
|
out[6] = Math.max(0, finite(rim.contrast, 1));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's maps placed by where a point is in the world: `SurfaceMaterial.projection`, and the
|
|
3
|
+
* one vector `shaders/flat/worldUv.ts` reads it from.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Texture coordinates from the world rather than from the mesh.
|
|
7
|
+
*
|
|
8
|
+
* **`'planar'`** lays the maps across the two horizontal axes, `x` and `z`: ground whose mesh
|
|
9
|
+
* carries flat coordinates, or none worth reading, shows its texture at `scale` repeats a metre.
|
|
10
|
+
* **`'triplanar'`** reads each map on the three planes across `x`, `y` and `z` and blends them by
|
|
11
|
+
* how squarely the surface faces each, the normal's components raised to `sharpness`, so walls and
|
|
12
|
+
* rock wear a texture without stretching. See `worldUv.ts` for the normal map's blend.
|
|
13
|
+
*
|
|
14
|
+
* **What it gives up**: the world's coordinates do not move with a mesh, so a moving mesh slides
|
|
15
|
+
* under its texture; triplanar reads every map three times; and the material's `uScale`, `vScale`
|
|
16
|
+
* and offsets, which are the mesh's coordinates', do not apply.
|
|
17
|
+
*/
|
|
18
|
+
export interface SurfaceProjection {
|
|
19
|
+
readonly kind: 'planar' | 'triplanar';
|
|
20
|
+
/** Repeats a metre: 2 lays a texture every half metre. */
|
|
21
|
+
readonly scale: number;
|
|
22
|
+
/** How sharply triplanar's planes meet: the exponent on the normal. 4 unless given. */
|
|
23
|
+
readonly sharpness?: number;
|
|
24
|
+
}
|
|
25
|
+
/** Floats `packSurfaceProjection` writes: one vector. */
|
|
26
|
+
export declare const PROJECTION_FLOATS = 4;
|
|
27
|
+
/**
|
|
28
|
+
* `projection` into `out`: its kind (0 none, 1 planar, 2 triplanar), its repeats a metre and its
|
|
29
|
+
* sharpness; zeros for none. A scale that is not finite and above zero projects nothing, so a
|
|
30
|
+
* material is never drawn at a scale that collapses its texture to a point.
|
|
31
|
+
*/
|
|
32
|
+
export declare function packSurfaceProjection(projection: SurfaceProjection | null | undefined, out: Float32Array): void;
|
|
33
|
+
/** Whether a material projects its maps at all. */
|
|
34
|
+
export declare function projects(projection: SurfaceProjection | null | undefined): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's maps placed by where a point is in the world: `SurfaceMaterial.projection`, and the
|
|
3
|
+
* one vector `shaders/flat/worldUv.ts` reads it from.
|
|
4
|
+
*/
|
|
5
|
+
/** Floats `packSurfaceProjection` writes: one vector. */
|
|
6
|
+
export const PROJECTION_FLOATS = 4;
|
|
7
|
+
/**
|
|
8
|
+
* `projection` into `out`: its kind (0 none, 1 planar, 2 triplanar), its repeats a metre and its
|
|
9
|
+
* sharpness; zeros for none. A scale that is not finite and above zero projects nothing, so a
|
|
10
|
+
* material is never drawn at a scale that collapses its texture to a point.
|
|
11
|
+
*/
|
|
12
|
+
export function packSurfaceProjection(projection, out) {
|
|
13
|
+
out.fill(0);
|
|
14
|
+
if (projection === null || projection === undefined)
|
|
15
|
+
return;
|
|
16
|
+
const scale = projection.scale;
|
|
17
|
+
if (!Number.isFinite(scale) || scale <= 0)
|
|
18
|
+
return;
|
|
19
|
+
out[0] = projection.kind === 'triplanar' ? 2 : 1;
|
|
20
|
+
out[1] = scale;
|
|
21
|
+
const sharpness = projection.sharpness ?? 4;
|
|
22
|
+
out[2] = Number.isFinite(sharpness) ? Math.min(Math.max(sharpness, 1), 64) : 4;
|
|
23
|
+
}
|
|
24
|
+
/** Whether a material projects its maps at all. */
|
|
25
|
+
export function projects(projection) {
|
|
26
|
+
return (projection !== null &&
|
|
27
|
+
projection !== undefined &&
|
|
28
|
+
Number.isFinite(projection.scale) &&
|
|
29
|
+
projection.scale > 0);
|
|
30
|
+
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* array samples exactly as a 2D texture does. A mesh names its layer per vertex (`MeshData.layers`),
|
|
9
9
|
* so a merged block wearing forty facades is one draw.
|
|
10
10
|
*/
|
|
11
|
+
import type { SurfaceLayers } from './surfaceLayers.ts';
|
|
12
|
+
import type { SurfaceProjection } from './surfaceProjection.ts';
|
|
11
13
|
import type { SurfaceModel } from './surfaceModel.ts';
|
|
12
14
|
import type { CompressedTextureFormat, CompressedTextureSource, SurfaceSource } from './compressedSource.ts';
|
|
13
15
|
import type { SurfaceLayerEffect } from './surfaceEffects.ts';
|
|
@@ -196,6 +198,43 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
|
|
|
196
198
|
* and an 8-bit frame clips the brighter peak without `hdrScene`.
|
|
197
199
|
*/
|
|
198
200
|
physicalSpecular?: boolean;
|
|
201
|
+
/**
|
|
202
|
+
* Texture coordinates from where a point is in the world rather than from the mesh: `'planar'`
|
|
203
|
+
* across the horizontal axes for ground, `'triplanar'` on three planes for walls and rock, at so
|
|
204
|
+
* many repeats a metre. Absent, the mesh's, as every material's were. **A lit switch**, compiled in
|
|
205
|
+
* the first time a material asks, so a scene that never asks pays nothing. See
|
|
206
|
+
* `surfaceProjection.ts` for what it gives up.
|
|
207
|
+
*/
|
|
208
|
+
projection?: SurfaceProjection | null;
|
|
209
|
+
/**
|
|
210
|
+
* How much light from behind a thin surface lets through, 0 to 1: a banner lit from behind, a
|
|
211
|
+
* leaf against the sun, a lampshade. The light falling on the far side — the sun, lamps, area
|
|
212
|
+
* lights, DriftLight — reaches the eye through the surface, coloured by its own colour; a metal
|
|
213
|
+
* lets none through. 0, the default, is every material as it was. Two-sided or not: a one-sided
|
|
214
|
+
* surface seen from its front shows the light behind it as well.
|
|
215
|
+
*
|
|
216
|
+
* **What it gives up**: a thin surface, not a volume, so a thick one lets through as much as a
|
|
217
|
+
* sheet; the light behind is not blurred by the surface; and none of it on glass, which lets
|
|
218
|
+
* light through by its own rule (`TranslucentMeshOptions.glass`).
|
|
219
|
+
*/
|
|
220
|
+
diffuseTransmission?: number;
|
|
221
|
+
/**
|
|
222
|
+
* The colour the light from behind takes through the surface, linear, in place of its own colour:
|
|
223
|
+
* a banner whose cloth glows a flat red behind a printed face, a leaf whose veins are not what the
|
|
224
|
+
* light through it shows. Absent, the surface's colour, as `diffuseTransmission` has always used.
|
|
225
|
+
* Each component is held at zero or above. Read only where `diffuseTransmission` lets light through.
|
|
226
|
+
*/
|
|
227
|
+
transmissionColor?: readonly [number, number, number] | null;
|
|
228
|
+
/**
|
|
229
|
+
* Up to five layers blended by a mask, each at its own repeat: `albedo`, `normal` and `orm` arrays
|
|
230
|
+
* whose layers are the material's, and a mask laying each over the ones before it or summing them,
|
|
231
|
+
* from a map, the ORM array or the vertex colour; under a `projection` the layers are placed by the
|
|
232
|
+
* world. Absent, the material is one layer, as every material was. **A lit switch**, compiled in the
|
|
233
|
+
* first time a material asks. A mask that is a map is read where `modelMap` goes, so such a
|
|
234
|
+
* material carries one or the other; a lightmapped one keeps its mask in its ORM array or its
|
|
235
|
+
* vertices. See `surfaceLayers.ts` for the rest and what it gives up.
|
|
236
|
+
*/
|
|
237
|
+
layers?: SurfaceLayers<Texture> | null;
|
|
199
238
|
}
|
|
200
239
|
export interface SurfaceTextureOptions {
|
|
201
240
|
/**
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A thin surface's light from behind as the lit stage reads it: `SurfaceMaterial.diffuseTransmission`
|
|
3
|
+
* and `transmissionColor` into the four lanes of the glass tint an opaque material leaves unused.
|
|
4
|
+
*
|
|
5
|
+
* **The colour rides lanes glass owns**, because the lit stage's uniform rows are spent
|
|
6
|
+
* (`flat/preamble.ts`): a pane writes its own tint there for its draw, so whatever binds a pane
|
|
7
|
+
* puts this back after it, or the next draw of the material lets light through in the pane's colour.
|
|
8
|
+
* **−1 in the colour lanes means "the albedo"**, which is what a material naming no colour has
|
|
9
|
+
* always let through, so a scene that names none draws to the bit as it did.
|
|
10
|
+
*/
|
|
11
|
+
/** What a material lets through from behind and in which colour, as the glass tint carries it. */
|
|
12
|
+
export interface Transmission {
|
|
13
|
+
readonly diffuseTransmission?: number;
|
|
14
|
+
readonly transmissionColor?: readonly [number, number, number] | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `material` into `out`: the colour, or −1 three times where it names none, then the amount held
|
|
18
|
+
* to 0..1. A colour component below zero is held at zero, so a mistyped colour lets no light
|
|
19
|
+
* through rather than reading as "the albedo".
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveTransmission(material: Transmission | null, out: Float32Array): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A thin surface's light from behind as the lit stage reads it: `SurfaceMaterial.diffuseTransmission`
|
|
3
|
+
* and `transmissionColor` into the four lanes of the glass tint an opaque material leaves unused.
|
|
4
|
+
*
|
|
5
|
+
* **The colour rides lanes glass owns**, because the lit stage's uniform rows are spent
|
|
6
|
+
* (`flat/preamble.ts`): a pane writes its own tint there for its draw, so whatever binds a pane
|
|
7
|
+
* puts this back after it, or the next draw of the material lets light through in the pane's colour.
|
|
8
|
+
* **−1 in the colour lanes means "the albedo"**, which is what a material naming no colour has
|
|
9
|
+
* always let through, so a scene that names none draws to the bit as it did.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* `material` into `out`: the colour, or −1 three times where it names none, then the amount held
|
|
13
|
+
* to 0..1. A colour component below zero is held at zero, so a mistyped colour lets no light
|
|
14
|
+
* through rather than reading as "the albedo".
|
|
15
|
+
*/
|
|
16
|
+
export function resolveTransmission(material, out) {
|
|
17
|
+
const colour = material?.transmissionColor ?? null;
|
|
18
|
+
for (let i = 0; i < 3; i++)
|
|
19
|
+
out[i] = colour === null ? -1 : Math.max(colour[i] ?? 0, 0) || 0;
|
|
20
|
+
out[3] = Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
|
|
21
|
+
}
|