reze-engine 0.1.12 → 0.1.13
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 +104 -104
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +384 -484
- package/package.json +1 -1
- package/src/camera.ts +358 -358
- package/src/engine.ts +2311 -2416
- package/src/math.ts +546 -546
- package/src/model.ts +421 -421
- package/src/physics.ts +680 -680
- package/src/pmx-loader.ts +1054 -1054
package/dist/engine.js
CHANGED
|
@@ -81,239 +81,120 @@ export class Engine {
|
|
|
81
81
|
});
|
|
82
82
|
const shaderModule = this.device.createShaderModule({
|
|
83
83
|
label: "model shaders",
|
|
84
|
-
code: /* wgsl */ `
|
|
85
|
-
struct CameraUniforms {
|
|
86
|
-
view: mat4x4f,
|
|
87
|
-
projection: mat4x4f,
|
|
88
|
-
viewPos: vec3f,
|
|
89
|
-
_padding: f32,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
struct Light {
|
|
93
|
-
direction: vec3f,
|
|
94
|
-
_padding1: f32,
|
|
95
|
-
color: vec3f,
|
|
96
|
-
intensity: f32,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
struct LightUniforms {
|
|
100
|
-
ambient: f32,
|
|
101
|
-
lightCount: f32,
|
|
102
|
-
_padding1: f32,
|
|
103
|
-
_padding2: f32,
|
|
104
|
-
lights: array<Light, 4>,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
struct MaterialUniforms {
|
|
108
|
-
alpha: f32,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
rimColor: vec3f,
|
|
113
|
-
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
struct VertexOutput {
|
|
117
|
-
@builtin(position) position: vec4f,
|
|
118
|
-
@location(0) normal: vec3f,
|
|
119
|
-
@location(1) uv: vec2f,
|
|
120
|
-
@location(2) worldPos: vec3f,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
124
|
-
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
125
|
-
@group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
|
|
126
|
-
@group(0) @binding(3) var diffuseSampler: sampler;
|
|
127
|
-
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
128
|
-
@group(0) @binding(5) var toonTexture: texture_2d<f32>;
|
|
129
|
-
@group(0) @binding(6) var toonSampler: sampler;
|
|
130
|
-
@group(0) @binding(7) var<uniform> material: MaterialUniforms;
|
|
131
|
-
|
|
132
|
-
@vertex fn vs(
|
|
133
|
-
@location(0) position: vec3f,
|
|
134
|
-
@location(1) normal: vec3f,
|
|
135
|
-
@location(2) uv: vec2f,
|
|
136
|
-
@location(3) joints0: vec4<u32>,
|
|
137
|
-
@location(4) weights0: vec4<f32>
|
|
138
|
-
) -> VertexOutput {
|
|
139
|
-
var output: VertexOutput;
|
|
140
|
-
let pos4 = vec4f(position, 1.0);
|
|
141
|
-
|
|
142
|
-
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
143
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
144
|
-
var normalizedWeights: vec4f;
|
|
145
|
-
if (weightSum > 0.0001) {
|
|
146
|
-
normalizedWeights = weights0 / weightSum;
|
|
147
|
-
} else {
|
|
148
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
152
|
-
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
153
|
-
for (var i = 0u; i < 4u; i++) {
|
|
154
|
-
let j = joints0[i];
|
|
155
|
-
let w = normalizedWeights[i];
|
|
156
|
-
let m = skinMats[j];
|
|
157
|
-
skinnedPos += (m * pos4) * w;
|
|
158
|
-
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
159
|
-
skinnedNrm += (r3 * normal) * w;
|
|
160
|
-
}
|
|
161
|
-
let worldPos = skinnedPos.xyz;
|
|
162
|
-
output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
163
|
-
output.normal = normalize(skinnedNrm);
|
|
164
|
-
output.uv = uv;
|
|
165
|
-
output.worldPos = worldPos;
|
|
166
|
-
return output;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
170
|
-
let n = normalize(input.normal);
|
|
171
|
-
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
172
|
-
|
|
173
|
-
var lightAccum = vec3f(light.ambient);
|
|
174
|
-
let numLights = u32(light.lightCount);
|
|
175
|
-
for (var i = 0u; i < numLights; i++) {
|
|
176
|
-
let l = -light.lights[i].direction;
|
|
177
|
-
let nDotL = max(dot(n, l), 0.0);
|
|
178
|
-
let toonUV = vec2f(nDotL, 0.5);
|
|
179
|
-
let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
|
|
180
|
-
let radiance = light.lights[i].color * light.lights[i].intensity;
|
|
181
|
-
lightAccum += toonFactor * radiance * nDotL;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Rim light calculation
|
|
185
|
-
let viewDir = normalize(camera.viewPos - input.worldPos);
|
|
186
|
-
var rimFactor = 1.0 - max(dot(n, viewDir), 0.0);
|
|
187
|
-
rimFactor = pow(rimFactor, material.rimPower);
|
|
188
|
-
let rimLight = material.rimColor * material.rimIntensity * rimFactor;
|
|
189
|
-
|
|
190
|
-
let color = albedo * lightAccum + rimLight;
|
|
191
|
-
let finalAlpha = material.alpha;
|
|
192
|
-
if (finalAlpha < 0.001) {
|
|
193
|
-
discard;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
197
|
-
}
|
|
198
|
-
`,
|
|
199
|
-
});
|
|
200
|
-
// Unified hair shader that can handle both over-eyes and over-non-eyes cases
|
|
201
|
-
// Uses material.alpha multiplier to control opacity (0.5 for over-eyes, 1.0 for over-non-eyes)
|
|
202
|
-
const hairShaderModule = this.device.createShaderModule({
|
|
203
|
-
label: "unified hair shaders",
|
|
204
|
-
code: /* wgsl */ `
|
|
205
|
-
struct CameraUniforms {
|
|
206
|
-
view: mat4x4f,
|
|
207
|
-
projection: mat4x4f,
|
|
208
|
-
viewPos: vec3f,
|
|
209
|
-
_padding: f32,
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
struct Light {
|
|
213
|
-
direction: vec3f,
|
|
214
|
-
_padding1: f32,
|
|
215
|
-
color: vec3f,
|
|
216
|
-
intensity: f32,
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
struct LightUniforms {
|
|
220
|
-
ambient: f32,
|
|
221
|
-
lightCount: f32,
|
|
222
|
-
_padding1: f32,
|
|
223
|
-
_padding2: f32,
|
|
224
|
-
lights: array<Light, 4>,
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
struct MaterialUniforms {
|
|
228
|
-
alpha: f32,
|
|
229
|
-
alphaMultiplier: f32, // New: multiplier for alpha (0.5 for over-eyes, 1.0 for over-non-eyes)
|
|
230
|
-
rimIntensity: f32,
|
|
231
|
-
rimPower: f32,
|
|
232
|
-
rimColor: vec3f,
|
|
233
|
-
_padding1: f32,
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
struct VertexOutput {
|
|
237
|
-
@builtin(position) position: vec4f,
|
|
238
|
-
@location(0) normal: vec3f,
|
|
239
|
-
@location(1) uv: vec2f,
|
|
240
|
-
@location(2) worldPos: vec3f,
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
244
|
-
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
245
|
-
@group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
|
|
246
|
-
@group(0) @binding(3) var diffuseSampler: sampler;
|
|
247
|
-
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
248
|
-
@group(0) @binding(5) var toonTexture: texture_2d<f32>;
|
|
249
|
-
@group(0) @binding(6) var toonSampler: sampler;
|
|
250
|
-
@group(0) @binding(7) var<uniform> material: MaterialUniforms;
|
|
251
|
-
|
|
252
|
-
@vertex fn vs(
|
|
253
|
-
@location(0) position: vec3f,
|
|
254
|
-
@location(1) normal: vec3f,
|
|
255
|
-
@location(2) uv: vec2f,
|
|
256
|
-
@location(3) joints0: vec4<u32>,
|
|
257
|
-
@location(4) weights0: vec4<f32>
|
|
258
|
-
) -> VertexOutput {
|
|
259
|
-
var output: VertexOutput;
|
|
260
|
-
let pos4 = vec4f(position, 1.0);
|
|
261
|
-
|
|
262
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
263
|
-
var normalizedWeights: vec4f;
|
|
264
|
-
if (weightSum > 0.0001) {
|
|
265
|
-
normalizedWeights = weights0 / weightSum;
|
|
266
|
-
} else {
|
|
267
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
271
|
-
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
272
|
-
for (var i = 0u; i < 4u; i++) {
|
|
273
|
-
let j = joints0[i];
|
|
274
|
-
let w = normalizedWeights[i];
|
|
275
|
-
let m = skinMats[j];
|
|
276
|
-
skinnedPos += (m * pos4) * w;
|
|
277
|
-
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
278
|
-
skinnedNrm += (r3 * normal) * w;
|
|
279
|
-
}
|
|
280
|
-
let worldPos = skinnedPos.xyz;
|
|
281
|
-
output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
282
|
-
output.normal = normalize(skinnedNrm);
|
|
283
|
-
output.uv = uv;
|
|
284
|
-
output.worldPos = worldPos;
|
|
285
|
-
return output;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
289
|
-
let n = normalize(input.normal);
|
|
290
|
-
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
291
|
-
|
|
292
|
-
var lightAccum = vec3f(light.ambient);
|
|
293
|
-
let numLights = u32(light.lightCount);
|
|
294
|
-
for (var i = 0u; i < numLights; i++) {
|
|
295
|
-
let l = -light.lights[i].direction;
|
|
296
|
-
let nDotL = max(dot(n, l), 0.0);
|
|
297
|
-
let toonUV = vec2f(nDotL, 0.5);
|
|
298
|
-
let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
|
|
299
|
-
let radiance = light.lights[i].color * light.lights[i].intensity;
|
|
300
|
-
lightAccum += toonFactor * radiance * nDotL;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Rim light calculation
|
|
304
|
-
let viewDir = normalize(camera.viewPos - input.worldPos);
|
|
305
|
-
var rimFactor = 1.0 - max(dot(n, viewDir), 0.0);
|
|
306
|
-
rimFactor = pow(rimFactor, material.rimPower);
|
|
307
|
-
let rimLight = material.rimColor * material.rimIntensity * rimFactor;
|
|
308
|
-
|
|
309
|
-
let color = albedo * lightAccum + rimLight;
|
|
310
|
-
let finalAlpha = material.alpha * material.alphaMultiplier;
|
|
311
|
-
if (finalAlpha < 0.001) {
|
|
312
|
-
discard;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
316
|
-
}
|
|
84
|
+
code: /* wgsl */ `
|
|
85
|
+
struct CameraUniforms {
|
|
86
|
+
view: mat4x4f,
|
|
87
|
+
projection: mat4x4f,
|
|
88
|
+
viewPos: vec3f,
|
|
89
|
+
_padding: f32,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
struct Light {
|
|
93
|
+
direction: vec3f,
|
|
94
|
+
_padding1: f32,
|
|
95
|
+
color: vec3f,
|
|
96
|
+
intensity: f32,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
struct LightUniforms {
|
|
100
|
+
ambient: f32,
|
|
101
|
+
lightCount: f32,
|
|
102
|
+
_padding1: f32,
|
|
103
|
+
_padding2: f32,
|
|
104
|
+
lights: array<Light, 4>,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
struct MaterialUniforms {
|
|
108
|
+
alpha: f32,
|
|
109
|
+
alphaMultiplier: f32,
|
|
110
|
+
rimIntensity: f32,
|
|
111
|
+
rimPower: f32,
|
|
112
|
+
rimColor: vec3f,
|
|
113
|
+
_padding1: f32,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
struct VertexOutput {
|
|
117
|
+
@builtin(position) position: vec4f,
|
|
118
|
+
@location(0) normal: vec3f,
|
|
119
|
+
@location(1) uv: vec2f,
|
|
120
|
+
@location(2) worldPos: vec3f,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
124
|
+
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
125
|
+
@group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
|
|
126
|
+
@group(0) @binding(3) var diffuseSampler: sampler;
|
|
127
|
+
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
128
|
+
@group(0) @binding(5) var toonTexture: texture_2d<f32>;
|
|
129
|
+
@group(0) @binding(6) var toonSampler: sampler;
|
|
130
|
+
@group(0) @binding(7) var<uniform> material: MaterialUniforms;
|
|
131
|
+
|
|
132
|
+
@vertex fn vs(
|
|
133
|
+
@location(0) position: vec3f,
|
|
134
|
+
@location(1) normal: vec3f,
|
|
135
|
+
@location(2) uv: vec2f,
|
|
136
|
+
@location(3) joints0: vec4<u32>,
|
|
137
|
+
@location(4) weights0: vec4<f32>
|
|
138
|
+
) -> VertexOutput {
|
|
139
|
+
var output: VertexOutput;
|
|
140
|
+
let pos4 = vec4f(position, 1.0);
|
|
141
|
+
|
|
142
|
+
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
143
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
144
|
+
var normalizedWeights: vec4f;
|
|
145
|
+
if (weightSum > 0.0001) {
|
|
146
|
+
normalizedWeights = weights0 / weightSum;
|
|
147
|
+
} else {
|
|
148
|
+
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
152
|
+
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
153
|
+
for (var i = 0u; i < 4u; i++) {
|
|
154
|
+
let j = joints0[i];
|
|
155
|
+
let w = normalizedWeights[i];
|
|
156
|
+
let m = skinMats[j];
|
|
157
|
+
skinnedPos += (m * pos4) * w;
|
|
158
|
+
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
159
|
+
skinnedNrm += (r3 * normal) * w;
|
|
160
|
+
}
|
|
161
|
+
let worldPos = skinnedPos.xyz;
|
|
162
|
+
output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
163
|
+
output.normal = normalize(skinnedNrm);
|
|
164
|
+
output.uv = uv;
|
|
165
|
+
output.worldPos = worldPos;
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
170
|
+
let n = normalize(input.normal);
|
|
171
|
+
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
172
|
+
|
|
173
|
+
var lightAccum = vec3f(light.ambient);
|
|
174
|
+
let numLights = u32(light.lightCount);
|
|
175
|
+
for (var i = 0u; i < numLights; i++) {
|
|
176
|
+
let l = -light.lights[i].direction;
|
|
177
|
+
let nDotL = max(dot(n, l), 0.0);
|
|
178
|
+
let toonUV = vec2f(nDotL, 0.5);
|
|
179
|
+
let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
|
|
180
|
+
let radiance = light.lights[i].color * light.lights[i].intensity;
|
|
181
|
+
lightAccum += toonFactor * radiance * nDotL;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Rim light calculation
|
|
185
|
+
let viewDir = normalize(camera.viewPos - input.worldPos);
|
|
186
|
+
var rimFactor = 1.0 - max(dot(n, viewDir), 0.0);
|
|
187
|
+
rimFactor = pow(rimFactor, material.rimPower);
|
|
188
|
+
let rimLight = material.rimColor * material.rimIntensity * rimFactor;
|
|
189
|
+
|
|
190
|
+
let color = albedo * lightAccum + rimLight;
|
|
191
|
+
let finalAlpha = material.alpha * material.alphaMultiplier;
|
|
192
|
+
if (finalAlpha < 0.001) {
|
|
193
|
+
discard;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
197
|
+
}
|
|
317
198
|
`,
|
|
318
199
|
});
|
|
319
200
|
// Create explicit bind group layout for all pipelines using the main shader
|
|
@@ -405,72 +286,71 @@ export class Engine {
|
|
|
405
286
|
});
|
|
406
287
|
const outlineShaderModule = this.device.createShaderModule({
|
|
407
288
|
label: "outline shaders",
|
|
408
|
-
code: /* wgsl */ `
|
|
409
|
-
struct CameraUniforms {
|
|
410
|
-
view: mat4x4f,
|
|
411
|
-
projection: mat4x4f,
|
|
412
|
-
viewPos: vec3f,
|
|
413
|
-
_padding: f32,
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
struct MaterialUniforms {
|
|
417
|
-
edgeColor: vec4f,
|
|
418
|
-
edgeSize: f32,
|
|
419
|
-
_padding1: f32,
|
|
420
|
-
_padding2: f32,
|
|
421
|
-
_padding3: f32,
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
425
|
-
@group(0) @binding(1) var<uniform> material: MaterialUniforms;
|
|
426
|
-
@group(0) @binding(2) var<storage, read> skinMats: array<mat4x4f>;
|
|
427
|
-
|
|
428
|
-
struct VertexOutput {
|
|
429
|
-
@builtin(position) position: vec4f,
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
@vertex fn vs(
|
|
433
|
-
@location(0) position: vec3f,
|
|
434
|
-
@location(1) normal: vec3f,
|
|
435
|
-
@location(
|
|
436
|
-
@location(
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
var
|
|
452
|
-
var
|
|
453
|
-
|
|
454
|
-
let
|
|
455
|
-
let
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
let
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
let
|
|
466
|
-
|
|
467
|
-
output
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
289
|
+
code: /* wgsl */ `
|
|
290
|
+
struct CameraUniforms {
|
|
291
|
+
view: mat4x4f,
|
|
292
|
+
projection: mat4x4f,
|
|
293
|
+
viewPos: vec3f,
|
|
294
|
+
_padding: f32,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
struct MaterialUniforms {
|
|
298
|
+
edgeColor: vec4f,
|
|
299
|
+
edgeSize: f32,
|
|
300
|
+
_padding1: f32,
|
|
301
|
+
_padding2: f32,
|
|
302
|
+
_padding3: f32,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
306
|
+
@group(0) @binding(1) var<uniform> material: MaterialUniforms;
|
|
307
|
+
@group(0) @binding(2) var<storage, read> skinMats: array<mat4x4f>;
|
|
308
|
+
|
|
309
|
+
struct VertexOutput {
|
|
310
|
+
@builtin(position) position: vec4f,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
@vertex fn vs(
|
|
314
|
+
@location(0) position: vec3f,
|
|
315
|
+
@location(1) normal: vec3f,
|
|
316
|
+
@location(3) joints0: vec4<u32>,
|
|
317
|
+
@location(4) weights0: vec4<f32>
|
|
318
|
+
) -> VertexOutput {
|
|
319
|
+
var output: VertexOutput;
|
|
320
|
+
let pos4 = vec4f(position, 1.0);
|
|
321
|
+
|
|
322
|
+
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
323
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
324
|
+
var normalizedWeights: vec4f;
|
|
325
|
+
if (weightSum > 0.0001) {
|
|
326
|
+
normalizedWeights = weights0 / weightSum;
|
|
327
|
+
} else {
|
|
328
|
+
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
332
|
+
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
333
|
+
for (var i = 0u; i < 4u; i++) {
|
|
334
|
+
let j = joints0[i];
|
|
335
|
+
let w = normalizedWeights[i];
|
|
336
|
+
let m = skinMats[j];
|
|
337
|
+
skinnedPos += (m * pos4) * w;
|
|
338
|
+
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
339
|
+
skinnedNrm += (r3 * normal) * w;
|
|
340
|
+
}
|
|
341
|
+
let worldPos = skinnedPos.xyz;
|
|
342
|
+
let worldNormal = normalize(skinnedNrm);
|
|
343
|
+
|
|
344
|
+
// MMD invert hull: expand vertices outward along normals
|
|
345
|
+
let scaleFactor = 0.01;
|
|
346
|
+
let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
|
|
347
|
+
output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
|
|
348
|
+
return output;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@fragment fn fs() -> @location(0) vec4f {
|
|
352
|
+
return material.edgeColor;
|
|
353
|
+
}
|
|
474
354
|
`,
|
|
475
355
|
});
|
|
476
356
|
this.outlinePipeline = this.device.createRenderPipeline({
|
|
@@ -492,11 +372,6 @@ export class Engine {
|
|
|
492
372
|
offset: 3 * 4,
|
|
493
373
|
format: "float32x3",
|
|
494
374
|
},
|
|
495
|
-
{
|
|
496
|
-
shaderLocation: 2,
|
|
497
|
-
offset: 6 * 4,
|
|
498
|
-
format: "float32x2",
|
|
499
|
-
},
|
|
500
375
|
],
|
|
501
376
|
},
|
|
502
377
|
{
|
|
@@ -561,11 +436,6 @@ export class Engine {
|
|
|
561
436
|
offset: 3 * 4,
|
|
562
437
|
format: "float32x3",
|
|
563
438
|
},
|
|
564
|
-
{
|
|
565
|
-
shaderLocation: 2,
|
|
566
|
-
offset: 6 * 4,
|
|
567
|
-
format: "float32x2",
|
|
568
|
-
},
|
|
569
439
|
],
|
|
570
440
|
},
|
|
571
441
|
{
|
|
@@ -642,11 +512,6 @@ export class Engine {
|
|
|
642
512
|
offset: 3 * 4,
|
|
643
513
|
format: "float32x3",
|
|
644
514
|
},
|
|
645
|
-
{
|
|
646
|
-
shaderLocation: 2,
|
|
647
|
-
offset: 6 * 4,
|
|
648
|
-
format: "float32x2",
|
|
649
|
-
},
|
|
650
515
|
],
|
|
651
516
|
},
|
|
652
517
|
{
|
|
@@ -712,7 +577,7 @@ export class Engine {
|
|
|
712
577
|
label: "hair pipeline (over eyes)",
|
|
713
578
|
layout: sharedPipelineLayout,
|
|
714
579
|
vertex: {
|
|
715
|
-
module:
|
|
580
|
+
module: shaderModule,
|
|
716
581
|
buffers: [
|
|
717
582
|
{
|
|
718
583
|
arrayStride: 8 * 4,
|
|
@@ -733,7 +598,7 @@ export class Engine {
|
|
|
733
598
|
],
|
|
734
599
|
},
|
|
735
600
|
fragment: {
|
|
736
|
-
module:
|
|
601
|
+
module: shaderModule,
|
|
737
602
|
targets: [
|
|
738
603
|
{
|
|
739
604
|
format: this.presentationFormat,
|
|
@@ -777,7 +642,7 @@ export class Engine {
|
|
|
777
642
|
label: "hair pipeline (over non-eyes)",
|
|
778
643
|
layout: sharedPipelineLayout,
|
|
779
644
|
vertex: {
|
|
780
|
-
module:
|
|
645
|
+
module: shaderModule,
|
|
781
646
|
buffers: [
|
|
782
647
|
{
|
|
783
648
|
arrayStride: 8 * 4,
|
|
@@ -798,7 +663,7 @@ export class Engine {
|
|
|
798
663
|
],
|
|
799
664
|
},
|
|
800
665
|
fragment: {
|
|
801
|
-
module:
|
|
666
|
+
module: shaderModule,
|
|
802
667
|
targets: [
|
|
803
668
|
{
|
|
804
669
|
format: this.presentationFormat,
|
|
@@ -907,31 +772,31 @@ export class Engine {
|
|
|
907
772
|
createSkinMatrixComputePipeline() {
|
|
908
773
|
const computeShader = this.device.createShaderModule({
|
|
909
774
|
label: "skin matrix compute",
|
|
910
|
-
code: /* wgsl */ `
|
|
911
|
-
struct BoneCountUniform {
|
|
912
|
-
count: u32,
|
|
913
|
-
_padding1: u32,
|
|
914
|
-
_padding2: u32,
|
|
915
|
-
_padding3: u32,
|
|
916
|
-
_padding4: vec4<u32>,
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
@group(0) @binding(0) var<uniform> boneCount: BoneCountUniform;
|
|
920
|
-
@group(0) @binding(1) var<storage, read> worldMatrices: array<mat4x4f>;
|
|
921
|
-
@group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
|
|
922
|
-
@group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
|
|
923
|
-
|
|
924
|
-
@compute @workgroup_size(64)
|
|
925
|
-
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
926
|
-
let boneIndex = globalId.x;
|
|
927
|
-
// Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
|
|
928
|
-
if (boneIndex >= boneCount.count) {
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
let worldMat = worldMatrices[boneIndex];
|
|
932
|
-
let invBindMat = inverseBindMatrices[boneIndex];
|
|
933
|
-
skinMatrices[boneIndex] = worldMat * invBindMat;
|
|
934
|
-
}
|
|
775
|
+
code: /* wgsl */ `
|
|
776
|
+
struct BoneCountUniform {
|
|
777
|
+
count: u32,
|
|
778
|
+
_padding1: u32,
|
|
779
|
+
_padding2: u32,
|
|
780
|
+
_padding3: u32,
|
|
781
|
+
_padding4: vec4<u32>,
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
@group(0) @binding(0) var<uniform> boneCount: BoneCountUniform;
|
|
785
|
+
@group(0) @binding(1) var<storage, read> worldMatrices: array<mat4x4f>;
|
|
786
|
+
@group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
|
|
787
|
+
@group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
|
|
788
|
+
|
|
789
|
+
@compute @workgroup_size(64)
|
|
790
|
+
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
791
|
+
let boneIndex = globalId.x;
|
|
792
|
+
// Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
|
|
793
|
+
if (boneIndex >= boneCount.count) {
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
let worldMat = worldMatrices[boneIndex];
|
|
797
|
+
let invBindMat = inverseBindMatrices[boneIndex];
|
|
798
|
+
skinMatrices[boneIndex] = worldMat * invBindMat;
|
|
799
|
+
}
|
|
935
800
|
`,
|
|
936
801
|
});
|
|
937
802
|
this.skinMatrixComputePipeline = this.device.createComputePipeline({
|
|
@@ -985,143 +850,143 @@ export class Engine {
|
|
|
985
850
|
// Bloom extraction shader (extracts bright areas)
|
|
986
851
|
const bloomExtractShader = this.device.createShaderModule({
|
|
987
852
|
label: "bloom extract",
|
|
988
|
-
code: /* wgsl */ `
|
|
989
|
-
struct VertexOutput {
|
|
990
|
-
@builtin(position) position: vec4f,
|
|
991
|
-
@location(0) uv: vec2f,
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
995
|
-
var output: VertexOutput;
|
|
996
|
-
// Generate fullscreen quad from vertex index
|
|
997
|
-
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
998
|
-
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
999
|
-
output.position = vec4f(x, y, 0.0, 1.0);
|
|
1000
|
-
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
1001
|
-
return output;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
struct BloomExtractUniforms {
|
|
1005
|
-
threshold: f32,
|
|
1006
|
-
_padding1: f32,
|
|
1007
|
-
_padding2: f32,
|
|
1008
|
-
_padding3: f32,
|
|
1009
|
-
_padding4: f32,
|
|
1010
|
-
_padding5: f32,
|
|
1011
|
-
_padding6: f32,
|
|
1012
|
-
_padding7: f32,
|
|
1013
|
-
};
|
|
1014
|
-
|
|
1015
|
-
@group(0) @binding(0) var inputTexture: texture_2d<f32>;
|
|
1016
|
-
@group(0) @binding(1) var inputSampler: sampler;
|
|
1017
|
-
@group(0) @binding(2) var<uniform> extractUniforms: BloomExtractUniforms;
|
|
1018
|
-
|
|
1019
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
1020
|
-
let color = textureSample(inputTexture, inputSampler, input.uv);
|
|
1021
|
-
// Extract bright areas above threshold
|
|
1022
|
-
let threshold = extractUniforms.threshold;
|
|
1023
|
-
let bloom = max(vec3f(0.0), color.rgb - vec3f(threshold)) / max(0.001, 1.0 - threshold);
|
|
1024
|
-
return vec4f(bloom, color.a);
|
|
1025
|
-
}
|
|
853
|
+
code: /* wgsl */ `
|
|
854
|
+
struct VertexOutput {
|
|
855
|
+
@builtin(position) position: vec4f,
|
|
856
|
+
@location(0) uv: vec2f,
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
860
|
+
var output: VertexOutput;
|
|
861
|
+
// Generate fullscreen quad from vertex index
|
|
862
|
+
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
863
|
+
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
864
|
+
output.position = vec4f(x, y, 0.0, 1.0);
|
|
865
|
+
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
866
|
+
return output;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
struct BloomExtractUniforms {
|
|
870
|
+
threshold: f32,
|
|
871
|
+
_padding1: f32,
|
|
872
|
+
_padding2: f32,
|
|
873
|
+
_padding3: f32,
|
|
874
|
+
_padding4: f32,
|
|
875
|
+
_padding5: f32,
|
|
876
|
+
_padding6: f32,
|
|
877
|
+
_padding7: f32,
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
@group(0) @binding(0) var inputTexture: texture_2d<f32>;
|
|
881
|
+
@group(0) @binding(1) var inputSampler: sampler;
|
|
882
|
+
@group(0) @binding(2) var<uniform> extractUniforms: BloomExtractUniforms;
|
|
883
|
+
|
|
884
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
885
|
+
let color = textureSample(inputTexture, inputSampler, input.uv);
|
|
886
|
+
// Extract bright areas above threshold
|
|
887
|
+
let threshold = extractUniforms.threshold;
|
|
888
|
+
let bloom = max(vec3f(0.0), color.rgb - vec3f(threshold)) / max(0.001, 1.0 - threshold);
|
|
889
|
+
return vec4f(bloom, color.a);
|
|
890
|
+
}
|
|
1026
891
|
`,
|
|
1027
892
|
});
|
|
1028
893
|
// Bloom blur shader (gaussian blur - can be used for both horizontal and vertical)
|
|
1029
894
|
const bloomBlurShader = this.device.createShaderModule({
|
|
1030
895
|
label: "bloom blur",
|
|
1031
|
-
code: /* wgsl */ `
|
|
1032
|
-
struct VertexOutput {
|
|
1033
|
-
@builtin(position) position: vec4f,
|
|
1034
|
-
@location(0) uv: vec2f,
|
|
1035
|
-
};
|
|
1036
|
-
|
|
1037
|
-
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
1038
|
-
var output: VertexOutput;
|
|
1039
|
-
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
1040
|
-
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
1041
|
-
output.position = vec4f(x, y, 0.0, 1.0);
|
|
1042
|
-
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
1043
|
-
return output;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
struct BlurUniforms {
|
|
1047
|
-
direction: vec2f,
|
|
1048
|
-
_padding1: f32,
|
|
1049
|
-
_padding2: f32,
|
|
1050
|
-
_padding3: f32,
|
|
1051
|
-
_padding4: f32,
|
|
1052
|
-
_padding5: f32,
|
|
1053
|
-
_padding6: f32,
|
|
1054
|
-
};
|
|
1055
|
-
|
|
1056
|
-
@group(0) @binding(0) var inputTexture: texture_2d<f32>;
|
|
1057
|
-
@group(0) @binding(1) var inputSampler: sampler;
|
|
1058
|
-
@group(0) @binding(2) var<uniform> blurUniforms: BlurUniforms;
|
|
1059
|
-
|
|
1060
|
-
// 9-tap gaussian blur
|
|
1061
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
1062
|
-
let texelSize = 1.0 / vec2f(textureDimensions(inputTexture));
|
|
1063
|
-
var result = vec4f(0.0);
|
|
1064
|
-
|
|
1065
|
-
// Gaussian weights for 9-tap filter
|
|
1066
|
-
let weights = array<f32, 9>(
|
|
1067
|
-
0.01621622, 0.05405405, 0.12162162,
|
|
1068
|
-
0.19459459, 0.22702703,
|
|
1069
|
-
0.19459459, 0.12162162, 0.05405405, 0.01621622
|
|
1070
|
-
);
|
|
1071
|
-
|
|
1072
|
-
let offsets = array<f32, 9>(-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0);
|
|
1073
|
-
|
|
1074
|
-
for (var i = 0u; i < 9u; i++) {
|
|
1075
|
-
let offset = offsets[i] * texelSize * blurUniforms.direction;
|
|
1076
|
-
result += textureSample(inputTexture, inputSampler, input.uv + offset) * weights[i];
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
return result;
|
|
1080
|
-
}
|
|
896
|
+
code: /* wgsl */ `
|
|
897
|
+
struct VertexOutput {
|
|
898
|
+
@builtin(position) position: vec4f,
|
|
899
|
+
@location(0) uv: vec2f,
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
903
|
+
var output: VertexOutput;
|
|
904
|
+
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
905
|
+
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
906
|
+
output.position = vec4f(x, y, 0.0, 1.0);
|
|
907
|
+
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
908
|
+
return output;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
struct BlurUniforms {
|
|
912
|
+
direction: vec2f,
|
|
913
|
+
_padding1: f32,
|
|
914
|
+
_padding2: f32,
|
|
915
|
+
_padding3: f32,
|
|
916
|
+
_padding4: f32,
|
|
917
|
+
_padding5: f32,
|
|
918
|
+
_padding6: f32,
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
@group(0) @binding(0) var inputTexture: texture_2d<f32>;
|
|
922
|
+
@group(0) @binding(1) var inputSampler: sampler;
|
|
923
|
+
@group(0) @binding(2) var<uniform> blurUniforms: BlurUniforms;
|
|
924
|
+
|
|
925
|
+
// 9-tap gaussian blur
|
|
926
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
927
|
+
let texelSize = 1.0 / vec2f(textureDimensions(inputTexture));
|
|
928
|
+
var result = vec4f(0.0);
|
|
929
|
+
|
|
930
|
+
// Gaussian weights for 9-tap filter
|
|
931
|
+
let weights = array<f32, 9>(
|
|
932
|
+
0.01621622, 0.05405405, 0.12162162,
|
|
933
|
+
0.19459459, 0.22702703,
|
|
934
|
+
0.19459459, 0.12162162, 0.05405405, 0.01621622
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
let offsets = array<f32, 9>(-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0);
|
|
938
|
+
|
|
939
|
+
for (var i = 0u; i < 9u; i++) {
|
|
940
|
+
let offset = offsets[i] * texelSize * blurUniforms.direction;
|
|
941
|
+
result += textureSample(inputTexture, inputSampler, input.uv + offset) * weights[i];
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
1081
946
|
`,
|
|
1082
947
|
});
|
|
1083
948
|
// Bloom composition shader (combines original scene with bloom)
|
|
1084
949
|
const bloomComposeShader = this.device.createShaderModule({
|
|
1085
950
|
label: "bloom compose",
|
|
1086
|
-
code: /* wgsl */ `
|
|
1087
|
-
struct VertexOutput {
|
|
1088
|
-
@builtin(position) position: vec4f,
|
|
1089
|
-
@location(0) uv: vec2f,
|
|
1090
|
-
};
|
|
1091
|
-
|
|
1092
|
-
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
1093
|
-
var output: VertexOutput;
|
|
1094
|
-
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
1095
|
-
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
1096
|
-
output.position = vec4f(x, y, 0.0, 1.0);
|
|
1097
|
-
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
1098
|
-
return output;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
struct BloomComposeUniforms {
|
|
1102
|
-
intensity: f32,
|
|
1103
|
-
_padding1: f32,
|
|
1104
|
-
_padding2: f32,
|
|
1105
|
-
_padding3: f32,
|
|
1106
|
-
_padding4: f32,
|
|
1107
|
-
_padding5: f32,
|
|
1108
|
-
_padding6: f32,
|
|
1109
|
-
_padding7: f32,
|
|
1110
|
-
};
|
|
1111
|
-
|
|
1112
|
-
@group(0) @binding(0) var sceneTexture: texture_2d<f32>;
|
|
1113
|
-
@group(0) @binding(1) var sceneSampler: sampler;
|
|
1114
|
-
@group(0) @binding(2) var bloomTexture: texture_2d<f32>;
|
|
1115
|
-
@group(0) @binding(3) var bloomSampler: sampler;
|
|
1116
|
-
@group(0) @binding(4) var<uniform> composeUniforms: BloomComposeUniforms;
|
|
1117
|
-
|
|
1118
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
1119
|
-
let scene = textureSample(sceneTexture, sceneSampler, input.uv);
|
|
1120
|
-
let bloom = textureSample(bloomTexture, bloomSampler, input.uv);
|
|
1121
|
-
// Additive blending with intensity control
|
|
1122
|
-
let result = scene.rgb + bloom.rgb * composeUniforms.intensity;
|
|
1123
|
-
return vec4f(result, scene.a);
|
|
1124
|
-
}
|
|
951
|
+
code: /* wgsl */ `
|
|
952
|
+
struct VertexOutput {
|
|
953
|
+
@builtin(position) position: vec4f,
|
|
954
|
+
@location(0) uv: vec2f,
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
958
|
+
var output: VertexOutput;
|
|
959
|
+
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
960
|
+
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
961
|
+
output.position = vec4f(x, y, 0.0, 1.0);
|
|
962
|
+
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
963
|
+
return output;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
struct BloomComposeUniforms {
|
|
967
|
+
intensity: f32,
|
|
968
|
+
_padding1: f32,
|
|
969
|
+
_padding2: f32,
|
|
970
|
+
_padding3: f32,
|
|
971
|
+
_padding4: f32,
|
|
972
|
+
_padding5: f32,
|
|
973
|
+
_padding6: f32,
|
|
974
|
+
_padding7: f32,
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
@group(0) @binding(0) var sceneTexture: texture_2d<f32>;
|
|
978
|
+
@group(0) @binding(1) var sceneSampler: sampler;
|
|
979
|
+
@group(0) @binding(2) var bloomTexture: texture_2d<f32>;
|
|
980
|
+
@group(0) @binding(3) var bloomSampler: sampler;
|
|
981
|
+
@group(0) @binding(4) var<uniform> composeUniforms: BloomComposeUniforms;
|
|
982
|
+
|
|
983
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
984
|
+
let scene = textureSample(sceneTexture, sceneSampler, input.uv);
|
|
985
|
+
let bloom = textureSample(bloomTexture, bloomSampler, input.uv);
|
|
986
|
+
// Additive blending with intensity control
|
|
987
|
+
let result = scene.rgb + bloom.rgb * composeUniforms.intensity;
|
|
988
|
+
return vec4f(result, scene.a);
|
|
989
|
+
}
|
|
1125
990
|
`,
|
|
1126
991
|
});
|
|
1127
992
|
// Create uniform buffer for blur direction (minimum 32 bytes for WebGPU)
|
|
@@ -1520,17 +1385,16 @@ export class Engine {
|
|
|
1520
1385
|
const materialAlpha = mat.diffuse[3];
|
|
1521
1386
|
const EPSILON = 0.001;
|
|
1522
1387
|
const isTransparent = materialAlpha < 1.0 - EPSILON;
|
|
1523
|
-
// Create material uniform data
|
|
1524
|
-
// MaterialUniforms struct: alpha, rimIntensity, rimPower, _padding1, rimColor (vec3), _padding2
|
|
1388
|
+
// Create material uniform data
|
|
1525
1389
|
const materialUniformData = new Float32Array(8);
|
|
1526
1390
|
materialUniformData[0] = materialAlpha;
|
|
1527
|
-
materialUniformData[1] =
|
|
1528
|
-
materialUniformData[2] = this.
|
|
1529
|
-
materialUniformData[3] =
|
|
1391
|
+
materialUniformData[1] = 1.0; // alphaMultiplier: 1.0 for non-hair materials
|
|
1392
|
+
materialUniformData[2] = this.rimLightIntensity;
|
|
1393
|
+
materialUniformData[3] = this.rimLightPower;
|
|
1530
1394
|
materialUniformData[4] = this.rimLightColor[0]; // rimColor.r
|
|
1531
1395
|
materialUniformData[5] = this.rimLightColor[1]; // rimColor.g
|
|
1532
1396
|
materialUniformData[6] = this.rimLightColor[2]; // rimColor.b
|
|
1533
|
-
materialUniformData[7] = 0.0; //
|
|
1397
|
+
materialUniformData[7] = 0.0; // _padding1
|
|
1534
1398
|
const materialUniformBuffer = this.device.createBuffer({
|
|
1535
1399
|
label: `material uniform: ${mat.name}`,
|
|
1536
1400
|
size: materialUniformData.byteLength,
|
|
@@ -1563,7 +1427,6 @@ export class Engine {
|
|
|
1563
1427
|
}
|
|
1564
1428
|
else if (mat.isHair) {
|
|
1565
1429
|
// For hair materials, create two bind groups: one for over-eyes (alphaMultiplier = 0.5) and one for over-non-eyes (alphaMultiplier = 1.0)
|
|
1566
|
-
// Hair MaterialUniforms struct: alpha, alphaMultiplier, rimIntensity, rimPower, rimColor (vec3), _padding1
|
|
1567
1430
|
const materialUniformDataOverEyes = new Float32Array(8);
|
|
1568
1431
|
materialUniformDataOverEyes[0] = materialAlpha;
|
|
1569
1432
|
materialUniformDataOverEyes[1] = 0.5; // alphaMultiplier: 0.5 for over-eyes
|
|
@@ -2010,16 +1873,17 @@ export class Engine {
|
|
|
2010
1873
|
}
|
|
2011
1874
|
// Update model pose and physics
|
|
2012
1875
|
updateModelPose(deltaTime) {
|
|
1876
|
+
// Step 1: Animation evaluation (computes matrices to CPU memory, no upload yet)
|
|
2013
1877
|
this.currentModel.evaluatePose();
|
|
2014
|
-
//
|
|
1878
|
+
// Step 2: Get world matrices (still in CPU memory)
|
|
2015
1879
|
const worldMats = this.currentModel.getBoneWorldMatrices();
|
|
2016
|
-
|
|
1880
|
+
// Step 3: Physics modifies matrices in-place
|
|
2017
1881
|
if (this.physics) {
|
|
2018
1882
|
this.physics.step(deltaTime, worldMats, this.currentModel.getBoneInverseBindMatrices());
|
|
2019
|
-
// Re-upload world matrices after physics (physics may have updated bones)
|
|
2020
|
-
this.device.queue.writeBuffer(this.worldMatrixBuffer, 0, worldMats.buffer, worldMats.byteOffset, worldMats.byteLength);
|
|
2021
1883
|
}
|
|
2022
|
-
//
|
|
1884
|
+
// Step 4: Upload ONCE with final result (animation + physics)
|
|
1885
|
+
this.device.queue.writeBuffer(this.worldMatrixBuffer, 0, worldMats.buffer, worldMats.byteOffset, worldMats.byteLength);
|
|
1886
|
+
// Step 5: GPU skinning
|
|
2023
1887
|
this.computeSkinMatrices();
|
|
2024
1888
|
}
|
|
2025
1889
|
// Compute skin matrices on GPU
|
|
@@ -2123,20 +1987,56 @@ export class Engine {
|
|
|
2123
1987
|
if (skeleton)
|
|
2124
1988
|
bufferMemoryBytes += Math.max(256, skeleton.bones.length * 16 * 4);
|
|
2125
1989
|
}
|
|
1990
|
+
if (this.worldMatrixBuffer) {
|
|
1991
|
+
const skeleton = this.currentModel?.getSkeleton();
|
|
1992
|
+
if (skeleton)
|
|
1993
|
+
bufferMemoryBytes += Math.max(256, skeleton.bones.length * 16 * 4);
|
|
1994
|
+
}
|
|
1995
|
+
if (this.inverseBindMatrixBuffer) {
|
|
1996
|
+
const skeleton = this.currentModel?.getSkeleton();
|
|
1997
|
+
if (skeleton)
|
|
1998
|
+
bufferMemoryBytes += Math.max(256, skeleton.bones.length * 16 * 4);
|
|
1999
|
+
}
|
|
2126
2000
|
bufferMemoryBytes += 40 * 4; // cameraUniformBuffer
|
|
2127
2001
|
bufferMemoryBytes += 64 * 4; // lightUniformBuffer
|
|
2002
|
+
bufferMemoryBytes += 32; // boneCountBuffer
|
|
2003
|
+
bufferMemoryBytes += 32; // blurDirectionBuffer
|
|
2004
|
+
bufferMemoryBytes += 32; // bloomIntensityBuffer
|
|
2005
|
+
bufferMemoryBytes += 32; // bloomThresholdBuffer
|
|
2006
|
+
if (this.fullscreenQuadBuffer) {
|
|
2007
|
+
bufferMemoryBytes += 24 * 4; // fullscreenQuadBuffer (6 vertices * 4 floats)
|
|
2008
|
+
}
|
|
2009
|
+
// Material uniform buffers: Float32Array(8) = 32 bytes each
|
|
2128
2010
|
const totalMaterialDraws = this.opaqueNonEyeNonHairDraws.length +
|
|
2129
2011
|
this.eyeDraws.length +
|
|
2130
2012
|
this.hairDrawsOverEyes.length +
|
|
2131
2013
|
this.hairDrawsOverNonEyes.length +
|
|
2132
2014
|
this.transparentNonEyeNonHairDraws.length;
|
|
2133
|
-
bufferMemoryBytes += totalMaterialDraws *
|
|
2015
|
+
bufferMemoryBytes += totalMaterialDraws * 32; // Material uniform buffers (8 floats = 32 bytes)
|
|
2016
|
+
// Outline material uniform buffers: Float32Array(8) = 32 bytes each
|
|
2017
|
+
const totalOutlineDraws = this.opaqueNonEyeNonHairOutlineDraws.length +
|
|
2018
|
+
this.eyeOutlineDraws.length +
|
|
2019
|
+
this.hairOutlineDraws.length +
|
|
2020
|
+
this.transparentNonEyeNonHairOutlineDraws.length;
|
|
2021
|
+
bufferMemoryBytes += totalOutlineDraws * 32; // Outline material uniform buffers
|
|
2134
2022
|
let renderTargetMemoryBytes = 0;
|
|
2135
2023
|
if (this.multisampleTexture) {
|
|
2136
2024
|
const width = this.canvas.width;
|
|
2137
2025
|
const height = this.canvas.height;
|
|
2138
2026
|
renderTargetMemoryBytes += width * height * 4 * this.sampleCount; // multisample color
|
|
2139
|
-
renderTargetMemoryBytes += width * height * 4; // depth
|
|
2027
|
+
renderTargetMemoryBytes += width * height * 4; // depth (depth24plus-stencil8 = 4 bytes)
|
|
2028
|
+
}
|
|
2029
|
+
if (this.sceneRenderTexture) {
|
|
2030
|
+
const width = this.canvas.width;
|
|
2031
|
+
const height = this.canvas.height;
|
|
2032
|
+
renderTargetMemoryBytes += width * height * 4; // sceneRenderTexture (non-multisampled)
|
|
2033
|
+
}
|
|
2034
|
+
if (this.bloomExtractTexture) {
|
|
2035
|
+
const width = Math.floor(this.canvas.width / 2);
|
|
2036
|
+
const height = Math.floor(this.canvas.height / 2);
|
|
2037
|
+
renderTargetMemoryBytes += width * height * 4; // bloomExtractTexture
|
|
2038
|
+
renderTargetMemoryBytes += width * height * 4; // bloomBlurTexture1
|
|
2039
|
+
renderTargetMemoryBytes += width * height * 4; // bloomBlurTexture2
|
|
2140
2040
|
}
|
|
2141
2041
|
const totalGPUMemoryBytes = textureMemoryBytes + bufferMemoryBytes + renderTargetMemoryBytes;
|
|
2142
2042
|
this.stats.gpuMemory = Math.round((totalGPUMemoryBytes / 1024 / 1024) * 100) / 100;
|