reze-engine 0.60.5 → 0.61.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/_dbg-loader.mjs +1100 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +8 -1
- package/dist/engine.d.ts +110 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +258 -7
- package/dist/graph/compile.d.ts.map +1 -1
- package/dist/graph/compile.js +4 -1
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +14 -2
- package/dist/graph/slots.d.ts +1 -1
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +16 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/physics-debug.d.ts +30 -0
- package/dist/physics-debug.d.ts.map +1 -0
- package/dist/physics-debug.js +526 -0
- package/dist/shaders/lights.d.ts.map +1 -1
- package/dist/shaders/lights.js +4 -0
- package/dist/shaders/materials/body.d.ts +2 -0
- package/dist/shaders/materials/body.d.ts.map +1 -0
- package/dist/shaders/materials/body.js +95 -0
- package/dist/shaders/materials/cloth_rough.d.ts +2 -0
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -0
- package/dist/shaders/materials/cloth_rough.js +69 -0
- package/dist/shaders/materials/cloth_smooth.d.ts +2 -0
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -0
- package/dist/shaders/materials/cloth_smooth.js +61 -0
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +47 -0
- package/dist/shaders/materials/default.d.ts +2 -0
- package/dist/shaders/materials/default.d.ts.map +1 -0
- package/dist/shaders/materials/default.js +43 -0
- package/dist/shaders/materials/eye.d.ts +2 -0
- package/dist/shaders/materials/eye.d.ts.map +1 -0
- package/dist/shaders/materials/eye.js +60 -0
- package/dist/shaders/materials/face.d.ts +2 -0
- package/dist/shaders/materials/face.d.ts.map +1 -0
- package/dist/shaders/materials/face.js +95 -0
- package/dist/shaders/materials/hair.d.ts +2 -0
- package/dist/shaders/materials/hair.d.ts.map +1 -0
- package/dist/shaders/materials/hair.js +90 -0
- package/dist/shaders/materials/metal.d.ts +2 -0
- package/dist/shaders/materials/metal.d.ts.map +1 -0
- package/dist/shaders/materials/metal.js +77 -0
- package/dist/shaders/materials/mmd_classic.d.ts +2 -0
- package/dist/shaders/materials/mmd_classic.d.ts.map +1 -0
- package/dist/shaders/materials/mmd_classic.js +66 -0
- 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 +81 -18
- package/dist/shaders/materials/stockings.d.ts +2 -0
- package/dist/shaders/materials/stockings.d.ts.map +1 -0
- package/dist/shaders/materials/stockings.js +122 -0
- 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 +34 -2
- package/dist/shaders/passes/physics-debug.d.ts +2 -0
- package/dist/shaders/passes/physics-debug.d.ts.map +1 -0
- package/dist/shaders/passes/physics-debug.js +69 -0
- package/dist/shaders/scene-tap.js +1 -1
- package/dist/shadow-cascades.d.ts +0 -6
- package/dist/shadow-cascades.d.ts.map +1 -1
- package/dist/shadow-cascades.js +7 -1
- package/package.json +1 -1
- package/src/camera.ts +8 -1
- package/src/engine.ts +288 -7
- package/src/graph/compile.ts +4 -1
- package/src/graph/registry.ts +14 -2
- package/src/graph/slots.ts +17 -5
- package/src/index.ts +4 -0
- package/src/shaders/lights.ts +4 -0
- package/src/shaders/materials/common.ts +47 -0
- package/src/shaders/materials/nodes.ts +81 -18
- package/src/shaders/passes/composite.ts +34 -2
- package/src/shaders/scene-tap.ts +1 -1
- package/src/shadow-cascades.ts +8 -1
- package/dist/graph/presets/pool_floor.d.ts +0 -3
- package/dist/graph/presets/pool_floor.d.ts.map +0 -1
- package/dist/graph/presets/pool_floor.js +0 -42
- package/dist/graph/presets/water.d.ts +0 -3
- package/dist/graph/presets/water.d.ts.map +0 -1
- package/dist/graph/presets/water.js +0 -31
- package/dist/physics/autofit.d.ts +0 -147
- package/dist/physics/autofit.d.ts.map +0 -1
- package/dist/physics/autofit.js +0 -501
- package/dist/shaders/passes/field-blit.d.ts +0 -26
- package/dist/shaders/passes/field-blit.d.ts.map +0 -1
- package/dist/shaders/passes/field-blit.js +0 -65
- package/dist/shaders/passes/ground-noise.d.ts +0 -7
- package/dist/shaders/passes/ground-noise.d.ts.map +0 -1
- package/dist/shaders/passes/ground-noise.js +0 -88
- package/dist/shaders/passes/sim.d.ts +0 -34
- package/dist/shaders/passes/sim.d.ts.map +0 -1
- package/dist/shaders/passes/sim.js +0 -169
- package/dist/shaders/score-api.d.ts +0 -10
- package/dist/shaders/score-api.d.ts.map +0 -1
- package/dist/shaders/score-api.js +0 -114
- package/dist/shaders/shadow-api.d.ts +0 -29
- package/dist/shaders/shadow-api.d.ts.map +0 -1
- package/dist/shaders/shadow-api.js +0 -112
package/src/graph/slots.ts
CHANGED
|
@@ -154,7 +154,7 @@ ${gate}
|
|
|
154
154
|
// Tail of fs(): consumes `final_color` + locals, writes FSOut. hashed forces output
|
|
155
155
|
// alpha to 1 (the discard already did the cutout); hair scales alpha for the over-eyes
|
|
156
156
|
// pass when IS_OVER_EYES is compiled true.
|
|
157
|
-
function epilogue(renderClass: RenderClass, alphaMode: AlphaMode, hasOpacity: boolean, blend: StyleBlend): string {
|
|
157
|
+
function epilogue(renderClass: RenderClass, alphaMode: AlphaMode, hasOpacity: boolean, blend: StyleBlend, takesLight: boolean): string {
|
|
158
158
|
// A graph that computes its own opacity wins, including over hashed: hashed
|
|
159
159
|
// writes 1 because its discard already decided the cutout, and a graph asking
|
|
160
160
|
// for a curve is asking for the opposite of a cutout. Additive writes 1 for a
|
|
@@ -181,7 +181,13 @@ function epilogue(renderClass: RenderClass, alphaMode: AlphaMode, hasOpacity: bo
|
|
|
181
181
|
// so the fix has an address — when graphs gain an optional albedo output
|
|
182
182
|
// (material-track era), it lands here and every light is corrected at once,
|
|
183
183
|
// instead of a hunt through the epilogue's string templates.
|
|
184
|
-
|
|
184
|
+
//
|
|
185
|
+
// ONLY ON A GRAPH THAT TAKES LIGHT. An emission-only graph — Unlit, a stage's
|
|
186
|
+
// painted sheet — is Blender's Emission shader, which no lamp reaches; adding
|
|
187
|
+
// the lamps to it turned X340's floor shadow, a white-RGB picture at a soft
|
|
188
|
+
// alpha, into a glowing disc under the spot above it.
|
|
189
|
+
const LIT = takesLight ? ` + rzLightsDiffuseOnce(input.worldPos, n) * albedo` : ""
|
|
190
|
+
|
|
185
191
|
const ALBEDO = ` let albedo = tex_color;
|
|
186
192
|
`
|
|
187
193
|
// The dissolve's burning edge, ADDED after the graph and after the lights —
|
|
@@ -189,19 +195,24 @@ function epilogue(renderClass: RenderClass, alphaMode: AlphaMode, hasOpacity: bo
|
|
|
189
195
|
// on every fragment of every material that is not dissolving, which is what
|
|
190
196
|
// keeps this out of the way of a scene that never uses it.
|
|
191
197
|
const BURN = ` + RZ_BURN_COLOR * rz_burn`
|
|
198
|
+
// The scene fog, last, as the game lays it over a lit surface; an
|
|
199
|
+
// emission-only graph takes none (the game's effect shaders have no fog).
|
|
200
|
+
const FOG = takesLight
|
|
201
|
+
? ` out.color = vec4f(mix(mix(out.color.rgb, light.fog[0].rgb, input.fog.x), light.fog[2].rgb, input.fog.y), out.color.a);\n`
|
|
202
|
+
: ""
|
|
192
203
|
if (renderClass === "hair") {
|
|
193
204
|
return `${ALBEDO} var outAlpha = ${alphaBase};
|
|
194
205
|
if (IS_OVER_EYES) { outAlpha = ${alphaBase} * 0.25; }
|
|
195
206
|
|
|
196
207
|
var out: FSOut;
|
|
197
208
|
out.color = vec4f(final_color${LIT}${BURN}, outAlpha);
|
|
198
|
-
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
209
|
+
${FOG} out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
199
210
|
${ID_WRITE} return out;
|
|
200
211
|
`
|
|
201
212
|
}
|
|
202
213
|
return `${ALBEDO} var out: FSOut;
|
|
203
214
|
out.color = vec4f(final_color${LIT}${BURN}, ${alphaBase});
|
|
204
|
-
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
215
|
+
${FOG} out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
205
216
|
${ID_WRITE} return out;
|
|
206
217
|
`
|
|
207
218
|
}
|
|
@@ -218,6 +229,7 @@ export function assembleModule(
|
|
|
218
229
|
includeStyleUniforms: boolean,
|
|
219
230
|
hasOpacity = false,
|
|
220
231
|
blend: StyleBlend = "over",
|
|
232
|
+
takesLight = true,
|
|
221
233
|
): string {
|
|
222
234
|
return (
|
|
223
235
|
NODES_WGSL +
|
|
@@ -231,7 +243,7 @@ export function assembleModule(
|
|
|
231
243
|
prelude(renderClass, alphaMode, blend) +
|
|
232
244
|
fsBody +
|
|
233
245
|
"\n" +
|
|
234
|
-
epilogue(renderClass, alphaMode, hasOpacity, blend) +
|
|
246
|
+
epilogue(renderClass, alphaMode, hasOpacity, blend, takesLight) +
|
|
235
247
|
"}\n"
|
|
236
248
|
)
|
|
237
249
|
}
|
package/src/index.ts
CHANGED
package/src/shaders/lights.ts
CHANGED
|
@@ -370,9 +370,13 @@ fn rzLightsDiffuse(p: vec3f, n: vec3f) -> vec3f {
|
|
|
370
370
|
var<private> _rzLampN: vec3f;
|
|
371
371
|
var<private> _rzLampDiffuse: vec3f;
|
|
372
372
|
var<private> _rzLampDiffuseSet: bool = false;
|
|
373
|
+
// Set by a closure that shaded the lamps' diffuse itself (a Unity-mode
|
|
374
|
+
// principled, which tints it by its own base) — the epilogue then adds none.
|
|
375
|
+
var<private> _rzLampDiffuseTaken: bool = false;
|
|
373
376
|
|
|
374
377
|
/** rzLightsDiffuse(p, _rzLampN), taken from a principled walk when one ran. */
|
|
375
378
|
fn rzLightsDiffuseOnce(p: vec3f, n: vec3f) -> vec3f {
|
|
379
|
+
if (_rzLampDiffuseTaken) { return vec3f(0.0); }
|
|
376
380
|
if (_rzLampDiffuseSet) { return _rzLampDiffuse; }
|
|
377
381
|
return rzLightsDiffuse(p, n);
|
|
378
382
|
}
|
|
@@ -54,6 +54,13 @@ struct LightUniforms {
|
|
|
54
54
|
lights: array<Light, 4>,
|
|
55
55
|
/** Irradiance SH of the HDRI world; [0].w is the on-flag. */
|
|
56
56
|
sh: array<vec4f, 9>,
|
|
57
|
+
/** The scene fog (Engine.setSceneFog): haze (colour, amount), (distance,
|
|
58
|
+
* height), then the second layer the same. Amount 0 is none. */
|
|
59
|
+
fog: array<vec4f, 4>,
|
|
60
|
+
/** The cast's shadow on a stage (Engine.setStageCastShadow): colour, amount;
|
|
61
|
+
* amount 0 is off. Then the view-projection its map was drawn with. */
|
|
62
|
+
castShadow: vec4f,
|
|
63
|
+
castVP: mat4x4f,
|
|
57
64
|
};
|
|
58
65
|
|
|
59
66
|
// Per-material uniforms. Every material binds this layout even if it ignores fields;
|
|
@@ -121,8 +128,19 @@ struct VertexOutput {
|
|
|
121
128
|
* must agree — a pass that disagrees punches holes through her.
|
|
122
129
|
*/
|
|
123
130
|
@location(4) @interpolate(flat) faceT: f32,
|
|
131
|
+
/** The scene fog's two amounts, computed PER VERTEX as the game does — see
|
|
132
|
+
* Engine.setSceneFog — and interpolated across the face. */
|
|
133
|
+
@location(5) fog: vec2f,
|
|
124
134
|
};
|
|
125
135
|
|
|
136
|
+
/** One fog layer's amount at a vertex of view depth d and world height y. */
|
|
137
|
+
fn _rzFogAmount(d: f32, y: f32, col: vec4f, p: vec4f) -> f32 {
|
|
138
|
+
if (col.w <= 0.0) { return 0.0; }
|
|
139
|
+
let f = clamp(d * p.x + p.y, 0.0, 1.0);
|
|
140
|
+
let h = clamp((p.z - y) / p.w, -1.0, 1.0);
|
|
141
|
+
return (1.0 - clamp(f - (1.0 - f) * h, 0.0, 1.0)) * col.w;
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
// One view-projection per shadow cascade, inner to outer — the volumes built
|
|
127
145
|
// by shadow-cascades.ts, in the same order.
|
|
128
146
|
struct LightVP { viewProj: array<mat4x4f, ${SHADOW_CASCADES.length}>, };
|
|
@@ -144,6 +162,30 @@ struct LightVP { viewProj: array<mat4x4f, ${SHADOW_CASCADES.length}>, };
|
|
|
144
162
|
// has the 1x1 fallback bound here; rzWorldSpecular checks the SH flag and
|
|
145
163
|
// answers with the flat ambient instead, so nothing samples the placeholder.
|
|
146
164
|
@group(0) @binding(8) var worldEnvTexture: texture_2d<f32>;
|
|
165
|
+
// The cast's own shadow map for a stage's floor — see _rzCastShadow.
|
|
166
|
+
@group(0) @binding(10) var castShadowMap: texture_depth_2d;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* How much of the cast stands between this point and the stage's shadow
|
|
170
|
+
* direction, 0–1 — setStageCastShadow. A 5×5 comparison filter at 1.5 texels:
|
|
171
|
+
* the game blurs its character shadow before laying it, and a single tap reads
|
|
172
|
+
* as a stencil.
|
|
173
|
+
*/
|
|
174
|
+
fn _rzCastShadow(wp: vec3f) -> f32 {
|
|
175
|
+
if (light.castShadow.w <= 0.0) { return 0.0; }
|
|
176
|
+
let c = light.castVP * vec4f(wp, 1.0);
|
|
177
|
+
let ndc = c.xyz / c.w;
|
|
178
|
+
if (abs(ndc.x) >= 1.0 || abs(ndc.y) >= 1.0 || ndc.z <= 0.0 || ndc.z >= 1.0) { return 0.0; }
|
|
179
|
+
let uv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
180
|
+
let ts = 1.5 / f32(textureDimensions(castShadowMap).x);
|
|
181
|
+
var lit = 0.0;
|
|
182
|
+
for (var y = -2; y <= 2; y++) {
|
|
183
|
+
for (var x = -2; x <= 2; x++) {
|
|
184
|
+
lit += textureSampleCompareLevel(castShadowMap, shadowSampler, uv + vec2f(f32(x), f32(y)) * ts, ndc.z - 0.001);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return 1.0 - lit / 25.0;
|
|
188
|
+
}
|
|
147
189
|
// binding(9) brdfLut is declared inside NODES_WGSL (nodes.ts).
|
|
148
190
|
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
149
191
|
// The light this model takes apart from the scene's — see Engine.setModelFill
|
|
@@ -273,6 +315,11 @@ const COMMON_VS_WGSL = /* wgsl */ `
|
|
|
273
315
|
output.worldPos = skinnedPos.xyz;
|
|
274
316
|
output.restPos = position;
|
|
275
317
|
output.faceT = rz_dissolve_threshold(position);
|
|
318
|
+
let viewZ = (camera.view * vec4f(skinnedPos.xyz, 1.0)).z;
|
|
319
|
+
output.fog = vec2f(
|
|
320
|
+
_rzFogAmount(viewZ, skinnedPos.y, light.fog[0], light.fog[1]),
|
|
321
|
+
_rzFogAmount(viewZ, skinnedPos.y, light.fog[2], light.fog[3]),
|
|
322
|
+
);
|
|
276
323
|
return output;
|
|
277
324
|
}
|
|
278
325
|
|
|
@@ -312,6 +312,21 @@ fn point_world_to_camera(p: vec3f) -> vec3f { return (camera.view * vec4f(p, 1.0
|
|
|
312
312
|
* texture sees no change at all.
|
|
313
313
|
*/
|
|
314
314
|
fn rzWorldSpecular(dir: vec3f, roughness: f32) -> vec3f {
|
|
315
|
+
return rzWorldSpecularLod(dir, roughness, 0.0);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The same, with the blur curve chosen. unity = 0 is the sqrt ramp above; 1 is
|
|
320
|
+
* Unity's reflection-probe lookup, mip = p·(1.7 − 0.7p)·8 — the curve a game's
|
|
321
|
+
* glossy floor was tuned under, about a level sharper than sqrt through the
|
|
322
|
+
* glossy range (roughness 0.1–0.3) and the same by 0.5.
|
|
323
|
+
*
|
|
324
|
+
* Its levels map one to one onto this chain's: a cube of N texels a face and an
|
|
325
|
+
* equirect 4N wide resolve the same angle per texel, so a cube's last level —
|
|
326
|
+
* one texel a face — is this chain's log2(width/4), and the curve stops there
|
|
327
|
+
* rather than averaging the whole sky, as the game's does.
|
|
328
|
+
*/
|
|
329
|
+
fn rzWorldSpecularLod(dir: vec3f, roughness: f32, unity: f32) -> vec3f {
|
|
315
330
|
// A SKY TEXTURE, or the best the scene has. The flag is 2 when an HDRI is
|
|
316
331
|
// installed, 1 when the world is a gradient fitted to the same harmonics,
|
|
317
332
|
// and 0 when it is one flat colour — and in the last two cases the ambient
|
|
@@ -327,7 +342,9 @@ fn rzWorldSpecular(dir: vec3f, roughness: f32) -> vec3f {
|
|
|
327
342
|
// sqrt, not linear: mip n covers roughly twice the angle of n-1, so a linear
|
|
328
343
|
// ramp spends most of its travel in the blurry end and a mirror-to-satin
|
|
329
344
|
// sweep happens entirely in the first tenth of the dial.
|
|
330
|
-
let
|
|
345
|
+
let r = clamp(roughness, 0.0, 1.0);
|
|
346
|
+
let unityLod = min(r * (1.7 - 0.7 * r) * 8.0, max(levels - 2.0, 0.0));
|
|
347
|
+
let lod = clamp(mix(sqrt(r) * levels, unityLod, unity), 0.0, levels);
|
|
331
348
|
return textureSampleLevel(worldEnvTexture, diffuseSampler, vec2f(u, v), lod).rgb * light.ambientColor.w;
|
|
332
349
|
}
|
|
333
350
|
|
|
@@ -866,6 +883,28 @@ fn bsdf_ggx(N: vec3f, L: vec3f, V: vec3f, NL_in: f32, NV_in: f32, roughness: f32
|
|
|
866
883
|
return NL * a2 / (D_opti * G);
|
|
867
884
|
}
|
|
868
885
|
|
|
886
|
+
// URP's direct-light specular, as a game on URP (or a fork of it) shades its
|
|
887
|
+
// lamps and sun: D·V·F folded into a2 / (d²·max(0.1, LoH²)·(4a + 2)), a the
|
|
888
|
+
// SQUARED roughness. Unity folds π into its light units, and a light that came
|
|
889
|
+
// from one reaches this engine with that π restored (see the app's stage
|
|
890
|
+
// loader), so it comes back out here. Returns BRDF × NL, like bsdf_ggx.
|
|
891
|
+
fn bsdf_urp(N: vec3f, L: vec3f, V: vec3f, NL: f32, roughness: f32) -> f32 {
|
|
892
|
+
let a = max(roughness * roughness, 6.1035156e-5);
|
|
893
|
+
let a2 = a * a;
|
|
894
|
+
let H = normalize(L + V);
|
|
895
|
+
let NH = max(dot(N, H), 0.0);
|
|
896
|
+
let LH = max(dot(L, H), 0.0);
|
|
897
|
+
let d = NH * NH * (a2 - 1.0) + 1.00001;
|
|
898
|
+
return a2 / (d * d * max(0.1, LH * LH) * (a * 4.0 + 2.0)) * NL / EEVEE_PI;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/** The direct-light lobe a principled closure asked for: 0 the engine's own,
|
|
902
|
+
* 1 URP's (principled's unity_direct). */
|
|
903
|
+
fn _rzDirectLobe(N: vec3f, L: vec3f, V: vec3f, NL: f32, NV: f32, roughness: f32, unity: f32) -> f32 {
|
|
904
|
+
if (unity > 0.5) { return bsdf_urp(N, L, V, NL, roughness); }
|
|
905
|
+
return bsdf_ggx(N, L, V, NL, NV, roughness);
|
|
906
|
+
}
|
|
907
|
+
|
|
869
908
|
// Split-sum DFG LUT — Karis 2013 curve fit stand-in for the 64×64 baked LUT.
|
|
870
909
|
// Returns (lut.x, lut.y) in Blender convention: tint = f0·lut.x + f90·lut.y.
|
|
871
910
|
fn brdf_lut_approx(NV: f32, roughness: f32) -> vec2f {
|
|
@@ -1003,7 +1042,7 @@ fn principled_specular(ior: f32, level: f32) -> f32 {
|
|
|
1003
1042
|
// second pass over the grid. See lights.ts.
|
|
1004
1043
|
struct _RzLampPair { d: vec3f, s: vec3f };
|
|
1005
1044
|
|
|
1006
|
-
fn _rzLampOne(i: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32) -> _RzLampPair {
|
|
1045
|
+
fn _rzLampOne(i: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32, unity: f32) -> _RzLampPair {
|
|
1007
1046
|
var out = _RzLampPair(vec3f(0.0), vec3f(0.0));
|
|
1008
1047
|
let pr = _rzLightVec(i, 0u);
|
|
1009
1048
|
let d = pr.xyz - p;
|
|
@@ -1021,40 +1060,40 @@ fn _rzLampOne(i: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32) ->
|
|
|
1021
1060
|
let aim = clamp((dot(-toLight, rzLightAim(i)) - cone.x) / max(cone.y - cone.x, 1e-4), 0.0, 1.0);
|
|
1022
1061
|
let c = rzLightColor(i);
|
|
1023
1062
|
if (ndlD > 0.0) { out.d = c * (ndlD * falloff * aim * aim); }
|
|
1024
|
-
if (ndl > 0.0) { out.s = c * (
|
|
1063
|
+
if (ndl > 0.0) { out.s = c * (_rzDirectLobe(n, toLight, v, ndl, ndv, roughness, unity) * falloff * aim * aim); }
|
|
1025
1064
|
return out;
|
|
1026
1065
|
}
|
|
1027
1066
|
|
|
1028
|
-
fn _rzLampWord(bits0: u32, base: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32) -> _RzLampPair {
|
|
1067
|
+
fn _rzLampWord(bits0: u32, base: u32, p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32, unity: f32) -> _RzLampPair {
|
|
1029
1068
|
var acc = _RzLampPair(vec3f(0.0), vec3f(0.0));
|
|
1030
1069
|
var bits = bits0;
|
|
1031
1070
|
loop {
|
|
1032
1071
|
if (bits == 0u) { break; }
|
|
1033
1072
|
let i = base + firstTrailingBit(bits);
|
|
1034
1073
|
bits = bits & (bits - 1u);
|
|
1035
|
-
let one = _rzLampOne(i, p, n, v, ndv, roughness);
|
|
1074
|
+
let one = _rzLampOne(i, p, n, v, ndv, roughness, unity);
|
|
1036
1075
|
acc.d = acc.d + one.d;
|
|
1037
1076
|
acc.s = acc.s + one.s;
|
|
1038
1077
|
}
|
|
1039
1078
|
return acc;
|
|
1040
1079
|
}
|
|
1041
1080
|
|
|
1042
|
-
fn rzLampsSpecular(p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32) -> vec3f {
|
|
1081
|
+
fn rzLampsSpecular(p: vec3f, n: vec3f, v: vec3f, ndv: f32, roughness: f32, unity: f32) -> vec3f {
|
|
1043
1082
|
var d = vec3f(0.0);
|
|
1044
1083
|
var s = vec3f(0.0);
|
|
1045
1084
|
let count = rzLightCount();
|
|
1046
1085
|
let docs = _rzLightDocCount();
|
|
1047
1086
|
if (docs > 0u) {
|
|
1048
1087
|
let m = _rzLightCellMask(p);
|
|
1049
|
-
let wx = _rzLampWord(m.x, 0u, p, n, v, ndv, roughness);
|
|
1050
|
-
let wy = _rzLampWord(m.y, 32u, p, n, v, ndv, roughness);
|
|
1051
|
-
let wz = _rzLampWord(m.z, 64u, p, n, v, ndv, roughness);
|
|
1052
|
-
let ww = _rzLampWord(m.w, 96u, p, n, v, ndv, roughness);
|
|
1088
|
+
let wx = _rzLampWord(m.x, 0u, p, n, v, ndv, roughness, unity);
|
|
1089
|
+
let wy = _rzLampWord(m.y, 32u, p, n, v, ndv, roughness, unity);
|
|
1090
|
+
let wz = _rzLampWord(m.z, 64u, p, n, v, ndv, roughness, unity);
|
|
1091
|
+
let ww = _rzLampWord(m.w, 96u, p, n, v, ndv, roughness, unity);
|
|
1053
1092
|
d = d + wx.d + wy.d + wz.d + ww.d;
|
|
1054
1093
|
s = s + wx.s + wy.s + wz.s + ww.s;
|
|
1055
1094
|
}
|
|
1056
1095
|
for (var i = docs; i < count; i = i + 1u) {
|
|
1057
|
-
let one = _rzLampOne(i, p, n, v, ndv, roughness);
|
|
1096
|
+
let one = _rzLampOne(i, p, n, v, ndv, roughness, unity);
|
|
1058
1097
|
d = d + one.d;
|
|
1059
1098
|
s = s + one.s;
|
|
1060
1099
|
}
|
|
@@ -1075,6 +1114,8 @@ struct PrincipledIn {
|
|
|
1075
1114
|
spec_clamp: f32,
|
|
1076
1115
|
sheen: f32,
|
|
1077
1116
|
sheen_tint: f32,
|
|
1117
|
+
reflection_lod: f32,
|
|
1118
|
+
unity_direct: f32,
|
|
1078
1119
|
};
|
|
1079
1120
|
|
|
1080
1121
|
fn eval_principled(
|
|
@@ -1101,9 +1142,12 @@ fn eval_principled(
|
|
|
1101
1142
|
// split-sum indirect path, matching EEVEE closure_eval_glossy_lib behavior.
|
|
1102
1143
|
// The sun, and every lamp that reaches this point. One clamp over the pair:
|
|
1103
1144
|
// a candle a hand's width from a polished floor is a firefly otherwise.
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1145
|
+
// URP's lobe carries its own normalisation, so the LTC rescale that matches
|
|
1146
|
+
// EEVEE's direct light to its split-sum indirect does not apply to it.
|
|
1147
|
+
let direct_scale = select(ltc_brdf_scale_from_lut(lut), 1.0, p.unity_direct > 0.5);
|
|
1148
|
+
let spec_direct_raw = (_rzDirectLobe(N, L, V, NL, NV, p.roughness, p.unity_direct) * sun_rgb * shadow
|
|
1149
|
+
+ rzLampsSpecular(wp, N, V, NV, p.roughness, p.unity_direct))
|
|
1150
|
+
* direct_scale;
|
|
1107
1151
|
let spec_direct = min(spec_direct_raw, vec3f(p.spec_clamp));
|
|
1108
1152
|
// Indirect specular reads the world ALONG THE REFLECTION, at a roughness-
|
|
1109
1153
|
// picked level — EEVEE's probe_evaluate_world_spec, where the diffuse half
|
|
@@ -1112,8 +1156,13 @@ fn eval_principled(
|
|
|
1112
1156
|
// roughness, so a chrome rail mirrored what a matte wall did and a stage's
|
|
1113
1157
|
// metal only ever lost its diffuse. A uniform sky gives the same number
|
|
1114
1158
|
// either way, so this moves direction, not energy.
|
|
1115
|
-
let spec_indirect =
|
|
1116
|
-
|
|
1159
|
+
let spec_indirect = rzWorldSpecularLod(reflect(-V, N), p.roughness, p.reflection_lod);
|
|
1160
|
+
// URP tints a lamp's highlight by f0 alone — its D·V·F already folds the
|
|
1161
|
+
// Fresnel in — where the split-sum reflectance grows toward grazing; applied
|
|
1162
|
+
// to the direct term it made a far glossy floor several times brighter than
|
|
1163
|
+
// the game draws it. Reflections keep the split sum either way.
|
|
1164
|
+
let direct_tint = select(reflection_color, f0, p.unity_direct > 0.5);
|
|
1165
|
+
let spec_radiance = spec_direct * direct_tint + spec_indirect * reflection_color;
|
|
1117
1166
|
|
|
1118
1167
|
// Sheen add — when p.sheen=0 the whole term collapses, leaving diffuse_color=base.
|
|
1119
1168
|
let base_tint = tint_from_color(p.base);
|
|
@@ -1123,9 +1172,23 @@ fn eval_principled(
|
|
|
1123
1172
|
// diffuse_weight = (1-metallic). Indirect diffuse uses amb (L_w) with no π factor
|
|
1124
1173
|
// (probe_evaluate_world_diff returns SH-projected radiance, not cosine-convolved).
|
|
1125
1174
|
let diffuse_weight = 1.0 - p.metallic;
|
|
1126
|
-
|
|
1175
|
+
var diffuse_radiance = diffuse_color * (sun_rgb * NL * shadow / EEVEE_PI + amb_rgb) * diffuse_weight;
|
|
1176
|
+
// The lamps' diffuse, in Unity mode, as URP shades it: THIS surface's albedo
|
|
1177
|
+
// — its tint and its maps, not the raw texture the epilogue would use —
|
|
1178
|
+
// times 0.96·(1 − metal). X340's floor wears a 0.588 tint the epilogue never
|
|
1179
|
+
// saw, and every lamp on it landed 1.7× too bright.
|
|
1180
|
+
if (p.unity_direct > 0.5 && !_rzLampDiffuseTaken) {
|
|
1181
|
+
diffuse_radiance += p.base * (0.96 * (1.0 - p.metallic)) * rzLightsDiffuseOnce(wp, N);
|
|
1182
|
+
_rzLampDiffuseTaken = true;
|
|
1183
|
+
}
|
|
1127
1184
|
|
|
1128
|
-
|
|
1185
|
+
// The cast's shadow on a stage (Unity mode): the game lays its character
|
|
1186
|
+
// shadow over the whole lit surface as a multiply toward the shadow colour.
|
|
1187
|
+
var lit = diffuse_radiance + spec_radiance;
|
|
1188
|
+
if (p.unity_direct > 0.5) {
|
|
1189
|
+
lit = lit * mix(vec3f(1.0), light.castShadow.rgb, _rzCastShadow(wp) * light.castShadow.w);
|
|
1190
|
+
}
|
|
1191
|
+
return lit;
|
|
1129
1192
|
}
|
|
1130
1193
|
|
|
1131
1194
|
`;
|
|
@@ -157,7 +157,8 @@ override APPLY_GAMMA: bool = true;
|
|
|
157
157
|
// per frame while skybox/effect active.
|
|
158
158
|
// viewU[6] = (time seconds, view transform id, canvas width, canvas height).
|
|
159
159
|
// viewU[7] = (grade offset.rgb, contrast); viewU[8] = (grade power.rgb, saturation);
|
|
160
|
-
// viewU[9] = (grade slope.rgb, grade
|
|
160
|
+
// viewU[9] = (grade slope.rgb, grade flags) — bit 0 the CDL grade(), bit 1 the
|
|
161
|
+
// scene's own LUT (setStageGrade) — see _rzGradeScene() below.
|
|
161
162
|
// viewU[10] = (camera world position, _) — refreshed with the basis above.
|
|
162
163
|
// invGamma = 1/gamma precomputed on CPU — avoids a per-pixel divide.
|
|
163
164
|
@group(0) @binding(6) var bgEquirect: texture_2d<f32>;
|
|
@@ -178,6 +179,9 @@ override APPLY_GAMMA: bool = true;
|
|
|
178
179
|
// Blender's AgX, as the 57³ lookup it ships as rather than a reconstruction of
|
|
179
180
|
// it. Sampled in the log-encoded E-Gamut space the cube expects — see agxTransform.
|
|
180
181
|
@group(0) @binding(10) var agxLut: texture_3d<f32>;
|
|
182
|
+
// The scene's own colour grade, as a cube — see _rzStageGrade. sRGB-encoded
|
|
183
|
+
// storage, so what a sample returns is already linear.
|
|
184
|
+
@group(0) @binding(12) var stageGradeLut: texture_3d<f32>;
|
|
181
185
|
// The cast, as data. Read through rzSubject/rzAnchor below — the LAYOUT IS NOT
|
|
182
186
|
// STABLE and never will be, because it depends on what each effect declared.
|
|
183
187
|
// Reading it directly is the one thing that would freeze it forever.
|
|
@@ -306,6 +310,33 @@ fn viewTransform(c: vec3f) -> vec3f {
|
|
|
306
310
|
if (mode > 0.5) { return vec3f(srgb_encode(c.r), srgb_encode(c.g), srgb_encode(c.b)); }
|
|
307
311
|
return vec3f(filmic(c.r), filmic(c.g), filmic(c.b));
|
|
308
312
|
}
|
|
313
|
+
|
|
314
|
+
fn _rzSrgbDecode(x: f32) -> f32 {
|
|
315
|
+
let c = max(x, 0.0);
|
|
316
|
+
return select(pow((c + 0.055) / 1.055, 2.4), c / 12.92, c <= 0.04045);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** The grade a scene arrived with — a game's, baked to a cube by its converter
|
|
320
|
+
* (setStageGrade). Looked up the way that game's final pass looks it up: the
|
|
321
|
+
* formed colour, linear, clamped to the cube, with texel centres at the ends,
|
|
322
|
+
* and trilinear between. The lookup is the whole grade; it has no dials. */
|
|
323
|
+
fn _rzStageGrade(c: vec3f) -> vec3f {
|
|
324
|
+
let lin = clamp(vec3f(_rzSrgbDecode(c.r), _rzSrgbDecode(c.g), _rzSrgbDecode(c.b)), vec3f(0.0), vec3f(1.0));
|
|
325
|
+
let n = f32(textureDimensions(stageGradeLut).x);
|
|
326
|
+
let graded = textureSampleLevel(stageGradeLut, bloomSamp, lin * ((n - 1.0) / n) + 0.5 / n, 0.0).rgb;
|
|
327
|
+
return vec3f(srgb_encode(graded.r), srgb_encode(graded.g), srgb_encode(graded.b));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Both grades, in order: the scene's own first, as the room was graded, then
|
|
331
|
+
* the author's CDL on top of it. Each is behind its own flag bit in viewU[9].w
|
|
332
|
+
* so a scene with neither pays one uniform branch. */
|
|
333
|
+
fn _rzGradeScene(c: vec3f) -> vec3f {
|
|
334
|
+
let flags = u32(viewU[9].w);
|
|
335
|
+
var x = c;
|
|
336
|
+
if ((flags & 2u) != 0u) { x = _rzStageGrade(x); }
|
|
337
|
+
if ((flags & 1u) != 0u) { x = grade(x); }
|
|
338
|
+
return x;
|
|
339
|
+
}
|
|
309
340
|
`
|
|
310
341
|
|
|
311
342
|
/**
|
|
@@ -455,6 +486,7 @@ fn grade(c: vec3f) -> vec3f {
|
|
|
455
486
|
let luma = dot(x, vec3f(0.2126, 0.7152, 0.0722));
|
|
456
487
|
return max(mix(vec3f(luma), x, viewU[8].w), vec3f(0.0));
|
|
457
488
|
}
|
|
489
|
+
|
|
458
490
|
`
|
|
459
491
|
|
|
460
492
|
const COMPOSITE_BODY = /* wgsl */ `
|
|
@@ -537,7 +569,7 @@ const COMPOSITE_BODY = /* wgsl */ `
|
|
|
537
569
|
// load-bearing — leaves green-screen mode's key color unshifted so chroma
|
|
538
570
|
// keying still works. Skipped entirely when the grade is neutral.
|
|
539
571
|
if (viewU[9].w > 0.5) {
|
|
540
|
-
disp =
|
|
572
|
+
disp = _rzGradeScene(disp);
|
|
541
573
|
}
|
|
542
574
|
if (APPLY_GAMMA) {
|
|
543
575
|
disp = pow(disp, vec3f(viewU[0].y));
|
package/src/shaders/scene-tap.ts
CHANGED
|
@@ -202,7 +202,7 @@ fn rzSceneHit(uv: vec2f) -> bool {
|
|
|
202
202
|
*/
|
|
203
203
|
fn rzSceneDisplay(uv: vec2f) -> vec3f {
|
|
204
204
|
var c = max(viewTransform(rzScene(uv) * exp2(viewU[0].x)), vec3f(0.0));
|
|
205
|
-
if (viewU[9].w > 0.5) { c =
|
|
205
|
+
if (viewU[9].w > 0.5) { c = _rzGradeScene(c); }
|
|
206
206
|
return pow(max(c, vec3f(0.0)), vec3f(viewU[0].y));
|
|
207
207
|
}
|
|
208
208
|
|
package/src/shadow-cascades.ts
CHANGED
|
@@ -61,6 +61,10 @@ type XYZ = { x: number; y: number; z: number }
|
|
|
61
61
|
* ends, so an empty floor with a dancer on it keeps a short, sharp frustum
|
|
62
62
|
* rather than the camera's far plane.
|
|
63
63
|
*/
|
|
64
|
+
/** The deepest a cascade reaches along the view, in world units — the far
|
|
65
|
+
* plane's old cap, which is where every stage's shadows were fitted before. */
|
|
66
|
+
const SHADOW_FAR = 8000
|
|
67
|
+
|
|
64
68
|
export function cascadeSlices(view: ShadowView, bounds: ShadowBounds): [number, number][] {
|
|
65
69
|
let farFit = view.near + 200
|
|
66
70
|
if (bounds) {
|
|
@@ -73,7 +77,10 @@ export function cascadeSlices(view: ShadowView, bounds: ShadowBounds): [number,
|
|
|
73
77
|
}
|
|
74
78
|
farFit = deepest + 1
|
|
75
79
|
}
|
|
76
|
-
|
|
80
|
+
// Never past SHADOW_FAR, whatever the camera's far plane: a stage's sky dome
|
|
81
|
+
// a kilometre out is in the scene's bounds, and fitting the far cascade to it
|
|
82
|
+
// would spread the stage's shadow map across the sky.
|
|
83
|
+
farFit = Math.min(view.far, SHADOW_FAR, Math.max(view.near + 1, farFit))
|
|
77
84
|
const split = Math.min(farFit, Math.max(view.near + 8, view.focus + NEAR_REACH))
|
|
78
85
|
return [
|
|
79
86
|
[view.near, split],
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pool_floor.d.ts","sourceRoot":"","sources":["../../../src/graph/presets/pool_floor.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,eAAO,MAAM,gBAAgB,EAAE,WA6B9B,CAAA"}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// Pool floor — the tiled bottom seen through water, with caustics on it.
|
|
2
|
-
//
|
|
3
|
-
// The other half of a pool, and the half that does most of the work. A water
|
|
4
|
-
// surface can be tuned for a long time without the shot reading as a pool;
|
|
5
|
-
// putting the focused-light web on the FLOOR is what settles it, because that
|
|
6
|
-
// is the cue a photograph actually carries. It is a floor material, so it is
|
|
7
|
-
// applied to the tile under the water rather than to the water itself.
|
|
8
|
-
//
|
|
9
|
-
// Caustics land between the albedo and the shading deliberately: they are
|
|
10
|
-
// light, so they should still be dimmed by the floor's own shadow rather than
|
|
11
|
-
// glowing through it. That is the difference between water-focused sun and a
|
|
12
|
-
// decal.
|
|
13
|
-
export const POOL_FLOOR_GRAPH = {
|
|
14
|
-
version: 1,
|
|
15
|
-
name: "Pool floor",
|
|
16
|
-
tags: ["stage", "water", "pool"],
|
|
17
|
-
nodes: [
|
|
18
|
-
{ id: "tex", type: "texture" },
|
|
19
|
-
{ id: "mat", type: "material_diffuse" },
|
|
20
|
-
{ id: "base", type: "mix/multiply", inputs: { fac: 1.0 } },
|
|
21
|
-
{ id: "caust", type: "caustics" },
|
|
22
|
-
{
|
|
23
|
-
id: "principled",
|
|
24
|
-
type: "principled",
|
|
25
|
-
inputs: { metallic: 0.0, specular_ior_level: 0.4, roughness: 0.45, spec_clamp: 10.0, sheen_weight: 0.0, sheen_tint: 0.0 },
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
links: [
|
|
29
|
-
{ from: { node: "tex", socket: "color" }, to: { node: "base", socket: "a" } },
|
|
30
|
-
{ from: { node: "mat", socket: "color" }, to: { node: "base", socket: "b" } },
|
|
31
|
-
{ from: { node: "base", socket: "color" }, to: { node: "caust", socket: "color" } },
|
|
32
|
-
{ from: { node: "caust", socket: "color" }, to: { node: "principled", socket: "base_color" } },
|
|
33
|
-
],
|
|
34
|
-
output: { node: "principled", socket: "color" },
|
|
35
|
-
params: [
|
|
36
|
-
{ id: "tint", label: "Light tint", target: { node: "caust", socket: "tint" }, kind: "color", default: [1.0, 1.0, 1.0] },
|
|
37
|
-
{ id: "scale", label: "Caustic scale", target: { node: "caust", socket: "scale" }, kind: "float", min: 0.01, max: 1, default: 0.12 },
|
|
38
|
-
{ id: "speed", label: "Caustic speed", target: { node: "caust", socket: "speed" }, kind: "float", min: 0, max: 1, default: 0.15 },
|
|
39
|
-
{ id: "strength", label: "Caustic strength", target: { node: "caust", socket: "strength" }, kind: "float", min: 0, max: 3, default: 0.6 },
|
|
40
|
-
{ id: "fade", label: "Depth fade", target: { node: "caust", socket: "fade" }, kind: "float", min: 1, max: 200, default: 30 },
|
|
41
|
-
],
|
|
42
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"water.d.ts","sourceRoot":"","sources":["../../../src/graph/presets/water.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,eAAO,MAAM,WAAW,EAAE,WAgBzB,CAAA"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// Water — a planar reflection, rippled, seen through Fresnel.
|
|
2
|
-
//
|
|
3
|
-
// Put a stage's water materials in a style group carrying this graph and the
|
|
4
|
-
// pool starts reflecting. The reflection itself is the engine's mirror pass,
|
|
5
|
-
// which this is the only graph to read; everything else here is the shaping
|
|
6
|
-
// that keeps it from reading as a mirror laid flat in the floor.
|
|
7
|
-
//
|
|
8
|
-
// The exposed params are the ones worth a slider while judging a shot. Scale
|
|
9
|
-
// and speed are the ripple; a real pool's surface is slow and broad, so both
|
|
10
|
-
// defaults sit low. Tint is the water's own colour — a game rip carries that
|
|
11
|
-
// in its material, and the converter hands it straight over.
|
|
12
|
-
//
|
|
13
|
-
// How see-through the water is belongs to the MATERIAL's alpha, not to this
|
|
14
|
-
// graph, which is where every other group's coverage comes from too.
|
|
15
|
-
export const WATER_GRAPH = {
|
|
16
|
-
version: 1,
|
|
17
|
-
name: "Water",
|
|
18
|
-
tags: ["water", "stage"],
|
|
19
|
-
nodes: [{ id: "water", type: "water" }],
|
|
20
|
-
links: [],
|
|
21
|
-
output: { node: "water", socket: "color" },
|
|
22
|
-
params: [
|
|
23
|
-
{ id: "tint", label: "Tint", target: { node: "water", socket: "tint" }, kind: "color", default: [0.04, 0.62, 0.66] },
|
|
24
|
-
{ id: "scale", label: "Ripple scale", target: { node: "water", socket: "scale" }, kind: "float", min: 0.01, max: 1, default: 0.18 },
|
|
25
|
-
{ id: "speed", label: "Ripple speed", target: { node: "water", socket: "speed" }, kind: "float", min: 0, max: 1, default: 0.12 },
|
|
26
|
-
{ id: "strength", label: "Ripple depth", target: { node: "water", socket: "strength" }, kind: "float", min: 0, max: 2, default: 0.7 },
|
|
27
|
-
{ id: "blur", label: "Reflection blur", target: { node: "water", socket: "blur" }, kind: "float", min: 0, max: 1, default: 0.25 },
|
|
28
|
-
{ id: "mirroring", label: "Mirroring", target: { node: "water", socket: "mirroring" }, kind: "float", min: 0, max: 1, default: 0.20 },
|
|
29
|
-
{ id: "sparkle", label: "Sparkle", target: { node: "water", socket: "sparkle" }, kind: "float", min: 0, max: 4, default: 1.0 },
|
|
30
|
-
],
|
|
31
|
-
};
|