reze-engine 0.65.0 → 0.66.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/dist/engine.d.ts +156 -42
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +876 -186
- package/dist/graph/compile.d.ts.map +1 -1
- package/dist/graph/compile.js +5 -0
- package/dist/graph/presets/body.d.ts.map +1 -1
- package/dist/graph/presets/body.js +26 -24
- package/dist/graph/presets/cloth_rough.js +8 -8
- package/dist/graph/presets/cloth_smooth.js +8 -8
- package/dist/graph/presets/default.js +4 -4
- package/dist/graph/presets/eye.js +10 -10
- package/dist/graph/presets/face.d.ts.map +1 -1
- package/dist/graph/presets/face.js +28 -26
- package/dist/graph/presets/hair.js +6 -6
- package/dist/graph/presets/metal.js +8 -8
- package/dist/graph/presets/stockings.d.ts.map +1 -1
- package/dist/graph/presets/stockings.js +4 -11
- package/dist/graph/presets/unlit.d.ts.map +1 -1
- package/dist/graph/presets/unlit.js +9 -13
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +128 -157
- package/dist/graph/render-class.d.ts +5 -2
- package/dist/graph/render-class.d.ts.map +1 -1
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +5 -1
- package/dist/model.d.ts +14 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +27 -1
- package/dist/pmx-loader.d.ts +4 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +11 -2
- package/dist/shaders/anchor-table.d.ts +11 -0
- package/dist/shaders/anchor-table.d.ts.map +1 -1
- package/dist/shaders/anchor-table.js +9 -3
- package/dist/shaders/directives.d.ts +42 -5
- package/dist/shaders/directives.d.ts.map +1 -1
- package/dist/shaders/directives.js +51 -4
- package/dist/shaders/lights.d.ts +11 -7
- package/dist/shaders/lights.d.ts.map +1 -1
- package/dist/shaders/lights.js +315 -271
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +26 -3
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +191 -271
- package/dist/shaders/passes/composite.d.ts +1 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +3 -5
- package/dist/shaders/passes/ground.js +1 -1
- package/dist/shaders/passes/particles.d.ts.map +1 -1
- package/dist/shaders/passes/particles.js +9 -3
- package/dist/shaders/passes/probe.d.ts +45 -0
- package/dist/shaders/passes/probe.d.ts.map +1 -0
- package/dist/shaders/passes/probe.js +230 -0
- package/dist/shaders/passes/scene-contract.d.ts +13 -0
- package/dist/shaders/passes/scene-contract.d.ts.map +1 -1
- package/dist/shaders/passes/scene-contract.js +4 -0
- package/dist/shaders/scene-light-api.d.ts.map +1 -1
- package/dist/shaders/scene-light-api.js +1 -0
- package/dist/unity/globals.d.ts +7 -0
- package/dist/unity/globals.d.ts.map +1 -1
- package/dist/unity/globals.js +4 -3
- package/dist/unity/host.d.ts +3 -0
- package/dist/unity/host.d.ts.map +1 -1
- package/dist/unity/host.js +41 -7
- package/dist/unity/looks.d.ts +42 -0
- package/dist/unity/looks.d.ts.map +1 -1
- package/dist/unity/looks.js +131 -4
- package/dist/unity/stage.d.ts +17 -0
- package/dist/unity/stage.d.ts.map +1 -1
- package/dist/unity/stage.js +22 -5
- package/package.json +1 -1
- package/src/engine.ts +949 -215
- package/src/graph/compile.ts +4 -0
- package/src/graph/presets/body.ts +26 -24
- package/src/graph/presets/cloth_rough.ts +8 -8
- package/src/graph/presets/cloth_smooth.ts +8 -8
- package/src/graph/presets/default.ts +4 -4
- package/src/graph/presets/eye.ts +10 -10
- package/src/graph/presets/face.ts +28 -26
- package/src/graph/presets/hair.ts +6 -6
- package/src/graph/presets/metal.ts +8 -8
- package/src/graph/presets/stockings.ts +4 -11
- package/src/graph/presets/unlit.ts +9 -13
- package/src/graph/registry.ts +133 -158
- package/src/graph/render-class.ts +5 -2
- package/src/graph/slots.ts +5 -1
- package/src/model.ts +33 -1
- package/src/pmx-loader.ts +11 -2
- package/src/shaders/anchor-table.ts +15 -3
- package/src/shaders/directives.ts +88 -6
- package/src/shaders/lights.ts +464 -415
- package/src/shaders/materials/common.ts +26 -3
- package/src/shaders/materials/nodes.ts +191 -271
- package/src/shaders/passes/composite.ts +3 -5
- package/src/shaders/passes/ground.ts +1 -1
- package/src/shaders/passes/particles.ts +9 -3
- package/src/shaders/passes/probe.ts +240 -0
- package/src/shaders/passes/scene-contract.ts +17 -0
- package/src/shaders/scene-light-api.ts +1 -0
- package/src/unity/globals.ts +7 -3
- package/src/unity/host.ts +36 -6
- package/src/unity/looks.ts +161 -7
- package/src/unity/stage.ts +28 -5
- package/dist/physics-debug.d.ts +0 -30
- package/dist/physics-debug.d.ts.map +0 -1
- package/dist/physics-debug.js +0 -526
- package/dist/shaders/agx-lut.d.ts +0 -11
- package/dist/shaders/agx-lut.d.ts.map +0 -1
- package/dist/shaders/agx-lut.js +0 -29
- package/dist/shaders/dfg_lut.d.ts +0 -3
- package/dist/shaders/dfg_lut.d.ts.map +0 -1
- package/dist/shaders/dfg_lut.js +0 -129
- package/dist/shaders/ltc_mag_lut.d.ts +0 -3
- package/dist/shaders/ltc_mag_lut.d.ts.map +0 -1
- package/dist/shaders/ltc_mag_lut.js +0 -1033
- package/dist/shaders/materials/body.d.ts +0 -2
- package/dist/shaders/materials/body.d.ts.map +0 -1
- package/dist/shaders/materials/body.js +0 -95
- package/dist/shaders/materials/cloth_rough.d.ts +0 -2
- package/dist/shaders/materials/cloth_rough.d.ts.map +0 -1
- package/dist/shaders/materials/cloth_rough.js +0 -69
- package/dist/shaders/materials/cloth_smooth.d.ts +0 -2
- package/dist/shaders/materials/cloth_smooth.d.ts.map +0 -1
- package/dist/shaders/materials/cloth_smooth.js +0 -61
- package/dist/shaders/materials/default.d.ts +0 -2
- package/dist/shaders/materials/default.d.ts.map +0 -1
- package/dist/shaders/materials/default.js +0 -43
- package/dist/shaders/materials/eye.d.ts +0 -2
- package/dist/shaders/materials/eye.d.ts.map +0 -1
- package/dist/shaders/materials/eye.js +0 -60
- package/dist/shaders/materials/face.d.ts +0 -2
- package/dist/shaders/materials/face.d.ts.map +0 -1
- package/dist/shaders/materials/face.js +0 -95
- package/dist/shaders/materials/hair.d.ts +0 -2
- package/dist/shaders/materials/hair.d.ts.map +0 -1
- package/dist/shaders/materials/hair.js +0 -90
- package/dist/shaders/materials/metal.d.ts +0 -2
- package/dist/shaders/materials/metal.d.ts.map +0 -1
- package/dist/shaders/materials/metal.js +0 -77
- package/dist/shaders/materials/mmd_classic.d.ts +0 -2
- package/dist/shaders/materials/mmd_classic.d.ts.map +0 -1
- package/dist/shaders/materials/mmd_classic.js +0 -66
- package/dist/shaders/materials/stockings.d.ts +0 -2
- package/dist/shaders/materials/stockings.d.ts.map +0 -1
- package/dist/shaders/materials/stockings.js +0 -122
- package/dist/shaders/passes/physics-debug.d.ts +0 -2
- package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
- package/dist/shaders/passes/physics-debug.js +0 -69
- package/src/shaders/dfg_lut.ts +0 -131
- package/src/shaders/ltc_mag_lut.ts +0 -1035
|
@@ -636,11 +636,9 @@ const COMPOSITE_BODY = /* wgsl */ `
|
|
|
636
636
|
// ── Film grain, on the SCENE ONLY ─────────────────────────────────────────
|
|
637
637
|
//
|
|
638
638
|
// Applied here, before the background is composited under, so it rides on what
|
|
639
|
-
// the engine drew and nothing else
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
// rather than match it. A clean CG figure on a grainy plate is one of the
|
|
643
|
-
// loudest tells there is — the noise gives it away long before the geometry.
|
|
639
|
+
// the engine drew and nothing else: a backdrop photo or video already carries
|
|
640
|
+
// its own grain, and a second helping over the top would grade the picture
|
|
641
|
+
// rather than match it.
|
|
644
642
|
//
|
|
645
643
|
// Multiplicative and weighted toward the mid-tones, which is how film behaves:
|
|
646
644
|
// little grain in the blacks, and the highlights clip it off.
|
|
@@ -145,7 +145,7 @@ struct MirrorVP { viewProj: mat4x4f, params: vec4f, };
|
|
|
145
145
|
struct MirrorClip { plane: vec4f, on: f32, _p0: f32, _p1: f32, _p2: f32, };
|
|
146
146
|
@group(0) @binding(14) var<uniform> clip: MirrorClip;
|
|
147
147
|
${WORLD_AMBIENT_WGSL}
|
|
148
|
-
${lightsApi(0, 6, "1u")}
|
|
148
|
+
${lightsApi(0, 6, "1u", { binding: 15, sampler: "linearSampler" })}
|
|
149
149
|
|
|
150
150
|
struct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };
|
|
151
151
|
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {
|
|
@@ -388,9 +388,15 @@ ${src.orient ? ` // the author's own plane (particleOrient): a card on a surfac
|
|
|
388
388
|
let o = particleOrient(p, ii);
|
|
389
389
|
right = o[0];
|
|
390
390
|
up = o[1];
|
|
391
|
-
` : ""}
|
|
391
|
+
` : ""}${/\bfn\s+particlePivot\s*\(/.test(src.wgsl) ? ` // the card's pivot (particlePivot, in half-size units of the quad): the
|
|
392
|
+
// corners are offset by it BEFORE the turn, so the card turns about its
|
|
393
|
+
// pivot rather than its centre — Unity's renderer pivot, a lighthouse beam
|
|
394
|
+
// whose card starts at the lamp instead of straddling it
|
|
395
|
+
let cp = c + particlePivot(p, ii);
|
|
396
|
+
` : ` let cp = c;
|
|
397
|
+
`} let s = sin(p.rot);
|
|
392
398
|
let k = cos(p.rot);
|
|
393
|
-
var r = vec2f(
|
|
399
|
+
var r = vec2f(cp.x * k - cp.y * s, cp.x * s + cp.y * k);
|
|
394
400
|
// Stretched along the direction of travel ON SCREEN — which is not the world
|
|
395
401
|
// direction once the camera is off-axis. Rain falling straight down is nearly
|
|
396
402
|
// a point when viewed from above and a long streak from the side, and taking
|
|
@@ -402,7 +408,7 @@ ${src.orient ? ` // the author's own plane (particleOrient): a card on a surfac
|
|
|
402
408
|
let vlen = length(vec2f(vr, vu));
|
|
403
409
|
if (vlen > 1e-5) {
|
|
404
410
|
let d = vec2f(vr, vu) / vlen;
|
|
405
|
-
r = vec2f(d.y, -d.x) *
|
|
411
|
+
r = vec2f(d.y, -d.x) * cp.x + d * (cp.y * p.stretch);
|
|
406
412
|
}
|
|
407
413
|
}
|
|
408
414
|
let world = p.pos + (right * r.x + up * r.y) * p.size;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// The reflection probe's GPU passes, and the sky's prefilter.
|
|
2
|
+
//
|
|
3
|
+
// A game does not reflect the sky alone: it bakes a cube of the stage itself
|
|
4
|
+
// from somewhere in the middle of it, prefilters it down its mips with the
|
|
5
|
+
// specular lobe, and every surface reads it box-projected onto the stage's
|
|
6
|
+
// bounds. The engine does the same once a stage has loaded (Engine:
|
|
7
|
+
// captureReflectionProbe): six renders of the stage into a 128² cube, the
|
|
8
|
+
// sky laid in wherever nothing was drawn, a mip chain, then a GGX convolution
|
|
9
|
+
// into the 64² cube the materials sample (rzProbeSpecular in nodes.ts).
|
|
10
|
+
//
|
|
11
|
+
// The world's sky gets the same convolution down its equirect mips, in place
|
|
12
|
+
// of the box average they were built from.
|
|
13
|
+
|
|
14
|
+
import { NODES_WGSL } from "../materials/nodes"
|
|
15
|
+
import { COMMON_MATERIAL_PRELUDE_WGSL, DISSOLVE_WGSL } from "../materials/common"
|
|
16
|
+
|
|
17
|
+
/** Face size of the capture, and of the cube the materials read. The game
|
|
18
|
+
* bakes 64²; the capture is twice that so the base level is antialiased. */
|
|
19
|
+
export const PROBE_CAPTURE_SIZE = 128
|
|
20
|
+
export const PROBE_SIZE = 64
|
|
21
|
+
/** 64² down to 1²: seven levels, the game's own count. */
|
|
22
|
+
export const PROBE_LEVELS = 7
|
|
23
|
+
export const PROBE_CAPTURE_LEVELS = 8
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The perceptual roughness level m of the probe was convolved with, on
|
|
27
|
+
* Unity's probe curve: m = r·(1.7 − 0.7r)·6 (UNITY_SPECCUBE_LOD_STEPS), the
|
|
28
|
+
* convention the game's baked probes follow and its lookup (×8, clamped at 6)
|
|
29
|
+
* reads against. Level 0 is the unblurred capture.
|
|
30
|
+
*/
|
|
31
|
+
export function probeLevelRoughness(level: number): number {
|
|
32
|
+
if (level <= 0) return 0
|
|
33
|
+
const m = Math.min(level, 6)
|
|
34
|
+
return Math.min((10.2 - Math.sqrt(Math.max(104.04 - 16.8 * m, 0))) / 8.4, 1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The perceptual roughness level m of the sky's equirect chain was convolved
|
|
39
|
+
* with: the engine's own lookup is lod = sqrt(r)·(levels − 1), so level m is
|
|
40
|
+
* r = (m / (levels − 1))².
|
|
41
|
+
*/
|
|
42
|
+
export function skyLevelRoughness(level: number, levels: number): number {
|
|
43
|
+
if (level <= 0 || levels <= 1) return 0
|
|
44
|
+
const t = level / (levels - 1)
|
|
45
|
+
return Math.min(t * t, 1)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A cube face's direction for a uv on it (uv.y down), in the convention the
|
|
49
|
+
* GPU samples a cube with — the same one the capture cameras render. */
|
|
50
|
+
const FACE_DIR_WGSL = /* wgsl */ `
|
|
51
|
+
fn rz_face_dir(face: u32, uv: vec2f) -> vec3f {
|
|
52
|
+
let s = uv.x * 2.0 - 1.0;
|
|
53
|
+
let t = uv.y * 2.0 - 1.0;
|
|
54
|
+
switch face {
|
|
55
|
+
case 0u: { return normalize(vec3f(1.0, -t, -s)); }
|
|
56
|
+
case 1u: { return normalize(vec3f(-1.0, -t, s)); }
|
|
57
|
+
case 2u: { return normalize(vec3f(s, 1.0, t)); }
|
|
58
|
+
case 3u: { return normalize(vec3f(s, -1.0, -t)); }
|
|
59
|
+
case 4u: { return normalize(vec3f(s, -t, 1.0)); }
|
|
60
|
+
default: { return normalize(vec3f(-s, -t, -1.0)); }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
struct RzFullOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f };
|
|
65
|
+
|
|
66
|
+
@vertex fn vs_full(@builtin(vertex_index) i: u32) -> RzFullOut {
|
|
67
|
+
var o: RzFullOut;
|
|
68
|
+
let x = f32(i32(i / 2u) * 4 - 1);
|
|
69
|
+
let y = f32(i32(i % 2u) * 4 - 1);
|
|
70
|
+
o.pos = vec4f(x, y, 0.0, 1.0);
|
|
71
|
+
o.uv = vec2f(x * 0.5 + 0.5, 0.5 - y * 0.5);
|
|
72
|
+
return o;
|
|
73
|
+
}
|
|
74
|
+
`
|
|
75
|
+
|
|
76
|
+
/** GGX importance sampling, the filtered kind (Křivánek & Colbert): each
|
|
77
|
+
* sample reads the source at the mip whose texel matches its solid angle, so
|
|
78
|
+
* a few dozen taps integrate a wide lobe without the fireflies a point sample
|
|
79
|
+
* of a bright lamp would leave. */
|
|
80
|
+
const GGX_SAMPLING_WGSL = /* wgsl */ `
|
|
81
|
+
fn rz_hammersley(i: u32, n: u32) -> vec2f {
|
|
82
|
+
var b = (i << 16u) | (i >> 16u);
|
|
83
|
+
b = ((b & 0x55555555u) << 1u) | ((b & 0xAAAAAAAAu) >> 1u);
|
|
84
|
+
b = ((b & 0x33333333u) << 2u) | ((b & 0xCCCCCCCCu) >> 2u);
|
|
85
|
+
b = ((b & 0x0F0F0F0Fu) << 4u) | ((b & 0xF0F0F0F0u) >> 4u);
|
|
86
|
+
b = ((b & 0x00FF00FFu) << 8u) | ((b & 0xFF00FF00u) >> 8u);
|
|
87
|
+
return vec2f(f32(i) / f32(n), f32(b) * 2.3283064365386963e-10);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A half vector about N for the lobe at alpha = a (= perceptual r²). */
|
|
91
|
+
fn rz_ggx_half(xi: vec2f, N: vec3f, a: f32) -> vec3f {
|
|
92
|
+
let phi = 6.283185307 * xi.x;
|
|
93
|
+
let cosT = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
|
|
94
|
+
let sinT = sqrt(max(1.0 - cosT * cosT, 0.0));
|
|
95
|
+
let h = vec3f(sinT * cos(phi), sinT * sin(phi), cosT);
|
|
96
|
+
let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(N.z) < 0.999);
|
|
97
|
+
let tx = normalize(cross(up, N));
|
|
98
|
+
let ty = cross(N, tx);
|
|
99
|
+
return normalize(tx * h.x + ty * h.y + N * h.z);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn rz_ggx_d(nh: f32, a: f32) -> f32 {
|
|
103
|
+
let a2 = a * a;
|
|
104
|
+
let d = nh * nh * (a2 - 1.0) + 1.0;
|
|
105
|
+
return a2 / (3.141592653589793 * d * d);
|
|
106
|
+
}
|
|
107
|
+
`
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The compose step: the stage as captured, with the world's sky laid in
|
|
111
|
+
* behind it wherever the capture's coverage says nothing was drawn — an open
|
|
112
|
+
* stage reflects its sky, an interior its walls. Built on the material
|
|
113
|
+
* prelude for rzWorldSpecularLod, which knows every kind of sky the scene may
|
|
114
|
+
* have (an HDRI, a gradient, one colour). Only the bindings it reads end up in
|
|
115
|
+
* its layout ("auto"): the light uniform, the sampler and the sky at group 0,
|
|
116
|
+
* and its own three past the material set's.
|
|
117
|
+
*/
|
|
118
|
+
export const PROBE_COMPOSE_WGSL =
|
|
119
|
+
NODES_WGSL +
|
|
120
|
+
COMMON_MATERIAL_PRELUDE_WGSL +
|
|
121
|
+
DISSOLVE_WGSL +
|
|
122
|
+
FACE_DIR_WGSL +
|
|
123
|
+
/* wgsl */ `
|
|
124
|
+
struct RzProbeStep { face: u32, lod: f32, roughness: f32, srcSize: f32 };
|
|
125
|
+
@group(0) @binding(20) var rzCaptureColor: texture_2d_array<f32>;
|
|
126
|
+
@group(0) @binding(21) var rzCaptureAux: texture_2d_array<f32>;
|
|
127
|
+
@group(0) @binding(22) var<uniform> rzStep: RzProbeStep;
|
|
128
|
+
|
|
129
|
+
@fragment fn fs_compose(in: RzFullOut) -> @location(0) vec4f {
|
|
130
|
+
let size = textureDimensions(rzCaptureColor);
|
|
131
|
+
let px = vec2u(min(in.pos.xy, vec2f(size) - vec2f(1.0)));
|
|
132
|
+
let c = textureLoad(rzCaptureColor, px, rzStep.face, 0).rgb;
|
|
133
|
+
let cover = saturate(textureLoad(rzCaptureAux, px, rzStep.face, 0).g);
|
|
134
|
+
let sky = rzWorldSpecularLod(rz_face_dir(rzStep.face, in.uv), 0.0, 0.0);
|
|
135
|
+
return vec4f(c + sky * (1.0 - cover), 1.0);
|
|
136
|
+
}
|
|
137
|
+
`
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The cube's own passes: a mip from the level above (mode 0 — one bilinear
|
|
141
|
+
* tap at the child texel's direction is the 2×2 average), and the GGX
|
|
142
|
+
* convolution into the probe the materials read (mode 1).
|
|
143
|
+
*/
|
|
144
|
+
export const PROBE_FILTER_WGSL =
|
|
145
|
+
FACE_DIR_WGSL +
|
|
146
|
+
GGX_SAMPLING_WGSL +
|
|
147
|
+
/* wgsl */ `
|
|
148
|
+
struct RzProbeStep { face: u32, lod: f32, roughness: f32, srcSize: f32, mode: u32 };
|
|
149
|
+
@group(0) @binding(0) var rzSrc: texture_cube<f32>;
|
|
150
|
+
@group(0) @binding(1) var rzSamp: sampler;
|
|
151
|
+
@group(0) @binding(2) var<uniform> rzStep: RzProbeStep;
|
|
152
|
+
|
|
153
|
+
const RZ_PROBE_SAMPLES: u32 = 128u;
|
|
154
|
+
|
|
155
|
+
@fragment fn fs_filter(in: RzFullOut) -> @location(0) vec4f {
|
|
156
|
+
let N = rz_face_dir(rzStep.face, in.uv);
|
|
157
|
+
let r = rzStep.roughness;
|
|
158
|
+
if (rzStep.mode == 0u || r < 0.002) {
|
|
159
|
+
return vec4f(textureSampleLevel(rzSrc, rzSamp, N, rzStep.lod).rgb, 1.0);
|
|
160
|
+
}
|
|
161
|
+
let a = r * r;
|
|
162
|
+
let texelSolid = 4.0 * 3.141592653589793 / (6.0 * rzStep.srcSize * rzStep.srcSize);
|
|
163
|
+
let maxLod = f32(textureNumLevels(rzSrc) - 1u);
|
|
164
|
+
var sum = vec3f(0.0);
|
|
165
|
+
var weight = 0.0;
|
|
166
|
+
for (var i = 0u; i < RZ_PROBE_SAMPLES; i++) {
|
|
167
|
+
let H = rz_ggx_half(rz_hammersley(i, RZ_PROBE_SAMPLES), N, a);
|
|
168
|
+
let L = 2.0 * dot(N, H) * H - N;
|
|
169
|
+
let nl = dot(N, L);
|
|
170
|
+
if (nl <= 0.0) { continue; }
|
|
171
|
+
// N = V: the pdf over L is D·(N·H)/(4·V·H) = D/4.
|
|
172
|
+
let pdf = rz_ggx_d(saturate(dot(N, H)), a) * 0.25;
|
|
173
|
+
let sampleSolid = 1.0 / (f32(RZ_PROBE_SAMPLES) * pdf + 1e-6);
|
|
174
|
+
let lod = clamp(0.5 * log2(sampleSolid / texelSolid) + 1.0, 0.0, maxLod);
|
|
175
|
+
sum += textureSampleLevel(rzSrc, rzSamp, L, lod).rgb * nl;
|
|
176
|
+
weight += nl;
|
|
177
|
+
}
|
|
178
|
+
return vec4f(sum / max(weight, 1e-4), 1.0);
|
|
179
|
+
}
|
|
180
|
+
`
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The sky's mips, convolved: level m of the equirect the materials read
|
|
184
|
+
* (rzWorldSpecularLod) becomes the GGX-filtered sky at the roughness that
|
|
185
|
+
* level stands for, sampled from the box-averaged chain the upload built.
|
|
186
|
+
* Directions follow the composite's dome: u = 0.5 + atan2(x, z)/2π,
|
|
187
|
+
* v = 0.5 − asin(y)/π.
|
|
188
|
+
*/
|
|
189
|
+
export const SKY_PREFILTER_WGSL =
|
|
190
|
+
GGX_SAMPLING_WGSL +
|
|
191
|
+
/* wgsl */ `
|
|
192
|
+
struct RzFullOut { @builtin(position) pos: vec4f, @location(0) uv: vec2f };
|
|
193
|
+
|
|
194
|
+
@vertex fn vs_full(@builtin(vertex_index) i: u32) -> RzFullOut {
|
|
195
|
+
var o: RzFullOut;
|
|
196
|
+
let x = f32(i32(i / 2u) * 4 - 1);
|
|
197
|
+
let y = f32(i32(i % 2u) * 4 - 1);
|
|
198
|
+
o.pos = vec4f(x, y, 0.0, 1.0);
|
|
199
|
+
o.uv = vec2f(x * 0.5 + 0.5, 0.5 - y * 0.5);
|
|
200
|
+
return o;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
struct RzSkyStep { roughness: f32, srcWidth: f32, srcHeight: f32, spare: f32 };
|
|
204
|
+
@group(0) @binding(0) var rzSky: texture_2d<f32>;
|
|
205
|
+
@group(0) @binding(1) var rzSamp: sampler;
|
|
206
|
+
@group(0) @binding(2) var<uniform> rzStep: RzSkyStep;
|
|
207
|
+
|
|
208
|
+
const RZ_SKY_SAMPLES: u32 = 64u;
|
|
209
|
+
|
|
210
|
+
fn rz_sky_dir(uv: vec2f) -> vec3f {
|
|
211
|
+
let phi = (uv.x - 0.5) * 6.283185307;
|
|
212
|
+
let lat = (0.5 - uv.y) * 3.141592653589793;
|
|
213
|
+
return vec3f(cos(lat) * sin(phi), sin(lat), cos(lat) * cos(phi));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fn rz_sky_uv(d: vec3f) -> vec2f {
|
|
217
|
+
return vec2f(0.5 + atan2(d.x, d.z) * 0.15915494309, 0.5 - asin(clamp(d.y, -1.0, 1.0)) * 0.31830988618);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@fragment fn fs_sky(in: RzFullOut) -> @location(0) vec4f {
|
|
221
|
+
let N = rz_sky_dir(in.uv);
|
|
222
|
+
let a = rzStep.roughness * rzStep.roughness;
|
|
223
|
+
let texelSolid = 4.0 * 3.141592653589793 / (rzStep.srcWidth * rzStep.srcHeight);
|
|
224
|
+
let maxLod = f32(textureNumLevels(rzSky) - 1u);
|
|
225
|
+
var sum = vec3f(0.0);
|
|
226
|
+
var weight = 0.0;
|
|
227
|
+
for (var i = 0u; i < RZ_SKY_SAMPLES; i++) {
|
|
228
|
+
let H = rz_ggx_half(rz_hammersley(i, RZ_SKY_SAMPLES), N, a);
|
|
229
|
+
let L = 2.0 * dot(N, H) * H - N;
|
|
230
|
+
let nl = dot(N, L);
|
|
231
|
+
if (nl <= 0.0) { continue; }
|
|
232
|
+
let pdf = rz_ggx_d(saturate(dot(N, H)), a) * 0.25;
|
|
233
|
+
let sampleSolid = 1.0 / (f32(RZ_SKY_SAMPLES) * pdf + 1e-6);
|
|
234
|
+
let lod = clamp(0.5 * log2(sampleSolid / texelSolid) + 1.0, 0.0, maxLod);
|
|
235
|
+
sum += textureSampleLevel(rzSky, rzSamp, rz_sky_uv(L), lod).rgb * nl;
|
|
236
|
+
weight += nl;
|
|
237
|
+
}
|
|
238
|
+
return vec4f(sum / max(weight, 1e-4), 1.0);
|
|
239
|
+
}
|
|
240
|
+
`
|
|
@@ -98,6 +98,12 @@ type SceneRenderClass =
|
|
|
98
98
|
* X309's night sky was. Out of WRITES_ID for the mirror's reason — a sheet of
|
|
99
99
|
* light is not an object anything should be able to pick or outline. */
|
|
100
100
|
| "material-additive"
|
|
101
|
+
/** A material whose colour arrives ALREADY weighted by its coverage — a
|
|
102
|
+
* game's glass (Blend One OneMinusSrcAlpha): the pane's diffuse fades with
|
|
103
|
+
* its alpha while its reflection does not, which a src-alpha blend cannot
|
|
104
|
+
* express. Out of WRITES_ID for the mirror's reason: a window is not what a
|
|
105
|
+
* click on the figure behind it means. */
|
|
106
|
+
| "material-premultiplied"
|
|
101
107
|
/** Particles and ribbons in their default, non-additive mode. */
|
|
102
108
|
| "particle"
|
|
103
109
|
/** Particles declaring `#blend additive` — LIGHT rather than matter, so
|
|
@@ -110,6 +116,13 @@ type SceneRenderClass =
|
|
|
110
116
|
* whole, and an ordinary alpha-over aux so a ribbon's mask does not
|
|
111
117
|
* saturate along every overlap. */
|
|
112
118
|
| "trail"
|
|
119
|
+
/** Ribbons declaring `#blend over` — MATTER rather than light: laid over
|
|
120
|
+
* what is behind them by their own alpha, the way a game's trail material
|
|
121
|
+
* with Blend One OneMinusSrcAlpha draws. A dark band then darkens, which an
|
|
122
|
+
* additive ribbon can never do; it is how a deep-blue smoke tail reads as a
|
|
123
|
+
* tail at all. The fragment writes straight colour, so the colour blend is
|
|
124
|
+
* src-alpha like any other alpha-over class. */
|
|
125
|
+
| "trail-over"
|
|
113
126
|
/** The transparent depth prepass: it exists to write DEPTH after the fabric's
|
|
114
127
|
* colour blended, so an outline drawn later is occluded behind it. It must
|
|
115
128
|
* therefore write no colour at all — the targets exist only to make the
|
|
@@ -183,6 +196,9 @@ const BLENDS: Record<Exclude<SceneRenderClass, "depth-prepass">, [GPUBlendState,
|
|
|
183
196
|
// The same pair the additive particles use: light into the colour target, and
|
|
184
197
|
// coverage that still accumulates so bloom and the composite can see it.
|
|
185
198
|
"material-additive": [ADD_KEEP_ALPHA, ADD_BOTH],
|
|
199
|
+
// The colour premultiplied by the graph (game_pbr's glass); the aux is
|
|
200
|
+
// written straight, as every material's is.
|
|
201
|
+
"material-premultiplied": [PREMULTIPLIED_OVER, ALPHA_OVER],
|
|
186
202
|
// PREMULTIPLIED, like the ground and for the same reason: what a mirror
|
|
187
203
|
// writes is a sample of the HDR target, and that target already holds colour
|
|
188
204
|
// premultiplied by its own alpha. Handed to the src-alpha blend it would be
|
|
@@ -201,6 +217,7 @@ const BLENDS: Record<Exclude<SceneRenderClass, "depth-prepass">, [GPUBlendState,
|
|
|
201
217
|
particle: [PREMULTIPLIED_OVER, ALPHA_OVER],
|
|
202
218
|
"particle-additive": [ADD_KEEP_ALPHA, ADD_BOTH],
|
|
203
219
|
trail: [ADD_PREMULTIPLIED, ALPHA_OVER],
|
|
220
|
+
"trail-over": [ALPHA_OVER, ALPHA_OVER],
|
|
204
221
|
}
|
|
205
222
|
|
|
206
223
|
/**
|
|
@@ -131,6 +131,7 @@ export function sceneLightApi(on: boolean, group: number, binding: number): stri
|
|
|
131
131
|
return /* wgsl */ `
|
|
132
132
|
fn rzShadow(p: vec3f) -> f32 { return 1.0; }
|
|
133
133
|
fn rzWorldAmbient(n: vec3f) -> vec3f { return vec3f(0.0); }
|
|
134
|
+
fn rzWorldAmbientAvg() -> vec3f { return vec3f(0.0); }
|
|
134
135
|
`
|
|
135
136
|
}
|
|
136
137
|
const n = SHADOW_CASCADES.length
|
package/src/unity/globals.ts
CHANGED
|
@@ -71,6 +71,9 @@ export type UnityFrameInput = {
|
|
|
71
71
|
/** The direction the main light travels, unit; null for none. */
|
|
72
72
|
sunDirection: [number, number, number] | null
|
|
73
73
|
sunColor: [number, number, number]
|
|
74
|
+
/** A game stage's own main light as its pipeline packed it (recorded), which
|
|
75
|
+
* its shaders get in place of sunColor; null for the scene sun's. */
|
|
76
|
+
sunUnity?: { color: number[]; simColor: number[]; simColorNoInt: number[] } | null
|
|
74
77
|
sunShadow: number
|
|
75
78
|
lights: UnityLight[]
|
|
76
79
|
/** Ambient as the engine's folded SH9 (27 floats, ibl.ts), or null for flat. */
|
|
@@ -257,12 +260,13 @@ export function unityFrameGlobals(f: UnityFrameInput): Record<string, NativeValu
|
|
|
257
260
|
if (f.sunDirection) {
|
|
258
261
|
const d = gameDir(f.sunDirection)
|
|
259
262
|
g.SimMainLightDir = [-d[0], -d[1], -d[2], 0]
|
|
260
|
-
|
|
261
|
-
g.
|
|
263
|
+
const u = f.sunUnity
|
|
264
|
+
g.SimMainLightColor = u ? u.simColor.slice(0, 4) : [...f.sunColor, 1]
|
|
265
|
+
g.SimMainLightColorNoInt = u ? u.simColorNoInt.slice(0, 4) : [...f.sunColor, 1]
|
|
262
266
|
// As the game's pipeline leaves it (recorded): the way the light travels.
|
|
263
267
|
g._MainLightPosition = [d[0], d[1], d[2], 0]
|
|
264
268
|
g.sim_ShadowLightDirection = [-d[0], -d[1], -d[2], 0]
|
|
265
|
-
g._MainLightColor = [...f.sunColor, 1]
|
|
269
|
+
g._MainLightColor = u ? u.color.slice(0, 4) : [...f.sunColor, 1]
|
|
266
270
|
} else {
|
|
267
271
|
g.SimMainLightDir = [0, 0, 0, 0]
|
|
268
272
|
g.SimMainLightColor = [0, 0, 0, 0]
|
package/src/unity/host.ts
CHANGED
|
@@ -54,6 +54,21 @@ export type NativeValue = number | ArrayLike<number> | ArrayLike<ArrayLike<numbe
|
|
|
54
54
|
/** Name -> value; the first source that has a name wins. */
|
|
55
55
|
export type ValueSource = Record<string, NativeValue | undefined>
|
|
56
56
|
|
|
57
|
+
/** Whether two values carry the same numbers (and the same integer-ness). */
|
|
58
|
+
export function sameValue(a: NativeValue, b: NativeValue): boolean {
|
|
59
|
+
if (typeof a === "number" || typeof b === "number") return a === b
|
|
60
|
+
if (a.length !== b.length) return false
|
|
61
|
+
if (a instanceof Uint32Array !== b instanceof Uint32Array) return false
|
|
62
|
+
for (let i = 0; i < a.length; i++) {
|
|
63
|
+
const x = a[i] as number | ArrayLike<number>
|
|
64
|
+
const y = b[i] as number | ArrayLike<number>
|
|
65
|
+
if (typeof x === "number" || typeof y === "number") {
|
|
66
|
+
if (x !== y) return false
|
|
67
|
+
} else if (!sameValue(x, y)) return false
|
|
68
|
+
}
|
|
69
|
+
return true
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
/** One vertex stream a draw binds, by the semantic a shader input names. */
|
|
58
73
|
export type NativeStream = {
|
|
59
74
|
buffer: GPUBuffer
|
|
@@ -87,14 +102,25 @@ export function wrapFragment(frag: string, ids: boolean): string {
|
|
|
87
102
|
const at = frag.lastIndexOf("@fragment")
|
|
88
103
|
if (at < 0) throw new Error("native shader: no fragment entry point")
|
|
89
104
|
const head = frag.slice(at)
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
// A target narrower than four channels (a float3 SV_Target) leaves the rest
|
|
106
|
+
// unwritten: widened here with zeros and an alpha of one.
|
|
107
|
+
const sig = /->\s*@location\(0\)\s*(vec4<f32>|vec3<f32>|vec2<f32>|f32)\s*\{/
|
|
108
|
+
const kind = sig.exec(head)?.[1]
|
|
109
|
+
if (!kind) throw new Error("native shader: the fragment entry does not return one float target")
|
|
92
110
|
let body = head.replace(sig, "-> RzNativeOut {")
|
|
93
111
|
const ret = /return\s+(_e\d+)\s*;\s*\}\s*$/
|
|
94
112
|
const m = ret.exec(body)
|
|
95
113
|
if (!m) throw new Error("native shader: the fragment entry's return is not the translator's")
|
|
96
|
-
const
|
|
97
|
-
|
|
114
|
+
const e = m[1]
|
|
115
|
+
const v =
|
|
116
|
+
kind === "vec4<f32>"
|
|
117
|
+
? e
|
|
118
|
+
: kind === "vec3<f32>"
|
|
119
|
+
? `vec4f(${e}, 1.0)`
|
|
120
|
+
: kind === "vec2<f32>"
|
|
121
|
+
? `vec4f(${e}, 0.0, 1.0)`
|
|
122
|
+
: `vec4f(${e}, 0.0, 0.0, 1.0)`
|
|
123
|
+
body = body.replace(ret, `let rzColor = ${v};\n return RzNativeOut(rzColor, vec4f(1.0, 1.0, 0.0, rzColor.a)${ids ? ", vec2u(0u)" : ""});\n}\n`)
|
|
98
124
|
const struct = `struct RzNativeOut {\n @location(0) color: vec4f,\n @location(1) mask: vec4f,\n${ids ? " @location(2) id: vec2u,\n" : ""}};\n`
|
|
99
125
|
// After the module's directives (diagnostic, enable): WGSL wants them first.
|
|
100
126
|
const pre = frag.slice(0, at)
|
|
@@ -190,6 +216,7 @@ export class NativeHost {
|
|
|
190
216
|
white: GPUTextureView
|
|
191
217
|
black: GPUTextureView
|
|
192
218
|
blackCube: GPUTextureView
|
|
219
|
+
emptyVolume: GPUTextureView
|
|
193
220
|
depth: GPUTextureView
|
|
194
221
|
comparison: GPUSampler
|
|
195
222
|
},
|
|
@@ -598,14 +625,17 @@ export class NativeHost {
|
|
|
598
625
|
if (!samplers) return this.sampler
|
|
599
626
|
const bare = name.replace(/^sampler_?/, "")
|
|
600
627
|
const tex = info.bindings[bare] ? bare : info.bindings[`_${bare}`] ? `_${bare}` : bare
|
|
601
|
-
return samplers[tex] ?? this.sampler
|
|
628
|
+
return samplers[tex] ?? samplers[tex.replace(/_+$/, "")] ?? this.sampler
|
|
602
629
|
}
|
|
603
630
|
|
|
604
631
|
private textureView(name: string, type: string, textures: Record<string, GPUTextureView | undefined>): GPUTextureView {
|
|
605
|
-
|
|
632
|
+
// naga renames a binding that ends in a digit with a trailing "_", as it
|
|
633
|
+
// does a member (lookupFrom): _AlbedoTex_1 is declared _AlbedoTex_1_
|
|
634
|
+
const v = textures[name] ?? (name.endsWith("_") ? textures[name.replace(/_+$/, "")] : undefined)
|
|
606
635
|
if (v) return v
|
|
607
636
|
if (/texture_depth/.test(type)) return this.fallback.depth
|
|
608
637
|
if (/texture_cube/.test(type)) return this.fallback.blackCube
|
|
638
|
+
if (/texture_3d/.test(type)) return this.fallback.emptyVolume
|
|
609
639
|
return this.fallback.white
|
|
610
640
|
}
|
|
611
641
|
|