reze-engine 0.60.4 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_dbg-loader.mjs +1100 -0
- package/dist/engine.d.ts +121 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +284 -19
- 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 +19 -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 +19 -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 +110 -23
- 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/package.json +1 -1
- package/src/engine.ts +312 -19
- package/src/graph/compile.ts +4 -1
- package/src/graph/registry.ts +14 -2
- package/src/graph/slots.ts +20 -5
- package/src/index.ts +4 -0
- package/src/shaders/lights.ts +19 -0
- package/src/shaders/materials/common.ts +47 -0
- package/src/shaders/materials/nodes.ts +110 -23
- package/src/shaders/passes/composite.ts +34 -2
- package/src/shaders/scene-tap.ts +1 -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/dist/engine.js
CHANGED
|
@@ -731,7 +731,13 @@ export class Engine {
|
|
|
731
731
|
this.cameraMatrixData = new Float32Array(40);
|
|
732
732
|
// ambient vec4 (4) + 4 lights x 2 vec4 (32) + 9 irradiance-SH vec4s (36),
|
|
733
733
|
// padded to 80. sh[0].w is the IBL flag: 0 = flat world colour, 1 = the sky.
|
|
734
|
-
|
|
734
|
+
// …then the scene fog at [72..87] — see setSceneFog — and the cast's
|
|
735
|
+
// shadow on a stage at [88..107] (colour, amount; its view-projection).
|
|
736
|
+
this.lightData = new Float32Array(112);
|
|
737
|
+
this.castShadow = null;
|
|
738
|
+
this.castShadowBundle = null;
|
|
739
|
+
this.castShadowCleared = false;
|
|
740
|
+
this.castSphereScratch = new Float32Array(4);
|
|
735
741
|
this.lightCount = 0;
|
|
736
742
|
this.resizeObserver = null;
|
|
737
743
|
this.resizePending = false;
|
|
@@ -1029,6 +1035,7 @@ export class Engine {
|
|
|
1029
1035
|
this.cullReadbackInFlight = false;
|
|
1030
1036
|
this.depthOfField = { ...DEFAULT_DEPTH_OF_FIELD_OPTIONS };
|
|
1031
1037
|
this.dofUniformData = new Float32Array(12);
|
|
1038
|
+
this.sceneFog = null;
|
|
1032
1039
|
this.dofFocusScratch = new Vec3(0, 0, 0);
|
|
1033
1040
|
/** Depth-only view of the scene's MSAA depth buffer, read by the DoF gather. */
|
|
1034
1041
|
this.depthReadView = null;
|
|
@@ -1061,6 +1068,9 @@ export class Engine {
|
|
|
1061
1068
|
this.worldGradientSH = null;
|
|
1062
1069
|
/** The installed HDRI's folded irradiance SH (27 floats), or null. */
|
|
1063
1070
|
this.worldSH = null;
|
|
1071
|
+
/** A diffuse ambient stated outright (setWorldAmbient), outranking the one
|
|
1072
|
+
* fitted to the picture; the picture still answers reflections. */
|
|
1073
|
+
this.worldAmbientSH = null;
|
|
1064
1074
|
// The scene's user WGSL effect (setEffect). ONE per scene, mounted under the
|
|
1065
1075
|
// scene, over it, or both — whichever of background()/foreground() the code
|
|
1066
1076
|
// defines. The composite pipelines are REBUILT with the user code injected;
|
|
@@ -1121,6 +1131,10 @@ export class Engine {
|
|
|
1121
1131
|
* character samples in lockstep and their paths stay comparable. */
|
|
1122
1132
|
this.trailDue = 0;
|
|
1123
1133
|
this.agxLutTexture = null;
|
|
1134
|
+
/** The scene's own grade (setStageGrade): the cube as handed over, kept so a
|
|
1135
|
+
* call before init() still lands, and its texture once uploaded. */
|
|
1136
|
+
this.stageGradeCube = null;
|
|
1137
|
+
this.stageGradeTexture = null;
|
|
1124
1138
|
/** time=0 origin for the active effect — reset each setEffect. */
|
|
1125
1139
|
/** Scene-clock reading when the current effect was installed. The effect's
|
|
1126
1140
|
* `time` is measured from here — see where it is written. */
|
|
@@ -1650,6 +1664,57 @@ export class Engine {
|
|
|
1650
1664
|
if (this.device && this.compositeUniformBuffer)
|
|
1651
1665
|
this.writeCompositeViewUniforms();
|
|
1652
1666
|
}
|
|
1667
|
+
/**
|
|
1668
|
+
* The grade a scene arrived with, as a cube — a game's own colour grading,
|
|
1669
|
+
* baked by the converter that brought its stage. Applied to the formed frame
|
|
1670
|
+
* before setColorGrading's, which stays the author's to lay on top. Looked up
|
|
1671
|
+
* as the game looks it up: the view transform's output, linear, trilinear
|
|
1672
|
+
* between texel centres. Null takes it off.
|
|
1673
|
+
*
|
|
1674
|
+
* Bytes are the cube as that game stored it: 8-bit sRGB-encoded, red fastest,
|
|
1675
|
+
* then green, then blue; three bytes a texel or four. A cube whose byte count
|
|
1676
|
+
* does not match its size is refused rather than drawn wrong.
|
|
1677
|
+
*/
|
|
1678
|
+
setStageGrade(lut) {
|
|
1679
|
+
if (lut) {
|
|
1680
|
+
const stride = lut.data.byteLength / lut.size ** 3;
|
|
1681
|
+
if (!Number.isInteger(lut.size) || lut.size < 2 || lut.size > 64 || (stride !== 3 && stride !== 4)) {
|
|
1682
|
+
throw new Error(`setStageGrade: ${lut.data.byteLength} bytes is not a ${lut.size}³ cube`);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
this.stageGradeCube = lut;
|
|
1686
|
+
if (this.device && this.stageGradeFallback)
|
|
1687
|
+
this.uploadStageGrade();
|
|
1688
|
+
}
|
|
1689
|
+
uploadStageGrade() {
|
|
1690
|
+
const lut = this.stageGradeCube;
|
|
1691
|
+
this.stageGradeTexture?.destroy();
|
|
1692
|
+
this.stageGradeTexture = null;
|
|
1693
|
+
if (lut) {
|
|
1694
|
+
const n = lut.size;
|
|
1695
|
+
const texels = n ** 3;
|
|
1696
|
+
const stride = lut.data.byteLength / texels;
|
|
1697
|
+
const rgba = new Uint8Array(texels * 4);
|
|
1698
|
+
for (let i = 0; i < texels; i++) {
|
|
1699
|
+
rgba[i * 4] = lut.data[i * stride];
|
|
1700
|
+
rgba[i * 4 + 1] = lut.data[i * stride + 1];
|
|
1701
|
+
rgba[i * 4 + 2] = lut.data[i * stride + 2];
|
|
1702
|
+
rgba[i * 4 + 3] = 255;
|
|
1703
|
+
}
|
|
1704
|
+
const tex = this.device.createTexture({
|
|
1705
|
+
label: `stage grade ${n}³`,
|
|
1706
|
+
size: [n, n, n],
|
|
1707
|
+
dimension: "3d",
|
|
1708
|
+
format: "rgba8unorm-srgb",
|
|
1709
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
1710
|
+
});
|
|
1711
|
+
this.device.queue.writeTexture({ texture: tex }, rgba, { bytesPerRow: n * 4, rowsPerImage: n }, [n, n, n]);
|
|
1712
|
+
this.stageGradeTexture = tex;
|
|
1713
|
+
}
|
|
1714
|
+
this.rebuildCompositeBindGroup();
|
|
1715
|
+
if (this.compositeUniformBuffer)
|
|
1716
|
+
this.writeCompositeViewUniforms();
|
|
1717
|
+
}
|
|
1653
1718
|
/** Current grade (for serialization into a scene descriptor). */
|
|
1654
1719
|
getColorGrading() {
|
|
1655
1720
|
const g = this.colorGrading;
|
|
@@ -1786,7 +1851,8 @@ export class Engine {
|
|
|
1786
1851
|
u[32] === 1 && u[33] === 1 && u[34] === 1 &&
|
|
1787
1852
|
u[36] === 1 && u[37] === 1 && u[38] === 1 &&
|
|
1788
1853
|
g.contrast === 1 && g.saturation === 1;
|
|
1789
|
-
|
|
1854
|
+
// Bit 0 the CDL above, bit 1 the scene's own cube — see _rzGradeScene.
|
|
1855
|
+
u[39] = (neutral ? 0 : 1) | (this.stageGradeTexture ? 2 : 0);
|
|
1790
1856
|
this.device.queue.writeBuffer(this.compositeUniformBuffer, 0, u);
|
|
1791
1857
|
}
|
|
1792
1858
|
/**
|
|
@@ -1898,8 +1964,9 @@ export class Engine {
|
|
|
1898
1964
|
*/
|
|
1899
1965
|
getWorldLighting() {
|
|
1900
1966
|
const s = this.world.strength;
|
|
1901
|
-
|
|
1902
|
-
|
|
1967
|
+
const fitted = this.worldAmbientSH ?? this.worldSH;
|
|
1968
|
+
if (fitted) {
|
|
1969
|
+
const at = (n) => evalIrradianceSH(fitted, n).map((v) => Math.max(v * s, 0));
|
|
1903
1970
|
return { source: "hdri", strength: s, up: at({ x: 0, y: 1, z: 0 }), down: at({ x: 0, y: -1, z: 0 }) };
|
|
1904
1971
|
}
|
|
1905
1972
|
const c = this.world.color;
|
|
@@ -2561,6 +2628,7 @@ export class Engine {
|
|
|
2561
2628
|
{ binding: 8, resource: this.depthReadView },
|
|
2562
2629
|
{ binding: 9, resource: { buffer: this.dofUniformBuffer } },
|
|
2563
2630
|
{ binding: 10, resource: (this.agxLutTexture ?? this.agxFallbackTexture).createView({ dimension: "3d" }) },
|
|
2631
|
+
{ binding: 12, resource: (this.stageGradeTexture ?? this.stageGradeFallback).createView({ dimension: "3d" }) },
|
|
2564
2632
|
{ binding: 11, resource: { buffer: this.castBuffer } },
|
|
2565
2633
|
{ binding: 13, resource: { buffer: this.audioBuffer } },
|
|
2566
2634
|
{ binding: 19, resource: { buffer: this.midiBuffer } },
|
|
@@ -2985,6 +3053,7 @@ export class Engine {
|
|
|
2985
3053
|
{ binding: 2, resource: this.bloomSampler },
|
|
2986
3054
|
{ binding: 5, resource: this.filmicLutView },
|
|
2987
3055
|
{ binding: 10, resource: (this.agxLutTexture ?? this.agxFallbackTexture).createView({ dimension: "3d" }) },
|
|
3056
|
+
{ binding: 12, resource: (this.stageGradeTexture ?? this.stageGradeFallback).createView({ dimension: "3d" }) },
|
|
2988
3057
|
{ binding: 1, resource: bloom },
|
|
2989
3058
|
{ binding: 4, resource: this.maskResolveView },
|
|
2990
3059
|
],
|
|
@@ -5849,6 +5918,7 @@ export class Engine {
|
|
|
5849
5918
|
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
5850
5919
|
{ binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
5851
5920
|
{ binding: 10, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "3d" } },
|
|
5921
|
+
{ binding: 12, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float", viewDimension: "3d" } },
|
|
5852
5922
|
// And the scene's COVERAGE and bloom, without which the tap cannot
|
|
5853
5923
|
// reconstruct a pixel: the HDR target is premultiplied, so colour alone
|
|
5854
5924
|
// reads a half-transparent ground as a dark opaque one.
|
|
@@ -5999,7 +6069,8 @@ export class Engine {
|
|
|
5999
6069
|
label: "main per-frame bind group layout",
|
|
6000
6070
|
entries: [
|
|
6001
6071
|
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
6002
|
-
|
|
6072
|
+
// The vertex stage reads it too: the scene fog is per vertex (setSceneFog).
|
|
6073
|
+
{ binding: 1, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
6003
6074
|
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
6004
6075
|
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
|
|
6005
6076
|
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "comparison" } },
|
|
@@ -6013,6 +6084,8 @@ export class Engine {
|
|
|
6013
6084
|
{ binding: 7, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
|
|
6014
6085
|
{ binding: 8, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
6015
6086
|
{ binding: 9, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
6087
|
+
// The cast's shadow on a stage — setStageCastShadow.
|
|
6088
|
+
{ binding: 10, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
|
|
6016
6089
|
],
|
|
6017
6090
|
});
|
|
6018
6091
|
// group 1: per-instance (skinMats) — bound once per model
|
|
@@ -6226,6 +6299,22 @@ export class Engine {
|
|
|
6226
6299
|
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
6227
6300
|
}));
|
|
6228
6301
|
this.shadowMapDepthViews = this.shadowMapTextures.map((t) => t.createView());
|
|
6302
|
+
// The cast's shadow on a stage (setStageCastShadow): one map, only the cast
|
|
6303
|
+
// in it, from the stage's own direction. Always allocated so every material
|
|
6304
|
+
// bind group and every instance's shadow bind group stays one shape; drawn
|
|
6305
|
+
// only while a stage asks for it.
|
|
6306
|
+
this.castShadowTexture = this.device.createTexture({
|
|
6307
|
+
label: "stage cast shadow map",
|
|
6308
|
+
size: [1024, 1024],
|
|
6309
|
+
format: Engine.SHADOW_DEPTH_FORMAT,
|
|
6310
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
6311
|
+
});
|
|
6312
|
+
this.castShadowView = this.castShadowTexture.createView();
|
|
6313
|
+
this.castShadowVPBuffer = this.device.createBuffer({
|
|
6314
|
+
label: "stage cast shadow view-projection",
|
|
6315
|
+
size: 64,
|
|
6316
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
6317
|
+
});
|
|
6229
6318
|
// One-shot bake of Blender EEVEE's combined BRDF LUT (DFG + LTC packed rgba8unorm).
|
|
6230
6319
|
this.bakeBrdfLut();
|
|
6231
6320
|
this.bakeGroundNoise();
|
|
@@ -6237,6 +6326,14 @@ export class Engine {
|
|
|
6237
6326
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
6238
6327
|
});
|
|
6239
6328
|
void this.loadAgxLut();
|
|
6329
|
+
this.stageGradeFallback = this.device.createTexture({
|
|
6330
|
+
label: "stage grade fallback",
|
|
6331
|
+
size: [1, 1, 1],
|
|
6332
|
+
dimension: "3d",
|
|
6333
|
+
format: "rgba8unorm-srgb",
|
|
6334
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
6335
|
+
});
|
|
6336
|
+
this.uploadStageGrade();
|
|
6240
6337
|
this.bakeFilmicLut();
|
|
6241
6338
|
// BEFORE the bind group below, which binds it. Full size from the start:
|
|
6242
6339
|
// every material pipeline binds this, so sizing it to the light count would
|
|
@@ -6649,6 +6746,9 @@ export class Engine {
|
|
|
6649
6746
|
// AgX's 57³ cube. Decompressed and uploaded off the critical path, so a
|
|
6650
6747
|
// 1×1×1 stand-in keeps the bind group valid until it arrives.
|
|
6651
6748
|
{ binding: 10, visibility: GPUShaderStage.FRAGMENT, texture: { viewDimension: "3d" } },
|
|
6749
|
+
// The scene's own grade cube (setStageGrade) — 1×1×1 stand-in while it
|
|
6750
|
+
// has none, which the flag bit keeps from ever being sampled.
|
|
6751
|
+
{ binding: 12, visibility: GPUShaderStage.FRAGMENT, texture: { viewDimension: "3d" } },
|
|
6652
6752
|
// The cast, for rzSubject/rzAnchor. Always bound so the base shader's
|
|
6653
6753
|
// layout matches; the base shader simply never reads it.
|
|
6654
6754
|
{ binding: 11, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "read-only-storage" } },
|
|
@@ -8318,7 +8418,7 @@ export class Engine {
|
|
|
8318
8418
|
setupLighting() {
|
|
8319
8419
|
this.lightUniformBuffer = this.device.createBuffer({
|
|
8320
8420
|
label: "light uniforms",
|
|
8321
|
-
size:
|
|
8421
|
+
size: 112 * 4, // ambient (4) + 4 lights x 2 vec4 (32) + irradiance SH 9 x vec4 (36) + fog 4 x vec4 (16) + cast shadow (4 + 16)
|
|
8322
8422
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
8323
8423
|
});
|
|
8324
8424
|
this.lightData.fill(0);
|
|
@@ -8367,6 +8467,7 @@ export class Engine {
|
|
|
8367
8467
|
{ binding: 7, resource: this.shadowMapDepthViews[SHADOW_CASCADES.length - 1] },
|
|
8368
8468
|
{ binding: 8, resource: env },
|
|
8369
8469
|
{ binding: 9, resource: this.brdfLutView },
|
|
8470
|
+
{ binding: 10, resource: this.castShadowView },
|
|
8370
8471
|
],
|
|
8371
8472
|
});
|
|
8372
8473
|
this.mirrorPerFrameBindGroup = this.device.createBindGroup({
|
|
@@ -8383,6 +8484,7 @@ export class Engine {
|
|
|
8383
8484
|
{ binding: 7, resource: this.shadowMapDepthViews[SHADOW_CASCADES.length - 1] },
|
|
8384
8485
|
{ binding: 8, resource: env },
|
|
8385
8486
|
{ binding: 9, resource: this.brdfLutView },
|
|
8487
|
+
{ binding: 10, resource: this.castShadowView },
|
|
8386
8488
|
],
|
|
8387
8489
|
});
|
|
8388
8490
|
return true;
|
|
@@ -8406,7 +8508,7 @@ export class Engine {
|
|
|
8406
8508
|
// replaces the colour, strength applies to either).
|
|
8407
8509
|
// An HDRI outranks a gradient outranks the flat colour, which is the order
|
|
8408
8510
|
// of how much each one knows about the sky.
|
|
8409
|
-
const sh = this.worldSH ?? this.worldGradientSH;
|
|
8511
|
+
const sh = this.worldAmbientSH ?? this.worldSH ?? this.worldGradientSH;
|
|
8410
8512
|
for (let i = 0; i < 9; i++) {
|
|
8411
8513
|
const b = 36 + i * 4;
|
|
8412
8514
|
if (sh) {
|
|
@@ -8450,6 +8552,118 @@ export class Engine {
|
|
|
8450
8552
|
this.updateLightBuffer();
|
|
8451
8553
|
}
|
|
8452
8554
|
/** Update the world environment (Blender: World Background). Ambient recomputes immediately. */
|
|
8555
|
+
/**
|
|
8556
|
+
* Distance fog on every lit surface, as a game on SimPipeline lays it: per
|
|
8557
|
+
* layer, t = saturate(f − (1 − f)·h) with f = saturate(depth·distance[0] +
|
|
8558
|
+
* distance[1]) and h = clamp((height[0] − y) / height[1], −1, 1), and the
|
|
8559
|
+
* colour pulled toward the layer's by (1 − t)·amount — the haze first, then
|
|
8560
|
+
* `dyn`, which X340 uses to darken toward black. Depth is along the view
|
|
8561
|
+
* axis and y the world height, both in this engine's units; colours linear.
|
|
8562
|
+
*
|
|
8563
|
+
* PER VERTEX, as the game computes it, and that is load-bearing rather than
|
|
8564
|
+
* a saving: a floor built of long triangles carries the fog of its far
|
|
8565
|
+
* corners into the near half, which is most of what X340's checker floor
|
|
8566
|
+
* shows of its haze. Emission-only graphs take none, as the game's effect
|
|
8567
|
+
* shaders take none. Null clears.
|
|
8568
|
+
*/
|
|
8569
|
+
setSceneFog(fog) {
|
|
8570
|
+
this.sceneFog = fog;
|
|
8571
|
+
const u = this.lightData;
|
|
8572
|
+
const layer = (at, l) => {
|
|
8573
|
+
u[at] = l?.color.x ?? 0;
|
|
8574
|
+
u[at + 1] = l?.color.y ?? 0;
|
|
8575
|
+
u[at + 2] = l?.color.z ?? 0;
|
|
8576
|
+
u[at + 3] = l ? Math.max(l.amount, 0) : 0;
|
|
8577
|
+
u[at + 4] = l?.distance[0] ?? 0;
|
|
8578
|
+
u[at + 5] = l?.distance[1] ?? 1;
|
|
8579
|
+
u[at + 6] = l?.height[0] ?? 0;
|
|
8580
|
+
u[at + 7] = l?.height[1] ?? 1;
|
|
8581
|
+
};
|
|
8582
|
+
layer(72, fog);
|
|
8583
|
+
layer(80, fog?.dyn);
|
|
8584
|
+
this.updateLightBuffer();
|
|
8585
|
+
}
|
|
8586
|
+
/**
|
|
8587
|
+
* The cast's shadow on a stage, as a game on SimPipeline lays its ground
|
|
8588
|
+
* shadow: the characters drawn into a map of their own from `direction` (the
|
|
8589
|
+
* way TO the light, this engine's axes), and every Unity-mode stage surface
|
|
8590
|
+
* multiplied toward `color` (linear) by amount × how much of the cast stands
|
|
8591
|
+
* in the way. Independent of the sun — X340's is dim moonlight, which left a
|
|
8592
|
+
* kneeling figure no shadow at all. Only stage materials read it; a scene
|
|
8593
|
+
* without a stage is untouched. Null turns it off.
|
|
8594
|
+
*/
|
|
8595
|
+
setStageCastShadow(opts) {
|
|
8596
|
+
this.castShadow = opts;
|
|
8597
|
+
const u = this.lightData;
|
|
8598
|
+
u[88] = opts?.color.x ?? 1;
|
|
8599
|
+
u[89] = opts?.color.y ?? 1;
|
|
8600
|
+
u[90] = opts?.color.z ?? 1;
|
|
8601
|
+
u[91] = 0; // lit per frame by updateCastShadowVP, once there is a cast to fit
|
|
8602
|
+
this.updateLightBuffer();
|
|
8603
|
+
}
|
|
8604
|
+
/** Fit the cast map to this frame's cast; false when there is nothing to draw. */
|
|
8605
|
+
updateCastShadowVP(stage) {
|
|
8606
|
+
const opts = this.castShadow;
|
|
8607
|
+
let on = false;
|
|
8608
|
+
if (opts && stage) {
|
|
8609
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
8610
|
+
for (const inst of this.modelInstances.values()) {
|
|
8611
|
+
if (inst.isStage || inst.isPlane || inst.isProp || !inst.model.visible)
|
|
8612
|
+
continue;
|
|
8613
|
+
const s = this.castSphereScratch;
|
|
8614
|
+
this.writeCullSphere(inst, s, 0);
|
|
8615
|
+
if (!(s[3] < 1e6))
|
|
8616
|
+
continue;
|
|
8617
|
+
minX = Math.min(minX, s[0] - s[3]);
|
|
8618
|
+
maxX = Math.max(maxX, s[0] + s[3]);
|
|
8619
|
+
minY = Math.min(minY, s[1] - s[3]);
|
|
8620
|
+
maxY = Math.max(maxY, s[1] + s[3]);
|
|
8621
|
+
minZ = Math.min(minZ, s[2] - s[3]);
|
|
8622
|
+
maxZ = Math.max(maxZ, s[2] + s[3]);
|
|
8623
|
+
}
|
|
8624
|
+
if (minX <= maxX) {
|
|
8625
|
+
const c = new Vec3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
|
|
8626
|
+
const r = Math.max(maxX - minX, maxY - minY, maxZ - minZ) / 2;
|
|
8627
|
+
const d = new Vec3(opts.direction.x, opts.direction.y, opts.direction.z).normalize();
|
|
8628
|
+
// Wide enough for the shadow a low light throws, deep enough to reach
|
|
8629
|
+
// the floor under the figure from well above it.
|
|
8630
|
+
const half = r * 2.5;
|
|
8631
|
+
const reach = r * 8;
|
|
8632
|
+
const eye = new Vec3(c.x + d.x * reach, c.y + d.y * reach, c.z + d.z * reach);
|
|
8633
|
+
const up = Math.abs(d.y) > 0.99 ? new Vec3(0, 0, 1) : new Vec3(0, 1, 0);
|
|
8634
|
+
const vp = Mat4.orthographicLh(-half, half, -half, half, 1, reach * 2).multiply(Mat4.lookAt(eye, c, up)).values;
|
|
8635
|
+
this.device.queue.writeBuffer(this.castShadowVPBuffer, 0, new Float32Array(vp));
|
|
8636
|
+
this.lightData.set(vp, 92);
|
|
8637
|
+
on = true;
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
8640
|
+
const amount = on ? Math.max(opts.amount, 0) : 0;
|
|
8641
|
+
if (this.lightData[91] !== amount || on) {
|
|
8642
|
+
this.lightData[91] = amount;
|
|
8643
|
+
this.device.queue.writeBuffer(this.lightUniformBuffer, 88 * 4, this.lightData, 88, 20);
|
|
8644
|
+
}
|
|
8645
|
+
return on;
|
|
8646
|
+
}
|
|
8647
|
+
/**
|
|
8648
|
+
* The world's DIFFUSE light, stated rather than fitted: nine RGB coefficients
|
|
8649
|
+
* (27 floats) in the form rzWorldAmbient evaluates — c0 + c1·y + c2·z + c3·x +
|
|
8650
|
+
* c4·xy + c5·yz + c6·(3z²−1) + c7·xz + c8·(x²−y²), in this engine's axes, the
|
|
8651
|
+
* value a white surface facing n takes. Null goes back to the SH fitted to
|
|
8652
|
+
* the world's picture or gradient.
|
|
8653
|
+
*
|
|
8654
|
+
* A game lights a room's surfaces from an ambient probe that is not its
|
|
8655
|
+
* reflection probe — X340's is a dim blue trilight while its reflections
|
|
8656
|
+
* carry every lamp the probe baked — and one picture cannot be both. With
|
|
8657
|
+
* this set, the picture answers reflections alone. World strength scales it
|
|
8658
|
+
* as it scales the fitted one.
|
|
8659
|
+
*/
|
|
8660
|
+
setWorldAmbient(sh) {
|
|
8661
|
+
if (sh && sh.length !== 27)
|
|
8662
|
+
throw new Error(`setWorldAmbient: ${sh.length} floats, not 27`);
|
|
8663
|
+
this.worldAmbientSH = sh ? Float32Array.from(sh) : null;
|
|
8664
|
+
if (this.device && this.lightUniformBuffer)
|
|
8665
|
+
this.writeWorld();
|
|
8666
|
+
}
|
|
8453
8667
|
setWorld(options) {
|
|
8454
8668
|
if (options.color)
|
|
8455
8669
|
this.world.color = options.color;
|
|
@@ -8680,6 +8894,8 @@ export class Engine {
|
|
|
8680
8894
|
this.canvas.removeEventListener("dblclick", this.handleCanvasDoubleClick);
|
|
8681
8895
|
this.canvas.removeEventListener("touchend", this.handleCanvasTouch);
|
|
8682
8896
|
}
|
|
8897
|
+
this.stageGradeTexture?.destroy();
|
|
8898
|
+
this.stageGradeTexture = null;
|
|
8683
8899
|
this.overlayDepthTexture?.destroy();
|
|
8684
8900
|
this.overlayDepthTexture = null;
|
|
8685
8901
|
this.overlayMsaaTexture?.destroy();
|
|
@@ -11110,6 +11326,18 @@ export class Engine {
|
|
|
11110
11326
|
this.forEachInstance((inst) => this.drawInstanceShadow(shadow, inst, ci));
|
|
11111
11327
|
return shadow.finish({ label: `shadow pass, cascade ${ci}` });
|
|
11112
11328
|
});
|
|
11329
|
+
// The cast alone, for the shadow it lays on a stage.
|
|
11330
|
+
const cast = this.device.createRenderBundleEncoder({
|
|
11331
|
+
label: "stage cast shadow pass",
|
|
11332
|
+
colorFormats: [],
|
|
11333
|
+
depthStencilFormat: Engine.SHADOW_DEPTH_FORMAT,
|
|
11334
|
+
});
|
|
11335
|
+
cast.setPipeline(this.shadowDepthPipeline);
|
|
11336
|
+
this.forEachInstance((inst) => {
|
|
11337
|
+
if (!inst.isStage && !inst.isPlane && !inst.isProp)
|
|
11338
|
+
this.drawInstanceShadow(cast, inst, SHADOW_CASCADES.length);
|
|
11339
|
+
});
|
|
11340
|
+
this.castShadowBundle = cast.finish({ label: "stage cast shadow pass" });
|
|
11113
11341
|
this.bundleRecords++;
|
|
11114
11342
|
}
|
|
11115
11343
|
/** The two query slots for a pass, or undefined where the device has no
|
|
@@ -11543,6 +11771,16 @@ export class Engine {
|
|
|
11543
11771
|
{ binding: 2, resource: this.materialSampler },
|
|
11544
11772
|
],
|
|
11545
11773
|
}));
|
|
11774
|
+
// …and one more past the cascades, for the cast's shadow on a stage.
|
|
11775
|
+
shadowBindGroups.push(this.device.createBindGroup({
|
|
11776
|
+
label: `${name}: shadow bind, stage cast shadow`,
|
|
11777
|
+
layout: this.shadowDepthPipeline.getBindGroupLayout(0),
|
|
11778
|
+
entries: [
|
|
11779
|
+
{ binding: 0, resource: { buffer: this.castShadowVPBuffer } },
|
|
11780
|
+
{ binding: 1, resource: { buffer: skinMatrixBuffer } },
|
|
11781
|
+
{ binding: 2, resource: this.materialSampler },
|
|
11782
|
+
],
|
|
11783
|
+
}));
|
|
11546
11784
|
const mainPerInstanceBindGroup = this.perInstanceBindGroup(name, skinMatrixBuffer, null);
|
|
11547
11785
|
const pickPerInstanceBindGroup = this.device.createBindGroup({
|
|
11548
11786
|
label: `${name}: pick per-instance bind group`,
|
|
@@ -13576,6 +13814,19 @@ export class Engine {
|
|
|
13576
13814
|
sp.end();
|
|
13577
13815
|
this.shadowCascadeCleared[ci] = !wanted;
|
|
13578
13816
|
}
|
|
13817
|
+
// The cast's shadow on a stage: drawn while a stage asks for it and has a
|
|
13818
|
+
// cast to shade it with; cleared once when it stops, then left alone.
|
|
13819
|
+
const castOn = this.updateCastShadowVP(stage);
|
|
13820
|
+
if (castOn || !this.castShadowCleared) {
|
|
13821
|
+
const cp = encoder.beginRenderPass({
|
|
13822
|
+
colorAttachments: [],
|
|
13823
|
+
depthStencilAttachment: { view: this.castShadowView, depthClearValue: 1.0, depthLoadOp: "clear", depthStoreOp: "store" },
|
|
13824
|
+
});
|
|
13825
|
+
if (castOn && this.castShadowBundle)
|
|
13826
|
+
cp.executeBundles([this.castShadowBundle]);
|
|
13827
|
+
cp.end();
|
|
13828
|
+
this.castShadowCleared = !castOn;
|
|
13829
|
+
}
|
|
13579
13830
|
this.shadowMapPopulated = hasModels;
|
|
13580
13831
|
}
|
|
13581
13832
|
// Before the particles and before the field pass: both may read the grid,
|
|
@@ -14652,27 +14903,41 @@ export class Engine {
|
|
|
14652
14903
|
* each for a reason that would change pixels: EYE front-culls and gates on a
|
|
14653
14904
|
* bone read, and pre-filled hair depth over the socket would depth-reject
|
|
14654
14905
|
* the eye before it could write the stencil the see-through-hair pass needs
|
|
14655
|
-
* — which is also why HAIR stays out entirely.
|
|
14656
|
-
*
|
|
14657
|
-
*
|
|
14658
|
-
*
|
|
14659
|
-
*
|
|
14906
|
+
* — which is also why HAIR stays out entirely. They all still BENEFIT:
|
|
14907
|
+
* their fragments early-z against the primed depth of whatever plain opaque
|
|
14908
|
+
* surface sits in front of them.
|
|
14909
|
+
*
|
|
14910
|
+
* HASHED alpha primes through the SOLID pipeline (cutoff 1.0). Its colour
|
|
14911
|
+
* pass discards by a position hash this pass does not run, but that threshold
|
|
14912
|
+
* is clamped to at most 1, so a texel at alpha exactly 1 survives it always —
|
|
14913
|
+
* those are the only texels the solid prime claims, and it can never punch
|
|
14914
|
+
* the cutout into the wrong ones. A stage's foliage and railings are mostly
|
|
14915
|
+
* such texels, and without the prime every room surface behind them shaded
|
|
14916
|
+
* its full lamp walk and was then painted over.
|
|
14660
14917
|
*/
|
|
14661
14918
|
drawOpaqueDepthPrepass(pass, inst, view) {
|
|
14662
|
-
let bound =
|
|
14919
|
+
let bound = null;
|
|
14663
14920
|
for (const draw of inst.drawCalls) {
|
|
14664
14921
|
if (draw.type !== "opaque")
|
|
14665
14922
|
continue;
|
|
14923
|
+
let pipeline = this.depthPrepassPipeline;
|
|
14666
14924
|
if (draw.groupId) {
|
|
14667
14925
|
const install = inst.styleGroups.get(draw.groupId);
|
|
14668
|
-
if (install &&
|
|
14926
|
+
if (install && install.renderClass !== "auto")
|
|
14927
|
+
continue;
|
|
14928
|
+
if (install?.alphaMode === "hashed")
|
|
14929
|
+
pipeline = this.solidPrepassPipeline;
|
|
14930
|
+
else if (install && install.alphaMode !== "opaque")
|
|
14669
14931
|
continue;
|
|
14670
14932
|
}
|
|
14671
|
-
if (
|
|
14672
|
-
pass.setPipeline(
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14933
|
+
if (bound !== pipeline) {
|
|
14934
|
+
pass.setPipeline(pipeline);
|
|
14935
|
+
// One layout for both prepass pipelines, so groups 0 and 1 carry over.
|
|
14936
|
+
if (!bound) {
|
|
14937
|
+
pass.setBindGroup(0, view.perFrame);
|
|
14938
|
+
pass.setBindGroup(1, inst.mainPerInstanceBindGroup);
|
|
14939
|
+
}
|
|
14940
|
+
bound = pipeline;
|
|
14676
14941
|
}
|
|
14677
14942
|
pass.setBindGroup(2, draw.bindGroup);
|
|
14678
14943
|
this.issueDraw(pass, draw, view.args);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/graph/compile.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAA0B,WAAW,EAAE,MAAM,UAAU,CAAA;AAI7F,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAExE,MAAM,MAAM,cAAc,GAAG;IAC3B;8EAC0E;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;6DACyD;IACzD,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9C;0EACsE;IACtE,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,wEAAwE;IACxE,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,yFAAyF;AACzF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IACjC,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,OAAO,CAAA;IACX,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB,CAAA;AAcD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,SAAS,EAAE,CAuBpE;AAID,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,GAAE,cAAmB,GAAG,UAAU,EAAE,CA4GzF;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,GAAE,cAAmB,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/graph/compile.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAA0B,WAAW,EAAE,MAAM,UAAU,CAAA;AAI7F,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAExE,MAAM,MAAM,cAAc,GAAG;IAC3B;8EAC0E;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;6DACyD;IACzD,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9C;0EACsE;IACtE,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,wEAAwE;IACxE,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,yFAAyF;AACzF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IACjC,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,OAAO,CAAA;IACX,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB,CAAA;AAcD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,SAAS,EAAE,CAuBpE;AAID,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,GAAE,cAAmB,GAAG,UAAU,EAAE,CA4GzF;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,GAAE,cAAmB,GAAG,aAAa,CAsKzF"}
|
package/dist/graph/compile.js
CHANGED
|
@@ -309,6 +309,9 @@ export function compileGraph(graph, opts = {}) {
|
|
|
309
309
|
if (opacity)
|
|
310
310
|
lines.push(` let final_opacity = saturate(${outputExpr(opacity, "float")}); // @node:${opacity.node}`);
|
|
311
311
|
const fsBody = lines.join("\n");
|
|
312
|
-
|
|
312
|
+
// A graph takes light when its body reads any: the four shading nodes all
|
|
313
|
+
// pass `sun, amb`, and a hand-written node reaching for the lamps names them.
|
|
314
|
+
const takesLight = /\bsun\b|\bamb\b|rzLight|rzLamp/.test(fsBody);
|
|
315
|
+
const wgsl = assembleModule(opts.renderClass ?? "auto", opts.alphaMode ?? "opaque", fsBody, usesStyle.current, !!opacity, opts.blend ?? "over", takesLight);
|
|
313
316
|
return { ok: true, wgsl, fsBody, slotMap, diagnostics, prunedNodes };
|
|
314
317
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/graph/registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEzD,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,KAAK,CAAA;IACX,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,4FAA4F;IAC5F,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAC9B,2FAA2F;IAC3F,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAA;IAC/C,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACtC,CAAA;AAYD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,GAAG,MAAM,CAoBhE;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,CAIpE;AAMD,wBAAgB,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAM1D;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpE;AAyCD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/graph/registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEzD,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,KAAK,CAAA;IACX,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,4FAA4F;IAC5F,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAC9B,2FAA2F;IAC3F,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAA;IAC/C,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACtC,CAAA;AAYD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,GAAG,MAAM,CAoBhE;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,CAIpE;AAMD,wBAAgB,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAM1D;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpE;AAyCD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAsuBlD,CAAA"}
|
package/dist/graph/registry.js
CHANGED
|
@@ -791,6 +791,14 @@ export const NODE_REGISTRY = {
|
|
|
791
791
|
/** Ours, not Blender's: caps firefly speculars from noise-bumped NDF
|
|
792
792
|
* aliasing, which EEVEE hides behind TAA and we have none. */
|
|
793
793
|
spec_clamp: F(1e30),
|
|
794
|
+
/** Ours: how roughness picks the reflection's blur. 0 is the engine's
|
|
795
|
+
* sqrt ramp; 1 is Unity's probe curve, p·(1.7−0.7p) mip-steps per
|
|
796
|
+
* eighth, stopping where a cube's last face texel does — what a stage
|
|
797
|
+
* ripped from a Unity game was authored against. See rzWorldSpecularLod. */
|
|
798
|
+
reflection_lod: F(0),
|
|
799
|
+
/** Ours: 1 shades the sun's and the lamps' highlights with URP's
|
|
800
|
+
* direct-light BRDF, as a stage from a Unity game was lit — see bsdf_urp. */
|
|
801
|
+
unity_direct: F(0),
|
|
794
802
|
},
|
|
795
803
|
outputs: { color: "color" },
|
|
796
804
|
emit: (a) => {
|
|
@@ -801,11 +809,15 @@ export const NODE_REGISTRY = {
|
|
|
801
809
|
const spec = foldSpecular(a.ior, a.specular_ior_level);
|
|
802
810
|
const bsdf = `eval_principled(PrincipledIn(${a.base_color}, ${a.metallic}, ` +
|
|
803
811
|
`${spec}, ${a.roughness}, ` +
|
|
804
|
-
`${a.spec_clamp}, ${a.sheen_weight}, ${a.sheen_tint}), ${a.normal}, l, v, sun, amb, shadow, input.worldPos)`;
|
|
812
|
+
`${a.spec_clamp}, ${a.sheen_weight}, ${a.sheen_tint}, ${a.reflection_lod}, ${a.unity_direct}), ${a.normal}, l, v, sun, amb, shadow, input.worldPos)`;
|
|
805
813
|
// v2 defaults Emission Strength to 0, which is the overwhelming case. Emit
|
|
806
814
|
// the term only when it can do something, so the common shader carries no
|
|
807
815
|
// dead add and the output stays readable.
|
|
808
|
-
|
|
816
|
+
const lit = a.emission_strength === "0.0" ? bsdf : `${bsdf} + ${a.emission_color} * ${a.emission_strength}`;
|
|
817
|
+
// Unity mode also ends as SimPipeline's surfaces end: saturate()d, so no
|
|
818
|
+
// lit or glowing surface passes 1.0 before the post — its bloom works on
|
|
819
|
+
// what is left under that. A literal, as every stage graph states it.
|
|
820
|
+
return a.unity_direct === "1.0" ? `min(${lit}, vec3f(1.0))` : lit;
|
|
809
821
|
},
|
|
810
822
|
},
|
|
811
823
|
};
|
package/dist/graph/slots.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ import type { AlphaMode, RenderClass, StyleBlend } from "./render-class";
|
|
|
4
4
|
* StyleUniforms, render-class/alpha-mode declarations, the composed fs() shell, and the
|
|
5
5
|
* graph's node body.
|
|
6
6
|
*/
|
|
7
|
-
export declare function assembleModule(renderClass: RenderClass, alphaMode: AlphaMode, fsBody: string, includeStyleUniforms: boolean, hasOpacity?: boolean, blend?: StyleBlend): string;
|
|
7
|
+
export declare function assembleModule(renderClass: RenderClass, alphaMode: AlphaMode, fsBody: string, includeStyleUniforms: boolean, hasOpacity?: boolean, blend?: StyleBlend, takesLight?: boolean): string;
|
|
8
8
|
//# sourceMappingURL=slots.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../../src/graph/slots.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../../src/graph/slots.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AA+MxE;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,oBAAoB,EAAE,OAAO,EAC7B,UAAU,UAAQ,EAClB,KAAK,GAAE,UAAmB,EAC1B,UAAU,UAAO,GAChB,MAAM,CAgBR"}
|
package/dist/graph/slots.js
CHANGED
|
@@ -132,13 +132,16 @@ ${gate}
|
|
|
132
132
|
let amb = rzWorldAmbient(n) + modelLight.fill.rgb;
|
|
133
133
|
let shadow = sampleShadow(input.worldPos, n);
|
|
134
134
|
let tex_color = tex_s.rgb;
|
|
135
|
+
// The normal the lamps' diffuse layer is lit by, for a principled walk to
|
|
136
|
+
// compute it on the way (rzLightsDiffuseOnce). n is final from here on.
|
|
137
|
+
_rzLampN = n;
|
|
135
138
|
|
|
136
139
|
`;
|
|
137
140
|
}
|
|
138
141
|
// Tail of fs(): consumes `final_color` + locals, writes FSOut. hashed forces output
|
|
139
142
|
// alpha to 1 (the discard already did the cutout); hair scales alpha for the over-eyes
|
|
140
143
|
// pass when IS_OVER_EYES is compiled true.
|
|
141
|
-
function epilogue(renderClass, alphaMode, hasOpacity, blend) {
|
|
144
|
+
function epilogue(renderClass, alphaMode, hasOpacity, blend, takesLight) {
|
|
142
145
|
// A graph that computes its own opacity wins, including over hashed: hashed
|
|
143
146
|
// writes 1 because its discard already decided the cutout, and a graph asking
|
|
144
147
|
// for a curve is asking for the opposite of a cutout. Additive writes 1 for a
|
|
@@ -164,7 +167,12 @@ function epilogue(renderClass, alphaMode, hasOpacity, blend) {
|
|
|
164
167
|
// so the fix has an address — when graphs gain an optional albedo output
|
|
165
168
|
// (material-track era), it lands here and every light is corrected at once,
|
|
166
169
|
// instead of a hunt through the epilogue's string templates.
|
|
167
|
-
|
|
170
|
+
//
|
|
171
|
+
// ONLY ON A GRAPH THAT TAKES LIGHT. An emission-only graph — Unlit, a stage's
|
|
172
|
+
// painted sheet — is Blender's Emission shader, which no lamp reaches; adding
|
|
173
|
+
// the lamps to it turned X340's floor shadow, a white-RGB picture at a soft
|
|
174
|
+
// alpha, into a glowing disc under the spot above it.
|
|
175
|
+
const LIT = takesLight ? ` + rzLightsDiffuseOnce(input.worldPos, n) * albedo` : "";
|
|
168
176
|
const ALBEDO = ` let albedo = tex_color;
|
|
169
177
|
`;
|
|
170
178
|
// The dissolve's burning edge, ADDED after the graph and after the lights —
|
|
@@ -172,19 +180,24 @@ function epilogue(renderClass, alphaMode, hasOpacity, blend) {
|
|
|
172
180
|
// on every fragment of every material that is not dissolving, which is what
|
|
173
181
|
// keeps this out of the way of a scene that never uses it.
|
|
174
182
|
const BURN = ` + RZ_BURN_COLOR * rz_burn`;
|
|
183
|
+
// The scene fog, last, as the game lays it over a lit surface; an
|
|
184
|
+
// emission-only graph takes none (the game's effect shaders have no fog).
|
|
185
|
+
const FOG = takesLight
|
|
186
|
+
? ` 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`
|
|
187
|
+
: "";
|
|
175
188
|
if (renderClass === "hair") {
|
|
176
189
|
return `${ALBEDO} var outAlpha = ${alphaBase};
|
|
177
190
|
if (IS_OVER_EYES) { outAlpha = ${alphaBase} * 0.25; }
|
|
178
191
|
|
|
179
192
|
var out: FSOut;
|
|
180
193
|
out.color = vec4f(final_color${LIT}${BURN}, outAlpha);
|
|
181
|
-
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
194
|
+
${FOG} out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
182
195
|
${ID_WRITE} return out;
|
|
183
196
|
`;
|
|
184
197
|
}
|
|
185
198
|
return `${ALBEDO} var out: FSOut;
|
|
186
199
|
out.color = vec4f(final_color${LIT}${BURN}, ${alphaBase});
|
|
187
|
-
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
200
|
+
${FOG} out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
188
201
|
${ID_WRITE} return out;
|
|
189
202
|
`;
|
|
190
203
|
}
|
|
@@ -193,7 +206,7 @@ ${ID_WRITE} return out;
|
|
|
193
206
|
* StyleUniforms, render-class/alpha-mode declarations, the composed fs() shell, and the
|
|
194
207
|
* graph's node body.
|
|
195
208
|
*/
|
|
196
|
-
export function assembleModule(renderClass, alphaMode, fsBody, includeStyleUniforms, hasOpacity = false, blend = "over") {
|
|
209
|
+
export function assembleModule(renderClass, alphaMode, fsBody, includeStyleUniforms, hasOpacity = false, blend = "over", takesLight = true) {
|
|
197
210
|
return (NODES_WGSL +
|
|
198
211
|
COMMON_MATERIAL_PRELUDE_WGSL +
|
|
199
212
|
// Exactly where it sat inside the prelude constant. It moved out because
|
|
@@ -205,6 +218,6 @@ export function assembleModule(renderClass, alphaMode, fsBody, includeStyleUnifo
|
|
|
205
218
|
prelude(renderClass, alphaMode, blend) +
|
|
206
219
|
fsBody +
|
|
207
220
|
"\n" +
|
|
208
|
-
epilogue(renderClass, alphaMode, hasOpacity, blend) +
|
|
221
|
+
epilogue(renderClass, alphaMode, hasOpacity, blend, takesLight) +
|
|
209
222
|
"}\n");
|
|
210
223
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Engine, DEFAULT_BLOOM_OPTIONS, DEFAULT_DEPTH_OF_FIELD_OPTIONS, DEFAULT_VIEW_TRANSFORM, DEFAULT_COLOR_GRADING, type ColorGradingOptions, type EngineStats, type EngineOptions, type BloomOptions, type DepthOfFieldOptions, type ViewTransformOptions, type LoadModelFromFilesOptions, type MaterialPreset, type MaterialPresetMap, type ModelTransform, type SceneLight, type ModelAttachment, type GizmoDragEvent, type GizmoDragCallback, type GizmoDragKind, type DissolveCycle, type EffectParamValue, type EffectResult, type CullDiagnostics, type MidiNote, } from "./engine";
|
|
1
|
+
export { Engine, DEFAULT_BLOOM_OPTIONS, DEFAULT_DEPTH_OF_FIELD_OPTIONS, DEFAULT_VIEW_TRANSFORM, DEFAULT_COLOR_GRADING, type ColorGradingOptions, type StageGradeLut, type SceneFog, type StageCastShadow, type SceneFogLayer, type EngineStats, type EngineOptions, type BloomOptions, type DepthOfFieldOptions, type ViewTransformOptions, type LoadModelFromFilesOptions, type MaterialPreset, type MaterialPresetMap, type ModelTransform, type SceneLight, type ModelAttachment, type GizmoDragEvent, type GizmoDragCallback, type GizmoDragKind, type DissolveCycle, type EffectParamValue, type EffectResult, type CullDiagnostics, type MidiNote, } from "./engine";
|
|
2
2
|
export { parsePmxFolderInput, pmxFileAtRelativePath, type PmxFolderInputResult } from "./folder-upload";
|
|
3
3
|
export { parseMidi } from "./midi-loader";
|
|
4
4
|
export { parseHDR, type HdrImage } from "./hdr";
|