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/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.bloomThreshold = options.bloomThreshold;
55
- this.bloomIntensity = options.bloomIntensity;
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] = this.rimLightColor[0]; // rimColor.r
1570
- materialUniformData[5] = this.rimLightColor[1]; // rimColor.g
1571
- materialUniformData[6] = this.rimLightColor[2]; // rimColor.b
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] = this.rimLightColor[0]; // rimColor.r
1611
- materialUniformDataHair[5] = this.rimLightColor[1]; // rimColor.g
1612
- materialUniformDataHair[6] = this.rimLightColor[2]; // rimColor.b
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({