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 +2 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +99 -57
- package/package.json +5 -1
- package/src/engine.ts +111 -58
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
|
|
93
|
+
private hairDrawsOverEyes;
|
|
94
|
+
private hairDrawsOverNonEyes;
|
|
95
95
|
private transparentNonEyeNonHairDraws;
|
|
96
96
|
private opaqueNonEyeNonHairOutlineDraws;
|
|
97
97
|
private eyeOutlineDraws;
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAKnC,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,kBAAkB,CAAmB;IACtC,MAAM,EAAG,MAAM,CAAA;IACtB,OAAO,CAAC,mBAAmB,CAAY;IACvC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,kBAAkB,CAAY;IACtC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,YAAY,CAAY;IAChC,OAAO,CAAC,WAAW,
|
|
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.
|
|
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
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
692
|
+
label: "hair pipeline (over eyes)",
|
|
694
693
|
layout: sharedPipelineLayout,
|
|
695
694
|
vertex: {
|
|
696
|
-
module:
|
|
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:
|
|
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
|
|
757
|
+
label: "hair pipeline (over non-eyes)",
|
|
760
758
|
layout: sharedPipelineLayout,
|
|
761
759
|
vertex: {
|
|
762
|
-
module:
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
-
//
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
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
|
-
//
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
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
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
757
|
+
label: "hair pipeline (over eyes)",
|
|
760
758
|
layout: sharedPipelineLayout,
|
|
761
759
|
vertex: {
|
|
762
|
-
module:
|
|
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:
|
|
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
|
|
823
|
+
label: "hair pipeline (over non-eyes)",
|
|
827
824
|
layout: sharedPipelineLayout,
|
|
828
825
|
vertex: {
|
|
829
|
-
module:
|
|
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:
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
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
|
-
//
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
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
|
-
//
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
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
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
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.
|
|
2337
|
+
this.hairDrawsOverEyes.length +
|
|
2338
|
+
this.hairDrawsOverNonEyes.length +
|
|
2286
2339
|
this.transparentNonEyeNonHairDraws.length
|
|
2287
2340
|
bufferMemoryBytes += totalMaterialDraws * 4 // Material uniform buffers
|
|
2288
2341
|
|