reze-engine 0.68.1 → 0.68.3
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/dist/engine.d.ts +100 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +195 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/shaders/passes/aov.d.ts +37 -0
- package/dist/shaders/passes/aov.d.ts.map +1 -0
- package/dist/shaders/passes/aov.js +180 -0
- package/package.json +1 -1
- package/src/engine.ts +230 -3
- package/src/index.ts +4 -0
- package/src/shaders/passes/aov.ts +187 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { sceneLightApi } from "../scene-light-api"
|
|
2
|
+
import { lightsApi, MAX_LIGHTS } from "../lights"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The frame, read back as numbers — what an agent (or a test) measures instead
|
|
6
|
+
* of squinting at a picture.
|
|
7
|
+
*
|
|
8
|
+
* A screenshot answers "what does it look like". It cannot answer the questions
|
|
9
|
+
* a lighting artist actually asks of a render: is her face in the sun's shadow,
|
|
10
|
+
* which lamp is carrying the key, how many stops over is that highlight, which
|
|
11
|
+
* pixels are skin. Every one of those is a number the frame already computed
|
|
12
|
+
* and threw away; these passes keep them.
|
|
13
|
+
*
|
|
14
|
+
* TWO ENTRY POINTS over one prelude:
|
|
15
|
+
*
|
|
16
|
+
* pixels — one invocation per texel of the frame just rendered. Reads the id
|
|
17
|
+
* attachment, the depth and the linear HDR resolve, rebuilds the
|
|
18
|
+
* world point from the depth and a normal from its neighbours, and
|
|
19
|
+
* asks the same shadow atlas and lamp records the materials asked.
|
|
20
|
+
* probe — the same light questions at points the caller names (a bone, a
|
|
21
|
+
* face), with each lamp's share kept apart.
|
|
22
|
+
*
|
|
23
|
+
* The normal is RECONSTRUCTED from depth, not the one the material shaded
|
|
24
|
+
* with: the frame never stores normals, and storing them for every frame to
|
|
25
|
+
* serve an occasional readback would be the cost the id attachment's
|
|
26
|
+
* store-only-when-read rule exists to avoid. On a silhouette edge it is the
|
|
27
|
+
* flatter of the two neighbours, which is right for every texel that is not
|
|
28
|
+
* the edge itself.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** Floats per texel in the pixels output — see PIXEL_FIELDS. */
|
|
32
|
+
export const AOV_PIXEL_STRIDE = 12
|
|
33
|
+
|
|
34
|
+
/** What each of a texel's floats is, in order. */
|
|
35
|
+
export const AOV_PIXEL_FIELDS = [
|
|
36
|
+
"r", "g", "b", // linear HDR, before exposure and the view transform
|
|
37
|
+
"depth", // distance from the eye, world units; 0 where nothing drew
|
|
38
|
+
"sunShadow", // 1 lit, 0 in a caster's shadow; -1 where nothing drew
|
|
39
|
+
"sunFacing", // N·L toward the sun, -1..1 (the toon ramp's input)
|
|
40
|
+
"lampR", "lampG", "lampB", // every positional lamp's light arriving here
|
|
41
|
+
"ambR", "ambG", "ambB", // the world's light arriving here
|
|
42
|
+
] as const
|
|
43
|
+
|
|
44
|
+
/** Floats per probe point before its per-lamp block. */
|
|
45
|
+
export const AOV_PROBE_HEAD = 12
|
|
46
|
+
/** Floats per probe point: the head, then rgb+pad for every lamp slot. */
|
|
47
|
+
export const AOV_PROBE_STRIDE = AOV_PROBE_HEAD + MAX_LIGHTS * 4
|
|
48
|
+
|
|
49
|
+
// Bindings 0..3 are the scene light API, 4..6 the lamps and their cookies.
|
|
50
|
+
const PRELUDE = /* wgsl */ `
|
|
51
|
+
${sceneLightApi(true, 0, 0)}
|
|
52
|
+
${lightsApi(0, 4, "0xffffffffu", { binding: 5, sampler: "_aovSampler" })}
|
|
53
|
+
@group(0) @binding(6) var _aovSampler: sampler;
|
|
54
|
+
|
|
55
|
+
/** The sun's cast shadow at p, nudged along n off the surface — the
|
|
56
|
+
* materials' rzSunOcclusion, without the facing test (sunFacing carries it). */
|
|
57
|
+
fn aovSunShadow(p: vec3f, n: vec3f) -> f32 {
|
|
58
|
+
let castAmt = _rzLight.lights[0].direction.w;
|
|
59
|
+
if (castAmt <= 0.0) { return 1.0; }
|
|
60
|
+
let a = _rzSunLocate(p + n * 0.08);
|
|
61
|
+
if (a.w < 0.0) { return 1.0; }
|
|
62
|
+
return mix(1.0, _rzSunTaps(a), castAmt);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn aovToSun() -> vec3f { return normalize(-_rzLight.lights[0].direction.xyz); }
|
|
66
|
+
`
|
|
67
|
+
|
|
68
|
+
export const AOV_PIXELS_WGSL = /* wgsl */ `
|
|
69
|
+
${PRELUDE}
|
|
70
|
+
@group(0) @binding(7) var idTex: texture_multisampled_2d<u32>;
|
|
71
|
+
@group(0) @binding(8) var depthTex: texture_depth_multisampled_2d;
|
|
72
|
+
@group(0) @binding(9) var hdrTex: texture_2d<f32>;
|
|
73
|
+
struct AovU { invViewProj: mat4x4f, eye: vec4f, size: vec4u, }
|
|
74
|
+
@group(0) @binding(10) var<uniform> u: AovU;
|
|
75
|
+
@group(0) @binding(11) var<storage, read_write> outF: array<f32>;
|
|
76
|
+
@group(0) @binding(12) var<storage, read_write> outId: array<u32>;
|
|
77
|
+
|
|
78
|
+
fn worldAt(c: vec2i, z: f32) -> vec3f {
|
|
79
|
+
let uv = (vec2f(c) + 0.5) / vec2f(u.size.xy);
|
|
80
|
+
let h = u.invViewProj * vec4f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, z, 1.0);
|
|
81
|
+
return h.xyz / h.w;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn drawn(c: vec2i) -> bool {
|
|
85
|
+
if (any(c < vec2i(0)) || any(c >= vec2i(u.size.xy))) { return false; }
|
|
86
|
+
let id = textureLoad(idTex, c, 0).xy;
|
|
87
|
+
return id.x != 0u || id.y != 0u;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The step from p to the neighbour at c + d (or c - d, negated), whichever is
|
|
91
|
+
* nearer in depth: across a silhouette the far neighbour belongs to something
|
|
92
|
+
* else, and a normal built from it tilts toward the background. */
|
|
93
|
+
fn tangent(c: vec2i, d: vec2i, p: vec3f) -> vec3f {
|
|
94
|
+
let a = c + d;
|
|
95
|
+
let b = c - d;
|
|
96
|
+
let okA = drawn(a);
|
|
97
|
+
let okB = drawn(b);
|
|
98
|
+
var ta = vec3f(0.0);
|
|
99
|
+
var tb = vec3f(0.0);
|
|
100
|
+
if (okA) { ta = worldAt(a, textureLoad(depthTex, a, 0)) - p; }
|
|
101
|
+
if (okB) { tb = p - worldAt(b, textureLoad(depthTex, b, 0)); }
|
|
102
|
+
if (okA && okB) { return select(tb, ta, dot(ta, ta) <= dot(tb, tb)); }
|
|
103
|
+
if (okA) { return ta; }
|
|
104
|
+
return tb;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@compute @workgroup_size(8, 8)
|
|
108
|
+
fn pixels(@builtin(global_invocation_id) gid: vec3u) {
|
|
109
|
+
if (gid.x >= u.size.x || gid.y >= u.size.y) { return; }
|
|
110
|
+
let c = vec2i(gid.xy);
|
|
111
|
+
let i = gid.y * u.size.x + gid.x;
|
|
112
|
+
let o = i * ${AOV_PIXEL_STRIDE}u;
|
|
113
|
+
let id = textureLoad(idTex, c, 0).xy;
|
|
114
|
+
let rgb = textureLoad(hdrTex, c, 0).rgb;
|
|
115
|
+
// Material in the low half, object in the high — the attachment's own order
|
|
116
|
+
// (scene-contract: vec2u(material, object)).
|
|
117
|
+
outId[i] = (id.y << 16u) | (id.x & 0xffffu);
|
|
118
|
+
outF[o] = rgb.r;
|
|
119
|
+
outF[o + 1u] = rgb.g;
|
|
120
|
+
outF[o + 2u] = rgb.b;
|
|
121
|
+
for (var k = 3u; k < ${AOV_PIXEL_STRIDE}u; k++) { outF[o + k] = 0.0; }
|
|
122
|
+
if (id.x == 0u && id.y == 0u) {
|
|
123
|
+
outF[o + 4u] = -1.0;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
let p = worldAt(c, textureLoad(depthTex, c, 0));
|
|
127
|
+
var n = cross(tangent(c, vec2i(0, 1), p), tangent(c, vec2i(1, 0), p));
|
|
128
|
+
let toEye = u.eye.xyz - p;
|
|
129
|
+
if (dot(n, n) < 1e-12) { n = toEye; }
|
|
130
|
+
n = normalize(n);
|
|
131
|
+
// Toward the eye: the side of the surface this texel shows.
|
|
132
|
+
if (dot(n, toEye) < 0.0) { n = -n; }
|
|
133
|
+
let lamps = rzLightsDiffuse(p, n);
|
|
134
|
+
let amb = rzWorldAmbient(n);
|
|
135
|
+
outF[o + 3u] = length(toEye);
|
|
136
|
+
outF[o + 4u] = aovSunShadow(p, n);
|
|
137
|
+
outF[o + 5u] = dot(n, aovToSun());
|
|
138
|
+
outF[o + 6u] = lamps.r;
|
|
139
|
+
outF[o + 7u] = lamps.g;
|
|
140
|
+
outF[o + 8u] = lamps.b;
|
|
141
|
+
outF[o + 9u] = amb.r;
|
|
142
|
+
outF[o + 10u] = amb.g;
|
|
143
|
+
outF[o + 11u] = amb.b;
|
|
144
|
+
}
|
|
145
|
+
`
|
|
146
|
+
|
|
147
|
+
export const AOV_PROBE_WGSL = /* wgsl */ `
|
|
148
|
+
${PRELUDE}
|
|
149
|
+
struct ProbePoint { p: vec4f, n: vec4f, }
|
|
150
|
+
@group(0) @binding(7) var<storage, read> points: array<ProbePoint>;
|
|
151
|
+
@group(0) @binding(8) var<storage, read_write> outF: array<f32>;
|
|
152
|
+
|
|
153
|
+
@compute @workgroup_size(1)
|
|
154
|
+
fn probe(@builtin(global_invocation_id) gid: vec3u) {
|
|
155
|
+
let i = gid.x;
|
|
156
|
+
if (i >= arrayLength(&points)) { return; }
|
|
157
|
+
let p = points[i].p.xyz;
|
|
158
|
+
let n = normalize(points[i].n.xyz);
|
|
159
|
+
let o = i * ${AOV_PROBE_STRIDE}u;
|
|
160
|
+
// Colour and strength travel apart in the light uniform (writeSun); the
|
|
161
|
+
// light arriving is their product.
|
|
162
|
+
let sun = _rzLight.lights[0].color.rgb * _rzLight.lights[0].color.w;
|
|
163
|
+
let amb = rzWorldAmbient(n);
|
|
164
|
+
outF[o] = aovSunShadow(p, n);
|
|
165
|
+
outF[o + 1u] = dot(n, aovToSun());
|
|
166
|
+
outF[o + 2u] = sun.r;
|
|
167
|
+
outF[o + 3u] = sun.g;
|
|
168
|
+
outF[o + 4u] = sun.b;
|
|
169
|
+
outF[o + 5u] = amb.r;
|
|
170
|
+
outF[o + 6u] = amb.g;
|
|
171
|
+
outF[o + 7u] = amb.b;
|
|
172
|
+
let count = rzLightCount();
|
|
173
|
+
outF[o + 8u] = f32(count);
|
|
174
|
+
// The document's lamps come first (setLights order); the rest are effects'.
|
|
175
|
+
outF[o + 9u] = f32(_rzLightDocCount());
|
|
176
|
+
for (var k = 10u; k < ${AOV_PROBE_HEAD}u; k++) { outF[o + k] = 0.0; }
|
|
177
|
+
for (var l = 0u; l < ${MAX_LIGHTS}u; l++) {
|
|
178
|
+
var c = vec3f(0.0);
|
|
179
|
+
if (l < count) { c = _rzLightOne(l, p, n); }
|
|
180
|
+
let b = o + ${AOV_PROBE_HEAD}u + l * 4u;
|
|
181
|
+
outF[b] = c.r;
|
|
182
|
+
outF[b + 1u] = c.g;
|
|
183
|
+
outF[b + 2u] = c.b;
|
|
184
|
+
outF[b + 3u] = 0.0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
`
|