reze-engine 0.2.2 → 0.2.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 +104 -104
- package/dist/engine.d.ts +3 -6
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +402 -398
- package/package.json +1 -1
- package/src/camera.ts +358 -358
- package/src/engine.ts +2475 -2472
- package/src/math.ts +546 -546
- package/src/model.ts +421 -421
- package/src/pmx-loader.ts +1054 -1054
- package/src/vmd-loader.ts +179 -179
package/dist/engine.js
CHANGED
|
@@ -18,7 +18,6 @@ export class Engine {
|
|
|
18
18
|
// Rim light settings
|
|
19
19
|
this.rimLightIntensity = 0.45;
|
|
20
20
|
this.rimLightPower = 2.0;
|
|
21
|
-
this.rimLightColor = [1.0, 1.0, 1.0];
|
|
22
21
|
this.currentModel = null;
|
|
23
22
|
this.modelDir = "";
|
|
24
23
|
this.physics = null;
|
|
@@ -50,11 +49,9 @@ export class Engine {
|
|
|
50
49
|
this.transparentNonEyeNonHairOutlineDraws = [];
|
|
51
50
|
this.canvas = canvas;
|
|
52
51
|
if (options) {
|
|
53
|
-
this.ambient = options.ambient;
|
|
54
|
-
this.
|
|
55
|
-
this.
|
|
56
|
-
this.rimLightIntensity = options.rimLightIntensity;
|
|
57
|
-
this.rimLightPower = options.rimLightPower;
|
|
52
|
+
this.ambient = options.ambient ?? 1.0;
|
|
53
|
+
this.bloomIntensity = options.bloomIntensity ?? 0.12;
|
|
54
|
+
this.rimLightIntensity = options.rimLightIntensity ?? 0.45;
|
|
58
55
|
}
|
|
59
56
|
}
|
|
60
57
|
// Step 1: Get WebGPU device and context
|
|
@@ -93,125 +90,125 @@ export class Engine {
|
|
|
93
90
|
});
|
|
94
91
|
const shaderModule = this.device.createShaderModule({
|
|
95
92
|
label: "model shaders",
|
|
96
|
-
code: /* wgsl */ `
|
|
97
|
-
struct CameraUniforms {
|
|
98
|
-
view: mat4x4f,
|
|
99
|
-
projection: mat4x4f,
|
|
100
|
-
viewPos: vec3f,
|
|
101
|
-
_padding: f32,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
struct Light {
|
|
105
|
-
direction: vec3f,
|
|
106
|
-
_padding1: f32,
|
|
107
|
-
color: vec3f,
|
|
108
|
-
intensity: f32,
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
struct LightUniforms {
|
|
112
|
-
ambient: f32,
|
|
113
|
-
lightCount: f32,
|
|
114
|
-
_padding1: f32,
|
|
115
|
-
_padding2: f32,
|
|
116
|
-
lights: array<Light, 4>,
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
struct MaterialUniforms {
|
|
120
|
-
alpha: f32,
|
|
121
|
-
alphaMultiplier: f32,
|
|
122
|
-
rimIntensity: f32,
|
|
123
|
-
rimPower: f32,
|
|
124
|
-
rimColor: vec3f,
|
|
125
|
-
isOverEyes: f32, // 1.0 if rendering over eyes, 0.0 otherwise
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
struct VertexOutput {
|
|
129
|
-
@builtin(position) position: vec4f,
|
|
130
|
-
@location(0) normal: vec3f,
|
|
131
|
-
@location(1) uv: vec2f,
|
|
132
|
-
@location(2) worldPos: vec3f,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
136
|
-
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
137
|
-
@group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
|
|
138
|
-
@group(0) @binding(3) var diffuseSampler: sampler;
|
|
139
|
-
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
140
|
-
@group(0) @binding(5) var toonTexture: texture_2d<f32>;
|
|
141
|
-
@group(0) @binding(6) var toonSampler: sampler;
|
|
142
|
-
@group(0) @binding(7) var<uniform> material: MaterialUniforms;
|
|
143
|
-
|
|
144
|
-
@vertex fn vs(
|
|
145
|
-
@location(0) position: vec3f,
|
|
146
|
-
@location(1) normal: vec3f,
|
|
147
|
-
@location(2) uv: vec2f,
|
|
148
|
-
@location(3) joints0: vec4<u32>,
|
|
149
|
-
@location(4) weights0: vec4<f32>
|
|
150
|
-
) -> VertexOutput {
|
|
151
|
-
var output: VertexOutput;
|
|
152
|
-
let pos4 = vec4f(position, 1.0);
|
|
153
|
-
|
|
154
|
-
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
155
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
156
|
-
var normalizedWeights: vec4f;
|
|
157
|
-
if (weightSum > 0.0001) {
|
|
158
|
-
normalizedWeights = weights0 / weightSum;
|
|
159
|
-
} else {
|
|
160
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
164
|
-
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
165
|
-
for (var i = 0u; i < 4u; i++) {
|
|
166
|
-
let j = joints0[i];
|
|
167
|
-
let w = normalizedWeights[i];
|
|
168
|
-
let m = skinMats[j];
|
|
169
|
-
skinnedPos += (m * pos4) * w;
|
|
170
|
-
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
171
|
-
skinnedNrm += (r3 * normal) * w;
|
|
172
|
-
}
|
|
173
|
-
let worldPos = skinnedPos.xyz;
|
|
174
|
-
output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
175
|
-
output.normal = normalize(skinnedNrm);
|
|
176
|
-
output.uv = uv;
|
|
177
|
-
output.worldPos = worldPos;
|
|
178
|
-
return output;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
182
|
-
let n = normalize(input.normal);
|
|
183
|
-
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
184
|
-
|
|
185
|
-
var lightAccum = vec3f(light.ambient);
|
|
186
|
-
let numLights = u32(light.lightCount);
|
|
187
|
-
for (var i = 0u; i < numLights; i++) {
|
|
188
|
-
let l = -light.lights[i].direction;
|
|
189
|
-
let nDotL = max(dot(n, l), 0.0);
|
|
190
|
-
let toonUV = vec2f(nDotL, 0.5);
|
|
191
|
-
let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
|
|
192
|
-
let radiance = light.lights[i].color * light.lights[i].intensity;
|
|
193
|
-
lightAccum += toonFactor * radiance * nDotL;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Rim light calculation
|
|
197
|
-
let viewDir = normalize(camera.viewPos - input.worldPos);
|
|
198
|
-
var rimFactor = 1.0 - max(dot(n, viewDir), 0.0);
|
|
199
|
-
rimFactor = pow(rimFactor, material.rimPower);
|
|
200
|
-
let rimLight = material.rimColor * material.rimIntensity * rimFactor;
|
|
201
|
-
|
|
202
|
-
let color = albedo * lightAccum + rimLight;
|
|
203
|
-
|
|
204
|
-
var finalAlpha = material.alpha * material.alphaMultiplier;
|
|
205
|
-
if (material.isOverEyes > 0.5) {
|
|
206
|
-
finalAlpha *= 0.5; // Hair over eyes gets 50% alpha
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (finalAlpha < 0.001) {
|
|
210
|
-
discard;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
214
|
-
}
|
|
93
|
+
code: /* wgsl */ `
|
|
94
|
+
struct CameraUniforms {
|
|
95
|
+
view: mat4x4f,
|
|
96
|
+
projection: mat4x4f,
|
|
97
|
+
viewPos: vec3f,
|
|
98
|
+
_padding: f32,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
struct Light {
|
|
102
|
+
direction: vec3f,
|
|
103
|
+
_padding1: f32,
|
|
104
|
+
color: vec3f,
|
|
105
|
+
intensity: f32,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
struct LightUniforms {
|
|
109
|
+
ambient: f32,
|
|
110
|
+
lightCount: f32,
|
|
111
|
+
_padding1: f32,
|
|
112
|
+
_padding2: f32,
|
|
113
|
+
lights: array<Light, 4>,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
struct MaterialUniforms {
|
|
117
|
+
alpha: f32,
|
|
118
|
+
alphaMultiplier: f32,
|
|
119
|
+
rimIntensity: f32,
|
|
120
|
+
rimPower: f32,
|
|
121
|
+
rimColor: vec3f,
|
|
122
|
+
isOverEyes: f32, // 1.0 if rendering over eyes, 0.0 otherwise
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
struct VertexOutput {
|
|
126
|
+
@builtin(position) position: vec4f,
|
|
127
|
+
@location(0) normal: vec3f,
|
|
128
|
+
@location(1) uv: vec2f,
|
|
129
|
+
@location(2) worldPos: vec3f,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
133
|
+
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
134
|
+
@group(0) @binding(2) var diffuseTexture: texture_2d<f32>;
|
|
135
|
+
@group(0) @binding(3) var diffuseSampler: sampler;
|
|
136
|
+
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
137
|
+
@group(0) @binding(5) var toonTexture: texture_2d<f32>;
|
|
138
|
+
@group(0) @binding(6) var toonSampler: sampler;
|
|
139
|
+
@group(0) @binding(7) var<uniform> material: MaterialUniforms;
|
|
140
|
+
|
|
141
|
+
@vertex fn vs(
|
|
142
|
+
@location(0) position: vec3f,
|
|
143
|
+
@location(1) normal: vec3f,
|
|
144
|
+
@location(2) uv: vec2f,
|
|
145
|
+
@location(3) joints0: vec4<u32>,
|
|
146
|
+
@location(4) weights0: vec4<f32>
|
|
147
|
+
) -> VertexOutput {
|
|
148
|
+
var output: VertexOutput;
|
|
149
|
+
let pos4 = vec4f(position, 1.0);
|
|
150
|
+
|
|
151
|
+
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
152
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
153
|
+
var normalizedWeights: vec4f;
|
|
154
|
+
if (weightSum > 0.0001) {
|
|
155
|
+
normalizedWeights = weights0 / weightSum;
|
|
156
|
+
} else {
|
|
157
|
+
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
161
|
+
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
162
|
+
for (var i = 0u; i < 4u; i++) {
|
|
163
|
+
let j = joints0[i];
|
|
164
|
+
let w = normalizedWeights[i];
|
|
165
|
+
let m = skinMats[j];
|
|
166
|
+
skinnedPos += (m * pos4) * w;
|
|
167
|
+
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
168
|
+
skinnedNrm += (r3 * normal) * w;
|
|
169
|
+
}
|
|
170
|
+
let worldPos = skinnedPos.xyz;
|
|
171
|
+
output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
172
|
+
output.normal = normalize(skinnedNrm);
|
|
173
|
+
output.uv = uv;
|
|
174
|
+
output.worldPos = worldPos;
|
|
175
|
+
return output;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
179
|
+
let n = normalize(input.normal);
|
|
180
|
+
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
181
|
+
|
|
182
|
+
var lightAccum = vec3f(light.ambient);
|
|
183
|
+
let numLights = u32(light.lightCount);
|
|
184
|
+
for (var i = 0u; i < numLights; i++) {
|
|
185
|
+
let l = -light.lights[i].direction;
|
|
186
|
+
let nDotL = max(dot(n, l), 0.0);
|
|
187
|
+
let toonUV = vec2f(nDotL, 0.5);
|
|
188
|
+
let toonFactor = textureSample(toonTexture, toonSampler, toonUV).rgb;
|
|
189
|
+
let radiance = light.lights[i].color * light.lights[i].intensity;
|
|
190
|
+
lightAccum += toonFactor * radiance * nDotL;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Rim light calculation
|
|
194
|
+
let viewDir = normalize(camera.viewPos - input.worldPos);
|
|
195
|
+
var rimFactor = 1.0 - max(dot(n, viewDir), 0.0);
|
|
196
|
+
rimFactor = pow(rimFactor, material.rimPower);
|
|
197
|
+
let rimLight = material.rimColor * material.rimIntensity * rimFactor;
|
|
198
|
+
|
|
199
|
+
let color = albedo * lightAccum + rimLight;
|
|
200
|
+
|
|
201
|
+
var finalAlpha = material.alpha * material.alphaMultiplier;
|
|
202
|
+
if (material.isOverEyes > 0.5) {
|
|
203
|
+
finalAlpha *= 0.5; // Hair over eyes gets 50% alpha
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (finalAlpha < 0.001) {
|
|
207
|
+
discard;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
211
|
+
}
|
|
215
212
|
`,
|
|
216
213
|
});
|
|
217
214
|
// Create explicit bind group layout for all pipelines using the main shader
|
|
@@ -302,77 +299,77 @@ export class Engine {
|
|
|
302
299
|
});
|
|
303
300
|
const outlineShaderModule = this.device.createShaderModule({
|
|
304
301
|
label: "outline shaders",
|
|
305
|
-
code: /* wgsl */ `
|
|
306
|
-
struct CameraUniforms {
|
|
307
|
-
view: mat4x4f,
|
|
308
|
-
projection: mat4x4f,
|
|
309
|
-
viewPos: vec3f,
|
|
310
|
-
_padding: f32,
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
struct MaterialUniforms {
|
|
314
|
-
edgeColor: vec4f,
|
|
315
|
-
edgeSize: f32,
|
|
316
|
-
isOverEyes: f32, // 1.0 if rendering over eyes, 0.0 otherwise (for hair outlines)
|
|
317
|
-
_padding1: f32,
|
|
318
|
-
_padding2: f32,
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
322
|
-
@group(0) @binding(1) var<uniform> material: MaterialUniforms;
|
|
323
|
-
@group(0) @binding(2) var<storage, read> skinMats: array<mat4x4f>;
|
|
324
|
-
|
|
325
|
-
struct VertexOutput {
|
|
326
|
-
@builtin(position) position: vec4f,
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
@vertex fn vs(
|
|
330
|
-
@location(0) position: vec3f,
|
|
331
|
-
@location(1) normal: vec3f,
|
|
332
|
-
@location(3) joints0: vec4<u32>,
|
|
333
|
-
@location(4) weights0: vec4<f32>
|
|
334
|
-
) -> VertexOutput {
|
|
335
|
-
var output: VertexOutput;
|
|
336
|
-
let pos4 = vec4f(position, 1.0);
|
|
337
|
-
|
|
338
|
-
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
339
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
340
|
-
var normalizedWeights: vec4f;
|
|
341
|
-
if (weightSum > 0.0001) {
|
|
342
|
-
normalizedWeights = weights0 / weightSum;
|
|
343
|
-
} else {
|
|
344
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
348
|
-
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
349
|
-
for (var i = 0u; i < 4u; i++) {
|
|
350
|
-
let j = joints0[i];
|
|
351
|
-
let w = normalizedWeights[i];
|
|
352
|
-
let m = skinMats[j];
|
|
353
|
-
skinnedPos += (m * pos4) * w;
|
|
354
|
-
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
355
|
-
skinnedNrm += (r3 * normal) * w;
|
|
356
|
-
}
|
|
357
|
-
let worldPos = skinnedPos.xyz;
|
|
358
|
-
let worldNormal = normalize(skinnedNrm);
|
|
359
|
-
|
|
360
|
-
// MMD invert hull: expand vertices outward along normals
|
|
361
|
-
let scaleFactor = 0.01;
|
|
362
|
-
let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
|
|
363
|
-
output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
|
|
364
|
-
return output;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
@fragment fn fs() -> @location(0) vec4f {
|
|
368
|
-
var color = material.edgeColor;
|
|
369
|
-
|
|
370
|
-
if (material.isOverEyes > 0.5) {
|
|
371
|
-
color.a *= 0.5; // Hair outlines over eyes get 50% alpha
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return color;
|
|
375
|
-
}
|
|
302
|
+
code: /* wgsl */ `
|
|
303
|
+
struct CameraUniforms {
|
|
304
|
+
view: mat4x4f,
|
|
305
|
+
projection: mat4x4f,
|
|
306
|
+
viewPos: vec3f,
|
|
307
|
+
_padding: f32,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
struct MaterialUniforms {
|
|
311
|
+
edgeColor: vec4f,
|
|
312
|
+
edgeSize: f32,
|
|
313
|
+
isOverEyes: f32, // 1.0 if rendering over eyes, 0.0 otherwise (for hair outlines)
|
|
314
|
+
_padding1: f32,
|
|
315
|
+
_padding2: f32,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
319
|
+
@group(0) @binding(1) var<uniform> material: MaterialUniforms;
|
|
320
|
+
@group(0) @binding(2) var<storage, read> skinMats: array<mat4x4f>;
|
|
321
|
+
|
|
322
|
+
struct VertexOutput {
|
|
323
|
+
@builtin(position) position: vec4f,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
@vertex fn vs(
|
|
327
|
+
@location(0) position: vec3f,
|
|
328
|
+
@location(1) normal: vec3f,
|
|
329
|
+
@location(3) joints0: vec4<u32>,
|
|
330
|
+
@location(4) weights0: vec4<f32>
|
|
331
|
+
) -> VertexOutput {
|
|
332
|
+
var output: VertexOutput;
|
|
333
|
+
let pos4 = vec4f(position, 1.0);
|
|
334
|
+
|
|
335
|
+
// Normalize weights to ensure they sum to 1.0 (handles floating-point precision issues)
|
|
336
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
337
|
+
var normalizedWeights: vec4f;
|
|
338
|
+
if (weightSum > 0.0001) {
|
|
339
|
+
normalizedWeights = weights0 / weightSum;
|
|
340
|
+
} else {
|
|
341
|
+
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
345
|
+
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
346
|
+
for (var i = 0u; i < 4u; i++) {
|
|
347
|
+
let j = joints0[i];
|
|
348
|
+
let w = normalizedWeights[i];
|
|
349
|
+
let m = skinMats[j];
|
|
350
|
+
skinnedPos += (m * pos4) * w;
|
|
351
|
+
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
352
|
+
skinnedNrm += (r3 * normal) * w;
|
|
353
|
+
}
|
|
354
|
+
let worldPos = skinnedPos.xyz;
|
|
355
|
+
let worldNormal = normalize(skinnedNrm);
|
|
356
|
+
|
|
357
|
+
// MMD invert hull: expand vertices outward along normals
|
|
358
|
+
let scaleFactor = 0.01;
|
|
359
|
+
let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
|
|
360
|
+
output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
|
|
361
|
+
return output;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@fragment fn fs() -> @location(0) vec4f {
|
|
365
|
+
var color = material.edgeColor;
|
|
366
|
+
|
|
367
|
+
if (material.isOverEyes > 0.5) {
|
|
368
|
+
color.a *= 0.5; // Hair outlines over eyes get 50% alpha
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return color;
|
|
372
|
+
}
|
|
376
373
|
`,
|
|
377
374
|
});
|
|
378
375
|
this.outlinePipeline = this.device.createRenderPipeline({
|
|
@@ -573,49 +570,49 @@ export class Engine {
|
|
|
573
570
|
// Depth-only shader for hair pre-pass (reduces overdraw by early depth rejection)
|
|
574
571
|
const depthOnlyShaderModule = this.device.createShaderModule({
|
|
575
572
|
label: "depth only shader",
|
|
576
|
-
code: /* wgsl */ `
|
|
577
|
-
struct CameraUniforms {
|
|
578
|
-
view: mat4x4f,
|
|
579
|
-
projection: mat4x4f,
|
|
580
|
-
viewPos: vec3f,
|
|
581
|
-
_padding: f32,
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
585
|
-
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
586
|
-
|
|
587
|
-
@vertex fn vs(
|
|
588
|
-
@location(0) position: vec3f,
|
|
589
|
-
@location(1) normal: vec3f,
|
|
590
|
-
@location(3) joints0: vec4<u32>,
|
|
591
|
-
@location(4) weights0: vec4<f32>
|
|
592
|
-
) -> @builtin(position) vec4f {
|
|
593
|
-
let pos4 = vec4f(position, 1.0);
|
|
594
|
-
|
|
595
|
-
// Normalize weights
|
|
596
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
597
|
-
var normalizedWeights: vec4f;
|
|
598
|
-
if (weightSum > 0.0001) {
|
|
599
|
-
normalizedWeights = weights0 / weightSum;
|
|
600
|
-
} else {
|
|
601
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
605
|
-
for (var i = 0u; i < 4u; i++) {
|
|
606
|
-
let j = joints0[i];
|
|
607
|
-
let w = normalizedWeights[i];
|
|
608
|
-
let m = skinMats[j];
|
|
609
|
-
skinnedPos += (m * pos4) * w;
|
|
610
|
-
}
|
|
611
|
-
let worldPos = skinnedPos.xyz;
|
|
612
|
-
let clipPos = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
613
|
-
return clipPos;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
@fragment fn fs() -> @location(0) vec4f {
|
|
617
|
-
return vec4f(0.0, 0.0, 0.0, 0.0); // Transparent - color writes disabled via writeMask
|
|
618
|
-
}
|
|
573
|
+
code: /* wgsl */ `
|
|
574
|
+
struct CameraUniforms {
|
|
575
|
+
view: mat4x4f,
|
|
576
|
+
projection: mat4x4f,
|
|
577
|
+
viewPos: vec3f,
|
|
578
|
+
_padding: f32,
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
582
|
+
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
583
|
+
|
|
584
|
+
@vertex fn vs(
|
|
585
|
+
@location(0) position: vec3f,
|
|
586
|
+
@location(1) normal: vec3f,
|
|
587
|
+
@location(3) joints0: vec4<u32>,
|
|
588
|
+
@location(4) weights0: vec4<f32>
|
|
589
|
+
) -> @builtin(position) vec4f {
|
|
590
|
+
let pos4 = vec4f(position, 1.0);
|
|
591
|
+
|
|
592
|
+
// Normalize weights
|
|
593
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
594
|
+
var normalizedWeights: vec4f;
|
|
595
|
+
if (weightSum > 0.0001) {
|
|
596
|
+
normalizedWeights = weights0 / weightSum;
|
|
597
|
+
} else {
|
|
598
|
+
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
602
|
+
for (var i = 0u; i < 4u; i++) {
|
|
603
|
+
let j = joints0[i];
|
|
604
|
+
let w = normalizedWeights[i];
|
|
605
|
+
let m = skinMats[j];
|
|
606
|
+
skinnedPos += (m * pos4) * w;
|
|
607
|
+
}
|
|
608
|
+
let worldPos = skinnedPos.xyz;
|
|
609
|
+
let clipPos = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
610
|
+
return clipPos;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
@fragment fn fs() -> @location(0) vec4f {
|
|
614
|
+
return vec4f(0.0, 0.0, 0.0, 0.0); // Transparent - color writes disabled via writeMask
|
|
615
|
+
}
|
|
619
616
|
`,
|
|
620
617
|
});
|
|
621
618
|
// Hair depth pre-pass pipeline: depth-only with color writes disabled to eliminate overdraw
|
|
@@ -795,31 +792,31 @@ export class Engine {
|
|
|
795
792
|
createSkinMatrixComputePipeline() {
|
|
796
793
|
const computeShader = this.device.createShaderModule({
|
|
797
794
|
label: "skin matrix compute",
|
|
798
|
-
code: /* wgsl */ `
|
|
799
|
-
struct BoneCountUniform {
|
|
800
|
-
count: u32,
|
|
801
|
-
_padding1: u32,
|
|
802
|
-
_padding2: u32,
|
|
803
|
-
_padding3: u32,
|
|
804
|
-
_padding4: vec4<u32>,
|
|
805
|
-
};
|
|
806
|
-
|
|
807
|
-
@group(0) @binding(0) var<uniform> boneCount: BoneCountUniform;
|
|
808
|
-
@group(0) @binding(1) var<storage, read> worldMatrices: array<mat4x4f>;
|
|
809
|
-
@group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
|
|
810
|
-
@group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
|
|
811
|
-
|
|
812
|
-
@compute @workgroup_size(64)
|
|
813
|
-
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
814
|
-
let boneIndex = globalId.x;
|
|
815
|
-
// Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
|
|
816
|
-
if (boneIndex >= boneCount.count) {
|
|
817
|
-
return;
|
|
818
|
-
}
|
|
819
|
-
let worldMat = worldMatrices[boneIndex];
|
|
820
|
-
let invBindMat = inverseBindMatrices[boneIndex];
|
|
821
|
-
skinMatrices[boneIndex] = worldMat * invBindMat;
|
|
822
|
-
}
|
|
795
|
+
code: /* wgsl */ `
|
|
796
|
+
struct BoneCountUniform {
|
|
797
|
+
count: u32,
|
|
798
|
+
_padding1: u32,
|
|
799
|
+
_padding2: u32,
|
|
800
|
+
_padding3: u32,
|
|
801
|
+
_padding4: vec4<u32>,
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
@group(0) @binding(0) var<uniform> boneCount: BoneCountUniform;
|
|
805
|
+
@group(0) @binding(1) var<storage, read> worldMatrices: array<mat4x4f>;
|
|
806
|
+
@group(0) @binding(2) var<storage, read> inverseBindMatrices: array<mat4x4f>;
|
|
807
|
+
@group(0) @binding(3) var<storage, read_write> skinMatrices: array<mat4x4f>;
|
|
808
|
+
|
|
809
|
+
@compute @workgroup_size(64)
|
|
810
|
+
fn main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
811
|
+
let boneIndex = globalId.x;
|
|
812
|
+
// Bounds check: we dispatch workgroups (64 threads each), so some threads may be out of range
|
|
813
|
+
if (boneIndex >= boneCount.count) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
let worldMat = worldMatrices[boneIndex];
|
|
817
|
+
let invBindMat = inverseBindMatrices[boneIndex];
|
|
818
|
+
skinMatrices[boneIndex] = worldMat * invBindMat;
|
|
819
|
+
}
|
|
823
820
|
`,
|
|
824
821
|
});
|
|
825
822
|
this.skinMatrixComputePipeline = this.device.createComputePipeline({
|
|
@@ -873,143 +870,143 @@ export class Engine {
|
|
|
873
870
|
// Bloom extraction shader (extracts bright areas)
|
|
874
871
|
const bloomExtractShader = this.device.createShaderModule({
|
|
875
872
|
label: "bloom extract",
|
|
876
|
-
code: /* wgsl */ `
|
|
877
|
-
struct VertexOutput {
|
|
878
|
-
@builtin(position) position: vec4f,
|
|
879
|
-
@location(0) uv: vec2f,
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
883
|
-
var output: VertexOutput;
|
|
884
|
-
// Generate fullscreen quad from vertex index
|
|
885
|
-
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
886
|
-
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
887
|
-
output.position = vec4f(x, y, 0.0, 1.0);
|
|
888
|
-
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
889
|
-
return output;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
struct BloomExtractUniforms {
|
|
893
|
-
threshold: f32,
|
|
894
|
-
_padding1: f32,
|
|
895
|
-
_padding2: f32,
|
|
896
|
-
_padding3: f32,
|
|
897
|
-
_padding4: f32,
|
|
898
|
-
_padding5: f32,
|
|
899
|
-
_padding6: f32,
|
|
900
|
-
_padding7: f32,
|
|
901
|
-
};
|
|
902
|
-
|
|
903
|
-
@group(0) @binding(0) var inputTexture: texture_2d<f32>;
|
|
904
|
-
@group(0) @binding(1) var inputSampler: sampler;
|
|
905
|
-
@group(0) @binding(2) var<uniform> extractUniforms: BloomExtractUniforms;
|
|
906
|
-
|
|
907
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
908
|
-
let color = textureSample(inputTexture, inputSampler, input.uv);
|
|
909
|
-
// Extract bright areas above threshold
|
|
910
|
-
let threshold = extractUniforms.threshold;
|
|
911
|
-
let bloom = max(vec3f(0.0), color.rgb - vec3f(threshold)) / max(0.001, 1.0 - threshold);
|
|
912
|
-
return vec4f(bloom, color.a);
|
|
913
|
-
}
|
|
873
|
+
code: /* wgsl */ `
|
|
874
|
+
struct VertexOutput {
|
|
875
|
+
@builtin(position) position: vec4f,
|
|
876
|
+
@location(0) uv: vec2f,
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
880
|
+
var output: VertexOutput;
|
|
881
|
+
// Generate fullscreen quad from vertex index
|
|
882
|
+
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
883
|
+
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
884
|
+
output.position = vec4f(x, y, 0.0, 1.0);
|
|
885
|
+
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
886
|
+
return output;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
struct BloomExtractUniforms {
|
|
890
|
+
threshold: f32,
|
|
891
|
+
_padding1: f32,
|
|
892
|
+
_padding2: f32,
|
|
893
|
+
_padding3: f32,
|
|
894
|
+
_padding4: f32,
|
|
895
|
+
_padding5: f32,
|
|
896
|
+
_padding6: f32,
|
|
897
|
+
_padding7: f32,
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
@group(0) @binding(0) var inputTexture: texture_2d<f32>;
|
|
901
|
+
@group(0) @binding(1) var inputSampler: sampler;
|
|
902
|
+
@group(0) @binding(2) var<uniform> extractUniforms: BloomExtractUniforms;
|
|
903
|
+
|
|
904
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
905
|
+
let color = textureSample(inputTexture, inputSampler, input.uv);
|
|
906
|
+
// Extract bright areas above threshold
|
|
907
|
+
let threshold = extractUniforms.threshold;
|
|
908
|
+
let bloom = max(vec3f(0.0), color.rgb - vec3f(threshold)) / max(0.001, 1.0 - threshold);
|
|
909
|
+
return vec4f(bloom, color.a);
|
|
910
|
+
}
|
|
914
911
|
`,
|
|
915
912
|
});
|
|
916
913
|
// Bloom blur shader (gaussian blur - can be used for both horizontal and vertical)
|
|
917
914
|
const bloomBlurShader = this.device.createShaderModule({
|
|
918
915
|
label: "bloom blur",
|
|
919
|
-
code: /* wgsl */ `
|
|
920
|
-
struct VertexOutput {
|
|
921
|
-
@builtin(position) position: vec4f,
|
|
922
|
-
@location(0) uv: vec2f,
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
926
|
-
var output: VertexOutput;
|
|
927
|
-
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
928
|
-
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
929
|
-
output.position = vec4f(x, y, 0.0, 1.0);
|
|
930
|
-
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
931
|
-
return output;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
struct BlurUniforms {
|
|
935
|
-
direction: vec2f,
|
|
936
|
-
_padding1: f32,
|
|
937
|
-
_padding2: f32,
|
|
938
|
-
_padding3: f32,
|
|
939
|
-
_padding4: f32,
|
|
940
|
-
_padding5: f32,
|
|
941
|
-
_padding6: f32,
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
@group(0) @binding(0) var inputTexture: texture_2d<f32>;
|
|
945
|
-
@group(0) @binding(1) var inputSampler: sampler;
|
|
946
|
-
@group(0) @binding(2) var<uniform> blurUniforms: BlurUniforms;
|
|
947
|
-
|
|
948
|
-
// 9-tap gaussian blur
|
|
949
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
950
|
-
let texelSize = 1.0 / vec2f(textureDimensions(inputTexture));
|
|
951
|
-
var result = vec4f(0.0);
|
|
952
|
-
|
|
953
|
-
// Gaussian weights for 9-tap filter
|
|
954
|
-
let weights = array<f32, 9>(
|
|
955
|
-
0.01621622, 0.05405405, 0.12162162,
|
|
956
|
-
0.19459459, 0.22702703,
|
|
957
|
-
0.19459459, 0.12162162, 0.05405405, 0.01621622
|
|
958
|
-
);
|
|
959
|
-
|
|
960
|
-
let offsets = array<f32, 9>(-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0);
|
|
961
|
-
|
|
962
|
-
for (var i = 0u; i < 9u; i++) {
|
|
963
|
-
let offset = offsets[i] * texelSize * blurUniforms.direction;
|
|
964
|
-
result += textureSample(inputTexture, inputSampler, input.uv + offset) * weights[i];
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
return result;
|
|
968
|
-
}
|
|
916
|
+
code: /* wgsl */ `
|
|
917
|
+
struct VertexOutput {
|
|
918
|
+
@builtin(position) position: vec4f,
|
|
919
|
+
@location(0) uv: vec2f,
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
923
|
+
var output: VertexOutput;
|
|
924
|
+
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
925
|
+
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
926
|
+
output.position = vec4f(x, y, 0.0, 1.0);
|
|
927
|
+
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
928
|
+
return output;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
struct BlurUniforms {
|
|
932
|
+
direction: vec2f,
|
|
933
|
+
_padding1: f32,
|
|
934
|
+
_padding2: f32,
|
|
935
|
+
_padding3: f32,
|
|
936
|
+
_padding4: f32,
|
|
937
|
+
_padding5: f32,
|
|
938
|
+
_padding6: f32,
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
@group(0) @binding(0) var inputTexture: texture_2d<f32>;
|
|
942
|
+
@group(0) @binding(1) var inputSampler: sampler;
|
|
943
|
+
@group(0) @binding(2) var<uniform> blurUniforms: BlurUniforms;
|
|
944
|
+
|
|
945
|
+
// 9-tap gaussian blur
|
|
946
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
947
|
+
let texelSize = 1.0 / vec2f(textureDimensions(inputTexture));
|
|
948
|
+
var result = vec4f(0.0);
|
|
949
|
+
|
|
950
|
+
// Gaussian weights for 9-tap filter
|
|
951
|
+
let weights = array<f32, 9>(
|
|
952
|
+
0.01621622, 0.05405405, 0.12162162,
|
|
953
|
+
0.19459459, 0.22702703,
|
|
954
|
+
0.19459459, 0.12162162, 0.05405405, 0.01621622
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
let offsets = array<f32, 9>(-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0);
|
|
958
|
+
|
|
959
|
+
for (var i = 0u; i < 9u; i++) {
|
|
960
|
+
let offset = offsets[i] * texelSize * blurUniforms.direction;
|
|
961
|
+
result += textureSample(inputTexture, inputSampler, input.uv + offset) * weights[i];
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return result;
|
|
965
|
+
}
|
|
969
966
|
`,
|
|
970
967
|
});
|
|
971
968
|
// Bloom composition shader (combines original scene with bloom)
|
|
972
969
|
const bloomComposeShader = this.device.createShaderModule({
|
|
973
970
|
label: "bloom compose",
|
|
974
|
-
code: /* wgsl */ `
|
|
975
|
-
struct VertexOutput {
|
|
976
|
-
@builtin(position) position: vec4f,
|
|
977
|
-
@location(0) uv: vec2f,
|
|
978
|
-
};
|
|
979
|
-
|
|
980
|
-
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
981
|
-
var output: VertexOutput;
|
|
982
|
-
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
983
|
-
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
984
|
-
output.position = vec4f(x, y, 0.0, 1.0);
|
|
985
|
-
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
986
|
-
return output;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
struct BloomComposeUniforms {
|
|
990
|
-
intensity: f32,
|
|
991
|
-
_padding1: f32,
|
|
992
|
-
_padding2: f32,
|
|
993
|
-
_padding3: f32,
|
|
994
|
-
_padding4: f32,
|
|
995
|
-
_padding5: f32,
|
|
996
|
-
_padding6: f32,
|
|
997
|
-
_padding7: f32,
|
|
998
|
-
};
|
|
999
|
-
|
|
1000
|
-
@group(0) @binding(0) var sceneTexture: texture_2d<f32>;
|
|
1001
|
-
@group(0) @binding(1) var sceneSampler: sampler;
|
|
1002
|
-
@group(0) @binding(2) var bloomTexture: texture_2d<f32>;
|
|
1003
|
-
@group(0) @binding(3) var bloomSampler: sampler;
|
|
1004
|
-
@group(0) @binding(4) var<uniform> composeUniforms: BloomComposeUniforms;
|
|
1005
|
-
|
|
1006
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
1007
|
-
let scene = textureSample(sceneTexture, sceneSampler, input.uv);
|
|
1008
|
-
let bloom = textureSample(bloomTexture, bloomSampler, input.uv);
|
|
1009
|
-
// Additive blending with intensity control
|
|
1010
|
-
let result = scene.rgb + bloom.rgb * composeUniforms.intensity;
|
|
1011
|
-
return vec4f(result, scene.a);
|
|
1012
|
-
}
|
|
971
|
+
code: /* wgsl */ `
|
|
972
|
+
struct VertexOutput {
|
|
973
|
+
@builtin(position) position: vec4f,
|
|
974
|
+
@location(0) uv: vec2f,
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
978
|
+
var output: VertexOutput;
|
|
979
|
+
let x = f32((vertexIndex << 1u) & 2u) * 2.0 - 1.0;
|
|
980
|
+
let y = f32(vertexIndex & 2u) * 2.0 - 1.0;
|
|
981
|
+
output.position = vec4f(x, y, 0.0, 1.0);
|
|
982
|
+
output.uv = vec2f(x * 0.5 + 0.5, 1.0 - (y * 0.5 + 0.5));
|
|
983
|
+
return output;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
struct BloomComposeUniforms {
|
|
987
|
+
intensity: f32,
|
|
988
|
+
_padding1: f32,
|
|
989
|
+
_padding2: f32,
|
|
990
|
+
_padding3: f32,
|
|
991
|
+
_padding4: f32,
|
|
992
|
+
_padding5: f32,
|
|
993
|
+
_padding6: f32,
|
|
994
|
+
_padding7: f32,
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
@group(0) @binding(0) var sceneTexture: texture_2d<f32>;
|
|
998
|
+
@group(0) @binding(1) var sceneSampler: sampler;
|
|
999
|
+
@group(0) @binding(2) var bloomTexture: texture_2d<f32>;
|
|
1000
|
+
@group(0) @binding(3) var bloomSampler: sampler;
|
|
1001
|
+
@group(0) @binding(4) var<uniform> composeUniforms: BloomComposeUniforms;
|
|
1002
|
+
|
|
1003
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
1004
|
+
let scene = textureSample(sceneTexture, sceneSampler, input.uv);
|
|
1005
|
+
let bloom = textureSample(bloomTexture, bloomSampler, input.uv);
|
|
1006
|
+
// Additive blending with intensity control
|
|
1007
|
+
let result = scene.rgb + bloom.rgb * composeUniforms.intensity;
|
|
1008
|
+
return vec4f(result, scene.a);
|
|
1009
|
+
}
|
|
1013
1010
|
`,
|
|
1014
1011
|
});
|
|
1015
1012
|
// Create uniform buffer for blur direction (minimum 32 bytes for WebGPU)
|
|
@@ -1273,7 +1270,6 @@ export class Engine {
|
|
|
1273
1270
|
async loadAnimation(url) {
|
|
1274
1271
|
const frames = await VMDLoader.load(url);
|
|
1275
1272
|
this.animationFrames = frames;
|
|
1276
|
-
console.log(this.animationFrames);
|
|
1277
1273
|
}
|
|
1278
1274
|
playAnimation() {
|
|
1279
1275
|
if (this.animationFrames.length === 0)
|
|
@@ -1409,6 +1405,14 @@ export class Engine {
|
|
|
1409
1405
|
const dir = pathParts.join("/") + "/";
|
|
1410
1406
|
this.modelDir = dir;
|
|
1411
1407
|
const model = await PmxLoader.load(path);
|
|
1408
|
+
// console.log({
|
|
1409
|
+
// vertices: Array.from(model.getVertices()),
|
|
1410
|
+
// indices: Array.from(model.getIndices()),
|
|
1411
|
+
// materials: model.getMaterials(),
|
|
1412
|
+
// textures: model.getTextures(),
|
|
1413
|
+
// bones: model.getSkeleton().bones,
|
|
1414
|
+
// skinning: { joints: Array.from(model.getSkinning().joints), weights: Array.from(model.getSkinning().weights) },
|
|
1415
|
+
// })
|
|
1412
1416
|
this.physics = new Physics(model.getRigidbodies(), model.getJoints());
|
|
1413
1417
|
await this.setupModelBuffers(model);
|
|
1414
1418
|
}
|
|
@@ -1566,9 +1570,9 @@ export class Engine {
|
|
|
1566
1570
|
materialUniformData[1] = 1.0; // alphaMultiplier: 1.0 for non-hair materials
|
|
1567
1571
|
materialUniformData[2] = this.rimLightIntensity;
|
|
1568
1572
|
materialUniformData[3] = this.rimLightPower;
|
|
1569
|
-
materialUniformData[4] =
|
|
1570
|
-
materialUniformData[5] =
|
|
1571
|
-
materialUniformData[6] =
|
|
1573
|
+
materialUniformData[4] = 1.0; // rimColor.r
|
|
1574
|
+
materialUniformData[5] = 1.0; // rimColor.g
|
|
1575
|
+
materialUniformData[6] = 1.0; // rimColor.b
|
|
1572
1576
|
materialUniformData[7] = 0.0;
|
|
1573
1577
|
const materialUniformBuffer = this.device.createBuffer({
|
|
1574
1578
|
label: `material uniform: ${mat.name}`,
|
|
@@ -1607,9 +1611,9 @@ export class Engine {
|
|
|
1607
1611
|
materialUniformDataHair[1] = 1.0; // alphaMultiplier: base value, shader will adjust
|
|
1608
1612
|
materialUniformDataHair[2] = this.rimLightIntensity;
|
|
1609
1613
|
materialUniformDataHair[3] = this.rimLightPower;
|
|
1610
|
-
materialUniformDataHair[4] =
|
|
1611
|
-
materialUniformDataHair[5] =
|
|
1612
|
-
materialUniformDataHair[6] =
|
|
1614
|
+
materialUniformDataHair[4] = 1.0; // rimColor.r
|
|
1615
|
+
materialUniformDataHair[5] = 1.0; // rimColor.g
|
|
1616
|
+
materialUniformDataHair[6] = 1.0; // rimColor.b
|
|
1613
1617
|
materialUniformDataHair[7] = 0.0;
|
|
1614
1618
|
// Create uniform buffers for both modes
|
|
1615
1619
|
const materialUniformBufferOverEyes = this.device.createBuffer({
|