reze-engine 0.1.2 → 0.1.4
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 +99 -99
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +200 -207
- package/dist/math.d.ts +2 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +33 -0
- package/dist/model.d.ts +5 -25
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +49 -195
- package/package.json +2 -2
- package/src/camera.ts +358 -358
- package/src/engine.ts +1158 -1170
- package/src/math.ts +546 -505
- package/src/model.ts +418 -586
- package/src/physics.ts +680 -680
- package/src/pmx-loader.ts +1031 -1031
package/README.md
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
# Reze Engine
|
|
2
|
-
|
|
3
|
-
A lightweight engine built with WebGPU and TypeScript for real-time 3D anime character MMD model rendering.
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
export default function Home() {
|
|
9
|
-
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
10
|
-
const engineRef = useRef<Engine | null>(null)
|
|
11
|
-
const [engineError, setEngineError] = useState<string | null>(null)
|
|
12
|
-
const [loading, setLoading] = useState(true)
|
|
13
|
-
const [stats, setStats] = useState<EngineStats>({
|
|
14
|
-
fps: 0,
|
|
15
|
-
frameTime: 0,
|
|
16
|
-
memoryUsed: 0,
|
|
17
|
-
drawCalls: 0,
|
|
18
|
-
vertices: 0,
|
|
19
|
-
triangles: 0,
|
|
20
|
-
materials: 0,
|
|
21
|
-
textures: 0,
|
|
22
|
-
textureMemory: 0,
|
|
23
|
-
bufferMemory: 0,
|
|
24
|
-
gpuMemory: 0,
|
|
25
|
-
})
|
|
26
|
-
const [progress, setProgress] = useState(0)
|
|
27
|
-
|
|
28
|
-
const initEngine = useCallback(async () => {
|
|
29
|
-
if (canvasRef.current) {
|
|
30
|
-
// Initialize engine
|
|
31
|
-
try {
|
|
32
|
-
const engine = new Engine(canvasRef.current)
|
|
33
|
-
engineRef.current = engine
|
|
34
|
-
await engine.init()
|
|
35
|
-
await engine.loadModel("/models/塞尔凯特/塞尔凯特.pmx")
|
|
36
|
-
setLoading(false)
|
|
37
|
-
|
|
38
|
-
engine.runRenderLoop(() => {
|
|
39
|
-
setStats(engine.getStats())
|
|
40
|
-
})
|
|
41
|
-
} catch (error) {
|
|
42
|
-
setEngineError(error instanceof Error ? error.message : "Unknown error")
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}, [])
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
void (async () => {
|
|
49
|
-
initEngine()
|
|
50
|
-
})()
|
|
51
|
-
|
|
52
|
-
// Cleanup on unmount
|
|
53
|
-
return () => {
|
|
54
|
-
if (engineRef.current) {
|
|
55
|
-
engineRef.current.dispose()
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}, [initEngine])
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (loading) {
|
|
62
|
-
const interval = setInterval(() => {
|
|
63
|
-
setProgress((prev) => {
|
|
64
|
-
if (prev >= 100) {
|
|
65
|
-
return 0
|
|
66
|
-
}
|
|
67
|
-
return prev + 1
|
|
68
|
-
})
|
|
69
|
-
}, 50)
|
|
70
|
-
|
|
71
|
-
return () => clearInterval(interval)
|
|
72
|
-
}
|
|
73
|
-
}, [loading])
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<div
|
|
77
|
-
className="fixed inset-0 w-full h-full overflow-hidden touch-none"
|
|
78
|
-
style={{
|
|
79
|
-
background:
|
|
80
|
-
"radial-gradient(ellipse at center, rgba(35, 35, 45, 0.8) 0%, rgba(35, 35, 45, 0.8) 8%, rgba(8, 8, 12, 0.95) 65%, rgba(0, 0, 0, 1) 100%)",
|
|
81
|
-
}}
|
|
82
|
-
>
|
|
83
|
-
<Header stats={stats} />
|
|
84
|
-
|
|
85
|
-
{engineError && (
|
|
86
|
-
<div className="absolute inset-0 w-full h-full flex items-center justify-center text-white p-6">
|
|
87
|
-
Engine Error: {engineError}
|
|
88
|
-
</div>
|
|
89
|
-
)}
|
|
90
|
-
{loading && !engineError && (
|
|
91
|
-
<div className="absolute inset-0 max-w-xs mx-auto w-full h-full flex items-center justify-center text-white p-6">
|
|
92
|
-
<Progress value={progress} className="rounded-none" />
|
|
93
|
-
</div>
|
|
94
|
-
)}
|
|
95
|
-
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full touch-none z-1" />
|
|
96
|
-
</div>
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
```
|
|
1
|
+
# Reze Engine
|
|
2
|
+
|
|
3
|
+
A lightweight engine built with WebGPU and TypeScript for real-time 3D anime character MMD model rendering.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
export default function Home() {
|
|
9
|
+
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
10
|
+
const engineRef = useRef<Engine | null>(null)
|
|
11
|
+
const [engineError, setEngineError] = useState<string | null>(null)
|
|
12
|
+
const [loading, setLoading] = useState(true)
|
|
13
|
+
const [stats, setStats] = useState<EngineStats>({
|
|
14
|
+
fps: 0,
|
|
15
|
+
frameTime: 0,
|
|
16
|
+
memoryUsed: 0,
|
|
17
|
+
drawCalls: 0,
|
|
18
|
+
vertices: 0,
|
|
19
|
+
triangles: 0,
|
|
20
|
+
materials: 0,
|
|
21
|
+
textures: 0,
|
|
22
|
+
textureMemory: 0,
|
|
23
|
+
bufferMemory: 0,
|
|
24
|
+
gpuMemory: 0,
|
|
25
|
+
})
|
|
26
|
+
const [progress, setProgress] = useState(0)
|
|
27
|
+
|
|
28
|
+
const initEngine = useCallback(async () => {
|
|
29
|
+
if (canvasRef.current) {
|
|
30
|
+
// Initialize engine
|
|
31
|
+
try {
|
|
32
|
+
const engine = new Engine(canvasRef.current)
|
|
33
|
+
engineRef.current = engine
|
|
34
|
+
await engine.init()
|
|
35
|
+
await engine.loadModel("/models/塞尔凯特/塞尔凯特.pmx")
|
|
36
|
+
setLoading(false)
|
|
37
|
+
|
|
38
|
+
engine.runRenderLoop(() => {
|
|
39
|
+
setStats(engine.getStats())
|
|
40
|
+
})
|
|
41
|
+
} catch (error) {
|
|
42
|
+
setEngineError(error instanceof Error ? error.message : "Unknown error")
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
void (async () => {
|
|
49
|
+
initEngine()
|
|
50
|
+
})()
|
|
51
|
+
|
|
52
|
+
// Cleanup on unmount
|
|
53
|
+
return () => {
|
|
54
|
+
if (engineRef.current) {
|
|
55
|
+
engineRef.current.dispose()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, [initEngine])
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (loading) {
|
|
62
|
+
const interval = setInterval(() => {
|
|
63
|
+
setProgress((prev) => {
|
|
64
|
+
if (prev >= 100) {
|
|
65
|
+
return 0
|
|
66
|
+
}
|
|
67
|
+
return prev + 1
|
|
68
|
+
})
|
|
69
|
+
}, 50)
|
|
70
|
+
|
|
71
|
+
return () => clearInterval(interval)
|
|
72
|
+
}
|
|
73
|
+
}, [loading])
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
className="fixed inset-0 w-full h-full overflow-hidden touch-none"
|
|
78
|
+
style={{
|
|
79
|
+
background:
|
|
80
|
+
"radial-gradient(ellipse at center, rgba(35, 35, 45, 0.8) 0%, rgba(35, 35, 45, 0.8) 8%, rgba(8, 8, 12, 0.95) 65%, rgba(0, 0, 0, 1) 100%)",
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<Header stats={stats} />
|
|
84
|
+
|
|
85
|
+
{engineError && (
|
|
86
|
+
<div className="absolute inset-0 w-full h-full flex items-center justify-center text-white p-6">
|
|
87
|
+
Engine Error: {engineError}
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
{loading && !engineError && (
|
|
91
|
+
<div className="absolute inset-0 max-w-xs mx-auto w-full h-full flex items-center justify-center text-white p-6">
|
|
92
|
+
<Progress value={progress} className="rounded-none" />
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full touch-none z-1" />
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
```
|
package/dist/engine.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ export declare class Engine {
|
|
|
71
71
|
stopRenderLoop(): void;
|
|
72
72
|
dispose(): void;
|
|
73
73
|
loadModel(path: string): Promise<void>;
|
|
74
|
-
rotateBones(bones: string[], rotations: Quat[],
|
|
74
|
+
rotateBones(bones: string[], rotations: Quat[], durationMs?: number): void;
|
|
75
75
|
private setupModelBuffers;
|
|
76
76
|
private materialDraws;
|
|
77
77
|
private outlineDraws;
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAKnC,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,kBAAkB,CAAmB;IACtC,MAAM,EAAG,MAAM,CAAA;IACtB,OAAO,CAAC,mBAAmB,CAAY;IACvC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,kBAAkB,CAAY;IACtC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,YAAY,CAAY;IAChC,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAC,CAAW;IAC/B,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,eAAe,CAAoB;IAC3C,OAAO,CAAC,YAAY,CAAY;IAChC,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,gBAAgB,CAAC,CAAW;IACpC,OAAO,CAAC,iBAAiB,CAAC,CAAW;IACrC,OAAO,CAAC,uBAAuB,CAAC,CAAW;IAC3C,OAAO,CAAC,yBAAyB,CAAC,CAAoB;IACtD,OAAO,CAAC,eAAe,CAAC,CAAW;IACnC,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAI;IAChC,OAAO,CAAC,oBAAoB,CAA0B;IACtD,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,YAAY,CAAuD;IAE3E,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,qBAAqB,CAAI;IACjC,OAAO,CAAC,gBAAgB,CAAe;IACvC,OAAO,CAAC,YAAY,CAAY;IAChC,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,KAAK,CAYZ;IACD,OAAO,CAAC,gBAAgB,CAAsB;IAC9C,OAAO,CAAC,kBAAkB,CAA4B;gBAE1C,MAAM,EAAE,iBAAiB;IAKxB,IAAI;IA6BjB,OAAO,CAAC,eAAe;IA6TvB,OAAO,CAAC,+BAA+B;IAyCvC,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,YAAY;IA8DpB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,aAAa;IAgBd,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAE,MAAY,GAAG,OAAO;IAmBxE,UAAU,CAAC,SAAS,EAAE,MAAM;IAI5B,QAAQ,IAAI,WAAW;IAIvB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI;IAgBnC,cAAc;IAQd,OAAO;IAUD,SAAS,CAAC,IAAI,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Camera } from "./camera";
|
|
2
|
-
import {
|
|
2
|
+
import { Vec3 } from "./math";
|
|
3
3
|
import { PmxLoader } from "./pmx-loader";
|
|
4
4
|
import { Physics } from "./physics";
|
|
5
5
|
export class Engine {
|
|
@@ -74,112 +74,112 @@ export class Engine {
|
|
|
74
74
|
});
|
|
75
75
|
const shaderModule = this.device.createShaderModule({
|
|
76
76
|
label: "model shaders",
|
|
77
|
-
code: /* wgsl */ `
|
|
78
|
-
struct CameraUniforms {
|
|
79
|
-
view: mat4x4f,
|
|
80
|
-
projection: mat4x4f,
|
|
81
|
-
viewPos: vec3f,
|
|
82
|
-
_padding: f32,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
struct Light {
|
|
86
|
-
direction: vec3f,
|
|
87
|
-
_padding1: f32,
|
|
88
|
-
color: vec3f,
|
|
89
|
-
intensity: f32,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
struct LightUniforms {
|
|
93
|
-
ambient: f32,
|
|
94
|
-
lightCount: f32,
|
|
95
|
-
_padding1: f32,
|
|
96
|
-
_padding2: f32,
|
|
97
|
-
lights: array<Light, 4>,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
struct MaterialUniforms {
|
|
101
|
-
alpha: f32,
|
|
102
|
-
_padding1: f32,
|
|
103
|
-
_padding2: f32,
|
|
104
|
-
_padding3: f32,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
struct VertexOutput {
|
|
108
|
-
@builtin(position) position: vec4f,
|
|
109
|
-
@location(0) normal: vec3f,
|
|
110
|
-
@location(1) uv: vec2f,
|
|
111
|
-
@location(2) worldPos: vec3f,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
115
|
-
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
116
|
-
@group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
|
|
117
|
-
@group(0) @binding(3) var diffuseSampler: sampler;
|
|
118
|
-
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
119
|
-
@group(0) @binding(5) var toonTexture: texture_2d<f32>;
|
|
120
|
-
@group(0) @binding(6) var toonSampler: sampler;
|
|
121
|
-
@group(0) @binding(7) var<uniform> material: MaterialUniforms;
|
|
122
|
-
|
|
123
|
-
@vertex fn vs(
|
|
124
|
-
@location(0) position: vec3f,
|
|
125
|
-
@location(1) normal: vec3f,
|
|
126
|
-
@location(2) uv: vec2f,
|
|
127
|
-
@location(3) joints0: vec4<u32>,
|
|
128
|
-
@location(4) weights0: vec4<f32>
|
|
129
|
-
) -> VertexOutput {
|
|
130
|
-
var output: VertexOutput;
|
|
131
|
-
let pos4 = vec4f(position, 1.0);
|
|
132
|
-
|
|
133
|
-
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
134
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
135
|
-
var normalizedWeights: vec4f;
|
|
136
|
-
if (weightSum > 0.0001) {
|
|
137
|
-
normalizedWeights = weights0 / weightSum;
|
|
138
|
-
} else {
|
|
139
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
143
|
-
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
144
|
-
for (var i = 0u; i < 4u; i++) {
|
|
145
|
-
let j = joints0[i];
|
|
146
|
-
let w = normalizedWeights[i];
|
|
147
|
-
let m = skinMats[j];
|
|
148
|
-
skinnedPos += (m * pos4) * w;
|
|
149
|
-
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
150
|
-
skinnedNrm += (r3 * normal) * w;
|
|
151
|
-
}
|
|
152
|
-
let worldPos = skinnedPos.xyz;
|
|
153
|
-
output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
154
|
-
output.normal = normalize(skinnedNrm);
|
|
155
|
-
output.uv = uv;
|
|
156
|
-
output.worldPos = worldPos;
|
|
157
|
-
return output;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
161
|
-
let n = normalize(input.normal);
|
|
162
|
-
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
163
|
-
|
|
164
|
-
var lightAccum = vec3f(light.ambient);
|
|
165
|
-
let numLights = u32(light.lightCount);
|
|
166
|
-
for (var i = 0u; i < numLights; i++) {
|
|
167
|
-
let l = -light.lights[i].direction;
|
|
168
|
-
let nDotL = max(dot(n, l), 0.0);
|
|
169
|
-
let toonUV = vec2f(nDotL, 0.5);
|
|
170
|
-
let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
|
|
171
|
-
let radiance = light.lights[i].color * light.lights[i].intensity;
|
|
172
|
-
lightAccum += toonFactor * radiance * nDotL;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
let color = albedo * lightAccum;
|
|
176
|
-
let finalAlpha = material.alpha;
|
|
177
|
-
if (finalAlpha < 0.001) {
|
|
178
|
-
discard;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
182
|
-
}
|
|
77
|
+
code: /* wgsl */ `
|
|
78
|
+
struct CameraUniforms {
|
|
79
|
+
view: mat4x4f,
|
|
80
|
+
projection: mat4x4f,
|
|
81
|
+
viewPos: vec3f,
|
|
82
|
+
_padding: f32,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
struct Light {
|
|
86
|
+
direction: vec3f,
|
|
87
|
+
_padding1: f32,
|
|
88
|
+
color: vec3f,
|
|
89
|
+
intensity: f32,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
struct LightUniforms {
|
|
93
|
+
ambient: f32,
|
|
94
|
+
lightCount: f32,
|
|
95
|
+
_padding1: f32,
|
|
96
|
+
_padding2: f32,
|
|
97
|
+
lights: array<Light, 4>,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
struct MaterialUniforms {
|
|
101
|
+
alpha: f32,
|
|
102
|
+
_padding1: f32,
|
|
103
|
+
_padding2: f32,
|
|
104
|
+
_padding3: f32,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
struct VertexOutput {
|
|
108
|
+
@builtin(position) position: vec4f,
|
|
109
|
+
@location(0) normal: vec3f,
|
|
110
|
+
@location(1) uv: vec2f,
|
|
111
|
+
@location(2) worldPos: vec3f,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
115
|
+
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
116
|
+
@group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
|
|
117
|
+
@group(0) @binding(3) var diffuseSampler: sampler;
|
|
118
|
+
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
119
|
+
@group(0) @binding(5) var toonTexture: texture_2d<f32>;
|
|
120
|
+
@group(0) @binding(6) var toonSampler: sampler;
|
|
121
|
+
@group(0) @binding(7) var<uniform> material: MaterialUniforms;
|
|
122
|
+
|
|
123
|
+
@vertex fn vs(
|
|
124
|
+
@location(0) position: vec3f,
|
|
125
|
+
@location(1) normal: vec3f,
|
|
126
|
+
@location(2) uv: vec2f,
|
|
127
|
+
@location(3) joints0: vec4<u32>,
|
|
128
|
+
@location(4) weights0: vec4<f32>
|
|
129
|
+
) -> VertexOutput {
|
|
130
|
+
var output: VertexOutput;
|
|
131
|
+
let pos4 = vec4f(position, 1.0);
|
|
132
|
+
|
|
133
|
+
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
134
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
135
|
+
var normalizedWeights: vec4f;
|
|
136
|
+
if (weightSum > 0.0001) {
|
|
137
|
+
normalizedWeights = weights0 / weightSum;
|
|
138
|
+
} else {
|
|
139
|
+
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
143
|
+
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
144
|
+
for (var i = 0u; i < 4u; i++) {
|
|
145
|
+
let j = joints0[i];
|
|
146
|
+
let w = normalizedWeights[i];
|
|
147
|
+
let m = skinMats[j];
|
|
148
|
+
skinnedPos += (m * pos4) * w;
|
|
149
|
+
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
150
|
+
skinnedNrm += (r3 * normal) * w;
|
|
151
|
+
}
|
|
152
|
+
let worldPos = skinnedPos.xyz;
|
|
153
|
+
output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
154
|
+
output.normal = normalize(skinnedNrm);
|
|
155
|
+
output.uv = uv;
|
|
156
|
+
output.worldPos = worldPos;
|
|
157
|
+
return output;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
161
|
+
let n = normalize(input.normal);
|
|
162
|
+
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
163
|
+
|
|
164
|
+
var lightAccum = vec3f(light.ambient);
|
|
165
|
+
let numLights = u32(light.lightCount);
|
|
166
|
+
for (var i = 0u; i < numLights; i++) {
|
|
167
|
+
let l = -light.lights[i].direction;
|
|
168
|
+
let nDotL = max(dot(n, l), 0.0);
|
|
169
|
+
let toonUV = vec2f(nDotL, 0.5);
|
|
170
|
+
let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
|
|
171
|
+
let radiance = light.lights[i].color * light.lights[i].intensity;
|
|
172
|
+
lightAccum += toonFactor * radiance * nDotL;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let color = albedo * lightAccum;
|
|
176
|
+
let finalAlpha = material.alpha;
|
|
177
|
+
if (finalAlpha < 0.001) {
|
|
178
|
+
discard;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
182
|
+
}
|
|
183
183
|
`,
|
|
184
184
|
});
|
|
185
185
|
// Single pipeline for all materials with alpha blending
|
|
@@ -239,72 +239,72 @@ export class Engine {
|
|
|
239
239
|
});
|
|
240
240
|
const outlineShaderModule = this.device.createShaderModule({
|
|
241
241
|
label: "outline shaders",
|
|
242
|
-
code: /* wgsl */ `
|
|
243
|
-
struct CameraUniforms {
|
|
244
|
-
view: mat4x4f,
|
|
245
|
-
projection: mat4x4f,
|
|
246
|
-
viewPos: vec3f,
|
|
247
|
-
_padding: f32,
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
struct MaterialUniforms {
|
|
251
|
-
edgeColor: vec4f,
|
|
252
|
-
edgeSize: f32,
|
|
253
|
-
_padding1: f32,
|
|
254
|
-
_padding2: f32,
|
|
255
|
-
_padding3: f32,
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
259
|
-
@group(0) @binding(1) var<uniform> material: MaterialUniforms;
|
|
260
|
-
@group(0) @binding(2) var<storage, read> skinMats: array<mat4x4f>;
|
|
261
|
-
|
|
262
|
-
struct VertexOutput {
|
|
263
|
-
@builtin(position) position: vec4f,
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
@vertex fn vs(
|
|
267
|
-
@location(0) position: vec3f,
|
|
268
|
-
@location(1) normal: vec3f,
|
|
269
|
-
@location(2) uv: vec2f,
|
|
270
|
-
@location(3) joints0: vec4<u32>,
|
|
271
|
-
@location(4) weights0: vec4<f32>
|
|
272
|
-
) -> VertexOutput {
|
|
273
|
-
var output: VertexOutput;
|
|
274
|
-
let pos4 = vec4f(position, 1.0);
|
|
275
|
-
|
|
276
|
-
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
277
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
278
|
-
var normalizedWeights: vec4f;
|
|
279
|
-
if (weightSum > 0.0001) {
|
|
280
|
-
normalizedWeights = weights0 / weightSum;
|
|
281
|
-
} else {
|
|
282
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
286
|
-
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
287
|
-
for (var i = 0u; i < 4u; i++) {
|
|
288
|
-
let j = joints0[i];
|
|
289
|
-
let w = normalizedWeights[i];
|
|
290
|
-
let m = skinMats[j];
|
|
291
|
-
skinnedPos += (m * pos4) * w;
|
|
292
|
-
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
293
|
-
skinnedNrm += (r3 * normal) * w;
|
|
294
|
-
}
|
|
295
|
-
let worldPos = skinnedPos.xyz;
|
|
296
|
-
let worldNormal = normalize(skinnedNrm);
|
|
297
|
-
|
|
298
|
-
// MMD invert hull: expand vertices outward along normals
|
|
299
|
-
let scaleFactor = 0.01;
|
|
300
|
-
let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
|
|
301
|
-
output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
|
|
302
|
-
return output;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
@fragment fn fs() -> @location(0) vec4f {
|
|
306
|
-
return material.edgeColor;
|
|
307
|
-
}
|
|
242
|
+
code: /* wgsl */ `
|
|
243
|
+
struct CameraUniforms {
|
|
244
|
+
view: mat4x4f,
|
|
245
|
+
projection: mat4x4f,
|
|
246
|
+
viewPos: vec3f,
|
|
247
|
+
_padding: f32,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
struct MaterialUniforms {
|
|
251
|
+
edgeColor: vec4f,
|
|
252
|
+
edgeSize: f32,
|
|
253
|
+
_padding1: f32,
|
|
254
|
+
_padding2: f32,
|
|
255
|
+
_padding3: f32,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
259
|
+
@group(0) @binding(1) var<uniform> material: MaterialUniforms;
|
|
260
|
+
@group(0) @binding(2) var<storage, read> skinMats: array<mat4x4f>;
|
|
261
|
+
|
|
262
|
+
struct VertexOutput {
|
|
263
|
+
@builtin(position) position: vec4f,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
@vertex fn vs(
|
|
267
|
+
@location(0) position: vec3f,
|
|
268
|
+
@location(1) normal: vec3f,
|
|
269
|
+
@location(2) uv: vec2f,
|
|
270
|
+
@location(3) joints0: vec4<u32>,
|
|
271
|
+
@location(4) weights0: vec4<f32>
|
|
272
|
+
) -> VertexOutput {
|
|
273
|
+
var output: VertexOutput;
|
|
274
|
+
let pos4 = vec4f(position, 1.0);
|
|
275
|
+
|
|
276
|
+
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
277
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
278
|
+
var normalizedWeights: vec4f;
|
|
279
|
+
if (weightSum > 0.0001) {
|
|
280
|
+
normalizedWeights = weights0 / weightSum;
|
|
281
|
+
} else {
|
|
282
|
+
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
286
|
+
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
287
|
+
for (var i = 0u; i < 4u; i++) {
|
|
288
|
+
let j = joints0[i];
|
|
289
|
+
let w = normalizedWeights[i];
|
|
290
|
+
let m = skinMats[j];
|
|
291
|
+
skinnedPos += (m * pos4) * w;
|
|
292
|
+
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
293
|
+
skinnedNrm += (r3 * normal) * w;
|
|
294
|
+
}
|
|
295
|
+
let worldPos = skinnedPos.xyz;
|
|
296
|
+
let worldNormal = normalize(skinnedNrm);
|
|
297
|
+
|
|
298
|
+
// MMD invert hull: expand vertices outward along normals
|
|
299
|
+
let scaleFactor = 0.01;
|
|
300
|
+
let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
|
|
301
|
+
output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
|
|
302
|
+
return output;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@fragment fn fs() -> @location(0) vec4f {
|
|
306
|
+
return material.edgeColor;
|
|
307
|
+
}
|
|
308
308
|
`,
|
|
309
309
|
});
|
|
310
310
|
this.outlinePipeline = this.device.createRenderPipeline({
|
|
@@ -380,31 +380,31 @@ export class Engine {
|
|
|
380
380
|
createSkinMatrixComputePipeline() {
|
|
381
381
|
const computeShader = this.device.createShaderModule({
|
|
382
382
|
label: "skin matrix compute",
|
|
383
|
-
code: /* wgsl */ `
|
|
384
|
-
struct BoneCountUniform {
|
|
385
|
-
count: u32,
|
|
386
|
-
_padding1: u32,
|
|
387
|
-
_padding2: u32,
|
|
388
|
-
_padding3: u32,
|
|
389
|
-
_padding4: vec4<u32>,
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
@group(0) @binding(0) var<uniform> boneCount: BoneCountUniform;
|
|
393
|
-
@group(0) @binding(1) var<storage, read> worldMatrices: array<mat4x4f>;
|
|
394
|
-
@group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
|
|
395
|
-
@group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
|
|
396
|
-
|
|
397
|
-
@compute @workgroup_size(64)
|
|
398
|
-
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
399
|
-
let boneIndex = globalId.x;
|
|
400
|
-
// Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
|
|
401
|
-
if (boneIndex >= boneCount.count) {
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
let worldMat = worldMatrices[boneIndex];
|
|
405
|
-
let invBindMat = inverseBindMatrices[boneIndex];
|
|
406
|
-
skinMatrices[boneIndex] = worldMat * invBindMat;
|
|
407
|
-
}
|
|
383
|
+
code: /* wgsl */ `
|
|
384
|
+
struct BoneCountUniform {
|
|
385
|
+
count: u32,
|
|
386
|
+
_padding1: u32,
|
|
387
|
+
_padding2: u32,
|
|
388
|
+
_padding3: u32,
|
|
389
|
+
_padding4: vec4<u32>,
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
@group(0) @binding(0) var<uniform> boneCount: BoneCountUniform;
|
|
393
|
+
@group(0) @binding(1) var<storage, read> worldMatrices: array<mat4x4f>;
|
|
394
|
+
@group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
|
|
395
|
+
@group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
|
|
396
|
+
|
|
397
|
+
@compute @workgroup_size(64)
|
|
398
|
+
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
399
|
+
let boneIndex = globalId.x;
|
|
400
|
+
// Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
|
|
401
|
+
if (boneIndex >= boneCount.count) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
let worldMat = worldMatrices[boneIndex];
|
|
405
|
+
let invBindMat = inverseBindMatrices[boneIndex];
|
|
406
|
+
skinMatrices[boneIndex] = worldMat * invBindMat;
|
|
407
|
+
}
|
|
408
408
|
`,
|
|
409
409
|
});
|
|
410
410
|
this.skinMatrixComputePipeline = this.device.createComputePipeline({
|
|
@@ -556,16 +556,9 @@ export class Engine {
|
|
|
556
556
|
const model = await PmxLoader.load(path);
|
|
557
557
|
this.physics = new Physics(model.getRigidbodies(), model.getJoints());
|
|
558
558
|
await this.setupModelBuffers(model);
|
|
559
|
-
model.rotateBones(["腰", "首", "右腕", "左腕", "右ひざ"], [
|
|
560
|
-
new Quat(-0.4, -0.3, 0, 1),
|
|
561
|
-
new Quat(0.3, -0.3, -0.3, 1),
|
|
562
|
-
new Quat(0.3, 0.3, 0.3, 1),
|
|
563
|
-
new Quat(-0.3, 0.3, -0.3, 1),
|
|
564
|
-
new Quat(-1.0, -0.3, 0.0, 1),
|
|
565
|
-
], 1000);
|
|
566
559
|
}
|
|
567
|
-
rotateBones(bones, rotations,
|
|
568
|
-
this.currentModel?.rotateBones(bones, rotations,
|
|
560
|
+
rotateBones(bones, rotations, durationMs) {
|
|
561
|
+
this.currentModel?.rotateBones(bones, rotations, durationMs);
|
|
569
562
|
}
|
|
570
563
|
// Step 7: Create vertex, index, and joint buffers
|
|
571
564
|
async setupModelBuffers(model) {
|