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