reze-engine 0.1.13 → 0.1.15
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 +10 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +468 -136
- package/package.json +1 -1
- package/src/engine.ts +493 -145
package/src/engine.ts
CHANGED
|
@@ -29,8 +29,12 @@ export class Engine {
|
|
|
29
29
|
private outlinePipeline!: GPURenderPipeline
|
|
30
30
|
private hairOutlinePipeline!: GPURenderPipeline
|
|
31
31
|
private hairOutlineOverEyesPipeline!: GPURenderPipeline
|
|
32
|
+
private hairUnifiedOutlinePipeline!: GPURenderPipeline // Unified hair outline pipeline without stencil testing
|
|
32
33
|
private hairMultiplyPipeline!: GPURenderPipeline
|
|
33
34
|
private hairOpaquePipeline!: GPURenderPipeline
|
|
35
|
+
private hairUnifiedPipelineOverEyes!: GPURenderPipeline // Unified hair pipeline for over-eyes (stencil == 1)
|
|
36
|
+
private hairUnifiedPipelineOverNonEyes!: GPURenderPipeline // Unified hair pipeline for over-non-eyes (stencil != 1)
|
|
37
|
+
private hairDepthPipeline!: GPURenderPipeline // Depth-only pipeline for hair pre-pass
|
|
34
38
|
private eyePipeline!: GPURenderPipeline
|
|
35
39
|
private hairBindGroupLayout!: GPUBindGroupLayout
|
|
36
40
|
private outlineBindGroupLayout!: GPUBindGroupLayout
|
|
@@ -40,6 +44,7 @@ export class Engine {
|
|
|
40
44
|
private worldMatrixBuffer?: GPUBuffer
|
|
41
45
|
private inverseBindMatrixBuffer?: GPUBuffer
|
|
42
46
|
private skinMatrixComputePipeline?: GPUComputePipeline
|
|
47
|
+
private skinMatrixComputeBindGroup?: GPUBindGroup
|
|
43
48
|
private boneCountBuffer?: GPUBuffer
|
|
44
49
|
private multisampleTexture!: GPUTexture
|
|
45
50
|
private readonly sampleCount = 4 // MSAA 4x
|
|
@@ -60,6 +65,11 @@ export class Engine {
|
|
|
60
65
|
private bloomIntensityBuffer!: GPUBuffer
|
|
61
66
|
private bloomThresholdBuffer!: GPUBuffer
|
|
62
67
|
private linearSampler!: GPUSampler
|
|
68
|
+
// Bloom bind groups (created once, reused every frame)
|
|
69
|
+
private bloomExtractBindGroup?: GPUBindGroup
|
|
70
|
+
private bloomBlurHBindGroup?: GPUBindGroup
|
|
71
|
+
private bloomBlurVBindGroup?: GPUBindGroup
|
|
72
|
+
private bloomComposeBindGroup?: GPUBindGroup
|
|
63
73
|
// Bloom settings
|
|
64
74
|
public bloomThreshold: number = 0.3
|
|
65
75
|
public bloomIntensity: number = 0.1
|
|
@@ -163,7 +173,7 @@ export class Engine {
|
|
|
163
173
|
rimIntensity: f32,
|
|
164
174
|
rimPower: f32,
|
|
165
175
|
rimColor: vec3f,
|
|
166
|
-
|
|
176
|
+
isOverEyes: f32, // 1.0 if rendering over eyes, 0.0 otherwise
|
|
167
177
|
};
|
|
168
178
|
|
|
169
179
|
struct VertexOutput {
|
|
@@ -241,7 +251,14 @@ export class Engine {
|
|
|
241
251
|
let rimLight = material.rimColor * material.rimIntensity * rimFactor;
|
|
242
252
|
|
|
243
253
|
let color = albedo * lightAccum + rimLight;
|
|
244
|
-
|
|
254
|
+
|
|
255
|
+
// Dynamic branching: adjust alpha based on whether we're over eyes
|
|
256
|
+
// This allows single-pass hair rendering instead of two separate passes
|
|
257
|
+
var finalAlpha = material.alpha * material.alphaMultiplier;
|
|
258
|
+
if (material.isOverEyes > 0.5) {
|
|
259
|
+
finalAlpha *= 0.5; // Hair over eyes gets 50% alpha
|
|
260
|
+
}
|
|
261
|
+
|
|
245
262
|
if (finalAlpha < 0.001) {
|
|
246
263
|
discard;
|
|
247
264
|
}
|
|
@@ -356,9 +373,9 @@ export class Engine {
|
|
|
356
373
|
struct MaterialUniforms {
|
|
357
374
|
edgeColor: vec4f,
|
|
358
375
|
edgeSize: f32,
|
|
376
|
+
isOverEyes: f32, // 1.0 if rendering over eyes, 0.0 otherwise (for hair outlines)
|
|
359
377
|
_padding1: f32,
|
|
360
378
|
_padding2: f32,
|
|
361
|
-
_padding3: f32,
|
|
362
379
|
};
|
|
363
380
|
|
|
364
381
|
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
@@ -408,7 +425,15 @@ export class Engine {
|
|
|
408
425
|
}
|
|
409
426
|
|
|
410
427
|
@fragment fn fs() -> @location(0) vec4f {
|
|
411
|
-
|
|
428
|
+
var color = material.edgeColor;
|
|
429
|
+
|
|
430
|
+
// Dynamic branching: adjust alpha for hair outlines over eyes
|
|
431
|
+
// This allows single-pass outline rendering instead of two separate passes
|
|
432
|
+
if (material.isOverEyes > 0.5) {
|
|
433
|
+
color.a *= 0.5; // Hair outlines over eyes get 50% alpha
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return color;
|
|
412
437
|
}
|
|
413
438
|
`,
|
|
414
439
|
})
|
|
@@ -635,6 +660,77 @@ export class Engine {
|
|
|
635
660
|
},
|
|
636
661
|
})
|
|
637
662
|
|
|
663
|
+
// Unified hair outline pipeline: single pass without stencil testing
|
|
664
|
+
// Uses depth test "less-equal" to draw everywhere hair exists
|
|
665
|
+
// Shader branches on isOverEyes uniform to adjust alpha dynamically
|
|
666
|
+
// This eliminates the need for two separate outline passes
|
|
667
|
+
this.hairUnifiedOutlinePipeline = this.device.createRenderPipeline({
|
|
668
|
+
label: "unified hair outline pipeline",
|
|
669
|
+
layout: outlinePipelineLayout,
|
|
670
|
+
vertex: {
|
|
671
|
+
module: outlineShaderModule,
|
|
672
|
+
buffers: [
|
|
673
|
+
{
|
|
674
|
+
arrayStride: 8 * 4,
|
|
675
|
+
attributes: [
|
|
676
|
+
{
|
|
677
|
+
shaderLocation: 0,
|
|
678
|
+
offset: 0,
|
|
679
|
+
format: "float32x3" as GPUVertexFormat,
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
shaderLocation: 1,
|
|
683
|
+
offset: 3 * 4,
|
|
684
|
+
format: "float32x3" as GPUVertexFormat,
|
|
685
|
+
},
|
|
686
|
+
],
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
arrayStride: 4 * 2,
|
|
690
|
+
attributes: [{ shaderLocation: 3, offset: 0, format: "uint16x4" as GPUVertexFormat }],
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
arrayStride: 4,
|
|
694
|
+
attributes: [{ shaderLocation: 4, offset: 0, format: "unorm8x4" as GPUVertexFormat }],
|
|
695
|
+
},
|
|
696
|
+
],
|
|
697
|
+
},
|
|
698
|
+
fragment: {
|
|
699
|
+
module: outlineShaderModule,
|
|
700
|
+
targets: [
|
|
701
|
+
{
|
|
702
|
+
format: this.presentationFormat,
|
|
703
|
+
blend: {
|
|
704
|
+
color: {
|
|
705
|
+
srcFactor: "src-alpha",
|
|
706
|
+
dstFactor: "one-minus-src-alpha",
|
|
707
|
+
operation: "add",
|
|
708
|
+
},
|
|
709
|
+
alpha: {
|
|
710
|
+
srcFactor: "one",
|
|
711
|
+
dstFactor: "one-minus-src-alpha",
|
|
712
|
+
operation: "add",
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
primitive: {
|
|
719
|
+
cullMode: "back",
|
|
720
|
+
},
|
|
721
|
+
depthStencil: {
|
|
722
|
+
format: "depth24plus-stencil8",
|
|
723
|
+
depthWriteEnabled: false, // Don't write depth - let hair geometry control depth
|
|
724
|
+
depthCompare: "less-equal", // Only draw where hair depth exists (no stencil test needed)
|
|
725
|
+
depthBias: -0.0001, // Small negative bias to bring outline slightly closer for depth test
|
|
726
|
+
depthBiasSlopeScale: 0.0,
|
|
727
|
+
depthBiasClamp: 0.0,
|
|
728
|
+
},
|
|
729
|
+
multisample: {
|
|
730
|
+
count: this.sampleCount,
|
|
731
|
+
},
|
|
732
|
+
})
|
|
733
|
+
|
|
638
734
|
// Unified hair pipeline - can be used for both over-eyes and over-non-eyes
|
|
639
735
|
// The difference is controlled by stencil state and alpha multiplier in material uniform
|
|
640
736
|
this.hairMultiplyPipeline = this.device.createRenderPipeline({
|
|
@@ -833,6 +929,240 @@ export class Engine {
|
|
|
833
929
|
},
|
|
834
930
|
multisample: { count: this.sampleCount },
|
|
835
931
|
})
|
|
932
|
+
|
|
933
|
+
// Depth-only shader for hair pre-pass (reduces overdraw by early depth rejection)
|
|
934
|
+
const depthOnlyShaderModule = this.device.createShaderModule({
|
|
935
|
+
label: "depth only shader",
|
|
936
|
+
code: /* wgsl */ `
|
|
937
|
+
struct CameraUniforms {
|
|
938
|
+
view: mat4x4f,
|
|
939
|
+
projection: mat4x4f,
|
|
940
|
+
viewPos: vec3f,
|
|
941
|
+
_padding: f32,
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
945
|
+
@group(0) @binding(4) var<storage, read> skinMats: array<mat4x4f>;
|
|
946
|
+
|
|
947
|
+
@vertex fn vs(
|
|
948
|
+
@location(0) position: vec3f,
|
|
949
|
+
@location(1) normal: vec3f,
|
|
950
|
+
@location(3) joints0: vec4<u32>,
|
|
951
|
+
@location(4) weights0: vec4<f32>
|
|
952
|
+
) -> @builtin(position) vec4f {
|
|
953
|
+
let pos4 = vec4f(position, 1.0);
|
|
954
|
+
|
|
955
|
+
// Normalize weights
|
|
956
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
957
|
+
var normalizedWeights: vec4f;
|
|
958
|
+
if (weightSum > 0.0001) {
|
|
959
|
+
normalizedWeights = weights0 / weightSum;
|
|
960
|
+
} else {
|
|
961
|
+
normalizedWeights = vec4f(1.0, 0.0, 0.0, 0.0);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
965
|
+
for (var i = 0u; i < 4u; i++) {
|
|
966
|
+
let j = joints0[i];
|
|
967
|
+
let w = normalizedWeights[i];
|
|
968
|
+
let m = skinMats[j];
|
|
969
|
+
skinnedPos += (m * pos4) * w;
|
|
970
|
+
}
|
|
971
|
+
let worldPos = skinnedPos.xyz;
|
|
972
|
+
let clipPos = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
973
|
+
return clipPos;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Minimal fragment shader - returns transparent, no color writes (writeMask: 0)
|
|
977
|
+
// Required because render pass has color attachments
|
|
978
|
+
// Depth is still written even though we don't write color
|
|
979
|
+
@fragment fn fs() -> @location(0) vec4f {
|
|
980
|
+
return vec4f(0.0, 0.0, 0.0, 0.0); // Transparent - color writes disabled via writeMask
|
|
981
|
+
}
|
|
982
|
+
`,
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
// Hair depth pre-pass pipeline (depth-only, no color writes)
|
|
986
|
+
// This eliminates most overdraw by rejecting fragments early before expensive shading
|
|
987
|
+
// Note: Must have a color target to match render pass, but we disable all color writes
|
|
988
|
+
this.hairDepthPipeline = this.device.createRenderPipeline({
|
|
989
|
+
label: "hair depth pre-pass",
|
|
990
|
+
layout: sharedPipelineLayout,
|
|
991
|
+
vertex: {
|
|
992
|
+
module: depthOnlyShaderModule,
|
|
993
|
+
buffers: [
|
|
994
|
+
{
|
|
995
|
+
arrayStride: 8 * 4,
|
|
996
|
+
attributes: [
|
|
997
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" as GPUVertexFormat },
|
|
998
|
+
{ shaderLocation: 1, offset: 3 * 4, format: "float32x3" as GPUVertexFormat },
|
|
999
|
+
],
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
arrayStride: 4 * 2,
|
|
1003
|
+
attributes: [{ shaderLocation: 3, offset: 0, format: "uint16x4" as GPUVertexFormat }],
|
|
1004
|
+
},
|
|
1005
|
+
{
|
|
1006
|
+
arrayStride: 4,
|
|
1007
|
+
attributes: [{ shaderLocation: 4, offset: 0, format: "unorm8x4" as GPUVertexFormat }],
|
|
1008
|
+
},
|
|
1009
|
+
],
|
|
1010
|
+
},
|
|
1011
|
+
fragment: {
|
|
1012
|
+
module: depthOnlyShaderModule,
|
|
1013
|
+
entryPoint: "fs",
|
|
1014
|
+
targets: [
|
|
1015
|
+
{
|
|
1016
|
+
format: this.presentationFormat,
|
|
1017
|
+
writeMask: 0, // Disable all color writes - we only care about depth
|
|
1018
|
+
},
|
|
1019
|
+
],
|
|
1020
|
+
},
|
|
1021
|
+
primitive: { cullMode: "none" },
|
|
1022
|
+
depthStencil: {
|
|
1023
|
+
format: "depth24plus-stencil8",
|
|
1024
|
+
depthWriteEnabled: true,
|
|
1025
|
+
depthCompare: "less",
|
|
1026
|
+
},
|
|
1027
|
+
multisample: { count: this.sampleCount },
|
|
1028
|
+
})
|
|
1029
|
+
|
|
1030
|
+
// Unified hair pipeline: single pass with dynamic branching in shader
|
|
1031
|
+
// Uses stencil testing to filter fragments, then shader branches on isOverEyes uniform
|
|
1032
|
+
// This eliminates the need for separate pipelines - same shader, different stencil states
|
|
1033
|
+
// We create two variants: one for over-eyes (stencil == 1) and one for over-non-eyes (stencil != 1)
|
|
1034
|
+
|
|
1035
|
+
// Unified pipeline for hair over eyes (stencil == 1)
|
|
1036
|
+
this.hairUnifiedPipelineOverEyes = this.device.createRenderPipeline({
|
|
1037
|
+
label: "unified hair pipeline (over eyes)",
|
|
1038
|
+
layout: sharedPipelineLayout,
|
|
1039
|
+
vertex: {
|
|
1040
|
+
module: shaderModule,
|
|
1041
|
+
buffers: [
|
|
1042
|
+
{
|
|
1043
|
+
arrayStride: 8 * 4,
|
|
1044
|
+
attributes: [
|
|
1045
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" as GPUVertexFormat },
|
|
1046
|
+
{ shaderLocation: 1, offset: 3 * 4, format: "float32x3" as GPUVertexFormat },
|
|
1047
|
+
{ shaderLocation: 2, offset: 6 * 4, format: "float32x2" as GPUVertexFormat },
|
|
1048
|
+
],
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
arrayStride: 4 * 2,
|
|
1052
|
+
attributes: [{ shaderLocation: 3, offset: 0, format: "uint16x4" as GPUVertexFormat }],
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
arrayStride: 4,
|
|
1056
|
+
attributes: [{ shaderLocation: 4, offset: 0, format: "unorm8x4" as GPUVertexFormat }],
|
|
1057
|
+
},
|
|
1058
|
+
],
|
|
1059
|
+
},
|
|
1060
|
+
fragment: {
|
|
1061
|
+
module: shaderModule,
|
|
1062
|
+
targets: [
|
|
1063
|
+
{
|
|
1064
|
+
format: this.presentationFormat,
|
|
1065
|
+
blend: {
|
|
1066
|
+
color: {
|
|
1067
|
+
srcFactor: "src-alpha",
|
|
1068
|
+
dstFactor: "one-minus-src-alpha",
|
|
1069
|
+
operation: "add",
|
|
1070
|
+
},
|
|
1071
|
+
alpha: {
|
|
1072
|
+
srcFactor: "one",
|
|
1073
|
+
dstFactor: "one-minus-src-alpha",
|
|
1074
|
+
operation: "add",
|
|
1075
|
+
},
|
|
1076
|
+
},
|
|
1077
|
+
},
|
|
1078
|
+
],
|
|
1079
|
+
},
|
|
1080
|
+
primitive: { cullMode: "none" },
|
|
1081
|
+
depthStencil: {
|
|
1082
|
+
format: "depth24plus-stencil8",
|
|
1083
|
+
depthWriteEnabled: false, // Don't write depth (already written in pre-pass)
|
|
1084
|
+
depthCompare: "equal", // Only render where depth matches pre-pass
|
|
1085
|
+
stencilFront: {
|
|
1086
|
+
compare: "equal", // Only render where stencil == 1 (over eyes)
|
|
1087
|
+
failOp: "keep",
|
|
1088
|
+
depthFailOp: "keep",
|
|
1089
|
+
passOp: "keep",
|
|
1090
|
+
},
|
|
1091
|
+
stencilBack: {
|
|
1092
|
+
compare: "equal",
|
|
1093
|
+
failOp: "keep",
|
|
1094
|
+
depthFailOp: "keep",
|
|
1095
|
+
passOp: "keep",
|
|
1096
|
+
},
|
|
1097
|
+
},
|
|
1098
|
+
multisample: { count: this.sampleCount },
|
|
1099
|
+
})
|
|
1100
|
+
|
|
1101
|
+
// Unified pipeline for hair over non-eyes (stencil != 1)
|
|
1102
|
+
this.hairUnifiedPipelineOverNonEyes = this.device.createRenderPipeline({
|
|
1103
|
+
label: "unified hair pipeline (over non-eyes)",
|
|
1104
|
+
layout: sharedPipelineLayout,
|
|
1105
|
+
vertex: {
|
|
1106
|
+
module: shaderModule,
|
|
1107
|
+
buffers: [
|
|
1108
|
+
{
|
|
1109
|
+
arrayStride: 8 * 4,
|
|
1110
|
+
attributes: [
|
|
1111
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" as GPUVertexFormat },
|
|
1112
|
+
{ shaderLocation: 1, offset: 3 * 4, format: "float32x3" as GPUVertexFormat },
|
|
1113
|
+
{ shaderLocation: 2, offset: 6 * 4, format: "float32x2" as GPUVertexFormat },
|
|
1114
|
+
],
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
arrayStride: 4 * 2,
|
|
1118
|
+
attributes: [{ shaderLocation: 3, offset: 0, format: "uint16x4" as GPUVertexFormat }],
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
arrayStride: 4,
|
|
1122
|
+
attributes: [{ shaderLocation: 4, offset: 0, format: "unorm8x4" as GPUVertexFormat }],
|
|
1123
|
+
},
|
|
1124
|
+
],
|
|
1125
|
+
},
|
|
1126
|
+
fragment: {
|
|
1127
|
+
module: shaderModule,
|
|
1128
|
+
targets: [
|
|
1129
|
+
{
|
|
1130
|
+
format: this.presentationFormat,
|
|
1131
|
+
blend: {
|
|
1132
|
+
color: {
|
|
1133
|
+
srcFactor: "src-alpha",
|
|
1134
|
+
dstFactor: "one-minus-src-alpha",
|
|
1135
|
+
operation: "add",
|
|
1136
|
+
},
|
|
1137
|
+
alpha: {
|
|
1138
|
+
srcFactor: "one",
|
|
1139
|
+
dstFactor: "one-minus-src-alpha",
|
|
1140
|
+
operation: "add",
|
|
1141
|
+
},
|
|
1142
|
+
},
|
|
1143
|
+
},
|
|
1144
|
+
],
|
|
1145
|
+
},
|
|
1146
|
+
primitive: { cullMode: "none" },
|
|
1147
|
+
depthStencil: {
|
|
1148
|
+
format: "depth24plus-stencil8",
|
|
1149
|
+
depthWriteEnabled: false, // Don't write depth (already written in pre-pass)
|
|
1150
|
+
depthCompare: "equal", // Only render where depth matches pre-pass
|
|
1151
|
+
stencilFront: {
|
|
1152
|
+
compare: "not-equal", // Only render where stencil != 1 (over non-eyes)
|
|
1153
|
+
failOp: "keep",
|
|
1154
|
+
depthFailOp: "keep",
|
|
1155
|
+
passOp: "keep",
|
|
1156
|
+
},
|
|
1157
|
+
stencilBack: {
|
|
1158
|
+
compare: "not-equal",
|
|
1159
|
+
failOp: "keep",
|
|
1160
|
+
depthFailOp: "keep",
|
|
1161
|
+
passOp: "keep",
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
multisample: { count: this.sampleCount },
|
|
1165
|
+
})
|
|
836
1166
|
}
|
|
837
1167
|
|
|
838
1168
|
// Create compute shader for skin matrix computation
|
|
@@ -1156,6 +1486,70 @@ export class Engine {
|
|
|
1156
1486
|
this.linearSampler = linearSampler
|
|
1157
1487
|
}
|
|
1158
1488
|
|
|
1489
|
+
// Setup bloom textures and bind groups (called when canvas is resized)
|
|
1490
|
+
private setupBloom(width: number, height: number) {
|
|
1491
|
+
// Create bloom textures (half resolution for performance)
|
|
1492
|
+
const bloomWidth = Math.floor(width / 2)
|
|
1493
|
+
const bloomHeight = Math.floor(height / 2)
|
|
1494
|
+
this.bloomExtractTexture = this.device.createTexture({
|
|
1495
|
+
label: "bloom extract",
|
|
1496
|
+
size: [bloomWidth, bloomHeight],
|
|
1497
|
+
format: this.presentationFormat,
|
|
1498
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1499
|
+
})
|
|
1500
|
+
this.bloomBlurTexture1 = this.device.createTexture({
|
|
1501
|
+
label: "bloom blur 1",
|
|
1502
|
+
size: [bloomWidth, bloomHeight],
|
|
1503
|
+
format: this.presentationFormat,
|
|
1504
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1505
|
+
})
|
|
1506
|
+
this.bloomBlurTexture2 = this.device.createTexture({
|
|
1507
|
+
label: "bloom blur 2",
|
|
1508
|
+
size: [bloomWidth, bloomHeight],
|
|
1509
|
+
format: this.presentationFormat,
|
|
1510
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1511
|
+
})
|
|
1512
|
+
|
|
1513
|
+
// Create bloom bind groups
|
|
1514
|
+
this.bloomExtractBindGroup = this.device.createBindGroup({
|
|
1515
|
+
layout: this.bloomExtractPipeline.getBindGroupLayout(0),
|
|
1516
|
+
entries: [
|
|
1517
|
+
{ binding: 0, resource: this.sceneRenderTexture.createView() },
|
|
1518
|
+
{ binding: 1, resource: this.linearSampler },
|
|
1519
|
+
{ binding: 2, resource: { buffer: this.bloomThresholdBuffer } },
|
|
1520
|
+
],
|
|
1521
|
+
})
|
|
1522
|
+
|
|
1523
|
+
this.bloomBlurHBindGroup = this.device.createBindGroup({
|
|
1524
|
+
layout: this.bloomBlurPipeline.getBindGroupLayout(0),
|
|
1525
|
+
entries: [
|
|
1526
|
+
{ binding: 0, resource: this.bloomExtractTexture.createView() },
|
|
1527
|
+
{ binding: 1, resource: this.linearSampler },
|
|
1528
|
+
{ binding: 2, resource: { buffer: this.blurDirectionBuffer } },
|
|
1529
|
+
],
|
|
1530
|
+
})
|
|
1531
|
+
|
|
1532
|
+
this.bloomBlurVBindGroup = this.device.createBindGroup({
|
|
1533
|
+
layout: this.bloomBlurPipeline.getBindGroupLayout(0),
|
|
1534
|
+
entries: [
|
|
1535
|
+
{ binding: 0, resource: this.bloomBlurTexture1.createView() },
|
|
1536
|
+
{ binding: 1, resource: this.linearSampler },
|
|
1537
|
+
{ binding: 2, resource: { buffer: this.blurDirectionBuffer } },
|
|
1538
|
+
],
|
|
1539
|
+
})
|
|
1540
|
+
|
|
1541
|
+
this.bloomComposeBindGroup = this.device.createBindGroup({
|
|
1542
|
+
layout: this.bloomComposePipeline.getBindGroupLayout(0),
|
|
1543
|
+
entries: [
|
|
1544
|
+
{ binding: 0, resource: this.sceneRenderTexture.createView() },
|
|
1545
|
+
{ binding: 1, resource: this.linearSampler },
|
|
1546
|
+
{ binding: 2, resource: this.bloomBlurTexture2.createView() },
|
|
1547
|
+
{ binding: 3, resource: this.linearSampler },
|
|
1548
|
+
{ binding: 4, resource: { buffer: this.bloomIntensityBuffer } },
|
|
1549
|
+
],
|
|
1550
|
+
})
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1159
1553
|
// Step 3: Setup canvas resize handling
|
|
1160
1554
|
private setupResize() {
|
|
1161
1555
|
this.resizeObserver = new ResizeObserver(() => this.handleResize())
|
|
@@ -1200,27 +1594,8 @@ export class Engine {
|
|
|
1200
1594
|
})
|
|
1201
1595
|
this.sceneRenderTextureView = this.sceneRenderTexture.createView()
|
|
1202
1596
|
|
|
1203
|
-
//
|
|
1204
|
-
|
|
1205
|
-
const bloomHeight = Math.floor(height / 2)
|
|
1206
|
-
this.bloomExtractTexture = this.device.createTexture({
|
|
1207
|
-
label: "bloom extract",
|
|
1208
|
-
size: [bloomWidth, bloomHeight],
|
|
1209
|
-
format: this.presentationFormat,
|
|
1210
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1211
|
-
})
|
|
1212
|
-
this.bloomBlurTexture1 = this.device.createTexture({
|
|
1213
|
-
label: "bloom blur 1",
|
|
1214
|
-
size: [bloomWidth, bloomHeight],
|
|
1215
|
-
format: this.presentationFormat,
|
|
1216
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1217
|
-
})
|
|
1218
|
-
this.bloomBlurTexture2 = this.device.createTexture({
|
|
1219
|
-
label: "bloom blur 2",
|
|
1220
|
-
size: [bloomWidth, bloomHeight],
|
|
1221
|
-
format: this.presentationFormat,
|
|
1222
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1223
|
-
})
|
|
1597
|
+
// Setup bloom textures and bind groups
|
|
1598
|
+
this.setupBloom(width, height)
|
|
1224
1599
|
|
|
1225
1600
|
const depthTextureView = this.depthTexture.createView()
|
|
1226
1601
|
|
|
@@ -1447,6 +1822,17 @@ export class Engine {
|
|
|
1447
1822
|
|
|
1448
1823
|
this.createSkinMatrixComputePipeline()
|
|
1449
1824
|
|
|
1825
|
+
// Create compute bind group once (reused every frame)
|
|
1826
|
+
this.skinMatrixComputeBindGroup = this.device.createBindGroup({
|
|
1827
|
+
layout: this.skinMatrixComputePipeline!.getBindGroupLayout(0),
|
|
1828
|
+
entries: [
|
|
1829
|
+
{ binding: 0, resource: { buffer: this.boneCountBuffer } },
|
|
1830
|
+
{ binding: 1, resource: { buffer: this.worldMatrixBuffer } },
|
|
1831
|
+
{ binding: 2, resource: { buffer: this.inverseBindMatrixBuffer } },
|
|
1832
|
+
{ binding: 3, resource: { buffer: this.skinMatrixBuffer } },
|
|
1833
|
+
],
|
|
1834
|
+
})
|
|
1835
|
+
|
|
1450
1836
|
const indices = model.getIndices()
|
|
1451
1837
|
if (indices) {
|
|
1452
1838
|
this.indexBuffer = this.device.createBuffer({
|
|
@@ -1590,7 +1976,7 @@ export class Engine {
|
|
|
1590
1976
|
materialUniformData[4] = this.rimLightColor[0] // rimColor.r
|
|
1591
1977
|
materialUniformData[5] = this.rimLightColor[1] // rimColor.g
|
|
1592
1978
|
materialUniformData[6] = this.rimLightColor[2] // rimColor.b
|
|
1593
|
-
materialUniformData[7] = 0.0 //
|
|
1979
|
+
materialUniformData[7] = 0.0 // isOverEyes: 0.0 for non-hair materials
|
|
1594
1980
|
|
|
1595
1981
|
const materialUniformBuffer = this.device.createBuffer({
|
|
1596
1982
|
label: `material uniform: ${mat.name}`,
|
|
@@ -1624,24 +2010,39 @@ export class Engine {
|
|
|
1624
2010
|
isTransparent,
|
|
1625
2011
|
})
|
|
1626
2012
|
} else if (mat.isHair) {
|
|
1627
|
-
// For hair materials, create
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
2013
|
+
// For hair materials, create a single bind group that will be used with the unified pipeline
|
|
2014
|
+
// The shader will dynamically branch based on isOverEyes uniform
|
|
2015
|
+
// We still need two uniform buffers (one for each render mode) but can reuse the same bind group structure
|
|
2016
|
+
const materialUniformDataHair = new Float32Array(8)
|
|
2017
|
+
materialUniformDataHair[0] = materialAlpha
|
|
2018
|
+
materialUniformDataHair[1] = 1.0 // alphaMultiplier: base value, shader will adjust
|
|
2019
|
+
materialUniformDataHair[2] = this.rimLightIntensity
|
|
2020
|
+
materialUniformDataHair[3] = this.rimLightPower
|
|
2021
|
+
materialUniformDataHair[4] = this.rimLightColor[0] // rimColor.r
|
|
2022
|
+
materialUniformDataHair[5] = this.rimLightColor[1] // rimColor.g
|
|
2023
|
+
materialUniformDataHair[6] = this.rimLightColor[2] // rimColor.b
|
|
2024
|
+
materialUniformDataHair[7] = 0.0 // isOverEyes: will be set per draw call
|
|
2025
|
+
|
|
2026
|
+
// Create uniform buffers for both modes (we'll update them per frame)
|
|
1638
2027
|
const materialUniformBufferOverEyes = this.device.createBuffer({
|
|
1639
2028
|
label: `material uniform (over eyes): ${mat.name}`,
|
|
1640
|
-
size:
|
|
2029
|
+
size: materialUniformDataHair.byteLength,
|
|
1641
2030
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1642
2031
|
})
|
|
2032
|
+
const materialUniformDataOverEyes = new Float32Array(materialUniformDataHair)
|
|
2033
|
+
materialUniformDataOverEyes[7] = 1.0 // isOverEyes = 1.0
|
|
1643
2034
|
this.device.queue.writeBuffer(materialUniformBufferOverEyes, 0, materialUniformDataOverEyes)
|
|
1644
2035
|
|
|
2036
|
+
const materialUniformBufferOverNonEyes = this.device.createBuffer({
|
|
2037
|
+
label: `material uniform (over non-eyes): ${mat.name}`,
|
|
2038
|
+
size: materialUniformDataHair.byteLength,
|
|
2039
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2040
|
+
})
|
|
2041
|
+
const materialUniformDataOverNonEyes = new Float32Array(materialUniformDataHair)
|
|
2042
|
+
materialUniformDataOverNonEyes[7] = 0.0 // isOverEyes = 0.0
|
|
2043
|
+
this.device.queue.writeBuffer(materialUniformBufferOverNonEyes, 0, materialUniformDataOverNonEyes)
|
|
2044
|
+
|
|
2045
|
+
// Create bind groups for both modes (they share everything except the uniform buffer)
|
|
1645
2046
|
const bindGroupOverEyes = this.device.createBindGroup({
|
|
1646
2047
|
label: `material bind group (over eyes): ${mat.name}`,
|
|
1647
2048
|
layout: this.hairBindGroupLayout,
|
|
@@ -1657,31 +2058,6 @@ export class Engine {
|
|
|
1657
2058
|
],
|
|
1658
2059
|
})
|
|
1659
2060
|
|
|
1660
|
-
this.hairDrawsOverEyes.push({
|
|
1661
|
-
count: matCount,
|
|
1662
|
-
firstIndex: runningFirstIndex,
|
|
1663
|
-
bindGroup: bindGroupOverEyes,
|
|
1664
|
-
isTransparent,
|
|
1665
|
-
})
|
|
1666
|
-
|
|
1667
|
-
// Create material uniform for hair over non-eyes (alphaMultiplier = 1.0)
|
|
1668
|
-
const materialUniformDataOverNonEyes = new Float32Array(8)
|
|
1669
|
-
materialUniformDataOverNonEyes[0] = materialAlpha
|
|
1670
|
-
materialUniformDataOverNonEyes[1] = 1.0 // alphaMultiplier: 1.0 for over-non-eyes
|
|
1671
|
-
materialUniformDataOverNonEyes[2] = this.rimLightIntensity
|
|
1672
|
-
materialUniformDataOverNonEyes[3] = this.rimLightPower
|
|
1673
|
-
materialUniformDataOverNonEyes[4] = this.rimLightColor[0] // rimColor.r
|
|
1674
|
-
materialUniformDataOverNonEyes[5] = this.rimLightColor[1] // rimColor.g
|
|
1675
|
-
materialUniformDataOverNonEyes[6] = this.rimLightColor[2] // rimColor.b
|
|
1676
|
-
materialUniformDataOverNonEyes[7] = 0.0 // _padding1
|
|
1677
|
-
|
|
1678
|
-
const materialUniformBufferOverNonEyes = this.device.createBuffer({
|
|
1679
|
-
label: `material uniform (over non-eyes): ${mat.name}`,
|
|
1680
|
-
size: materialUniformDataOverNonEyes.byteLength,
|
|
1681
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1682
|
-
})
|
|
1683
|
-
this.device.queue.writeBuffer(materialUniformBufferOverNonEyes, 0, materialUniformDataOverNonEyes)
|
|
1684
|
-
|
|
1685
2061
|
const bindGroupOverNonEyes = this.device.createBindGroup({
|
|
1686
2062
|
label: `material bind group (over non-eyes): ${mat.name}`,
|
|
1687
2063
|
layout: this.hairBindGroupLayout,
|
|
@@ -1697,6 +2073,14 @@ export class Engine {
|
|
|
1697
2073
|
],
|
|
1698
2074
|
})
|
|
1699
2075
|
|
|
2076
|
+
// Store both bind groups - we'll use them with the unified pipeline
|
|
2077
|
+
this.hairDrawsOverEyes.push({
|
|
2078
|
+
count: matCount,
|
|
2079
|
+
firstIndex: runningFirstIndex,
|
|
2080
|
+
bindGroup: bindGroupOverEyes,
|
|
2081
|
+
isTransparent,
|
|
2082
|
+
})
|
|
2083
|
+
|
|
1700
2084
|
this.hairDrawsOverNonEyes.push({
|
|
1701
2085
|
count: matCount,
|
|
1702
2086
|
firstIndex: runningFirstIndex,
|
|
@@ -1722,11 +2106,14 @@ export class Engine {
|
|
|
1722
2106
|
// Outline for all materials (including transparent) - Edge flag is at bit 4 (0x10) in PMX format, not bit 0 (0x01)
|
|
1723
2107
|
if ((mat.edgeFlag & 0x10) !== 0 && mat.edgeSize > 0) {
|
|
1724
2108
|
const materialUniformData = new Float32Array(8)
|
|
1725
|
-
materialUniformData[0] = mat.edgeColor[0]
|
|
1726
|
-
materialUniformData[1] = mat.edgeColor[1]
|
|
1727
|
-
materialUniformData[2] = mat.edgeColor[2]
|
|
1728
|
-
materialUniformData[3] = mat.edgeColor[3]
|
|
2109
|
+
materialUniformData[0] = mat.edgeColor[0] // edgeColor.r
|
|
2110
|
+
materialUniformData[1] = mat.edgeColor[1] // edgeColor.g
|
|
2111
|
+
materialUniformData[2] = mat.edgeColor[2] // edgeColor.b
|
|
2112
|
+
materialUniformData[3] = mat.edgeColor[3] // edgeColor.a
|
|
1729
2113
|
materialUniformData[4] = mat.edgeSize
|
|
2114
|
+
materialUniformData[5] = mat.isHair ? 0.0 : 0.0 // isOverEyes: 0.0 for all (unified pipeline doesn't use stencil)
|
|
2115
|
+
materialUniformData[6] = 0.0 // _padding1
|
|
2116
|
+
materialUniformData[7] = 0.0 // _padding2
|
|
1730
2117
|
|
|
1731
2118
|
const materialUniformBuffer = this.device.createBuffer({
|
|
1732
2119
|
label: `outline material uniform: ${mat.name}`,
|
|
@@ -1876,30 +2263,40 @@ export class Engine {
|
|
|
1876
2263
|
}
|
|
1877
2264
|
}
|
|
1878
2265
|
|
|
1879
|
-
// PASS 3: Hair rendering - optimized
|
|
1880
|
-
//
|
|
1881
|
-
//
|
|
2266
|
+
// PASS 3: Hair rendering - optimized with depth pre-pass and unified pipeline
|
|
2267
|
+
// Depth pre-pass: render hair depth-only to eliminate overdraw early
|
|
2268
|
+
// Then render shaded hair once with depth test "equal" to only shade visible fragments
|
|
1882
2269
|
|
|
1883
2270
|
this.drawOutlines(pass, false) // Opaque outlines
|
|
1884
2271
|
|
|
1885
|
-
// 3a: Hair
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
pass.
|
|
2272
|
+
// 3a: Hair depth pre-pass (depth-only, no color writes)
|
|
2273
|
+
// This eliminates most overdraw by rejecting fragments early before expensive shading
|
|
2274
|
+
if (this.hairDrawsOverEyes.length > 0 || this.hairDrawsOverNonEyes.length > 0) {
|
|
2275
|
+
pass.setPipeline(this.hairDepthPipeline)
|
|
2276
|
+
// Render all hair materials for depth (no stencil test needed for depth pass)
|
|
1889
2277
|
for (const draw of this.hairDrawsOverEyes) {
|
|
2278
|
+
if (draw.count > 0) {
|
|
2279
|
+
// Use the same bind group structure (camera, skin matrices) for depth pass
|
|
2280
|
+
pass.setBindGroup(0, draw.bindGroup)
|
|
2281
|
+
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
for (const draw of this.hairDrawsOverNonEyes) {
|
|
1890
2285
|
if (draw.count > 0) {
|
|
1891
2286
|
pass.setBindGroup(0, draw.bindGroup)
|
|
1892
2287
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
1893
|
-
this.drawCallCount++
|
|
1894
2288
|
}
|
|
1895
2289
|
}
|
|
1896
2290
|
}
|
|
1897
2291
|
|
|
1898
|
-
// 3b: Hair
|
|
1899
|
-
|
|
1900
|
-
|
|
2292
|
+
// 3b: Hair shading pass - unified pipeline with dynamic branching
|
|
2293
|
+
// Uses depth test "equal" to only render where depth was written in pre-pass
|
|
2294
|
+
// Shader branches on isOverEyes uniform to adjust alpha dynamically
|
|
2295
|
+
// This eliminates one full geometry pass compared to the old approach
|
|
2296
|
+
if (this.hairDrawsOverEyes.length > 0) {
|
|
2297
|
+
pass.setPipeline(this.hairUnifiedPipelineOverEyes)
|
|
1901
2298
|
pass.setStencilReference(1)
|
|
1902
|
-
for (const draw of this.
|
|
2299
|
+
for (const draw of this.hairDrawsOverEyes) {
|
|
1903
2300
|
if (draw.count > 0) {
|
|
1904
2301
|
pass.setBindGroup(0, draw.bindGroup)
|
|
1905
2302
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
@@ -1908,21 +2305,24 @@ export class Engine {
|
|
|
1908
2305
|
}
|
|
1909
2306
|
}
|
|
1910
2307
|
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
// Over eyes
|
|
1914
|
-
pass.setPipeline(this.hairOutlineOverEyesPipeline)
|
|
2308
|
+
if (this.hairDrawsOverNonEyes.length > 0) {
|
|
2309
|
+
pass.setPipeline(this.hairUnifiedPipelineOverNonEyes)
|
|
1915
2310
|
pass.setStencilReference(1)
|
|
1916
|
-
for (const draw of this.
|
|
2311
|
+
for (const draw of this.hairDrawsOverNonEyes) {
|
|
1917
2312
|
if (draw.count > 0) {
|
|
1918
2313
|
pass.setBindGroup(0, draw.bindGroup)
|
|
1919
2314
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
2315
|
+
this.drawCallCount++
|
|
1920
2316
|
}
|
|
1921
2317
|
}
|
|
2318
|
+
}
|
|
1922
2319
|
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
2320
|
+
// 3c: Hair outlines - unified single pass without stencil testing
|
|
2321
|
+
// Uses depth test "less-equal" to draw everywhere hair exists
|
|
2322
|
+
// Shader branches on isOverEyes uniform to adjust alpha dynamically (currently always 0.0)
|
|
2323
|
+
// This eliminates the need for two separate outline passes
|
|
2324
|
+
if (this.hairOutlineDraws.length > 0) {
|
|
2325
|
+
pass.setPipeline(this.hairUnifiedOutlinePipeline)
|
|
1926
2326
|
for (const draw of this.hairOutlineDraws) {
|
|
1927
2327
|
if (draw.count > 0) {
|
|
1928
2328
|
pass.setBindGroup(0, draw.bindGroup)
|
|
@@ -1987,17 +2387,8 @@ export class Engine {
|
|
|
1987
2387
|
],
|
|
1988
2388
|
})
|
|
1989
2389
|
|
|
1990
|
-
const extractBindGroup = this.device.createBindGroup({
|
|
1991
|
-
layout: this.bloomExtractPipeline.getBindGroupLayout(0),
|
|
1992
|
-
entries: [
|
|
1993
|
-
{ binding: 0, resource: this.sceneRenderTexture.createView() },
|
|
1994
|
-
{ binding: 1, resource: this.linearSampler },
|
|
1995
|
-
{ binding: 2, resource: { buffer: this.bloomThresholdBuffer } },
|
|
1996
|
-
],
|
|
1997
|
-
})
|
|
1998
|
-
|
|
1999
2390
|
extractPass.setPipeline(this.bloomExtractPipeline)
|
|
2000
|
-
extractPass.setBindGroup(0,
|
|
2391
|
+
extractPass.setBindGroup(0, this.bloomExtractBindGroup!)
|
|
2001
2392
|
extractPass.draw(6, 1, 0, 0)
|
|
2002
2393
|
extractPass.end()
|
|
2003
2394
|
|
|
@@ -2018,17 +2409,8 @@ export class Engine {
|
|
|
2018
2409
|
],
|
|
2019
2410
|
})
|
|
2020
2411
|
|
|
2021
|
-
const blurHBindGroup = this.device.createBindGroup({
|
|
2022
|
-
layout: this.bloomBlurPipeline.getBindGroupLayout(0),
|
|
2023
|
-
entries: [
|
|
2024
|
-
{ binding: 0, resource: this.bloomExtractTexture.createView() },
|
|
2025
|
-
{ binding: 1, resource: this.linearSampler },
|
|
2026
|
-
{ binding: 2, resource: { buffer: this.blurDirectionBuffer } },
|
|
2027
|
-
],
|
|
2028
|
-
})
|
|
2029
|
-
|
|
2030
2412
|
blurHPass.setPipeline(this.bloomBlurPipeline)
|
|
2031
|
-
blurHPass.setBindGroup(0,
|
|
2413
|
+
blurHPass.setBindGroup(0, this.bloomBlurHBindGroup!)
|
|
2032
2414
|
blurHPass.draw(6, 1, 0, 0)
|
|
2033
2415
|
blurHPass.end()
|
|
2034
2416
|
|
|
@@ -2049,17 +2431,8 @@ export class Engine {
|
|
|
2049
2431
|
],
|
|
2050
2432
|
})
|
|
2051
2433
|
|
|
2052
|
-
const blurVBindGroup = this.device.createBindGroup({
|
|
2053
|
-
layout: this.bloomBlurPipeline.getBindGroupLayout(0),
|
|
2054
|
-
entries: [
|
|
2055
|
-
{ binding: 0, resource: this.bloomBlurTexture1.createView() },
|
|
2056
|
-
{ binding: 1, resource: this.linearSampler },
|
|
2057
|
-
{ binding: 2, resource: { buffer: this.blurDirectionBuffer } },
|
|
2058
|
-
],
|
|
2059
|
-
})
|
|
2060
|
-
|
|
2061
2434
|
blurVPass.setPipeline(this.bloomBlurPipeline)
|
|
2062
|
-
blurVPass.setBindGroup(0,
|
|
2435
|
+
blurVPass.setBindGroup(0, this.bloomBlurVBindGroup!)
|
|
2063
2436
|
blurVPass.draw(6, 1, 0, 0)
|
|
2064
2437
|
blurVPass.end()
|
|
2065
2438
|
|
|
@@ -2076,19 +2449,8 @@ export class Engine {
|
|
|
2076
2449
|
],
|
|
2077
2450
|
})
|
|
2078
2451
|
|
|
2079
|
-
const composeBindGroup = this.device.createBindGroup({
|
|
2080
|
-
layout: this.bloomComposePipeline.getBindGroupLayout(0),
|
|
2081
|
-
entries: [
|
|
2082
|
-
{ binding: 0, resource: this.sceneRenderTexture.createView() },
|
|
2083
|
-
{ binding: 1, resource: this.linearSampler },
|
|
2084
|
-
{ binding: 2, resource: this.bloomBlurTexture2.createView() },
|
|
2085
|
-
{ binding: 3, resource: this.linearSampler },
|
|
2086
|
-
{ binding: 4, resource: { buffer: this.bloomIntensityBuffer } },
|
|
2087
|
-
],
|
|
2088
|
-
})
|
|
2089
|
-
|
|
2090
2452
|
composePass.setPipeline(this.bloomComposePipeline)
|
|
2091
|
-
composePass.setBindGroup(0,
|
|
2453
|
+
composePass.setBindGroup(0, this.bloomComposeBindGroup!)
|
|
2092
2454
|
composePass.draw(6, 1, 0, 0)
|
|
2093
2455
|
composePass.end()
|
|
2094
2456
|
|
|
@@ -2153,26 +2515,12 @@ export class Engine {
|
|
|
2153
2515
|
// Dispatch exactly enough threads for all bones (no bounds check needed)
|
|
2154
2516
|
const workgroupCount = Math.ceil(boneCount / workgroupSize)
|
|
2155
2517
|
|
|
2156
|
-
//
|
|
2157
|
-
const boneCountData = new Uint32Array(8) // 32 bytes total
|
|
2158
|
-
boneCountData[0] = boneCount
|
|
2159
|
-
this.device.queue.writeBuffer(this.boneCountBuffer!, 0, boneCountData)
|
|
2160
|
-
|
|
2161
|
-
const bindGroup = this.device.createBindGroup({
|
|
2162
|
-
label: "skin matrix compute bind group",
|
|
2163
|
-
layout: this.skinMatrixComputePipeline!.getBindGroupLayout(0),
|
|
2164
|
-
entries: [
|
|
2165
|
-
{ binding: 0, resource: { buffer: this.boneCountBuffer! } },
|
|
2166
|
-
{ binding: 1, resource: { buffer: this.worldMatrixBuffer! } },
|
|
2167
|
-
{ binding: 2, resource: { buffer: this.inverseBindMatrixBuffer! } },
|
|
2168
|
-
{ binding: 3, resource: { buffer: this.skinMatrixBuffer! } },
|
|
2169
|
-
],
|
|
2170
|
-
})
|
|
2518
|
+
// Bone count is written once in setupModelBuffers() and never changes
|
|
2171
2519
|
|
|
2172
2520
|
const encoder = this.device.createCommandEncoder()
|
|
2173
2521
|
const pass = encoder.beginComputePass()
|
|
2174
2522
|
pass.setPipeline(this.skinMatrixComputePipeline!)
|
|
2175
|
-
pass.setBindGroup(0,
|
|
2523
|
+
pass.setBindGroup(0, this.skinMatrixComputeBindGroup!)
|
|
2176
2524
|
pass.dispatchWorkgroups(workgroupCount)
|
|
2177
2525
|
pass.end()
|
|
2178
2526
|
this.device.queue.submit([encoder.finish()])
|