reze-engine 0.1.9 → 0.1.10

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.d.ts CHANGED
@@ -17,7 +17,6 @@ export declare class Engine {
17
17
  private lightData;
18
18
  private lightCount;
19
19
  private vertexBuffer;
20
- private vertexCount;
21
20
  private indexBuffer?;
22
21
  private resizeObserver;
23
22
  private depthTexture;
@@ -91,7 +90,8 @@ export declare class Engine {
91
90
  private setupModelBuffers;
92
91
  private opaqueNonEyeNonHairDraws;
93
92
  private eyeDraws;
94
- private hairDraws;
93
+ private hairDrawsOverEyes;
94
+ private hairDrawsOverNonEyes;
95
95
  private transparentNonEyeNonHairDraws;
96
96
  private opaqueNonEyeNonHairOutlineDraws;
97
97
  private eyeOutlineDraws;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -7,7 +7,6 @@ export class Engine {
7
7
  this.cameraMatrixData = new Float32Array(36);
8
8
  this.lightData = new Float32Array(64);
9
9
  this.lightCount = 0;
10
- this.vertexCount = 0;
11
10
  this.resizeObserver = null;
12
11
  this.sampleCount = 4; // MSAA 4x
13
12
  // Bloom settings
@@ -33,7 +32,8 @@ export class Engine {
33
32
  this.renderLoopCallback = null;
34
33
  this.opaqueNonEyeNonHairDraws = [];
35
34
  this.eyeDraws = [];
36
- this.hairDraws = [];
35
+ this.hairDrawsOverEyes = [];
36
+ this.hairDrawsOverNonEyes = [];
37
37
  this.transparentNonEyeNonHairDraws = [];
38
38
  this.opaqueNonEyeNonHairOutlineDraws = [];
39
39
  this.eyeOutlineDraws = [];
@@ -185,9 +185,10 @@ export class Engine {
185
185
  }
186
186
  `,
187
187
  });
188
- // Create a separate shader for hair-over-eyes that outputs pre-multiplied color for darkening effect
189
- const hairMultiplyShaderModule = this.device.createShaderModule({
190
- label: "hair multiply shaders",
188
+ // Unified hair shader that can handle both over-eyes and over-non-eyes cases
189
+ // Uses material.alpha multiplier to control opacity (0.5 for over-eyes, 1.0 for over-non-eyes)
190
+ const hairShaderModule = this.device.createShaderModule({
191
+ label: "unified hair shaders",
191
192
  code: /* wgsl */ `
192
193
  struct CameraUniforms {
193
194
  view: mat4x4f,
@@ -213,9 +214,9 @@ export class Engine {
213
214
 
214
215
  struct MaterialUniforms {
215
216
  alpha: f32,
217
+ alphaMultiplier: f32, // New: multiplier for alpha (0.5 for over-eyes, 1.0 for over-non-eyes)
216
218
  _padding1: f32,
217
219
  _padding2: f32,
218
- _padding3: f32,
219
220
  };
220
221
 
221
222
  struct VertexOutput {
@@ -286,15 +287,12 @@ export class Engine {
286
287
  }
287
288
 
288
289
  let color = albedo * lightAccum;
289
- let finalAlpha = material.alpha;
290
+ let finalAlpha = material.alpha * material.alphaMultiplier;
290
291
  if (finalAlpha < 0.001) {
291
292
  discard;
292
293
  }
293
294
 
294
- // For hair-over-eyes effect: simple half-transparent overlay - Use 50% opacity to create a semi-transparent hair color overlay
295
- let overlayAlpha = finalAlpha * 0.5;
296
-
297
- return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), overlayAlpha);
295
+ return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
298
296
  }
299
297
  `,
300
298
  });
@@ -688,12 +686,13 @@ export class Engine {
688
686
  count: this.sampleCount,
689
687
  },
690
688
  });
691
- // Hair pipeline with multiplicative blending (for hair over eyes)
689
+ // Unified hair pipeline - can be used for both over-eyes and over-non-eyes
690
+ // The difference is controlled by stencil state and alpha multiplier in material uniform
692
691
  this.hairMultiplyPipeline = this.device.createRenderPipeline({
693
- label: "hair multiply pipeline",
692
+ label: "hair pipeline (over eyes)",
694
693
  layout: sharedPipelineLayout,
695
694
  vertex: {
696
- module: hairMultiplyShaderModule,
695
+ module: hairShaderModule,
697
696
  buffers: [
698
697
  {
699
698
  arrayStride: 8 * 4,
@@ -714,13 +713,12 @@ export class Engine {
714
713
  ],
715
714
  },
716
715
  fragment: {
717
- module: hairMultiplyShaderModule,
716
+ module: hairShaderModule,
718
717
  targets: [
719
718
  {
720
719
  format: this.presentationFormat,
721
720
  blend: {
722
721
  color: {
723
- // Simple half-transparent overlay effect - Blend: hairColor * overlayAlpha + eyeColor * (1 - overlayAlpha)
724
722
  srcFactor: "src-alpha",
725
723
  dstFactor: "one-minus-src-alpha",
726
724
  operation: "add",
@@ -754,12 +752,12 @@ export class Engine {
754
752
  },
755
753
  multisample: { count: this.sampleCount },
756
754
  });
757
- // Hair pipeline for opaque rendering (hair over non-eyes)
755
+ // Hair pipeline for opaque rendering (hair over non-eyes) - uses same shader, different stencil state
758
756
  this.hairOpaquePipeline = this.device.createRenderPipeline({
759
- label: "hair opaque pipeline",
757
+ label: "hair pipeline (over non-eyes)",
760
758
  layout: sharedPipelineLayout,
761
759
  vertex: {
762
- module: shaderModule,
760
+ module: hairShaderModule,
763
761
  buffers: [
764
762
  {
765
763
  arrayStride: 8 * 4,
@@ -780,7 +778,7 @@ export class Engine {
780
778
  ],
781
779
  },
782
780
  fragment: {
783
- module: shaderModule,
781
+ module: hairShaderModule,
784
782
  targets: [
785
783
  {
786
784
  format: this.presentationFormat,
@@ -1379,7 +1377,6 @@ export class Engine {
1379
1377
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
1380
1378
  });
1381
1379
  this.device.queue.writeBuffer(this.vertexBuffer, 0, vertices);
1382
- this.vertexCount = model.getVertexCount();
1383
1380
  this.jointsBuffer = this.device.createBuffer({
1384
1381
  label: "joints buffer",
1385
1382
  size: skinning.joints.byteLength,
@@ -1484,7 +1481,8 @@ export class Engine {
1484
1481
  };
1485
1482
  this.opaqueNonEyeNonHairDraws = [];
1486
1483
  this.eyeDraws = [];
1487
- this.hairDraws = [];
1484
+ this.hairDrawsOverEyes = [];
1485
+ this.hairDrawsOverNonEyes = [];
1488
1486
  this.transparentNonEyeNonHairDraws = [];
1489
1487
  this.opaqueNonEyeNonHairOutlineDraws = [];
1490
1488
  this.eyeOutlineDraws = [];
@@ -1502,9 +1500,10 @@ export class Engine {
1502
1500
  const materialAlpha = mat.diffuse[3];
1503
1501
  const EPSILON = 0.001;
1504
1502
  const isTransparent = materialAlpha < 1.0 - EPSILON;
1503
+ // Create material uniform data - for hair materials, we'll create two versions
1505
1504
  const materialUniformData = new Float32Array(4);
1506
1505
  materialUniformData[0] = materialAlpha;
1507
- materialUniformData[1] = 0.0;
1506
+ materialUniformData[1] = 1.0; // alphaMultiplier: 1.0 for normal rendering
1508
1507
  materialUniformData[2] = 0.0;
1509
1508
  materialUniformData[3] = 0.0;
1510
1509
  const materialUniformBuffer = this.device.createBuffer({
@@ -1538,7 +1537,39 @@ export class Engine {
1538
1537
  });
1539
1538
  }
1540
1539
  else if (mat.isHair) {
1541
- this.hairDraws.push({
1540
+ // For hair materials, create two bind groups: one for over-eyes (alphaMultiplier = 0.5) and one for over-non-eyes (alphaMultiplier = 1.0)
1541
+ const materialUniformDataOverEyes = new Float32Array(4);
1542
+ materialUniformDataOverEyes[0] = materialAlpha;
1543
+ materialUniformDataOverEyes[1] = 0.5; // alphaMultiplier: 0.5 for over-eyes
1544
+ materialUniformDataOverEyes[2] = 0.0;
1545
+ materialUniformDataOverEyes[3] = 0.0;
1546
+ const materialUniformBufferOverEyes = this.device.createBuffer({
1547
+ label: `material uniform (over eyes): ${mat.name}`,
1548
+ size: materialUniformDataOverEyes.byteLength,
1549
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
1550
+ });
1551
+ this.device.queue.writeBuffer(materialUniformBufferOverEyes, 0, materialUniformDataOverEyes);
1552
+ const bindGroupOverEyes = this.device.createBindGroup({
1553
+ label: `material bind group (over eyes): ${mat.name}`,
1554
+ layout: this.hairBindGroupLayout,
1555
+ entries: [
1556
+ { binding: 0, resource: { buffer: this.cameraUniformBuffer } },
1557
+ { binding: 1, resource: { buffer: this.lightUniformBuffer } },
1558
+ { binding: 2, resource: diffuseTexture.createView() },
1559
+ { binding: 3, resource: this.textureSampler },
1560
+ { binding: 4, resource: { buffer: this.skinMatrixBuffer } },
1561
+ { binding: 5, resource: toonTexture.createView() },
1562
+ { binding: 6, resource: this.textureSampler },
1563
+ { binding: 7, resource: { buffer: materialUniformBufferOverEyes } },
1564
+ ],
1565
+ });
1566
+ this.hairDrawsOverEyes.push({
1567
+ count: matCount,
1568
+ firstIndex: runningFirstIndex,
1569
+ bindGroup: bindGroupOverEyes,
1570
+ isTransparent,
1571
+ });
1572
+ this.hairDrawsOverNonEyes.push({
1542
1573
  count: matCount,
1543
1574
  firstIndex: runningFirstIndex,
1544
1575
  bindGroup,
@@ -1702,45 +1733,55 @@ export class Engine {
1702
1733
  this.drawCallCount++;
1703
1734
  }
1704
1735
  }
1705
- // PASS 3a: Hair over eyes (stencil == 1, multiply blend) - Draw hair geometry first to establish depth
1706
- pass.setPipeline(this.hairMultiplyPipeline);
1707
- pass.setStencilReference(1); // Check against stencil value 1
1708
- for (const draw of this.hairDraws) {
1709
- if (draw.count > 0) {
1710
- pass.setBindGroup(0, draw.bindGroup);
1711
- pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1712
- this.drawCallCount++;
1736
+ // PASS 3: Hair rendering - optimized single pass approach
1737
+ // Since both hair passes use the same shader, we batch them together
1738
+ // but still need separate passes due to stencil requirements (equal vs not-equal)
1739
+ this.drawOutlines(pass, false); // Opaque outlines
1740
+ // 3a: Hair over eyes (stencil == 1, alphaMultiplier = 0.5)
1741
+ if (this.hairDrawsOverEyes.length > 0) {
1742
+ pass.setPipeline(this.hairMultiplyPipeline);
1743
+ pass.setStencilReference(1);
1744
+ for (const draw of this.hairDrawsOverEyes) {
1745
+ if (draw.count > 0) {
1746
+ pass.setBindGroup(0, draw.bindGroup);
1747
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1748
+ this.drawCallCount++;
1749
+ }
1713
1750
  }
1714
1751
  }
1715
- // PASS 3a.5: Hair outlines over eyes (stencil == 1, depth test to only draw near hair)
1716
- pass.setPipeline(this.hairOutlineOverEyesPipeline);
1717
- pass.setStencilReference(1); // Check against stencil value 1 (with equal test)
1718
- for (const draw of this.hairOutlineDraws) {
1719
- if (draw.count > 0) {
1720
- pass.setBindGroup(0, draw.bindGroup);
1721
- pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1752
+ // 3b: Hair over non-eyes (stencil != 1, alphaMultiplier = 1.0)
1753
+ if (this.hairDrawsOverNonEyes.length > 0) {
1754
+ pass.setPipeline(this.hairOpaquePipeline);
1755
+ pass.setStencilReference(1);
1756
+ for (const draw of this.hairDrawsOverNonEyes) {
1757
+ if (draw.count > 0) {
1758
+ pass.setBindGroup(0, draw.bindGroup);
1759
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1760
+ this.drawCallCount++;
1761
+ }
1722
1762
  }
1723
1763
  }
1724
- // PASS 3b: Hair over non-eyes (stencil != 1, opaque)
1725
- pass.setPipeline(this.hairOpaquePipeline);
1726
- pass.setStencilReference(1); // Check against stencil value 1 (with not-equal test)
1727
- for (const draw of this.hairDraws) {
1728
- if (draw.count > 0) {
1729
- pass.setBindGroup(0, draw.bindGroup);
1730
- pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1731
- this.drawCallCount++;
1764
+ // 3c: Hair outlines - batched together, only draw if outlines exist
1765
+ if (this.hairOutlineDraws.length > 0) {
1766
+ // Over eyes
1767
+ pass.setPipeline(this.hairOutlineOverEyesPipeline);
1768
+ pass.setStencilReference(1);
1769
+ for (const draw of this.hairOutlineDraws) {
1770
+ if (draw.count > 0) {
1771
+ pass.setBindGroup(0, draw.bindGroup);
1772
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1773
+ }
1732
1774
  }
1733
- }
1734
- // PASS 3b.5: Hair outlines over non-eyes (stencil != 1) - Draw hair outlines after hair geometry, so they only appear where hair exists
1735
- pass.setPipeline(this.hairOutlinePipeline);
1736
- pass.setStencilReference(1); // Check against stencil value 1 (with not-equal test)
1737
- for (const draw of this.hairOutlineDraws) {
1738
- if (draw.count > 0) {
1739
- pass.setBindGroup(0, draw.bindGroup);
1740
- pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1775
+ // Over non-eyes
1776
+ pass.setPipeline(this.hairOutlinePipeline);
1777
+ pass.setStencilReference(1);
1778
+ for (const draw of this.hairOutlineDraws) {
1779
+ if (draw.count > 0) {
1780
+ pass.setBindGroup(0, draw.bindGroup);
1781
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1782
+ }
1741
1783
  }
1742
1784
  }
1743
- this.drawOutlines(pass, false); // Opaque outlines
1744
1785
  // PASS 4: Transparent non-eye, non-hair
1745
1786
  pass.setPipeline(this.pipeline);
1746
1787
  for (const draw of this.transparentNonEyeNonHairDraws) {
@@ -2026,7 +2067,8 @@ export class Engine {
2026
2067
  bufferMemoryBytes += 64 * 4; // lightUniformBuffer
2027
2068
  const totalMaterialDraws = this.opaqueNonEyeNonHairDraws.length +
2028
2069
  this.eyeDraws.length +
2029
- this.hairDraws.length +
2070
+ this.hairDrawsOverEyes.length +
2071
+ this.hairDrawsOverNonEyes.length +
2030
2072
  this.transparentNonEyeNonHairDraws.length;
2031
2073
  bufferMemoryBytes += totalMaterialDraws * 4; // Material uniform buffers
2032
2074
  let renderTargetMemoryBytes = 0;
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "A WebGPU-based MMD model renderer",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/reze-engine/reze-engine"
11
+ },
8
12
  "exports": {
9
13
  ".": {
10
14
  "types": "./dist/index.d.ts",
package/src/engine.ts CHANGED
@@ -22,7 +22,6 @@ export class Engine {
22
22
  private lightData = new Float32Array(64)
23
23
  private lightCount = 0
24
24
  private vertexBuffer!: GPUBuffer
25
- private vertexCount: number = 0
26
25
  private indexBuffer?: GPUBuffer
27
26
  private resizeObserver: ResizeObserver | null = null
28
27
  private depthTexture!: GPUTexture
@@ -240,9 +239,10 @@ export class Engine {
240
239
  `,
241
240
  })
242
241
 
243
- // Create a separate shader for hair-over-eyes that outputs pre-multiplied color for darkening effect
244
- const hairMultiplyShaderModule = this.device.createShaderModule({
245
- label: "hair multiply shaders",
242
+ // Unified hair shader that can handle both over-eyes and over-non-eyes cases
243
+ // Uses material.alpha multiplier to control opacity (0.5 for over-eyes, 1.0 for over-non-eyes)
244
+ const hairShaderModule = this.device.createShaderModule({
245
+ label: "unified hair shaders",
246
246
  code: /* wgsl */ `
247
247
  struct CameraUniforms {
248
248
  view: mat4x4f,
@@ -268,9 +268,9 @@ export class Engine {
268
268
 
269
269
  struct MaterialUniforms {
270
270
  alpha: f32,
271
+ alphaMultiplier: f32, // New: multiplier for alpha (0.5 for over-eyes, 1.0 for over-non-eyes)
271
272
  _padding1: f32,
272
273
  _padding2: f32,
273
- _padding3: f32,
274
274
  };
275
275
 
276
276
  struct VertexOutput {
@@ -341,15 +341,12 @@ export class Engine {
341
341
  }
342
342
 
343
343
  let color = albedo * lightAccum;
344
- let finalAlpha = material.alpha;
344
+ let finalAlpha = material.alpha * material.alphaMultiplier;
345
345
  if (finalAlpha < 0.001) {
346
346
  discard;
347
347
  }
348
348
 
349
- // For hair-over-eyes effect: simple half-transparent overlay - Use 50% opacity to create a semi-transparent hair color overlay
350
- let overlayAlpha = finalAlpha * 0.5;
351
-
352
- return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), overlayAlpha);
349
+ return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
353
350
  }
354
351
  `,
355
352
  })
@@ -754,12 +751,13 @@ export class Engine {
754
751
  },
755
752
  })
756
753
 
757
- // Hair pipeline with multiplicative blending (for hair over eyes)
754
+ // Unified hair pipeline - can be used for both over-eyes and over-non-eyes
755
+ // The difference is controlled by stencil state and alpha multiplier in material uniform
758
756
  this.hairMultiplyPipeline = this.device.createRenderPipeline({
759
- label: "hair multiply pipeline",
757
+ label: "hair pipeline (over eyes)",
760
758
  layout: sharedPipelineLayout,
761
759
  vertex: {
762
- module: hairMultiplyShaderModule,
760
+ module: hairShaderModule,
763
761
  buffers: [
764
762
  {
765
763
  arrayStride: 8 * 4,
@@ -780,13 +778,12 @@ export class Engine {
780
778
  ],
781
779
  },
782
780
  fragment: {
783
- module: hairMultiplyShaderModule,
781
+ module: hairShaderModule,
784
782
  targets: [
785
783
  {
786
784
  format: this.presentationFormat,
787
785
  blend: {
788
786
  color: {
789
- // Simple half-transparent overlay effect - Blend: hairColor * overlayAlpha + eyeColor * (1 - overlayAlpha)
790
787
  srcFactor: "src-alpha",
791
788
  dstFactor: "one-minus-src-alpha",
792
789
  operation: "add",
@@ -821,12 +818,12 @@ export class Engine {
821
818
  multisample: { count: this.sampleCount },
822
819
  })
823
820
 
824
- // Hair pipeline for opaque rendering (hair over non-eyes)
821
+ // Hair pipeline for opaque rendering (hair over non-eyes) - uses same shader, different stencil state
825
822
  this.hairOpaquePipeline = this.device.createRenderPipeline({
826
- label: "hair opaque pipeline",
823
+ label: "hair pipeline (over non-eyes)",
827
824
  layout: sharedPipelineLayout,
828
825
  vertex: {
829
- module: shaderModule,
826
+ module: hairShaderModule,
830
827
  buffers: [
831
828
  {
832
829
  arrayStride: 8 * 4,
@@ -847,7 +844,7 @@ export class Engine {
847
844
  ],
848
845
  },
849
846
  fragment: {
850
- module: shaderModule,
847
+ module: hairShaderModule,
851
848
  targets: [
852
849
  {
853
850
  format: this.presentationFormat,
@@ -1498,7 +1495,6 @@ export class Engine {
1498
1495
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
1499
1496
  })
1500
1497
  this.device.queue.writeBuffer(this.vertexBuffer, 0, vertices)
1501
- this.vertexCount = model.getVertexCount()
1502
1498
 
1503
1499
  this.jointsBuffer = this.device.createBuffer({
1504
1500
  label: "joints buffer",
@@ -1589,7 +1585,14 @@ export class Engine {
1589
1585
  isTransparent: boolean
1590
1586
  }[] = []
1591
1587
  private eyeDraws: { count: number; firstIndex: number; bindGroup: GPUBindGroup; isTransparent: boolean }[] = []
1592
- private hairDraws: { count: number; firstIndex: number; bindGroup: GPUBindGroup; isTransparent: boolean }[] = []
1588
+ private hairDrawsOverEyes: { count: number; firstIndex: number; bindGroup: GPUBindGroup; isTransparent: boolean }[] =
1589
+ []
1590
+ private hairDrawsOverNonEyes: {
1591
+ count: number
1592
+ firstIndex: number
1593
+ bindGroup: GPUBindGroup
1594
+ isTransparent: boolean
1595
+ }[] = []
1593
1596
  private transparentNonEyeNonHairDraws: {
1594
1597
  count: number
1595
1598
  firstIndex: number
@@ -1672,7 +1675,8 @@ export class Engine {
1672
1675
 
1673
1676
  this.opaqueNonEyeNonHairDraws = []
1674
1677
  this.eyeDraws = []
1675
- this.hairDraws = []
1678
+ this.hairDrawsOverEyes = []
1679
+ this.hairDrawsOverNonEyes = []
1676
1680
  this.transparentNonEyeNonHairDraws = []
1677
1681
  this.opaqueNonEyeNonHairOutlineDraws = []
1678
1682
  this.eyeOutlineDraws = []
@@ -1693,9 +1697,10 @@ export class Engine {
1693
1697
  const EPSILON = 0.001
1694
1698
  const isTransparent = materialAlpha < 1.0 - EPSILON
1695
1699
 
1700
+ // Create material uniform data - for hair materials, we'll create two versions
1696
1701
  const materialUniformData = new Float32Array(4)
1697
1702
  materialUniformData[0] = materialAlpha
1698
- materialUniformData[1] = 0.0
1703
+ materialUniformData[1] = 1.0 // alphaMultiplier: 1.0 for normal rendering
1699
1704
  materialUniformData[2] = 0.0
1700
1705
  materialUniformData[3] = 0.0
1701
1706
 
@@ -1731,7 +1736,43 @@ export class Engine {
1731
1736
  isTransparent,
1732
1737
  })
1733
1738
  } else if (mat.isHair) {
1734
- this.hairDraws.push({
1739
+ // For hair materials, create two bind groups: one for over-eyes (alphaMultiplier = 0.5) and one for over-non-eyes (alphaMultiplier = 1.0)
1740
+ const materialUniformDataOverEyes = new Float32Array(4)
1741
+ materialUniformDataOverEyes[0] = materialAlpha
1742
+ materialUniformDataOverEyes[1] = 0.5 // alphaMultiplier: 0.5 for over-eyes
1743
+ materialUniformDataOverEyes[2] = 0.0
1744
+ materialUniformDataOverEyes[3] = 0.0
1745
+
1746
+ const materialUniformBufferOverEyes = this.device.createBuffer({
1747
+ label: `material uniform (over eyes): ${mat.name}`,
1748
+ size: materialUniformDataOverEyes.byteLength,
1749
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
1750
+ })
1751
+ this.device.queue.writeBuffer(materialUniformBufferOverEyes, 0, materialUniformDataOverEyes)
1752
+
1753
+ const bindGroupOverEyes = this.device.createBindGroup({
1754
+ label: `material bind group (over eyes): ${mat.name}`,
1755
+ layout: this.hairBindGroupLayout,
1756
+ entries: [
1757
+ { binding: 0, resource: { buffer: this.cameraUniformBuffer } },
1758
+ { binding: 1, resource: { buffer: this.lightUniformBuffer } },
1759
+ { binding: 2, resource: diffuseTexture.createView() },
1760
+ { binding: 3, resource: this.textureSampler },
1761
+ { binding: 4, resource: { buffer: this.skinMatrixBuffer! } },
1762
+ { binding: 5, resource: toonTexture.createView() },
1763
+ { binding: 6, resource: this.textureSampler },
1764
+ { binding: 7, resource: { buffer: materialUniformBufferOverEyes } },
1765
+ ],
1766
+ })
1767
+
1768
+ this.hairDrawsOverEyes.push({
1769
+ count: matCount,
1770
+ firstIndex: runningFirstIndex,
1771
+ bindGroup: bindGroupOverEyes,
1772
+ isTransparent,
1773
+ })
1774
+
1775
+ this.hairDrawsOverNonEyes.push({
1735
1776
  count: matCount,
1736
1777
  firstIndex: runningFirstIndex,
1737
1778
  bindGroup,
@@ -1910,50 +1951,61 @@ export class Engine {
1910
1951
  }
1911
1952
  }
1912
1953
 
1913
- // PASS 3a: Hair over eyes (stencil == 1, multiply blend) - Draw hair geometry first to establish depth
1914
- pass.setPipeline(this.hairMultiplyPipeline)
1915
- pass.setStencilReference(1) // Check against stencil value 1
1916
- for (const draw of this.hairDraws) {
1917
- if (draw.count > 0) {
1918
- pass.setBindGroup(0, draw.bindGroup)
1919
- pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1920
- this.drawCallCount++
1954
+ // PASS 3: Hair rendering - optimized single pass approach
1955
+ // Since both hair passes use the same shader, we batch them together
1956
+ // but still need separate passes due to stencil requirements (equal vs not-equal)
1957
+
1958
+ this.drawOutlines(pass, false) // Opaque outlines
1959
+
1960
+ // 3a: Hair over eyes (stencil == 1, alphaMultiplier = 0.5)
1961
+ if (this.hairDrawsOverEyes.length > 0) {
1962
+ pass.setPipeline(this.hairMultiplyPipeline)
1963
+ pass.setStencilReference(1)
1964
+ for (const draw of this.hairDrawsOverEyes) {
1965
+ if (draw.count > 0) {
1966
+ pass.setBindGroup(0, draw.bindGroup)
1967
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1968
+ this.drawCallCount++
1969
+ }
1921
1970
  }
1922
1971
  }
1923
1972
 
1924
- // PASS 3a.5: Hair outlines over eyes (stencil == 1, depth test to only draw near hair)
1925
- pass.setPipeline(this.hairOutlineOverEyesPipeline)
1926
- pass.setStencilReference(1) // Check against stencil value 1 (with equal test)
1927
- for (const draw of this.hairOutlineDraws) {
1928
- if (draw.count > 0) {
1929
- pass.setBindGroup(0, draw.bindGroup)
1930
- pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1973
+ // 3b: Hair over non-eyes (stencil != 1, alphaMultiplier = 1.0)
1974
+ if (this.hairDrawsOverNonEyes.length > 0) {
1975
+ pass.setPipeline(this.hairOpaquePipeline)
1976
+ pass.setStencilReference(1)
1977
+ for (const draw of this.hairDrawsOverNonEyes) {
1978
+ if (draw.count > 0) {
1979
+ pass.setBindGroup(0, draw.bindGroup)
1980
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1981
+ this.drawCallCount++
1982
+ }
1931
1983
  }
1932
1984
  }
1933
1985
 
1934
- // PASS 3b: Hair over non-eyes (stencil != 1, opaque)
1935
- pass.setPipeline(this.hairOpaquePipeline)
1936
- pass.setStencilReference(1) // Check against stencil value 1 (with not-equal test)
1937
- for (const draw of this.hairDraws) {
1938
- if (draw.count > 0) {
1939
- pass.setBindGroup(0, draw.bindGroup)
1940
- pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1941
- this.drawCallCount++
1986
+ // 3c: Hair outlines - batched together, only draw if outlines exist
1987
+ if (this.hairOutlineDraws.length > 0) {
1988
+ // Over eyes
1989
+ pass.setPipeline(this.hairOutlineOverEyesPipeline)
1990
+ pass.setStencilReference(1)
1991
+ for (const draw of this.hairOutlineDraws) {
1992
+ if (draw.count > 0) {
1993
+ pass.setBindGroup(0, draw.bindGroup)
1994
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1995
+ }
1942
1996
  }
1943
- }
1944
1997
 
1945
- // PASS 3b.5: Hair outlines over non-eyes (stencil != 1) - Draw hair outlines after hair geometry, so they only appear where hair exists
1946
- pass.setPipeline(this.hairOutlinePipeline)
1947
- pass.setStencilReference(1) // Check against stencil value 1 (with not-equal test)
1948
- for (const draw of this.hairOutlineDraws) {
1949
- if (draw.count > 0) {
1950
- pass.setBindGroup(0, draw.bindGroup)
1951
- pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1998
+ // Over non-eyes
1999
+ pass.setPipeline(this.hairOutlinePipeline)
2000
+ pass.setStencilReference(1)
2001
+ for (const draw of this.hairOutlineDraws) {
2002
+ if (draw.count > 0) {
2003
+ pass.setBindGroup(0, draw.bindGroup)
2004
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
2005
+ }
1952
2006
  }
1953
2007
  }
1954
2008
 
1955
- this.drawOutlines(pass, false) // Opaque outlines
1956
-
1957
2009
  // PASS 4: Transparent non-eye, non-hair
1958
2010
  pass.setPipeline(this.pipeline)
1959
2011
  for (const draw of this.transparentNonEyeNonHairDraws) {
@@ -2282,7 +2334,8 @@ export class Engine {
2282
2334
  const totalMaterialDraws =
2283
2335
  this.opaqueNonEyeNonHairDraws.length +
2284
2336
  this.eyeDraws.length +
2285
- this.hairDraws.length +
2337
+ this.hairDrawsOverEyes.length +
2338
+ this.hairDrawsOverNonEyes.length +
2286
2339
  this.transparentNonEyeNonHairDraws.length
2287
2340
  bufferMemoryBytes += totalMaterialDraws * 4 // Material uniform buffers
2288
2341