reze-engine 0.11.3 → 0.12.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/README.md +59 -39
- package/dist/bezier-interpolate.d.ts +15 -0
- package/dist/bezier-interpolate.d.ts.map +1 -0
- package/dist/bezier-interpolate.js +40 -0
- package/dist/engine.d.ts +11 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +101 -1
- package/dist/engine_ts.d.ts +143 -0
- package/dist/engine_ts.d.ts.map +1 -0
- package/dist/engine_ts.js +1575 -0
- package/dist/ik.d.ts +32 -0
- package/dist/ik.d.ts.map +1 -0
- package/dist/ik.js +337 -0
- package/dist/player.d.ts +64 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +220 -0
- package/dist/pool-scene.d.ts +52 -0
- package/dist/pool-scene.d.ts.map +1 -0
- package/dist/pool-scene.js +1122 -0
- package/dist/pool.d.ts +38 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +422 -0
- package/dist/rzm-converter.d.ts +12 -0
- package/dist/rzm-converter.d.ts.map +1 -0
- package/dist/rzm-converter.js +40 -0
- package/dist/rzm-loader.d.ts +24 -0
- package/dist/rzm-loader.d.ts.map +1 -0
- package/dist/rzm-loader.js +488 -0
- package/dist/rzm-writer.d.ts +27 -0
- package/dist/rzm-writer.d.ts.map +1 -0
- package/dist/rzm-writer.js +701 -0
- package/dist/shaders/body.d.ts +1 -1
- package/dist/shaders/body.d.ts.map +1 -1
- package/dist/shaders/body.js +28 -7
- package/dist/shaders/cloth_rough.d.ts +1 -1
- package/dist/shaders/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/cloth_rough.js +16 -4
- package/dist/shaders/cloth_smooth.d.ts +1 -1
- package/dist/shaders/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/cloth_smooth.js +17 -5
- package/dist/shaders/default.d.ts +1 -1
- package/dist/shaders/default.d.ts.map +1 -1
- package/dist/shaders/eye.d.ts +1 -1
- package/dist/shaders/eye.d.ts.map +1 -1
- package/dist/shaders/face.d.ts +1 -1
- package/dist/shaders/face.d.ts.map +1 -1
- package/dist/shaders/face.js +57 -21
- package/dist/shaders/hair.d.ts +1 -1
- package/dist/shaders/hair.d.ts.map +1 -1
- package/dist/shaders/hair.js +27 -7
- package/dist/shaders/materials/hair.d.ts +1 -1
- package/dist/shaders/materials/hair.d.ts.map +1 -1
- package/dist/shaders/materials/hair.js +10 -1
- package/dist/shaders/metal.d.ts +1 -1
- package/dist/shaders/metal.d.ts.map +1 -1
- package/dist/shaders/metal.js +17 -4
- package/dist/shaders/nodes.d.ts +1 -1
- package/dist/shaders/nodes.d.ts.map +1 -1
- package/dist/shaders/nodes.js +9 -0
- package/dist/shaders/stockings.d.ts +1 -1
- package/dist/shaders/stockings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/engine.ts +104 -1
- package/src/shaders/materials/hair.ts +10 -1
package/src/engine.ts
CHANGED
|
@@ -232,6 +232,7 @@ export class Engine {
|
|
|
232
232
|
private metalPipeline!: GPURenderPipeline
|
|
233
233
|
private bodyPipeline!: GPURenderPipeline
|
|
234
234
|
private eyePipeline!: GPURenderPipeline
|
|
235
|
+
private hairOverEyesPipeline!: GPURenderPipeline
|
|
235
236
|
private stockingsPipeline!: GPURenderPipeline
|
|
236
237
|
private groundShadowPipeline!: GPURenderPipeline
|
|
237
238
|
private groundShadowBindGroupLayout!: GPUBindGroupLayout
|
|
@@ -247,6 +248,9 @@ export class Engine {
|
|
|
247
248
|
private hdrResolveTexture!: GPUTexture
|
|
248
249
|
private static readonly MULTISAMPLE_COUNT = 4
|
|
249
250
|
private static readonly HDR_FORMAT: GPUTextureFormat = "rgba16float"
|
|
251
|
+
/** Stencil value stamped by eye draws so hair can stencil-test against it and
|
|
252
|
+
* alpha-blend a second pass over eye silhouette pixels (see-through-hair effect). */
|
|
253
|
+
private static readonly STENCIL_EYE_VALUE = 1
|
|
250
254
|
/** Single-channel mask written alongside HDR color — 1 = model geometry (contributes
|
|
251
255
|
* to bloom), 0 = ground (never blooms). Sampled by the bloom blit pass to gate the
|
|
252
256
|
* prefilter so ground brightness can't halo the scene. */
|
|
@@ -822,6 +826,9 @@ export class Engine {
|
|
|
822
826
|
},
|
|
823
827
|
})
|
|
824
828
|
|
|
829
|
+
// Hair opaque: stencil != EYE_VALUE so fragments on top of eyes are skipped entirely —
|
|
830
|
+
// depth and color stay as the eye wrote them; the follow-up hairOverEyesPipeline then
|
|
831
|
+
// draws those skipped fragments alpha-blended so the eye reads through the hair.
|
|
825
832
|
this.hairPipeline = this.createRenderPipeline({
|
|
826
833
|
label: "hair NPR pipeline",
|
|
827
834
|
layout: mainPipelineLayout,
|
|
@@ -833,9 +840,39 @@ export class Engine {
|
|
|
833
840
|
format: "depth24plus-stencil8",
|
|
834
841
|
depthWriteEnabled: true,
|
|
835
842
|
depthCompare: "less-equal",
|
|
843
|
+
stencilFront: { compare: "not-equal", failOp: "keep", depthFailOp: "keep", passOp: "keep" },
|
|
844
|
+
stencilBack: { compare: "not-equal", failOp: "keep", depthFailOp: "keep", passOp: "keep" },
|
|
845
|
+
stencilReadMask: 0xff,
|
|
846
|
+
stencilWriteMask: 0,
|
|
836
847
|
},
|
|
837
848
|
})
|
|
838
849
|
|
|
850
|
+
// Hair-over-eyes: same shader with IS_OVER_EYES=true so alpha is halved at compile time.
|
|
851
|
+
// Only fragments where eye stencil == EYE_VALUE pass; depth test still culls fragments
|
|
852
|
+
// that are further from camera than the eye, so hair behind the eye never shows through.
|
|
853
|
+
// depthWriteEnabled=false keeps the eye's depth authoritative for everything drawn after.
|
|
854
|
+
this.hairOverEyesPipeline = this.device.createRenderPipeline({
|
|
855
|
+
label: "hair over eyes pipeline",
|
|
856
|
+
layout: mainPipelineLayout,
|
|
857
|
+
vertex: { module: hairShaderModule, buffers: fullVertexBuffers },
|
|
858
|
+
fragment: {
|
|
859
|
+
module: hairShaderModule,
|
|
860
|
+
constants: { IS_OVER_EYES: 1 },
|
|
861
|
+
targets: sceneTargets,
|
|
862
|
+
},
|
|
863
|
+
primitive: { cullMode: "none" },
|
|
864
|
+
depthStencil: {
|
|
865
|
+
format: "depth24plus-stencil8",
|
|
866
|
+
depthWriteEnabled: false,
|
|
867
|
+
depthCompare: "less-equal",
|
|
868
|
+
stencilFront: { compare: "equal", failOp: "keep", depthFailOp: "keep", passOp: "keep" },
|
|
869
|
+
stencilBack: { compare: "equal", failOp: "keep", depthFailOp: "keep", passOp: "keep" },
|
|
870
|
+
stencilReadMask: 0xff,
|
|
871
|
+
stencilWriteMask: 0,
|
|
872
|
+
},
|
|
873
|
+
multisample: { count: Engine.MULTISAMPLE_COUNT },
|
|
874
|
+
})
|
|
875
|
+
|
|
839
876
|
this.clothSmoothPipeline = this.createRenderPipeline({
|
|
840
877
|
label: "cloth smooth NPR pipeline",
|
|
841
878
|
layout: mainPipelineLayout,
|
|
@@ -892,17 +929,30 @@ export class Engine {
|
|
|
892
929
|
},
|
|
893
930
|
})
|
|
894
931
|
|
|
932
|
+
// Eye: stamps stencil = EYE_VALUE on every fragment it writes. Later hair passes read
|
|
933
|
+
// this stamp to split into "draw normally (not over eye)" vs "draw alpha-blended".
|
|
934
|
+
// cullMode="front" + small negative depthBias is the MMD post-alpha-eye trick: only the
|
|
935
|
+
// back half of the eye sphere renders, it passes depth against the face (via bias) when
|
|
936
|
+
// viewed from the front, and it gets culled when viewed from behind — so eye fragments
|
|
937
|
+
// can't leak through the back of the head without needing a per-model skull occluder.
|
|
895
938
|
this.eyePipeline = this.createRenderPipeline({
|
|
896
939
|
label: "eye pipeline",
|
|
897
940
|
layout: mainPipelineLayout,
|
|
898
941
|
shaderModule: eyeShaderModule,
|
|
899
942
|
vertexBuffers: fullVertexBuffers,
|
|
900
943
|
fragmentTargets: sceneTargets,
|
|
901
|
-
cullMode: "
|
|
944
|
+
cullMode: "front",
|
|
902
945
|
depthStencil: {
|
|
903
946
|
format: "depth24plus-stencil8",
|
|
904
947
|
depthWriteEnabled: true,
|
|
905
948
|
depthCompare: "less-equal",
|
|
949
|
+
depthBias: -0.00005,
|
|
950
|
+
depthBiasSlopeScale: 0.0,
|
|
951
|
+
depthBiasClamp: 0.0,
|
|
952
|
+
stencilFront: { compare: "always", failOp: "keep", depthFailOp: "keep", passOp: "replace" },
|
|
953
|
+
stencilBack: { compare: "always", failOp: "keep", depthFailOp: "keep", passOp: "replace" },
|
|
954
|
+
stencilReadMask: 0xff,
|
|
955
|
+
stencilWriteMask: 0xff,
|
|
906
956
|
},
|
|
907
957
|
})
|
|
908
958
|
|
|
@@ -1052,6 +1102,13 @@ export class Engine {
|
|
|
1052
1102
|
// Don’t write outline into depth buffer — stops z-fighting / black cracks vs body (MMD-style; body depth stays authoritative)
|
|
1053
1103
|
depthWriteEnabled: false,
|
|
1054
1104
|
depthCompare: "less-equal",
|
|
1105
|
+
// Skip fragments where the eye stamped stencil=EYE_VALUE. Those pixels are owned by
|
|
1106
|
+
// the see-through-hair blend (hair-over-eyes), so letting the outline's near-black
|
|
1107
|
+
// edge color overwrite them would re-introduce the dark almond we just killed.
|
|
1108
|
+
stencilFront: { compare: "not-equal", failOp: "keep", depthFailOp: "keep", passOp: "keep" },
|
|
1109
|
+
stencilBack: { compare: "not-equal", failOp: "keep", depthFailOp: "keep", passOp: "keep" },
|
|
1110
|
+
stencilReadMask: 0xff,
|
|
1111
|
+
stencilWriteMask: 0,
|
|
1055
1112
|
},
|
|
1056
1113
|
})
|
|
1057
1114
|
|
|
@@ -2223,6 +2280,26 @@ export class Engine {
|
|
|
2223
2280
|
currentIndexOffset += indexCount
|
|
2224
2281
|
}
|
|
2225
2282
|
|
|
2283
|
+
// Sort so the opaque bucket is emitted in the order the stencil-based
|
|
2284
|
+
// see-through-hair effect requires: {non-hair, non-eye} → {eye} → {hair}.
|
|
2285
|
+
// Eye writes stencil=EYE_VALUE; hair's pipeline stencil-tests "not equal" so
|
|
2286
|
+
// it skips eye pixels; a follow-up hairOverEyes pass (see renderOneModel)
|
|
2287
|
+
// re-fills those skipped pixels alpha-blended. Array.sort is stable in
|
|
2288
|
+
// ES2019+, so within a bucket the PMX material order is preserved.
|
|
2289
|
+
const typeOrder: Record<DrawCallType, number> = {
|
|
2290
|
+
opaque: 0,
|
|
2291
|
+
"opaque-outline": 1,
|
|
2292
|
+
transparent: 2,
|
|
2293
|
+
"transparent-outline": 3,
|
|
2294
|
+
ground: 4,
|
|
2295
|
+
}
|
|
2296
|
+
const presetRank = (p: MaterialPreset): number => (p === "hair" ? 2 : p === "eye" ? 1 : 0)
|
|
2297
|
+
inst.drawCalls.sort((a, b) => {
|
|
2298
|
+
const ta = typeOrder[a.type] - typeOrder[b.type]
|
|
2299
|
+
if (ta !== 0) return ta
|
|
2300
|
+
return presetRank(a.preset) - presetRank(b.preset)
|
|
2301
|
+
})
|
|
2302
|
+
|
|
2226
2303
|
for (const d of inst.drawCalls) {
|
|
2227
2304
|
if (d.type === "opaque") inst.shadowDrawCalls.push(d)
|
|
2228
2305
|
}
|
|
@@ -2675,12 +2752,38 @@ export class Engine {
|
|
|
2675
2752
|
pass.setVertexBuffer(2, inst.weightsBuffer)
|
|
2676
2753
|
pass.setIndexBuffer(inst.indexBuffer, "uint32")
|
|
2677
2754
|
|
|
2755
|
+
// Single stencil-reference set covers eye (write), hair (read not-equal),
|
|
2756
|
+
// and hairOverEyes (read equal). Non-stencil pipelines ignore the value.
|
|
2757
|
+
pass.setStencilReference(Engine.STENCIL_EYE_VALUE)
|
|
2758
|
+
|
|
2678
2759
|
this.drawMaterials(pass, inst, "opaque")
|
|
2679
2760
|
this.drawOutlines(pass, inst, "opaque-outline")
|
|
2761
|
+
this.drawHairOverEyes(pass, inst)
|
|
2680
2762
|
this.drawMaterials(pass, inst, "transparent")
|
|
2681
2763
|
this.drawOutlines(pass, inst, "transparent-outline")
|
|
2682
2764
|
}
|
|
2683
2765
|
|
|
2766
|
+
/**
|
|
2767
|
+
* Second hair pass for the see-through-hair effect. Re-draws every hair opaque
|
|
2768
|
+
* draw using `hairOverEyesPipeline` — which stencil-matches `EYE_VALUE` and runs
|
|
2769
|
+
* the hair shader with `IS_OVER_EYES=true` so alpha is halved. depthWriteEnabled
|
|
2770
|
+
* is off, so the eye's depth stays authoritative for anything drawn after.
|
|
2771
|
+
*/
|
|
2772
|
+
private drawHairOverEyes(pass: GPURenderPassEncoder, inst: ModelInstance): void {
|
|
2773
|
+
let bound = false
|
|
2774
|
+
for (const draw of inst.drawCalls) {
|
|
2775
|
+
if (draw.type !== "opaque" || draw.preset !== "hair" || !this.shouldRenderDrawCall(inst, draw)) continue
|
|
2776
|
+
if (!bound) {
|
|
2777
|
+
pass.setPipeline(this.hairOverEyesPipeline)
|
|
2778
|
+
pass.setBindGroup(0, this.perFrameBindGroup)
|
|
2779
|
+
pass.setBindGroup(1, inst.mainPerInstanceBindGroup)
|
|
2780
|
+
bound = true
|
|
2781
|
+
}
|
|
2782
|
+
pass.setBindGroup(2, draw.bindGroup)
|
|
2783
|
+
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2684
2787
|
private updateCameraUniforms() {
|
|
2685
2788
|
const viewMatrix = this.camera.getViewMatrix()
|
|
2686
2789
|
const projectionMatrix = this.camera.getProjectionMatrix()
|
|
@@ -7,6 +7,12 @@ export const HAIR_SHADER_WGSL = /* wgsl */ `
|
|
|
7
7
|
|
|
8
8
|
${NODES_WGSL}
|
|
9
9
|
|
|
10
|
+
// Pipeline-override: the engine compiles two variants — the normal opaque hair pipeline
|
|
11
|
+
// (IS_OVER_EYES=false) and a second pipeline that re-draws hair fragments stencil-matched
|
|
12
|
+
// against the eye stamp with 50% alpha so eyes read through the hair silhouette. Resolved
|
|
13
|
+
// at pipeline-compile time; the dead branch is dropped by the shader compiler.
|
|
14
|
+
override IS_OVER_EYES: bool = false;
|
|
15
|
+
|
|
10
16
|
struct CameraUniforms {
|
|
11
17
|
view: mat4x4f,
|
|
12
18
|
projection: mat4x4f,
|
|
@@ -169,8 +175,11 @@ struct FSOut {
|
|
|
169
175
|
|
|
170
176
|
let final_color = mix(add_shader, principled, 0.2);
|
|
171
177
|
|
|
178
|
+
var outAlpha = alpha;
|
|
179
|
+
if (IS_OVER_EYES) { outAlpha = alpha * 0.5; }
|
|
180
|
+
|
|
172
181
|
var out: FSOut;
|
|
173
|
-
out.color = vec4f(final_color,
|
|
182
|
+
out.color = vec4f(final_color, outAlpha);
|
|
174
183
|
out.mask = 1.0;
|
|
175
184
|
return out;
|
|
176
185
|
}
|