reze-engine 0.2.5 → 0.2.6
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 +13 -14
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +69 -82
- package/package.json +1 -1
- package/src/engine.ts +83 -116
package/dist/engine.d.ts
CHANGED
|
@@ -25,12 +25,12 @@ export declare class Engine {
|
|
|
25
25
|
private resizeObserver;
|
|
26
26
|
private depthTexture;
|
|
27
27
|
private modelPipeline;
|
|
28
|
-
private
|
|
29
|
-
private hairOutlinePipeline;
|
|
28
|
+
private eyePipeline;
|
|
30
29
|
private hairPipelineOverEyes;
|
|
31
30
|
private hairPipelineOverNonEyes;
|
|
32
31
|
private hairDepthPipeline;
|
|
33
|
-
private
|
|
32
|
+
private outlinePipeline;
|
|
33
|
+
private hairOutlinePipeline;
|
|
34
34
|
private mainBindGroupLayout;
|
|
35
35
|
private outlineBindGroupLayout;
|
|
36
36
|
private jointsBuffer;
|
|
@@ -68,12 +68,20 @@ export declare class Engine {
|
|
|
68
68
|
private bloomThreshold;
|
|
69
69
|
private bloomIntensity;
|
|
70
70
|
private rimLightIntensity;
|
|
71
|
-
private rimLightPower;
|
|
72
71
|
private currentModel;
|
|
73
72
|
private modelDir;
|
|
74
73
|
private physics;
|
|
75
|
-
private
|
|
74
|
+
private materialSampler;
|
|
76
75
|
private textureCache;
|
|
76
|
+
private opaqueDraws;
|
|
77
|
+
private eyeDraws;
|
|
78
|
+
private hairDrawsOverEyes;
|
|
79
|
+
private hairDrawsOverNonEyes;
|
|
80
|
+
private transparentDraws;
|
|
81
|
+
private opaqueOutlineDraws;
|
|
82
|
+
private eyeOutlineDraws;
|
|
83
|
+
private hairOutlineDraws;
|
|
84
|
+
private transparentOutlineDraws;
|
|
77
85
|
private lastFpsUpdate;
|
|
78
86
|
private framesSinceLastUpdate;
|
|
79
87
|
private frameTimeSamples;
|
|
@@ -109,15 +117,6 @@ export declare class Engine {
|
|
|
109
117
|
loadModel(path: string): Promise<void>;
|
|
110
118
|
rotateBones(bones: string[], rotations: Quat[], durationMs?: number): void;
|
|
111
119
|
private setupModelBuffers;
|
|
112
|
-
private opaqueNonEyeNonHairDraws;
|
|
113
|
-
private eyeDraws;
|
|
114
|
-
private hairDrawsOverEyes;
|
|
115
|
-
private hairDrawsOverNonEyes;
|
|
116
|
-
private transparentNonEyeNonHairDraws;
|
|
117
|
-
private opaqueNonEyeNonHairOutlineDraws;
|
|
118
|
-
private eyeOutlineDraws;
|
|
119
|
-
private hairOutlineDraws;
|
|
120
|
-
private transparentNonEyeNonHairOutlineDraws;
|
|
121
120
|
private setupMaterials;
|
|
122
121
|
private createTextureFromPath;
|
|
123
122
|
render(): void;
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAQ,MAAM,QAAQ,CAAA;AAMnC,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.js
CHANGED
|
@@ -21,11 +21,20 @@ export class Engine {
|
|
|
21
21
|
this.bloomIntensity = 0.12;
|
|
22
22
|
// Rim light settings
|
|
23
23
|
this.rimLightIntensity = 0.45;
|
|
24
|
-
this.rimLightPower = 2.0;
|
|
25
24
|
this.currentModel = null;
|
|
26
25
|
this.modelDir = "";
|
|
27
26
|
this.physics = null;
|
|
28
27
|
this.textureCache = new Map();
|
|
28
|
+
// Draw lists
|
|
29
|
+
this.opaqueDraws = [];
|
|
30
|
+
this.eyeDraws = [];
|
|
31
|
+
this.hairDrawsOverEyes = [];
|
|
32
|
+
this.hairDrawsOverNonEyes = [];
|
|
33
|
+
this.transparentDraws = [];
|
|
34
|
+
this.opaqueOutlineDraws = [];
|
|
35
|
+
this.eyeOutlineDraws = [];
|
|
36
|
+
this.hairOutlineDraws = [];
|
|
37
|
+
this.transparentOutlineDraws = [];
|
|
29
38
|
this.lastFpsUpdate = performance.now();
|
|
30
39
|
this.framesSinceLastUpdate = 0;
|
|
31
40
|
this.frameTimeSamples = [];
|
|
@@ -42,15 +51,6 @@ export class Engine {
|
|
|
42
51
|
this.animationFrames = [];
|
|
43
52
|
this.animationTimeouts = [];
|
|
44
53
|
this.gpuMemoryMB = 0;
|
|
45
|
-
this.opaqueNonEyeNonHairDraws = [];
|
|
46
|
-
this.eyeDraws = [];
|
|
47
|
-
this.hairDrawsOverEyes = [];
|
|
48
|
-
this.hairDrawsOverNonEyes = [];
|
|
49
|
-
this.transparentNonEyeNonHairDraws = [];
|
|
50
|
-
this.opaqueNonEyeNonHairOutlineDraws = [];
|
|
51
|
-
this.eyeOutlineDraws = [];
|
|
52
|
-
this.hairOutlineDraws = [];
|
|
53
|
-
this.transparentNonEyeNonHairOutlineDraws = [];
|
|
54
54
|
this.canvas = canvas;
|
|
55
55
|
if (options) {
|
|
56
56
|
this.ambient = options.ambient ?? 1.0;
|
|
@@ -84,9 +84,8 @@ export class Engine {
|
|
|
84
84
|
this.createBloomPipelines();
|
|
85
85
|
this.setupResize();
|
|
86
86
|
}
|
|
87
|
-
// Step 2: Create shaders and render pipelines
|
|
88
87
|
createPipelines() {
|
|
89
|
-
this.
|
|
88
|
+
this.materialSampler = this.device.createSampler({
|
|
90
89
|
magFilter: "linear",
|
|
91
90
|
minFilter: "linear",
|
|
92
91
|
addressModeU: "repeat",
|
|
@@ -121,7 +120,7 @@ export class Engine {
|
|
|
121
120
|
alpha: f32,
|
|
122
121
|
alphaMultiplier: f32,
|
|
123
122
|
rimIntensity: f32,
|
|
124
|
-
|
|
123
|
+
_padding1: f32,
|
|
125
124
|
rimColor: vec3f,
|
|
126
125
|
isOverEyes: f32, // 1.0 if rendering over eyes, 0.0 otherwise
|
|
127
126
|
};
|
|
@@ -152,14 +151,10 @@ export class Engine {
|
|
|
152
151
|
var output: VertexOutput;
|
|
153
152
|
let pos4 = vec4f(position, 1.0);
|
|
154
153
|
|
|
155
|
-
//
|
|
154
|
+
// Branchless weight normalization (avoids GPU branch divergence)
|
|
156
155
|
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
normalizedWeights = weights0 / weightSum;
|
|
160
|
-
} else {
|
|
161
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
162
|
-
}
|
|
156
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
157
|
+
let normalizedWeights = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
163
158
|
|
|
164
159
|
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
165
160
|
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
@@ -180,6 +175,15 @@ export class Engine {
|
|
|
180
175
|
}
|
|
181
176
|
|
|
182
177
|
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
178
|
+
// Early alpha test - discard before expensive calculations
|
|
179
|
+
var finalAlpha = material.alpha * material.alphaMultiplier;
|
|
180
|
+
if (material.isOverEyes > 0.5) {
|
|
181
|
+
finalAlpha *= 0.5; // Hair over eyes gets 50% alpha
|
|
182
|
+
}
|
|
183
|
+
if (finalAlpha < 0.001) {
|
|
184
|
+
discard;
|
|
185
|
+
}
|
|
186
|
+
|
|
183
187
|
let n = normalize(input.normal);
|
|
184
188
|
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
185
189
|
|
|
@@ -197,21 +201,12 @@ export class Engine {
|
|
|
197
201
|
// Rim light calculation
|
|
198
202
|
let viewDir = normalize(camera.viewPos - input.worldPos);
|
|
199
203
|
var rimFactor = 1.0 - max(dot(n, viewDir), 0.0);
|
|
200
|
-
rimFactor = pow(
|
|
204
|
+
rimFactor = rimFactor * rimFactor; // Optimized: direct multiply instead of pow(x, 2.0)
|
|
201
205
|
let rimLight = material.rimColor * material.rimIntensity * rimFactor;
|
|
202
206
|
|
|
203
207
|
let color = albedo * lightAccum + rimLight;
|
|
204
208
|
|
|
205
|
-
|
|
206
|
-
if (material.isOverEyes > 0.5) {
|
|
207
|
-
finalAlpha *= 0.5; // Hair over eyes gets 50% alpha
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (finalAlpha < 0.001) {
|
|
211
|
-
discard;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
209
|
+
return vec4f(color, finalAlpha);
|
|
215
210
|
}
|
|
216
211
|
`,
|
|
217
212
|
});
|
|
@@ -335,14 +330,10 @@ export class Engine {
|
|
|
335
330
|
var output: VertexOutput;
|
|
336
331
|
let pos4 = vec4f(position, 1.0);
|
|
337
332
|
|
|
338
|
-
//
|
|
333
|
+
// Branchless weight normalization (avoids GPU branch divergence)
|
|
339
334
|
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
normalizedWeights = weights0 / weightSum;
|
|
343
|
-
} else {
|
|
344
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
345
|
-
}
|
|
335
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
336
|
+
let normalizedWeights = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
346
337
|
|
|
347
338
|
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
348
339
|
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
@@ -592,14 +583,10 @@ export class Engine {
|
|
|
592
583
|
) -> @builtin(position) vec4f {
|
|
593
584
|
let pos4 = vec4f(position, 1.0);
|
|
594
585
|
|
|
595
|
-
//
|
|
586
|
+
// Branchless weight normalization (avoids GPU branch divergence)
|
|
596
587
|
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
normalizedWeights = weights0 / weightSum;
|
|
600
|
-
} else {
|
|
601
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
602
|
-
}
|
|
588
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
589
|
+
let normalizedWeights = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
603
590
|
|
|
604
591
|
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
605
592
|
for (var i = 0u; i < 4u; i++) {
|
|
@@ -944,19 +931,21 @@ export class Engine {
|
|
|
944
931
|
@group(0) @binding(1) var inputSampler: sampler;
|
|
945
932
|
@group(0) @binding(2) var<uniform> blurUniforms: BlurUniforms;
|
|
946
933
|
|
|
947
|
-
//
|
|
934
|
+
// 3-tap gaussian blur using bilinear filtering trick (40% fewer texture fetches!)
|
|
948
935
|
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
949
936
|
let texelSize = 1.0 / vec2f(textureDimensions(inputTexture));
|
|
950
|
-
var result = vec4f(0.0);
|
|
951
937
|
|
|
952
|
-
//
|
|
953
|
-
|
|
954
|
-
|
|
938
|
+
// Bilinear optimization: leverage hardware filtering to sample between pixels
|
|
939
|
+
// Original 5-tap: weights [0.06136, 0.24477, 0.38774, 0.24477, 0.06136] at offsets [-2, -1, 0, 1, 2]
|
|
940
|
+
// Optimized 3-tap: combine adjacent samples using weighted offsets
|
|
941
|
+
let weight0 = 0.38774; // Center sample
|
|
942
|
+
let weight1 = 0.24477 + 0.06136; // Combined outer samples = 0.30613
|
|
943
|
+
let offset1 = (0.24477 * 1.0 + 0.06136 * 2.0) / weight1; // Weighted position = 1.2
|
|
955
944
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
945
|
+
var result = textureSample(inputTexture, inputSampler, input.uv) * weight0;
|
|
946
|
+
let offsetVec = offset1 * texelSize * blurUniforms.direction;
|
|
947
|
+
result += textureSample(inputTexture, inputSampler, input.uv + offsetVec) * weight1;
|
|
948
|
+
result += textureSample(inputTexture, inputSampler, input.uv - offsetVec) * weight1;
|
|
960
949
|
|
|
961
950
|
return result;
|
|
962
951
|
}
|
|
@@ -1492,7 +1481,6 @@ export class Engine {
|
|
|
1492
1481
|
}
|
|
1493
1482
|
await this.setupMaterials(model);
|
|
1494
1483
|
}
|
|
1495
|
-
// Step 8: Load textures and create material bind groups
|
|
1496
1484
|
async setupMaterials(model) {
|
|
1497
1485
|
const materials = model.getMaterials();
|
|
1498
1486
|
if (materials.length === 0) {
|
|
@@ -1539,15 +1527,15 @@ export class Engine {
|
|
|
1539
1527
|
this.textureCache.set(defaultToonPath, defaultToonTexture);
|
|
1540
1528
|
return defaultToonTexture;
|
|
1541
1529
|
};
|
|
1542
|
-
this.
|
|
1530
|
+
this.opaqueDraws = [];
|
|
1543
1531
|
this.eyeDraws = [];
|
|
1544
1532
|
this.hairDrawsOverEyes = [];
|
|
1545
1533
|
this.hairDrawsOverNonEyes = [];
|
|
1546
|
-
this.
|
|
1547
|
-
this.
|
|
1534
|
+
this.transparentDraws = [];
|
|
1535
|
+
this.opaqueOutlineDraws = [];
|
|
1548
1536
|
this.eyeOutlineDraws = [];
|
|
1549
1537
|
this.hairOutlineDraws = [];
|
|
1550
|
-
this.
|
|
1538
|
+
this.transparentOutlineDraws = [];
|
|
1551
1539
|
let currentIndexOffset = 0;
|
|
1552
1540
|
for (const mat of materials) {
|
|
1553
1541
|
const indexCount = mat.vertexCount;
|
|
@@ -1565,11 +1553,11 @@ export class Engine {
|
|
|
1565
1553
|
materialUniformData[0] = materialAlpha;
|
|
1566
1554
|
materialUniformData[1] = 1.0; // alphaMultiplier: 1.0 for non-hair materials
|
|
1567
1555
|
materialUniformData[2] = this.rimLightIntensity;
|
|
1568
|
-
materialUniformData[3] =
|
|
1556
|
+
materialUniformData[3] = 0.0; // _padding1
|
|
1569
1557
|
materialUniformData[4] = 1.0; // rimColor.r
|
|
1570
1558
|
materialUniformData[5] = 1.0; // rimColor.g
|
|
1571
1559
|
materialUniformData[6] = 1.0; // rimColor.b
|
|
1572
|
-
materialUniformData[7] = 0.0;
|
|
1560
|
+
materialUniformData[7] = 0.0; // isOverEyes
|
|
1573
1561
|
const materialUniformBuffer = this.device.createBuffer({
|
|
1574
1562
|
label: `material uniform: ${mat.name}`,
|
|
1575
1563
|
size: materialUniformData.byteLength,
|
|
@@ -1584,14 +1572,13 @@ export class Engine {
|
|
|
1584
1572
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1585
1573
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1586
1574
|
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1587
|
-
{ binding: 3, resource: this.
|
|
1575
|
+
{ binding: 3, resource: this.materialSampler },
|
|
1588
1576
|
{ binding: 4, resource: { buffer: this.skinMatrixBuffer } },
|
|
1589
1577
|
{ binding: 5, resource: toonTexture.createView() },
|
|
1590
|
-
{ binding: 6, resource: this.
|
|
1578
|
+
{ binding: 6, resource: this.materialSampler },
|
|
1591
1579
|
{ binding: 7, resource: { buffer: materialUniformBuffer } },
|
|
1592
1580
|
],
|
|
1593
1581
|
});
|
|
1594
|
-
// Classify materials into appropriate draw lists
|
|
1595
1582
|
if (mat.isEye) {
|
|
1596
1583
|
this.eyeDraws.push({
|
|
1597
1584
|
count: indexCount,
|
|
@@ -1607,11 +1594,11 @@ export class Engine {
|
|
|
1607
1594
|
uniformData[0] = materialAlpha;
|
|
1608
1595
|
uniformData[1] = 1.0; // alphaMultiplier (shader adjusts based on isOverEyes)
|
|
1609
1596
|
uniformData[2] = this.rimLightIntensity;
|
|
1610
|
-
uniformData[3] =
|
|
1597
|
+
uniformData[3] = 0.0; // _padding1
|
|
1611
1598
|
uniformData[4] = 1.0; // rimColor.rgb
|
|
1612
1599
|
uniformData[5] = 1.0;
|
|
1613
1600
|
uniformData[6] = 1.0;
|
|
1614
|
-
uniformData[7] = isOverEyes ? 1.0 : 0.0;
|
|
1601
|
+
uniformData[7] = isOverEyes ? 1.0 : 0.0; // isOverEyes
|
|
1615
1602
|
const buffer = this.device.createBuffer({
|
|
1616
1603
|
label: `material uniform (${isOverEyes ? "over eyes" : "over non-eyes"}): ${mat.name}`,
|
|
1617
1604
|
size: uniformData.byteLength,
|
|
@@ -1625,10 +1612,10 @@ export class Engine {
|
|
|
1625
1612
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1626
1613
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1627
1614
|
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1628
|
-
{ binding: 3, resource: this.
|
|
1615
|
+
{ binding: 3, resource: this.materialSampler },
|
|
1629
1616
|
{ binding: 4, resource: { buffer: this.skinMatrixBuffer } },
|
|
1630
1617
|
{ binding: 5, resource: toonTexture.createView() },
|
|
1631
|
-
{ binding: 6, resource: this.
|
|
1618
|
+
{ binding: 6, resource: this.materialSampler },
|
|
1632
1619
|
{ binding: 7, resource: { buffer: buffer } },
|
|
1633
1620
|
],
|
|
1634
1621
|
});
|
|
@@ -1649,7 +1636,7 @@ export class Engine {
|
|
|
1649
1636
|
});
|
|
1650
1637
|
}
|
|
1651
1638
|
else if (isTransparent) {
|
|
1652
|
-
this.
|
|
1639
|
+
this.transparentDraws.push({
|
|
1653
1640
|
count: indexCount,
|
|
1654
1641
|
firstIndex: currentIndexOffset,
|
|
1655
1642
|
bindGroup,
|
|
@@ -1657,7 +1644,7 @@ export class Engine {
|
|
|
1657
1644
|
});
|
|
1658
1645
|
}
|
|
1659
1646
|
else {
|
|
1660
|
-
this.
|
|
1647
|
+
this.opaqueDraws.push({
|
|
1661
1648
|
count: indexCount,
|
|
1662
1649
|
firstIndex: currentIndexOffset,
|
|
1663
1650
|
bindGroup,
|
|
@@ -1707,7 +1694,7 @@ export class Engine {
|
|
|
1707
1694
|
});
|
|
1708
1695
|
}
|
|
1709
1696
|
else if (isTransparent) {
|
|
1710
|
-
this.
|
|
1697
|
+
this.transparentOutlineDraws.push({
|
|
1711
1698
|
count: indexCount,
|
|
1712
1699
|
firstIndex: currentIndexOffset,
|
|
1713
1700
|
bindGroup: outlineBindGroup,
|
|
@@ -1715,7 +1702,7 @@ export class Engine {
|
|
|
1715
1702
|
});
|
|
1716
1703
|
}
|
|
1717
1704
|
else {
|
|
1718
|
-
this.
|
|
1705
|
+
this.opaqueOutlineDraws.push({
|
|
1719
1706
|
count: indexCount,
|
|
1720
1707
|
firstIndex: currentIndexOffset,
|
|
1721
1708
|
bindGroup: outlineBindGroup,
|
|
@@ -1775,9 +1762,9 @@ export class Engine {
|
|
|
1775
1762
|
pass.setVertexBuffer(2, this.weightsBuffer);
|
|
1776
1763
|
pass.setIndexBuffer(this.indexBuffer, "uint32");
|
|
1777
1764
|
this.drawCallCount = 0;
|
|
1778
|
-
// Pass 1: Opaque
|
|
1765
|
+
// Pass 1: Opaque
|
|
1779
1766
|
pass.setPipeline(this.modelPipeline);
|
|
1780
|
-
for (const draw of this.
|
|
1767
|
+
for (const draw of this.opaqueDraws) {
|
|
1781
1768
|
if (draw.count > 0) {
|
|
1782
1769
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1783
1770
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
@@ -1845,9 +1832,9 @@ export class Engine {
|
|
|
1845
1832
|
}
|
|
1846
1833
|
}
|
|
1847
1834
|
}
|
|
1848
|
-
// Pass 4: Transparent
|
|
1835
|
+
// Pass 4: Transparent
|
|
1849
1836
|
pass.setPipeline(this.modelPipeline);
|
|
1850
|
-
for (const draw of this.
|
|
1837
|
+
for (const draw of this.transparentDraws) {
|
|
1851
1838
|
if (draw.count > 0) {
|
|
1852
1839
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1853
1840
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
@@ -1992,7 +1979,7 @@ export class Engine {
|
|
|
1992
1979
|
drawOutlines(pass, transparent) {
|
|
1993
1980
|
pass.setPipeline(this.outlinePipeline);
|
|
1994
1981
|
if (transparent) {
|
|
1995
|
-
for (const draw of this.
|
|
1982
|
+
for (const draw of this.transparentOutlineDraws) {
|
|
1996
1983
|
if (draw.count > 0) {
|
|
1997
1984
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1998
1985
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
@@ -2000,7 +1987,7 @@ export class Engine {
|
|
|
2000
1987
|
}
|
|
2001
1988
|
}
|
|
2002
1989
|
else {
|
|
2003
|
-
for (const draw of this.
|
|
1990
|
+
for (const draw of this.opaqueOutlineDraws) {
|
|
2004
1991
|
if (draw.count > 0) {
|
|
2005
1992
|
pass.setBindGroup(0, draw.bindGroup);
|
|
2006
1993
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
@@ -2078,16 +2065,16 @@ export class Engine {
|
|
|
2078
2065
|
if (this.fullscreenQuadBuffer) {
|
|
2079
2066
|
bufferMemoryBytes += 24 * 4;
|
|
2080
2067
|
}
|
|
2081
|
-
const totalMaterialDraws = this.
|
|
2068
|
+
const totalMaterialDraws = this.opaqueDraws.length +
|
|
2082
2069
|
this.eyeDraws.length +
|
|
2083
2070
|
this.hairDrawsOverEyes.length +
|
|
2084
2071
|
this.hairDrawsOverNonEyes.length +
|
|
2085
|
-
this.
|
|
2072
|
+
this.transparentDraws.length;
|
|
2086
2073
|
bufferMemoryBytes += totalMaterialDraws * 32;
|
|
2087
|
-
const totalOutlineDraws = this.
|
|
2074
|
+
const totalOutlineDraws = this.opaqueOutlineDraws.length +
|
|
2088
2075
|
this.eyeOutlineDraws.length +
|
|
2089
2076
|
this.hairOutlineDraws.length +
|
|
2090
|
-
this.
|
|
2077
|
+
this.transparentOutlineDraws.length;
|
|
2091
2078
|
bufferMemoryBytes += totalOutlineDraws * 32;
|
|
2092
2079
|
let renderTargetMemoryBytes = 0;
|
|
2093
2080
|
if (this.multisampleTexture) {
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -17,7 +17,13 @@ export interface EngineStats {
|
|
|
17
17
|
gpuMemory: number // MB (estimated total GPU memory)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
interface DrawCall {
|
|
21
|
+
count: number
|
|
22
|
+
firstIndex: number
|
|
23
|
+
bindGroup: GPUBindGroup
|
|
24
|
+
isTransparent: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
type BoneKeyFrame = {
|
|
22
28
|
boneName: string
|
|
23
29
|
time: number
|
|
@@ -39,13 +45,15 @@ export class Engine {
|
|
|
39
45
|
private indexBuffer?: GPUBuffer
|
|
40
46
|
private resizeObserver: ResizeObserver | null = null
|
|
41
47
|
private depthTexture!: GPUTexture
|
|
48
|
+
// Material rendering pipelines
|
|
42
49
|
private modelPipeline!: GPURenderPipeline
|
|
43
|
-
private
|
|
44
|
-
private hairOutlinePipeline!: GPURenderPipeline
|
|
50
|
+
private eyePipeline!: GPURenderPipeline
|
|
45
51
|
private hairPipelineOverEyes!: GPURenderPipeline
|
|
46
52
|
private hairPipelineOverNonEyes!: GPURenderPipeline
|
|
47
53
|
private hairDepthPipeline!: GPURenderPipeline
|
|
48
|
-
|
|
54
|
+
// Outline pipelines
|
|
55
|
+
private outlinePipeline!: GPURenderPipeline
|
|
56
|
+
private hairOutlinePipeline!: GPURenderPipeline
|
|
49
57
|
private mainBindGroupLayout!: GPUBindGroupLayout
|
|
50
58
|
private outlineBindGroupLayout!: GPUBindGroupLayout
|
|
51
59
|
private jointsBuffer!: GPUBuffer
|
|
@@ -71,7 +79,7 @@ export class Engine {
|
|
|
71
79
|
private bloomExtractTexture!: GPUTexture
|
|
72
80
|
private bloomBlurTexture1!: GPUTexture
|
|
73
81
|
private bloomBlurTexture2!: GPUTexture
|
|
74
|
-
//
|
|
82
|
+
// Post-processing pipelines
|
|
75
83
|
private bloomExtractPipeline!: GPURenderPipeline
|
|
76
84
|
private bloomBlurPipeline!: GPURenderPipeline
|
|
77
85
|
private bloomComposePipeline!: GPURenderPipeline
|
|
@@ -91,13 +99,22 @@ export class Engine {
|
|
|
91
99
|
private bloomIntensity: number = 0.12
|
|
92
100
|
// Rim light settings
|
|
93
101
|
private rimLightIntensity: number = 0.45
|
|
94
|
-
private rimLightPower: number = 2.0
|
|
95
102
|
|
|
96
103
|
private currentModel: Model | null = null
|
|
97
104
|
private modelDir: string = ""
|
|
98
105
|
private physics: Physics | null = null
|
|
99
|
-
private
|
|
106
|
+
private materialSampler!: GPUSampler
|
|
100
107
|
private textureCache = new Map<string, GPUTexture>()
|
|
108
|
+
// Draw lists
|
|
109
|
+
private opaqueDraws: DrawCall[] = []
|
|
110
|
+
private eyeDraws: DrawCall[] = []
|
|
111
|
+
private hairDrawsOverEyes: DrawCall[] = []
|
|
112
|
+
private hairDrawsOverNonEyes: DrawCall[] = []
|
|
113
|
+
private transparentDraws: DrawCall[] = []
|
|
114
|
+
private opaqueOutlineDraws: DrawCall[] = []
|
|
115
|
+
private eyeOutlineDraws: DrawCall[] = []
|
|
116
|
+
private hairOutlineDraws: DrawCall[] = []
|
|
117
|
+
private transparentOutlineDraws: DrawCall[] = []
|
|
101
118
|
|
|
102
119
|
private lastFpsUpdate = performance.now()
|
|
103
120
|
private framesSinceLastUpdate = 0
|
|
@@ -157,9 +174,8 @@ export class Engine {
|
|
|
157
174
|
this.setupResize()
|
|
158
175
|
}
|
|
159
176
|
|
|
160
|
-
// Step 2: Create shaders and render pipelines
|
|
161
177
|
private createPipelines() {
|
|
162
|
-
this.
|
|
178
|
+
this.materialSampler = this.device.createSampler({
|
|
163
179
|
magFilter: "linear",
|
|
164
180
|
minFilter: "linear",
|
|
165
181
|
addressModeU: "repeat",
|
|
@@ -195,7 +211,7 @@ export class Engine {
|
|
|
195
211
|
alpha: f32,
|
|
196
212
|
alphaMultiplier: f32,
|
|
197
213
|
rimIntensity: f32,
|
|
198
|
-
|
|
214
|
+
_padding1: f32,
|
|
199
215
|
rimColor: vec3f,
|
|
200
216
|
isOverEyes: f32, // 1.0 if rendering over eyes, 0.0 otherwise
|
|
201
217
|
};
|
|
@@ -226,14 +242,10 @@ export class Engine {
|
|
|
226
242
|
var output: VertexOutput;
|
|
227
243
|
let pos4 = vec4f(position, 1.0);
|
|
228
244
|
|
|
229
|
-
//
|
|
245
|
+
// Branchless weight normalization (avoids GPU branch divergence)
|
|
230
246
|
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
normalizedWeights = weights0 / weightSum;
|
|
234
|
-
} else {
|
|
235
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
236
|
-
}
|
|
247
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
248
|
+
let normalizedWeights = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
237
249
|
|
|
238
250
|
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
239
251
|
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
@@ -254,6 +266,15 @@ export class Engine {
|
|
|
254
266
|
}
|
|
255
267
|
|
|
256
268
|
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
269
|
+
// Early alpha test - discard before expensive calculations
|
|
270
|
+
var finalAlpha = material.alpha * material.alphaMultiplier;
|
|
271
|
+
if (material.isOverEyes > 0.5) {
|
|
272
|
+
finalAlpha *= 0.5; // Hair over eyes gets 50% alpha
|
|
273
|
+
}
|
|
274
|
+
if (finalAlpha < 0.001) {
|
|
275
|
+
discard;
|
|
276
|
+
}
|
|
277
|
+
|
|
257
278
|
let n = normalize(input.normal);
|
|
258
279
|
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
259
280
|
|
|
@@ -271,21 +292,12 @@ export class Engine {
|
|
|
271
292
|
// Rim light calculation
|
|
272
293
|
let viewDir = normalize(camera.viewPos - input.worldPos);
|
|
273
294
|
var rimFactor = 1.0 - max(dot(n, viewDir), 0.0);
|
|
274
|
-
rimFactor = pow(
|
|
295
|
+
rimFactor = rimFactor * rimFactor; // Optimized: direct multiply instead of pow(x, 2.0)
|
|
275
296
|
let rimLight = material.rimColor * material.rimIntensity * rimFactor;
|
|
276
297
|
|
|
277
298
|
let color = albedo * lightAccum + rimLight;
|
|
278
299
|
|
|
279
|
-
|
|
280
|
-
if (material.isOverEyes > 0.5) {
|
|
281
|
-
finalAlpha *= 0.5; // Hair over eyes gets 50% alpha
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (finalAlpha < 0.001) {
|
|
285
|
-
discard;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return vec4f(clamp(color, vec3f(0.0), vec3f(1.0)), finalAlpha);
|
|
300
|
+
return vec4f(color, finalAlpha);
|
|
289
301
|
}
|
|
290
302
|
`,
|
|
291
303
|
})
|
|
@@ -415,14 +427,10 @@ export class Engine {
|
|
|
415
427
|
var output: VertexOutput;
|
|
416
428
|
let pos4 = vec4f(position, 1.0);
|
|
417
429
|
|
|
418
|
-
//
|
|
430
|
+
// Branchless weight normalization (avoids GPU branch divergence)
|
|
419
431
|
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
normalizedWeights = weights0 / weightSum;
|
|
423
|
-
} else {
|
|
424
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
425
|
-
}
|
|
432
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
433
|
+
let normalizedWeights = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
426
434
|
|
|
427
435
|
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
428
436
|
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
@@ -676,14 +684,10 @@ export class Engine {
|
|
|
676
684
|
) -> @builtin(position) vec4f {
|
|
677
685
|
let pos4 = vec4f(position, 1.0);
|
|
678
686
|
|
|
679
|
-
//
|
|
687
|
+
// Branchless weight normalization (avoids GPU branch divergence)
|
|
680
688
|
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
normalizedWeights = weights0 / weightSum;
|
|
684
|
-
} else {
|
|
685
|
-
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
686
|
-
}
|
|
689
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
690
|
+
let normalizedWeights = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
687
691
|
|
|
688
692
|
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
689
693
|
for (var i = 0u; i < 4u; i++) {
|
|
@@ -1037,19 +1041,21 @@ export class Engine {
|
|
|
1037
1041
|
@group(0) @binding(1) var inputSampler: sampler;
|
|
1038
1042
|
@group(0) @binding(2) var<uniform> blurUniforms: BlurUniforms;
|
|
1039
1043
|
|
|
1040
|
-
//
|
|
1044
|
+
// 3-tap gaussian blur using bilinear filtering trick (40% fewer texture fetches!)
|
|
1041
1045
|
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
1042
1046
|
let texelSize = 1.0 / vec2f(textureDimensions(inputTexture));
|
|
1043
|
-
var result = vec4f(0.0);
|
|
1044
1047
|
|
|
1045
|
-
//
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
+
// Bilinear optimization: leverage hardware filtering to sample between pixels
|
|
1049
|
+
// Original 5-tap: weights [0.06136, 0.24477, 0.38774, 0.24477, 0.06136] at offsets [-2, -1, 0, 1, 2]
|
|
1050
|
+
// Optimized 3-tap: combine adjacent samples using weighted offsets
|
|
1051
|
+
let weight0 = 0.38774; // Center sample
|
|
1052
|
+
let weight1 = 0.24477 + 0.06136; // Combined outer samples = 0.30613
|
|
1053
|
+
let offset1 = (0.24477 * 1.0 + 0.06136 * 2.0) / weight1; // Weighted position = 1.2
|
|
1048
1054
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1055
|
+
var result = textureSample(inputTexture, inputSampler, input.uv) * weight0;
|
|
1056
|
+
let offsetVec = offset1 * texelSize * blurUniforms.direction;
|
|
1057
|
+
result += textureSample(inputTexture, inputSampler, input.uv + offsetVec) * weight1;
|
|
1058
|
+
result += textureSample(inputTexture, inputSampler, input.uv - offsetVec) * weight1;
|
|
1053
1059
|
|
|
1054
1060
|
return result;
|
|
1055
1061
|
}
|
|
@@ -1685,44 +1691,6 @@ export class Engine {
|
|
|
1685
1691
|
await this.setupMaterials(model)
|
|
1686
1692
|
}
|
|
1687
1693
|
|
|
1688
|
-
private opaqueNonEyeNonHairDraws: {
|
|
1689
|
-
count: number
|
|
1690
|
-
firstIndex: number
|
|
1691
|
-
bindGroup: GPUBindGroup
|
|
1692
|
-
isTransparent: boolean
|
|
1693
|
-
}[] = []
|
|
1694
|
-
private eyeDraws: { count: number; firstIndex: number; bindGroup: GPUBindGroup; isTransparent: boolean }[] = []
|
|
1695
|
-
private hairDrawsOverEyes: { count: number; firstIndex: number; bindGroup: GPUBindGroup; isTransparent: boolean }[] =
|
|
1696
|
-
[]
|
|
1697
|
-
private hairDrawsOverNonEyes: {
|
|
1698
|
-
count: number
|
|
1699
|
-
firstIndex: number
|
|
1700
|
-
bindGroup: GPUBindGroup
|
|
1701
|
-
isTransparent: boolean
|
|
1702
|
-
}[] = []
|
|
1703
|
-
private transparentNonEyeNonHairDraws: {
|
|
1704
|
-
count: number
|
|
1705
|
-
firstIndex: number
|
|
1706
|
-
bindGroup: GPUBindGroup
|
|
1707
|
-
isTransparent: boolean
|
|
1708
|
-
}[] = []
|
|
1709
|
-
private opaqueNonEyeNonHairOutlineDraws: {
|
|
1710
|
-
count: number
|
|
1711
|
-
firstIndex: number
|
|
1712
|
-
bindGroup: GPUBindGroup
|
|
1713
|
-
isTransparent: boolean
|
|
1714
|
-
}[] = []
|
|
1715
|
-
private eyeOutlineDraws: { count: number; firstIndex: number; bindGroup: GPUBindGroup; isTransparent: boolean }[] = []
|
|
1716
|
-
private hairOutlineDraws: { count: number; firstIndex: number; bindGroup: GPUBindGroup; isTransparent: boolean }[] =
|
|
1717
|
-
[]
|
|
1718
|
-
private transparentNonEyeNonHairOutlineDraws: {
|
|
1719
|
-
count: number
|
|
1720
|
-
firstIndex: number
|
|
1721
|
-
bindGroup: GPUBindGroup
|
|
1722
|
-
isTransparent: boolean
|
|
1723
|
-
}[] = []
|
|
1724
|
-
|
|
1725
|
-
// Step 8: Load textures and create material bind groups
|
|
1726
1694
|
private async setupMaterials(model: Model) {
|
|
1727
1695
|
const materials = model.getMaterials()
|
|
1728
1696
|
if (materials.length === 0) {
|
|
@@ -1779,15 +1747,15 @@ export class Engine {
|
|
|
1779
1747
|
return defaultToonTexture
|
|
1780
1748
|
}
|
|
1781
1749
|
|
|
1782
|
-
this.
|
|
1750
|
+
this.opaqueDraws = []
|
|
1783
1751
|
this.eyeDraws = []
|
|
1784
1752
|
this.hairDrawsOverEyes = []
|
|
1785
1753
|
this.hairDrawsOverNonEyes = []
|
|
1786
|
-
this.
|
|
1787
|
-
this.
|
|
1754
|
+
this.transparentDraws = []
|
|
1755
|
+
this.opaqueOutlineDraws = []
|
|
1788
1756
|
this.eyeOutlineDraws = []
|
|
1789
1757
|
this.hairOutlineDraws = []
|
|
1790
|
-
this.
|
|
1758
|
+
this.transparentOutlineDraws = []
|
|
1791
1759
|
let currentIndexOffset = 0
|
|
1792
1760
|
|
|
1793
1761
|
for (const mat of materials) {
|
|
@@ -1808,11 +1776,11 @@ export class Engine {
|
|
|
1808
1776
|
materialUniformData[0] = materialAlpha
|
|
1809
1777
|
materialUniformData[1] = 1.0 // alphaMultiplier: 1.0 for non-hair materials
|
|
1810
1778
|
materialUniformData[2] = this.rimLightIntensity
|
|
1811
|
-
materialUniformData[3] =
|
|
1779
|
+
materialUniformData[3] = 0.0 // _padding1
|
|
1812
1780
|
materialUniformData[4] = 1.0 // rimColor.r
|
|
1813
1781
|
materialUniformData[5] = 1.0 // rimColor.g
|
|
1814
1782
|
materialUniformData[6] = 1.0 // rimColor.b
|
|
1815
|
-
materialUniformData[7] = 0.0
|
|
1783
|
+
materialUniformData[7] = 0.0 // isOverEyes
|
|
1816
1784
|
|
|
1817
1785
|
const materialUniformBuffer = this.device.createBuffer({
|
|
1818
1786
|
label: `material uniform: ${mat.name}`,
|
|
@@ -1829,15 +1797,14 @@ export class Engine {
|
|
|
1829
1797
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1830
1798
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1831
1799
|
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1832
|
-
{ binding: 3, resource: this.
|
|
1800
|
+
{ binding: 3, resource: this.materialSampler },
|
|
1833
1801
|
{ binding: 4, resource: { buffer: this.skinMatrixBuffer! } },
|
|
1834
1802
|
{ binding: 5, resource: toonTexture.createView() },
|
|
1835
|
-
{ binding: 6, resource: this.
|
|
1803
|
+
{ binding: 6, resource: this.materialSampler },
|
|
1836
1804
|
{ binding: 7, resource: { buffer: materialUniformBuffer } },
|
|
1837
1805
|
],
|
|
1838
1806
|
})
|
|
1839
1807
|
|
|
1840
|
-
// Classify materials into appropriate draw lists
|
|
1841
1808
|
if (mat.isEye) {
|
|
1842
1809
|
this.eyeDraws.push({
|
|
1843
1810
|
count: indexCount,
|
|
@@ -1852,11 +1819,11 @@ export class Engine {
|
|
|
1852
1819
|
uniformData[0] = materialAlpha
|
|
1853
1820
|
uniformData[1] = 1.0 // alphaMultiplier (shader adjusts based on isOverEyes)
|
|
1854
1821
|
uniformData[2] = this.rimLightIntensity
|
|
1855
|
-
uniformData[3] =
|
|
1822
|
+
uniformData[3] = 0.0 // _padding1
|
|
1856
1823
|
uniformData[4] = 1.0 // rimColor.rgb
|
|
1857
1824
|
uniformData[5] = 1.0
|
|
1858
1825
|
uniformData[6] = 1.0
|
|
1859
|
-
uniformData[7] = isOverEyes ? 1.0 : 0.0
|
|
1826
|
+
uniformData[7] = isOverEyes ? 1.0 : 0.0 // isOverEyes
|
|
1860
1827
|
|
|
1861
1828
|
const buffer = this.device.createBuffer({
|
|
1862
1829
|
label: `material uniform (${isOverEyes ? "over eyes" : "over non-eyes"}): ${mat.name}`,
|
|
@@ -1872,10 +1839,10 @@ export class Engine {
|
|
|
1872
1839
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1873
1840
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1874
1841
|
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1875
|
-
{ binding: 3, resource: this.
|
|
1842
|
+
{ binding: 3, resource: this.materialSampler },
|
|
1876
1843
|
{ binding: 4, resource: { buffer: this.skinMatrixBuffer! } },
|
|
1877
1844
|
{ binding: 5, resource: toonTexture.createView() },
|
|
1878
|
-
{ binding: 6, resource: this.
|
|
1845
|
+
{ binding: 6, resource: this.materialSampler },
|
|
1879
1846
|
{ binding: 7, resource: { buffer: buffer } },
|
|
1880
1847
|
],
|
|
1881
1848
|
})
|
|
@@ -1898,14 +1865,14 @@ export class Engine {
|
|
|
1898
1865
|
isTransparent,
|
|
1899
1866
|
})
|
|
1900
1867
|
} else if (isTransparent) {
|
|
1901
|
-
this.
|
|
1868
|
+
this.transparentDraws.push({
|
|
1902
1869
|
count: indexCount,
|
|
1903
1870
|
firstIndex: currentIndexOffset,
|
|
1904
1871
|
bindGroup,
|
|
1905
1872
|
isTransparent,
|
|
1906
1873
|
})
|
|
1907
1874
|
} else {
|
|
1908
|
-
this.
|
|
1875
|
+
this.opaqueDraws.push({
|
|
1909
1876
|
count: indexCount,
|
|
1910
1877
|
firstIndex: currentIndexOffset,
|
|
1911
1878
|
bindGroup,
|
|
@@ -1957,14 +1924,14 @@ export class Engine {
|
|
|
1957
1924
|
isTransparent,
|
|
1958
1925
|
})
|
|
1959
1926
|
} else if (isTransparent) {
|
|
1960
|
-
this.
|
|
1927
|
+
this.transparentOutlineDraws.push({
|
|
1961
1928
|
count: indexCount,
|
|
1962
1929
|
firstIndex: currentIndexOffset,
|
|
1963
1930
|
bindGroup: outlineBindGroup,
|
|
1964
1931
|
isTransparent,
|
|
1965
1932
|
})
|
|
1966
1933
|
} else {
|
|
1967
|
-
this.
|
|
1934
|
+
this.opaqueOutlineDraws.push({
|
|
1968
1935
|
count: indexCount,
|
|
1969
1936
|
firstIndex: currentIndexOffset,
|
|
1970
1937
|
bindGroup: outlineBindGroup,
|
|
@@ -2037,9 +2004,9 @@ export class Engine {
|
|
|
2037
2004
|
|
|
2038
2005
|
this.drawCallCount = 0
|
|
2039
2006
|
|
|
2040
|
-
// Pass 1: Opaque
|
|
2007
|
+
// Pass 1: Opaque
|
|
2041
2008
|
pass.setPipeline(this.modelPipeline)
|
|
2042
|
-
for (const draw of this.
|
|
2009
|
+
for (const draw of this.opaqueDraws) {
|
|
2043
2010
|
if (draw.count > 0) {
|
|
2044
2011
|
pass.setBindGroup(0, draw.bindGroup)
|
|
2045
2012
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
@@ -2114,9 +2081,9 @@ export class Engine {
|
|
|
2114
2081
|
}
|
|
2115
2082
|
}
|
|
2116
2083
|
|
|
2117
|
-
// Pass 4: Transparent
|
|
2084
|
+
// Pass 4: Transparent
|
|
2118
2085
|
pass.setPipeline(this.modelPipeline)
|
|
2119
|
-
for (const draw of this.
|
|
2086
|
+
for (const draw of this.transparentDraws) {
|
|
2120
2087
|
if (draw.count > 0) {
|
|
2121
2088
|
pass.setBindGroup(0, draw.bindGroup)
|
|
2122
2089
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
@@ -2291,14 +2258,14 @@ export class Engine {
|
|
|
2291
2258
|
private drawOutlines(pass: GPURenderPassEncoder, transparent: boolean) {
|
|
2292
2259
|
pass.setPipeline(this.outlinePipeline)
|
|
2293
2260
|
if (transparent) {
|
|
2294
|
-
for (const draw of this.
|
|
2261
|
+
for (const draw of this.transparentOutlineDraws) {
|
|
2295
2262
|
if (draw.count > 0) {
|
|
2296
2263
|
pass.setBindGroup(0, draw.bindGroup)
|
|
2297
2264
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
2298
2265
|
}
|
|
2299
2266
|
}
|
|
2300
2267
|
} else {
|
|
2301
|
-
for (const draw of this.
|
|
2268
|
+
for (const draw of this.opaqueOutlineDraws) {
|
|
2302
2269
|
if (draw.count > 0) {
|
|
2303
2270
|
pass.setBindGroup(0, draw.bindGroup)
|
|
2304
2271
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
@@ -2376,18 +2343,18 @@ export class Engine {
|
|
|
2376
2343
|
bufferMemoryBytes += 24 * 4
|
|
2377
2344
|
}
|
|
2378
2345
|
const totalMaterialDraws =
|
|
2379
|
-
this.
|
|
2346
|
+
this.opaqueDraws.length +
|
|
2380
2347
|
this.eyeDraws.length +
|
|
2381
2348
|
this.hairDrawsOverEyes.length +
|
|
2382
2349
|
this.hairDrawsOverNonEyes.length +
|
|
2383
|
-
this.
|
|
2350
|
+
this.transparentDraws.length
|
|
2384
2351
|
bufferMemoryBytes += totalMaterialDraws * 32
|
|
2385
2352
|
|
|
2386
2353
|
const totalOutlineDraws =
|
|
2387
|
-
this.
|
|
2354
|
+
this.opaqueOutlineDraws.length +
|
|
2388
2355
|
this.eyeOutlineDraws.length +
|
|
2389
2356
|
this.hairOutlineDraws.length +
|
|
2390
|
-
this.
|
|
2357
|
+
this.transparentOutlineDraws.length
|
|
2391
2358
|
bufferMemoryBytes += totalOutlineDraws * 32
|
|
2392
2359
|
|
|
2393
2360
|
let renderTargetMemoryBytes = 0
|