reze-engine 0.16.2 → 0.17.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/engine.d.ts +3 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +165 -26
- package/dist/model.d.ts +5 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +14 -1
- package/dist/physics/body.d.ts +4 -1
- package/dist/physics/body.d.ts.map +1 -1
- package/dist/physics/body.js +86 -20
- package/dist/physics/constraint.d.ts +15 -2
- package/dist/physics/constraint.d.ts.map +1 -1
- package/dist/physics/constraint.js +49 -2
- package/dist/physics/physics.d.ts +3 -0
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +69 -1
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +289 -113
- package/dist/physics/types.d.ts +2 -1
- package/dist/physics/types.d.ts.map +1 -1
- package/dist/physics/types.js +5 -0
- package/dist/physics/world.d.ts +3 -0
- package/dist/physics/world.d.ts.map +1 -1
- package/dist/physics/world.js +19 -3
- package/dist/pmx-loader.d.ts +2 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +38 -20
- package/dist/shaders/materials/body.d.ts +1 -1
- package/dist/shaders/materials/body.d.ts.map +1 -1
- package/dist/shaders/materials/body.js +90 -85
- package/dist/shaders/materials/cloth_rough.d.ts +1 -1
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_rough.js +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_smooth.js +2 -2
- package/dist/shaders/materials/common.d.ts +1 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +137 -112
- package/dist/shaders/materials/default.d.ts +1 -1
- package/dist/shaders/materials/default.d.ts.map +1 -1
- package/dist/shaders/materials/default.js +37 -32
- package/dist/shaders/materials/eye.d.ts +1 -1
- package/dist/shaders/materials/eye.d.ts.map +1 -1
- package/dist/shaders/materials/eye.js +54 -35
- package/dist/shaders/materials/face.d.ts +1 -1
- package/dist/shaders/materials/face.d.ts.map +1 -1
- package/dist/shaders/materials/face.js +90 -85
- 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 +85 -75
- package/dist/shaders/materials/metal.d.ts +1 -1
- package/dist/shaders/materials/metal.d.ts.map +1 -1
- package/dist/shaders/materials/metal.js +1 -1
- 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/stockings.d.ts +1 -1
- package/dist/shaders/materials/stockings.d.ts.map +1 -1
- package/dist/shaders/materials/stockings.js +1 -1
- package/package.json +1 -1
- package/src/engine.ts +4198 -4030
- package/src/model.ts +21 -1
- package/src/physics/body.ts +87 -24
- package/src/physics/constraint.ts +80 -6
- package/src/physics/physics.ts +68 -1
- package/src/physics/solver.ts +915 -744
- package/src/physics/types.ts +10 -2
- package/src/physics/world.ts +20 -3
- package/src/pmx-loader.ts +39 -20
- package/src/shaders/materials/body.ts +97 -92
- package/src/shaders/materials/cloth_rough.ts +1 -1
- package/src/shaders/materials/cloth_smooth.ts +2 -2
- package/src/shaders/materials/common.ts +204 -179
- package/src/shaders/materials/default.ts +45 -40
- package/src/shaders/materials/eye.ts +62 -43
- package/src/shaders/materials/face.ts +97 -92
- package/src/shaders/materials/hair.ts +92 -82
- package/src/shaders/materials/metal.ts +1 -1
- package/src/shaders/materials/mmd_classic.ts +68 -0
- package/src/shaders/materials/stockings.ts +1 -1
package/src/physics/types.ts
CHANGED
|
@@ -6,10 +6,15 @@ export enum RigidbodyShape {
|
|
|
6
6
|
Capsule = 2,
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// PMX physics mode: 0 = follow bone, 1 = physics, 2 = physics + bone-position
|
|
10
|
+
// alignment. Mode 2 is DYNAMIC — the loader maps it to Dynamic with
|
|
11
|
+
// `aligned: true` (bone gets rotation only, body position re-pins to the
|
|
12
|
+
// animated bone each frame). Mapping the raw byte 1:1 onto this enum froze
|
|
13
|
+
// mode-2 bodies (most modern cloth rigs, and every 胸_回転 breast body).
|
|
9
14
|
export enum RigidbodyType {
|
|
10
|
-
Static = 0,
|
|
15
|
+
Static = 0, // follows bone (anchor)
|
|
11
16
|
Dynamic = 1,
|
|
12
|
-
Kinematic = 2,
|
|
17
|
+
Kinematic = 2, // follows bone (legacy alias; loader no longer emits it)
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
export interface Rigidbody {
|
|
@@ -28,6 +33,9 @@ export interface Rigidbody {
|
|
|
28
33
|
restitution: number
|
|
29
34
|
friction: number
|
|
30
35
|
type: RigidbodyType
|
|
36
|
+
// PMX mode 2: dynamic body whose bone takes rotation only; the body's
|
|
37
|
+
// position re-pins to the animated bone each frame.
|
|
38
|
+
aligned?: boolean
|
|
31
39
|
bodyOffsetMatrixInverse: Mat4 // Inverse of body offset matrix, used to sync rigidbody to bone
|
|
32
40
|
bodyOffsetMatrix?: Mat4 // Cached non-inverse for performance (computed once during initialization)
|
|
33
41
|
}
|
package/src/physics/world.ts
CHANGED
|
@@ -14,6 +14,12 @@ export class World {
|
|
|
14
14
|
readonly gravity: Vec3
|
|
15
15
|
solverIterations = 10
|
|
16
16
|
|
|
17
|
+
// Per-body damping factors pow(1−damping, dt), cached because damping and
|
|
18
|
+
// the fixed dt never change — two Math.pow per body per substep otherwise.
|
|
19
|
+
private dampCacheDt = -1
|
|
20
|
+
private linDampFactor: Float32Array | null = null
|
|
21
|
+
private angDampFactor: Float32Array | null = null
|
|
22
|
+
|
|
17
23
|
constructor(gravity: Vec3) {
|
|
18
24
|
this.gravity = new Vec3(gravity.x, gravity.y, gravity.z)
|
|
19
25
|
}
|
|
@@ -43,15 +49,26 @@ export class World {
|
|
|
43
49
|
|
|
44
50
|
// 1. Predict — gravity + damping. The pow form (vs the linear
|
|
45
51
|
// 1−damping·dt approximation) stays stable at high PMX damping
|
|
46
|
-
// values like 0.99.
|
|
52
|
+
// values like 0.99. Factors are cached (damping and dt are constant).
|
|
53
|
+
if (this.dampCacheDt !== dt || !this.linDampFactor || this.linDampFactor.length !== N) {
|
|
54
|
+
this.dampCacheDt = dt
|
|
55
|
+
this.linDampFactor = new Float32Array(N)
|
|
56
|
+
this.angDampFactor = new Float32Array(N)
|
|
57
|
+
for (let i = 0; i < N; i++) {
|
|
58
|
+
this.linDampFactor[i] = Math.pow(Math.max(0, 1 - ldamp[i]), dt)
|
|
59
|
+
this.angDampFactor[i] = Math.pow(Math.max(0, 1 - adamp[i]), dt)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const linDamp = this.linDampFactor
|
|
63
|
+
const angDamp = this.angDampFactor!
|
|
47
64
|
for (let i = 0; i < N; i++) {
|
|
48
65
|
if (types[i] !== RigidbodyType.Dynamic || invMass[i] <= 0) continue
|
|
49
66
|
const i3 = i * 3
|
|
50
67
|
lv[i3 + 0] += gx * dt
|
|
51
68
|
lv[i3 + 1] += gy * dt
|
|
52
69
|
lv[i3 + 2] += gz * dt
|
|
53
|
-
const ld =
|
|
54
|
-
const ad =
|
|
70
|
+
const ld = linDamp[i]
|
|
71
|
+
const ad = angDamp[i]
|
|
55
72
|
lv[i3 + 0] *= ld; lv[i3 + 1] *= ld; lv[i3 + 2] *= ld
|
|
56
73
|
av[i3 + 0] *= ad; av[i3 + 1] *= ad; av[i3 + 2] *= ad
|
|
57
74
|
}
|
package/src/pmx-loader.ts
CHANGED
|
@@ -37,11 +37,22 @@ export class PmxLoader {
|
|
|
37
37
|
private vertexCount: number = 0
|
|
38
38
|
private rigidbodies: Rigidbody[] = []
|
|
39
39
|
private joints: Joint[] = []
|
|
40
|
+
private warnings: string[] = []
|
|
40
41
|
|
|
41
42
|
private constructor(buffer: ArrayBuffer) {
|
|
42
43
|
this.view = new DataView(buffer)
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Parse problems are non-fatal (the mesh may still render), but they must
|
|
47
|
+
// be queryable afterwards — a rig whose rigidbody section failed to parse
|
|
48
|
+
// silently loses physics, and console.warn alone drowns in browser log
|
|
49
|
+
// limits. Collected warnings ship on the Model (getLoadWarnings()).
|
|
50
|
+
private warn(message: string, error?: unknown): void {
|
|
51
|
+
const full = error instanceof Error ? `${message}: ${error.message}` : message
|
|
52
|
+
this.warnings.push(full)
|
|
53
|
+
console.warn(message, error ?? "")
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
static async load(url: string): Promise<Model> {
|
|
46
57
|
return PmxLoader.loadFromReader(createFetchAssetReader(), url)
|
|
47
58
|
}
|
|
@@ -82,7 +93,7 @@ export class PmxLoader {
|
|
|
82
93
|
const version = this.getFloat32()
|
|
83
94
|
if (version < 2.0 || version > 2.2) {
|
|
84
95
|
// Continue, but warn for unexpected version
|
|
85
|
-
|
|
96
|
+
this.warn(`PMX version ${version} may not be fully supported`)
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
// PMX: globals count (uint8) followed by that many bytes describing encoding and index sizes
|
|
@@ -297,6 +308,7 @@ export class PmxLoader {
|
|
|
297
308
|
sphereTextureIndex,
|
|
298
309
|
sphereMode: sphereTextureMode,
|
|
299
310
|
toonTextureIndex,
|
|
311
|
+
sharedToon: isSharedToonTexture,
|
|
300
312
|
edgeFlag: flag,
|
|
301
313
|
edgeColor,
|
|
302
314
|
edgeSize,
|
|
@@ -472,7 +484,7 @@ export class PmxLoader {
|
|
|
472
484
|
}
|
|
473
485
|
this.bones = bones
|
|
474
486
|
} catch (e) {
|
|
475
|
-
|
|
487
|
+
this.warn("Error parsing bones:", e)
|
|
476
488
|
this.bones = []
|
|
477
489
|
}
|
|
478
490
|
}
|
|
@@ -487,7 +499,7 @@ export class PmxLoader {
|
|
|
487
499
|
const count = this.getInt32()
|
|
488
500
|
if (count < 0 || count > 100000) {
|
|
489
501
|
// Suspicious count, likely corrupted
|
|
490
|
-
|
|
502
|
+
this.warn(`Suspicious morph count: ${count}`)
|
|
491
503
|
this.morphs = []
|
|
492
504
|
return
|
|
493
505
|
}
|
|
@@ -623,11 +635,11 @@ export class PmxLoader {
|
|
|
623
635
|
this.morphs.push(morph)
|
|
624
636
|
} catch (e) {
|
|
625
637
|
// If we fail to read a morph, skip it
|
|
626
|
-
|
|
638
|
+
this.warn(`Error reading morph ${i}:`, e)
|
|
627
639
|
}
|
|
628
640
|
}
|
|
629
641
|
} catch (e) {
|
|
630
|
-
|
|
642
|
+
this.warn("Error parsing morphs:", e)
|
|
631
643
|
this.morphs = []
|
|
632
644
|
}
|
|
633
645
|
}
|
|
@@ -669,13 +681,13 @@ export class PmxLoader {
|
|
|
669
681
|
}
|
|
670
682
|
} catch (e) {
|
|
671
683
|
// If we fail to read a frame, stop skipping
|
|
672
|
-
|
|
684
|
+
this.warn(`Error reading display frame ${i}:`, e)
|
|
673
685
|
return false
|
|
674
686
|
}
|
|
675
687
|
}
|
|
676
688
|
return true
|
|
677
689
|
} catch (e) {
|
|
678
|
-
|
|
690
|
+
this.warn("Error skipping display frames:", e)
|
|
679
691
|
return false
|
|
680
692
|
}
|
|
681
693
|
}
|
|
@@ -685,7 +697,7 @@ export class PmxLoader {
|
|
|
685
697
|
// Note: morphs and display frames are already skipped in parse() before calling this
|
|
686
698
|
// Check bounds before reading rigidbody count
|
|
687
699
|
if (this.offset + 4 > this.view.buffer.byteLength) {
|
|
688
|
-
|
|
700
|
+
this.warn("Not enough bytes for rigidbody count")
|
|
689
701
|
this.rigidbodies = []
|
|
690
702
|
return
|
|
691
703
|
}
|
|
@@ -693,7 +705,7 @@ export class PmxLoader {
|
|
|
693
705
|
const count = this.getInt32()
|
|
694
706
|
if (count < 0 || count > 10000) {
|
|
695
707
|
// Suspicious count
|
|
696
|
-
|
|
708
|
+
this.warn(`Suspicious rigidbody count: ${count}`)
|
|
697
709
|
this.rigidbodies = []
|
|
698
710
|
return
|
|
699
711
|
}
|
|
@@ -704,7 +716,7 @@ export class PmxLoader {
|
|
|
704
716
|
try {
|
|
705
717
|
// Check bounds before reading each rigidbody
|
|
706
718
|
if (this.offset >= this.view.buffer.byteLength) {
|
|
707
|
-
|
|
719
|
+
this.warn(`Reached end of buffer while reading rigidbody ${i} of ${count}`)
|
|
708
720
|
break
|
|
709
721
|
}
|
|
710
722
|
|
|
@@ -737,7 +749,12 @@ export class PmxLoader {
|
|
|
737
749
|
const angularDamping = this.getFloat32()
|
|
738
750
|
const restitution = this.getFloat32()
|
|
739
751
|
const friction = this.getFloat32()
|
|
740
|
-
|
|
752
|
+
// PMX physics mode: 0 = follow bone, 1 = physics,
|
|
753
|
+
// 2 = physics + bone-position alignment. Mode 2 is dynamic — it
|
|
754
|
+
// must NOT be cast onto RigidbodyType (2 = Kinematic there, which
|
|
755
|
+
// freezes the body to its bone and kills physics for most modern
|
|
756
|
+
// cloth rigs and every 胸_回転 breast body).
|
|
757
|
+
const mode = this.getUint8()
|
|
741
758
|
|
|
742
759
|
this.rigidbodies.push({
|
|
743
760
|
name,
|
|
@@ -754,17 +771,18 @@ export class PmxLoader {
|
|
|
754
771
|
angularDamping,
|
|
755
772
|
restitution,
|
|
756
773
|
friction,
|
|
757
|
-
type:
|
|
774
|
+
type: mode === 0 ? RigidbodyType.Static : RigidbodyType.Dynamic,
|
|
775
|
+
aligned: mode === 2,
|
|
758
776
|
bodyOffsetMatrixInverse: Mat4.identity(),
|
|
759
777
|
})
|
|
760
778
|
} catch (e) {
|
|
761
|
-
|
|
779
|
+
this.warn(`Error reading rigidbody ${i} of ${count}:`, e)
|
|
762
780
|
// Stop parsing if we encounter an error
|
|
763
781
|
break
|
|
764
782
|
}
|
|
765
783
|
}
|
|
766
784
|
} catch (e) {
|
|
767
|
-
|
|
785
|
+
this.warn("Error parsing rigidbodies:", e)
|
|
768
786
|
this.rigidbodies = []
|
|
769
787
|
}
|
|
770
788
|
}
|
|
@@ -773,14 +791,14 @@ export class PmxLoader {
|
|
|
773
791
|
try {
|
|
774
792
|
// Check bounds before reading joint count
|
|
775
793
|
if (this.offset + 4 > this.view.buffer.byteLength) {
|
|
776
|
-
|
|
794
|
+
this.warn("Not enough bytes for joint count")
|
|
777
795
|
this.joints = []
|
|
778
796
|
return
|
|
779
797
|
}
|
|
780
798
|
|
|
781
799
|
const count = this.getInt32()
|
|
782
800
|
if (count < 0 || count > 10000) {
|
|
783
|
-
|
|
801
|
+
this.warn(`Suspicious joint count: ${count}`)
|
|
784
802
|
this.joints = []
|
|
785
803
|
return
|
|
786
804
|
}
|
|
@@ -791,7 +809,7 @@ export class PmxLoader {
|
|
|
791
809
|
try {
|
|
792
810
|
// Check bounds before reading each joint
|
|
793
811
|
if (this.offset >= this.view.buffer.byteLength) {
|
|
794
|
-
|
|
812
|
+
this.warn(`Reached end of buffer while reading joint ${i} of ${count}`)
|
|
795
813
|
break
|
|
796
814
|
}
|
|
797
815
|
|
|
@@ -857,13 +875,13 @@ export class PmxLoader {
|
|
|
857
875
|
springRotation: new Vec3(springRotX, springRotY, springRotZ), // Store as Vec3 (stiffness values)
|
|
858
876
|
})
|
|
859
877
|
} catch (e) {
|
|
860
|
-
|
|
878
|
+
this.warn(`Error reading joint ${i} of ${count}:`, e)
|
|
861
879
|
// Stop parsing if we encounter an error
|
|
862
880
|
break
|
|
863
881
|
}
|
|
864
882
|
}
|
|
865
883
|
} catch (e) {
|
|
866
|
-
|
|
884
|
+
this.warn("Error parsing joints:", e)
|
|
867
885
|
this.joints = []
|
|
868
886
|
}
|
|
869
887
|
}
|
|
@@ -1044,7 +1062,8 @@ export class PmxLoader {
|
|
|
1044
1062
|
skinning,
|
|
1045
1063
|
morphing,
|
|
1046
1064
|
this.rigidbodies,
|
|
1047
|
-
this.joints
|
|
1065
|
+
this.joints,
|
|
1066
|
+
this.warnings
|
|
1048
1067
|
)
|
|
1049
1068
|
}
|
|
1050
1069
|
|
|
@@ -1,92 +1,97 @@
|
|
|
1
|
-
// M_Body — 仿深空之眼渲染预设v1.0_by_小绿毛猫 "M_Body". Toon + warm rim + rim1/rim2
|
|
2
|
-
// stack mixed 50/50 against a Principled BSDF with noise-bumped normal.
|
|
3
|
-
|
|
4
|
-
import { NODES_WGSL } from "./nodes"
|
|
5
|
-
import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
|
|
6
|
-
|
|
7
|
-
export const BODY_SHADER_WGSL = /* wgsl */ `
|
|
8
|
-
|
|
9
|
-
${NODES_WGSL}
|
|
10
|
-
${COMMON_MATERIAL_PRELUDE_WGSL}
|
|
11
|
-
|
|
12
|
-
const BODY_ROUGHNESS: f32 = 0.3;
|
|
13
|
-
const BODY_SPECULAR: f32 = 0.5;
|
|
14
|
-
const BODY_MIX_NPR: f32 = 0.5;
|
|
15
|
-
const BODY_SPEC_CLAMP: f32 = 10.0;
|
|
16
|
-
const BODY_RIM2_LAYER_BLEND: f32 = 0.20000000298023224;
|
|
17
|
-
const BODY_RIM2_POW: f32 = 1.4300000667572021;
|
|
18
|
-
const BODY_RIM2_BG: vec3f = vec3f(1.0, 0.4303792119026184, 0.3315804898738861);
|
|
19
|
-
const BODY_WARM_STR: f32 = 0.30000001192092896;
|
|
20
|
-
|
|
21
|
-
// smoothstep-based ramp: t*t*(3-2*t) between two color stops
|
|
22
|
-
fn ramp_ease(f: f32, p0: f32, c0: vec4f, p1: f32, c1: vec4f) -> vec4f {
|
|
23
|
-
let t = saturate((f - p0) / max(p1 - p0, 1e-6));
|
|
24
|
-
let ss = t * t * (3.0 - 2.0 * t);
|
|
25
|
-
return mix(c0, c1, ss);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let
|
|
38
|
-
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let
|
|
48
|
-
let
|
|
49
|
-
|
|
50
|
-
let
|
|
51
|
-
|
|
52
|
-
let
|
|
53
|
-
let
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
let
|
|
62
|
-
|
|
63
|
-
let
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
let
|
|
78
|
-
|
|
79
|
-
let
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
let
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
1
|
+
// M_Body — 仿深空之眼渲染预设v1.0_by_小绿毛猫 "M_Body". Toon + warm rim + rim1/rim2
|
|
2
|
+
// stack mixed 50/50 against a Principled BSDF with noise-bumped normal.
|
|
3
|
+
|
|
4
|
+
import { NODES_WGSL } from "./nodes"
|
|
5
|
+
import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
|
|
6
|
+
|
|
7
|
+
export const BODY_SHADER_WGSL = /* wgsl */ `
|
|
8
|
+
|
|
9
|
+
${NODES_WGSL}
|
|
10
|
+
${COMMON_MATERIAL_PRELUDE_WGSL}
|
|
11
|
+
|
|
12
|
+
const BODY_ROUGHNESS: f32 = 0.3;
|
|
13
|
+
const BODY_SPECULAR: f32 = 0.5;
|
|
14
|
+
const BODY_MIX_NPR: f32 = 0.5;
|
|
15
|
+
const BODY_SPEC_CLAMP: f32 = 10.0;
|
|
16
|
+
const BODY_RIM2_LAYER_BLEND: f32 = 0.20000000298023224;
|
|
17
|
+
const BODY_RIM2_POW: f32 = 1.4300000667572021;
|
|
18
|
+
const BODY_RIM2_BG: vec3f = vec3f(1.0, 0.4303792119026184, 0.3315804898738861);
|
|
19
|
+
const BODY_WARM_STR: f32 = 0.30000001192092896;
|
|
20
|
+
|
|
21
|
+
// smoothstep-based ramp: t*t*(3-2*t) between two color stops
|
|
22
|
+
fn ramp_ease(f: f32, p0: f32, c0: vec4f, p1: f32, c1: vec4f) -> vec4f {
|
|
23
|
+
let t = saturate((f - p0) / max(p1 - p0, 1e-6));
|
|
24
|
+
let ss = t * t * (3.0 - 2.0 * t);
|
|
25
|
+
return mix(c0, c1, ss);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
29
|
+
let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
|
|
30
|
+
// MMD alpha semantics: material alpha × texture alpha. Hair/lace/accessory
|
|
31
|
+
// textures cut their shapes in the alpha channel — ignoring it renders each
|
|
32
|
+
// card's full quad with the texture's padding color (white shimmer on dark
|
|
33
|
+
// hair for models whose textures pad with white).
|
|
34
|
+
let alpha = material.alpha * tex_s.a;
|
|
35
|
+
if (alpha < 0.001) { discard; }
|
|
36
|
+
|
|
37
|
+
let n = safe_normal(input.normal);
|
|
38
|
+
let v = normalize(camera.viewPos - input.worldPos);
|
|
39
|
+
let l = -light.lights[0].direction.xyz;
|
|
40
|
+
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
41
|
+
let amb = light.ambientColor.xyz;
|
|
42
|
+
let shadow = sampleShadow(input.worldPos, n);
|
|
43
|
+
|
|
44
|
+
let tex_color = tex_s.rgb;
|
|
45
|
+
|
|
46
|
+
// ═══ NPR STACK ═══
|
|
47
|
+
let ndotl_raw = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
|
|
48
|
+
let toon = ramp_constant(ndotl_raw, 0.0, vec4f(0,0,0,1), 0.2966, vec4f(1,1,1,1)).r;
|
|
49
|
+
|
|
50
|
+
let shadow_tint = hue_sat_id(2.0, 0.3499999940395355, 1.0, tex_color);
|
|
51
|
+
let lit_tint = hue_sat_id(1.5, 1.0, 1.0, tex_color);
|
|
52
|
+
let toon_color = mix_blend(toon, shadow_tint, lit_tint);
|
|
53
|
+
let bc = bright_contrast(toon_color, 0.1, 0.2);
|
|
54
|
+
|
|
55
|
+
let emission3 = bc * 4.0;
|
|
56
|
+
|
|
57
|
+
let warm_input = clamp(toon + 0.5, 0.0, 1.0);
|
|
58
|
+
let warm_color = ramp_cardinal(warm_input, 0.2409,
|
|
59
|
+
vec4f(0.2426, 0.068, 0.0588, 1.0), 0.4663,
|
|
60
|
+
vec4f(0.6677, 0.5024, 0.5126, 1.0)).rgb;
|
|
61
|
+
let warm_emission = warm_color * BODY_WARM_STR;
|
|
62
|
+
|
|
63
|
+
let rim1_str = fresnel(2.0, n, v) * layer_weight_facing(0.24000005424022675, n, v);
|
|
64
|
+
let rim1 = vec3f(0.984157919883728, 0.6110184788703918, 0.5736401677131653) * rim1_str;
|
|
65
|
+
|
|
66
|
+
let facing_raw = layer_weight_facing(BODY_RIM2_LAYER_BLEND, n, v);
|
|
67
|
+
let facing_pow = math_power(facing_raw, BODY_RIM2_POW);
|
|
68
|
+
let rim2_fac = ramp_ease(facing_pow, 0.0, vec4f(0,0,0,1), 0.5052, vec4f(1,1,1,1)).r;
|
|
69
|
+
let rim2_mixed = mix(emission3, BODY_RIM2_BG, rim2_fac);
|
|
70
|
+
|
|
71
|
+
let npr_stack = rim1 + rim2_mixed + warm_emission;
|
|
72
|
+
|
|
73
|
+
// ═══ PRINCIPLED BSDF with noise bump ═══
|
|
74
|
+
// Mapping loc=rot=0 in the Blender graph folds to a plain scale multiply.
|
|
75
|
+
// Height in rest space (restPos) so the bump rides with the skin; geometric basis stays
|
|
76
|
+
// worldPos so the perturbed normal matches the world-space n (see face.ts note).
|
|
77
|
+
let noise_val = tex_noise_d2(input.restPos * vec3f(1.0, 1.0, 1.5), 1.0);
|
|
78
|
+
let noise_ramp = ramp_linear(noise_val, 0.0, vec4f(0,0,0,1), 1.0, vec4f(1,1,1,1)).r;
|
|
79
|
+
let bumped_n = bump_lh(0.324644535779953, noise_ramp, n, input.worldPos);
|
|
80
|
+
|
|
81
|
+
let principled_base = mix_blend(noise_ramp, bc, vec3f(0.6831911206245422, 0.19474034011363983, 0.13732507824897766));
|
|
82
|
+
let p_emission = bc * 0.2;
|
|
83
|
+
|
|
84
|
+
let principled = eval_principled(
|
|
85
|
+
PrincipledIn(principled_base, 0.0, BODY_SPECULAR, BODY_ROUGHNESS, BODY_SPEC_CLAMP, 0.0, 0.0),
|
|
86
|
+
bumped_n, l, v, sun, amb, shadow
|
|
87
|
+
) + p_emission;
|
|
88
|
+
|
|
89
|
+
let final_color = mix(npr_stack, principled, BODY_MIX_NPR);
|
|
90
|
+
|
|
91
|
+
var out: FSOut;
|
|
92
|
+
out.color = vec4f(final_color, alpha);
|
|
93
|
+
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
`
|
|
@@ -20,7 +20,7 @@ const CLOTH_R_BUMP_STR: f32 = 1.0;
|
|
|
20
20
|
const CLOTH_R_SPEC_CLAMP: f32 = 10.0;
|
|
21
21
|
|
|
22
22
|
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
23
|
-
let n =
|
|
23
|
+
let n = safe_normal(input.normal);
|
|
24
24
|
let v = normalize(camera.viewPos - input.worldPos);
|
|
25
25
|
let l = -light.lights[0].direction.xyz;
|
|
26
26
|
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
@@ -17,7 +17,7 @@ const NPR_EMIT_STR: f32 = 18.200000762939453;
|
|
|
17
17
|
const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
|
|
18
18
|
|
|
19
19
|
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
20
|
-
let n =
|
|
20
|
+
let n = safe_normal(input.normal);
|
|
21
21
|
let v = normalize(camera.viewPos - input.worldPos);
|
|
22
22
|
let l = -light.lights[0].direction.xyz;
|
|
23
23
|
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
@@ -47,7 +47,7 @@ const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
|
|
|
47
47
|
// ═══ PRINCIPLED BSDF ═══
|
|
48
48
|
let principled_base = hue_sat_id(1.0, 0.800000011920929, 1.0, tex_rgb);
|
|
49
49
|
let principled = eval_principled(
|
|
50
|
-
PrincipledIn(principled_base, 0.0, CLOTH_SPECULAR, CLOTH_ROUGHNESS,
|
|
50
|
+
PrincipledIn(principled_base, 0.0, CLOTH_SPECULAR, CLOTH_ROUGHNESS, 10.0, 0.0, 0.0),
|
|
51
51
|
n, l, v, sun, amb, shadow
|
|
52
52
|
);
|
|
53
53
|
|