reze-engine 0.6.7 → 0.8.0
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/README.md +23 -35
- package/dist/animation.d.ts +14 -0
- package/dist/animation.d.ts.map +1 -0
- package/dist/animation.js +48 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +2 -2
- package/dist/engine.d.ts +29 -22
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +292 -178
- package/dist/ik-solver.d.ts +0 -11
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +1 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/math.d.ts +1 -10
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +24 -36
- package/dist/model.d.ts +3 -52
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +103 -170
- package/dist/pmx-loader.js +1 -1
- package/package.json +1 -1
- package/src/animation.ts +75 -0
- package/src/camera.ts +2 -2
- package/src/engine.ts +315 -218
- package/src/ik-solver.ts +1 -11
- package/src/index.ts +3 -2
- package/src/math.ts +27 -42
- package/src/model.ts +125 -220
- package/src/pmx-loader.ts +1 -1
package/src/engine.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Camera } from "./camera"
|
|
2
2
|
import { Mat4, Quat, Vec3 } from "./math"
|
|
3
3
|
import { Model } from "./model"
|
|
4
|
-
import { PmxLoader } from "./pmx-loader"
|
|
5
4
|
|
|
6
5
|
export type RaycastCallback = (material: string | null, screenX: number, screenY: number) => void
|
|
7
6
|
|
|
@@ -16,6 +15,8 @@ export type EngineOptions = {
|
|
|
16
15
|
onRaycast?: RaycastCallback
|
|
17
16
|
disableIK?: boolean
|
|
18
17
|
disablePhysics?: boolean
|
|
18
|
+
// multisampleCount: 1 | 4 (default 4)
|
|
19
|
+
multisampleCount?: 1 | 4
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export type RequiredEngineOptions = Required<Omit<EngineOptions, "onRaycast">> & Pick<EngineOptions, "onRaycast">
|
|
@@ -31,6 +32,7 @@ export const DEFAULT_ENGINE_OPTIONS: RequiredEngineOptions = {
|
|
|
31
32
|
onRaycast: undefined,
|
|
32
33
|
disableIK: false,
|
|
33
34
|
disablePhysics: false,
|
|
35
|
+
multisampleCount: 4,
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export interface EngineStats {
|
|
@@ -59,6 +61,15 @@ interface DrawCall {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export class Engine {
|
|
64
|
+
private static instance: Engine | null = null
|
|
65
|
+
|
|
66
|
+
public static getInstance(): Engine {
|
|
67
|
+
if (!Engine.instance) {
|
|
68
|
+
throw new Error("Engine not ready: create Engine, await init(), then load models via Model.loadPmx().")
|
|
69
|
+
}
|
|
70
|
+
return Engine.instance
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
private canvas: HTMLCanvasElement
|
|
63
74
|
private device!: GPUDevice
|
|
64
75
|
private context!: GPUCanvasContext
|
|
@@ -96,7 +107,7 @@ export class Engine {
|
|
|
96
107
|
private skinMatrixBuffer?: GPUBuffer
|
|
97
108
|
private inverseBindMatrixBuffer?: GPUBuffer
|
|
98
109
|
private multisampleTexture!: GPUTexture
|
|
99
|
-
private
|
|
110
|
+
private sampleCount: 1 | 4 = 4
|
|
100
111
|
private renderPassDescriptor!: GPURenderPassDescriptor
|
|
101
112
|
// Constants
|
|
102
113
|
private readonly STENCIL_EYE_VALUE = 1
|
|
@@ -117,6 +128,22 @@ export class Engine {
|
|
|
117
128
|
private groundReflectionBindGroup?: GPUBindGroup
|
|
118
129
|
private groundMaterialUniformBuffer?: GPUBuffer
|
|
119
130
|
private groundHasReflections = false
|
|
131
|
+
private groundMode: "reflection" | "shadow" = "reflection"
|
|
132
|
+
private shadowMapTexture?: GPUTexture
|
|
133
|
+
private shadowMapDepthView?: GPUTextureView
|
|
134
|
+
private shadowDepthPipeline!: GPURenderPipeline
|
|
135
|
+
private shadowBindGroup?: GPUBindGroup
|
|
136
|
+
private shadowLightVPBuffer!: GPUBuffer
|
|
137
|
+
private shadowLightVPMatrix = new Float32Array(16)
|
|
138
|
+
private groundShadowPipeline!: GPURenderPipeline
|
|
139
|
+
private groundShadowBindGroupLayout!: GPUBindGroupLayout
|
|
140
|
+
private groundShadowBindGroup?: GPUBindGroup
|
|
141
|
+
private shadowComparisonSampler!: GPUSampler
|
|
142
|
+
private groundShadowMaterialBuffer?: GPUBuffer
|
|
143
|
+
private shadowDrawCalls: DrawCall[] = []
|
|
144
|
+
private shadowVPLightX = Number.NaN
|
|
145
|
+
private shadowVPLightY = Number.NaN
|
|
146
|
+
private shadowVPLightZ = Number.NaN
|
|
120
147
|
|
|
121
148
|
// Raycasting
|
|
122
149
|
private onRaycast?: RaycastCallback
|
|
@@ -155,6 +182,7 @@ export class Engine {
|
|
|
155
182
|
|
|
156
183
|
constructor(canvas: HTMLCanvasElement, options?: EngineOptions) {
|
|
157
184
|
this.canvas = canvas
|
|
185
|
+
this.sampleCount = options?.multisampleCount ?? DEFAULT_ENGINE_OPTIONS.multisampleCount
|
|
158
186
|
if (options) {
|
|
159
187
|
this.ambientColor = options.ambientColor ?? DEFAULT_ENGINE_OPTIONS.ambientColor!
|
|
160
188
|
this.directionalLightIntensity =
|
|
@@ -197,6 +225,7 @@ export class Engine {
|
|
|
197
225
|
this.setupLighting()
|
|
198
226
|
this.createPipelines()
|
|
199
227
|
this.setupResize()
|
|
228
|
+
Engine.instance = this
|
|
200
229
|
}
|
|
201
230
|
|
|
202
231
|
private createRenderPipeline(config: {
|
|
@@ -476,121 +505,61 @@ export class Engine {
|
|
|
476
505
|
},
|
|
477
506
|
})
|
|
478
507
|
|
|
479
|
-
// Create ground/reflection pipeline with reflection texture support
|
|
480
508
|
this.groundBindGroupLayout = this.device.createBindGroupLayout({
|
|
481
509
|
label: "ground bind group layout",
|
|
482
510
|
entries: [
|
|
483
|
-
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
484
|
-
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
485
|
-
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
486
|
-
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
487
|
-
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
511
|
+
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
512
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
513
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
514
|
+
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
515
|
+
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
488
516
|
],
|
|
489
517
|
})
|
|
490
|
-
|
|
491
518
|
const groundPipelineLayout = this.device.createPipelineLayout({
|
|
492
519
|
label: "ground pipeline layout",
|
|
493
520
|
bindGroupLayouts: [this.groundBindGroupLayout],
|
|
494
521
|
})
|
|
495
|
-
|
|
496
522
|
const groundShaderModule = this.device.createShaderModule({
|
|
497
523
|
label: "ground shaders",
|
|
498
524
|
code: /* wgsl */ `
|
|
499
|
-
struct CameraUniforms {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
_padding: f32,
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
struct LightUniforms {
|
|
507
|
-
ambientColor: vec4f,
|
|
508
|
-
lights: array<Light, 4>,
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
struct Light {
|
|
512
|
-
direction: vec4f,
|
|
513
|
-
color: vec4f,
|
|
514
|
-
};
|
|
515
|
-
|
|
516
|
-
struct GroundMaterialUniforms {
|
|
517
|
-
diffuseColor: vec3f,
|
|
518
|
-
reflectionLevel: f32,
|
|
519
|
-
fadeStart: f32,
|
|
520
|
-
fadeEnd: f32,
|
|
521
|
-
_padding1: f32,
|
|
522
|
-
_padding2: f32,
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
+
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
526
|
+
struct Light { direction: vec4f, color: vec4f, };
|
|
527
|
+
struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
|
|
528
|
+
struct GroundMaterialUniforms { diffuseColor: vec3f, reflectionLevel: f32, fadeStart: f32, fadeEnd: f32, _a: f32, _b: f32, };
|
|
525
529
|
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
526
530
|
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
527
531
|
@group(0) @binding(2) var reflectionTexture: texture_2d<f32>;
|
|
528
532
|
@group(0) @binding(3) var reflectionSampler: sampler;
|
|
529
533
|
@group(0) @binding(4) var<uniform> material: GroundMaterialUniforms;
|
|
530
|
-
|
|
531
534
|
struct VertexOutput {
|
|
532
|
-
@builtin(position) position: vec4f,
|
|
533
|
-
@location(0) normal: vec3f,
|
|
534
|
-
@location(1) uv: vec2f,
|
|
535
|
-
@location(2) worldPos: vec3f,
|
|
535
|
+
@builtin(position) position: vec4f, @location(0) normal: vec3f, @location(1) uv: vec2f, @location(2) worldPos: vec3f,
|
|
536
536
|
};
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
) -> VertexOutput {
|
|
543
|
-
var output: VertexOutput;
|
|
544
|
-
let worldPos = position;
|
|
545
|
-
output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
|
|
546
|
-
output.normal = normal;
|
|
547
|
-
output.uv = uv;
|
|
548
|
-
output.worldPos = worldPos;
|
|
549
|
-
return output;
|
|
537
|
+
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VertexOutput {
|
|
538
|
+
var o: VertexOutput;
|
|
539
|
+
o.worldPos = position;
|
|
540
|
+
o.position = camera.projection * camera.view * vec4f(position, 1.0);
|
|
541
|
+
o.normal = normal; o.uv = uv; return o;
|
|
550
542
|
}
|
|
551
|
-
|
|
552
543
|
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
553
544
|
let n = normalize(input.normal);
|
|
554
|
-
|
|
545
|
+
let centerDist = length(input.worldPos.xz);
|
|
546
|
+
let t = clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0);
|
|
547
|
+
let edgeFade = 1.0 - smoothstep(0.0, 1.0, t);
|
|
555
548
|
let clipPos = camera.projection * camera.view * vec4f(input.worldPos, 1.0);
|
|
556
549
|
let ndcPos = clipPos.xyz / clipPos.w;
|
|
557
|
-
|
|
558
|
-
|
|
550
|
+
let reflectionUV = vec2f(ndcPos.x * 0.5 + 0.5, 0.5 - ndcPos.y * 0.5);
|
|
559
551
|
let sampledReflectionColor = textureSample(reflectionTexture, reflectionSampler, reflectionUV).rgb;
|
|
560
|
-
let isValidReflection = clipPos.w > 0.0 &&
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
let fadeFactor = clamp((distanceFromCamera - 15.0) / 20.0, 0.0, 1.0);
|
|
566
|
-
reflectionColor *= (1.0 - fadeFactor * 0.3);
|
|
567
|
-
|
|
568
|
-
let diffuseColor = material.diffuseColor;
|
|
569
|
-
var finalColor = mix(diffuseColor, reflectionColor, material.reflectionLevel);
|
|
570
|
-
|
|
571
|
-
// Ground edge fade effect - smooth fade out at edges based on distance from center
|
|
572
|
-
let centerDist = length(input.worldPos.xz); // Distance from ground center in XZ plane
|
|
573
|
-
|
|
574
|
-
// Smoothstep for much smoother gradient transition
|
|
575
|
-
let t = clamp((centerDist - material.fadeStart) / (material.fadeEnd - material.fadeStart), 0.0, 1.0);
|
|
576
|
-
let edgeFade = 1.0 - smoothstep(0.0, 1.0, t);
|
|
577
|
-
finalColor *= edgeFade;
|
|
578
|
-
|
|
579
|
-
// Single directional light
|
|
552
|
+
let isValidReflection = clipPos.w > 0.0 && all(reflectionUV >= vec2f(0.0)) && all(reflectionUV <= vec2f(1.0));
|
|
553
|
+
let reflectionColor = select(vec3f(1.0, 1.0, 1.0), sampledReflectionColor, isValidReflection);
|
|
554
|
+
let fadeFactor = clamp((length(input.worldPos - camera.viewPos) - 15.0) / 20.0, 0.0, 1.0);
|
|
555
|
+
let refl = reflectionColor * (1.0 - fadeFactor * 0.3);
|
|
556
|
+
var finalColor = mix(material.diffuseColor, refl, material.reflectionLevel) * edgeFade;
|
|
580
557
|
let l = -light.lights[0].direction.xyz;
|
|
581
|
-
let
|
|
582
|
-
|
|
583
|
-
let radiance = light.lights[0].color.xyz * intensity;
|
|
584
|
-
let lightAccum = light.ambientColor.xyz + radiance * nDotL;
|
|
585
|
-
|
|
586
|
-
// Apply lighting to the blended color
|
|
587
|
-
let litColor = finalColor * lightAccum;
|
|
588
|
-
|
|
589
|
-
return vec4f(litColor, edgeFade);
|
|
558
|
+
let lightAccum = light.ambientColor.xyz + light.lights[0].color.xyz * light.lights[0].color.w * max(dot(n, l), 0.0);
|
|
559
|
+
return vec4f(finalColor * lightAccum, edgeFade);
|
|
590
560
|
}
|
|
591
561
|
`,
|
|
592
562
|
})
|
|
593
|
-
|
|
594
563
|
this.groundPipeline = this.createRenderPipeline({
|
|
595
564
|
label: "ground pipeline",
|
|
596
565
|
layout: groundPipelineLayout,
|
|
@@ -598,12 +567,124 @@ export class Engine {
|
|
|
598
567
|
vertexBuffers: fullVertexBuffers,
|
|
599
568
|
fragmentTarget: standardBlend,
|
|
600
569
|
cullMode: "back",
|
|
570
|
+
depthStencil: { format: "depth24plus-stencil8", depthWriteEnabled: true, depthCompare: "less-equal" },
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
this.shadowLightVPBuffer = this.device.createBuffer({
|
|
574
|
+
size: 64,
|
|
575
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
576
|
+
})
|
|
577
|
+
const shadowBindGroupLayout = this.device.createBindGroupLayout({
|
|
578
|
+
label: "shadow depth bind layout",
|
|
579
|
+
entries: [
|
|
580
|
+
{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
|
|
581
|
+
{ binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } },
|
|
582
|
+
],
|
|
583
|
+
})
|
|
584
|
+
const shadowShader = this.device.createShaderModule({
|
|
585
|
+
label: "shadow depth",
|
|
586
|
+
code: /* wgsl */ `
|
|
587
|
+
struct LightVP { viewProj: mat4x4f, };
|
|
588
|
+
@group(0) @binding(0) var<uniform> lp: LightVP;
|
|
589
|
+
@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;
|
|
590
|
+
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,
|
|
591
|
+
@location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) -> @builtin(position) vec4f {
|
|
592
|
+
let pos4 = vec4f(position, 1.0);
|
|
593
|
+
let ws = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
594
|
+
let inv = select(1.0, 1.0 / ws, ws > 0.0001);
|
|
595
|
+
let nw = select(vec4f(1.0,0.0,0.0,0.0), weights0 * inv, ws > 0.0001);
|
|
596
|
+
var sp = vec4f(0.0);
|
|
597
|
+
for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }
|
|
598
|
+
return lp.viewProj * vec4f(sp.xyz, 1.0);
|
|
599
|
+
}
|
|
600
|
+
`,
|
|
601
|
+
})
|
|
602
|
+
this.shadowDepthPipeline = this.device.createRenderPipeline({
|
|
603
|
+
label: "shadow depth pipeline",
|
|
604
|
+
layout: this.device.createPipelineLayout({ bindGroupLayouts: [shadowBindGroupLayout] }),
|
|
605
|
+
vertex: { module: shadowShader, entryPoint: "vs", buffers: fullVertexBuffers as GPUVertexBufferLayout[] },
|
|
606
|
+
primitive: { cullMode: "none" },
|
|
601
607
|
depthStencil: {
|
|
602
|
-
format: "
|
|
608
|
+
format: "depth32float",
|
|
603
609
|
depthWriteEnabled: true,
|
|
604
610
|
depthCompare: "less-equal",
|
|
611
|
+
depthBias: 2,
|
|
612
|
+
depthBiasSlopeScale: 1.5,
|
|
613
|
+
depthBiasClamp: 0,
|
|
605
614
|
},
|
|
606
615
|
})
|
|
616
|
+
this.shadowComparisonSampler = this.device.createSampler({
|
|
617
|
+
compare: "less",
|
|
618
|
+
magFilter: "linear",
|
|
619
|
+
minFilter: "linear",
|
|
620
|
+
})
|
|
621
|
+
this.groundShadowBindGroupLayout = this.device.createBindGroupLayout({
|
|
622
|
+
label: "ground shadow layout",
|
|
623
|
+
entries: [
|
|
624
|
+
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
625
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
626
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "depth" } },
|
|
627
|
+
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "comparison" } },
|
|
628
|
+
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
629
|
+
{ binding: 5, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
630
|
+
],
|
|
631
|
+
})
|
|
632
|
+
const groundShadowShader = this.device.createShaderModule({
|
|
633
|
+
label: "ground shadow",
|
|
634
|
+
code: /* wgsl */ `
|
|
635
|
+
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
636
|
+
struct Light { direction: vec4f, color: vec4f, };
|
|
637
|
+
struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
|
|
638
|
+
struct GroundShadowMat { diffuseColor: vec3f, fadeStart: f32, fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, _y: f32, };
|
|
639
|
+
struct LightVP { viewProj: mat4x4f, };
|
|
640
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
641
|
+
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
642
|
+
@group(0) @binding(2) var shadowMap: texture_depth_2d;
|
|
643
|
+
@group(0) @binding(3) var shadowSampler: sampler_comparison;
|
|
644
|
+
@group(0) @binding(4) var<uniform> material: GroundShadowMat;
|
|
645
|
+
@group(0) @binding(5) var<uniform> lightVP: LightVP;
|
|
646
|
+
struct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };
|
|
647
|
+
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {
|
|
648
|
+
var o: VO; o.worldPos = position; o.normal = normal;
|
|
649
|
+
o.position = camera.projection * camera.view * vec4f(position, 1.0); return o;
|
|
650
|
+
}
|
|
651
|
+
@fragment fn fs(i: VO) -> @location(0) vec4f {
|
|
652
|
+
let n = normalize(i.normal);
|
|
653
|
+
let centerDist = length(i.worldPos.xz);
|
|
654
|
+
let edgeFade = 1.0 - smoothstep(0.0, 1.0, clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0));
|
|
655
|
+
let lclip = lightVP.viewProj * vec4f(i.worldPos, 1.0);
|
|
656
|
+
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
657
|
+
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
658
|
+
let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));
|
|
659
|
+
let st = material.pcfTexel;
|
|
660
|
+
let compareZ = ndc.z - 0.0035;
|
|
661
|
+
var vis = 0.0;
|
|
662
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(-st, -st), compareZ);
|
|
663
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(0.0, -st), compareZ);
|
|
664
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(st, -st), compareZ);
|
|
665
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(-st, 0.0), compareZ);
|
|
666
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c, compareZ);
|
|
667
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(st, 0.0), compareZ);
|
|
668
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(-st, st), compareZ);
|
|
669
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(0.0, st), compareZ);
|
|
670
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(st, st), compareZ);
|
|
671
|
+
vis *= 0.1111111;
|
|
672
|
+
let sun = light.ambientColor.xyz + light.lights[0].color.xyz * light.lights[0].color.w * max(dot(n, -light.lights[0].direction.xyz), 0.0);
|
|
673
|
+
let dark = (1.0 - vis) * material.shadowStrength;
|
|
674
|
+
let lit = material.diffuseColor * sun * (1.0 - dark * 0.65);
|
|
675
|
+
return vec4f(lit * edgeFade, edgeFade);
|
|
676
|
+
}
|
|
677
|
+
`,
|
|
678
|
+
})
|
|
679
|
+
this.groundShadowPipeline = this.createRenderPipeline({
|
|
680
|
+
label: "ground shadow pipeline",
|
|
681
|
+
layout: this.device.createPipelineLayout({ bindGroupLayouts: [this.groundShadowBindGroupLayout] }),
|
|
682
|
+
shaderModule: groundShadowShader,
|
|
683
|
+
vertexBuffers: fullVertexBuffers,
|
|
684
|
+
fragmentTarget: standardBlend,
|
|
685
|
+
cullMode: "back",
|
|
686
|
+
depthStencil: { format: "depth24plus-stencil8", depthWriteEnabled: true, depthCompare: "less-equal" },
|
|
687
|
+
})
|
|
607
688
|
|
|
608
689
|
// Create reflection pipeline (multisampled version for higher quality)
|
|
609
690
|
this.reflectionPipeline = this.createRenderPipeline({
|
|
@@ -1028,6 +1109,9 @@ export class Engine {
|
|
|
1028
1109
|
reflectionTextureSize?: number
|
|
1029
1110
|
fadeStart?: number
|
|
1030
1111
|
fadeEnd?: number
|
|
1112
|
+
mode?: "reflection" | "shadow"
|
|
1113
|
+
shadowMapSize?: number
|
|
1114
|
+
shadowStrength?: number
|
|
1031
1115
|
}): void {
|
|
1032
1116
|
const opts = {
|
|
1033
1117
|
width: 100,
|
|
@@ -1037,21 +1121,26 @@ export class Engine {
|
|
|
1037
1121
|
reflectionTextureSize: 1024,
|
|
1038
1122
|
fadeStart: 5.0,
|
|
1039
1123
|
fadeEnd: 60.0,
|
|
1124
|
+
mode: "reflection" as const,
|
|
1125
|
+
shadowMapSize: 4096,
|
|
1126
|
+
shadowStrength: 1.0,
|
|
1040
1127
|
...options,
|
|
1041
1128
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1129
|
+
this.groundMode = opts.mode
|
|
1130
|
+
this.drawCalls = this.drawCalls.filter((d) => d.type !== "ground")
|
|
1044
1131
|
this.createGroundGeometry(opts.width, opts.height)
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1132
|
+
if (opts.mode === "reflection") {
|
|
1133
|
+
this.createGroundMaterialBuffer(opts.diffuseColor, opts.reflectionLevel, opts.fadeStart, opts.fadeEnd)
|
|
1134
|
+
this.createReflectionTexture(opts.reflectionTextureSize)
|
|
1135
|
+
} else {
|
|
1136
|
+
this.createShadowGroundResources(opts.shadowMapSize, opts.diffuseColor, opts.fadeStart, opts.fadeEnd, opts.shadowStrength)
|
|
1137
|
+
}
|
|
1048
1138
|
this.groundHasReflections = true
|
|
1049
|
-
|
|
1050
1139
|
this.drawCalls.push({
|
|
1051
1140
|
type: "ground",
|
|
1052
|
-
count: 6,
|
|
1141
|
+
count: 6,
|
|
1053
1142
|
firstIndex: 0,
|
|
1054
|
-
bindGroup: this.groundReflectionBindGroup!,
|
|
1143
|
+
bindGroup: (opts.mode === "reflection" ? this.groundReflectionBindGroup : this.groundShadowBindGroup)!,
|
|
1055
1144
|
materialName: "Ground",
|
|
1056
1145
|
})
|
|
1057
1146
|
}
|
|
@@ -1060,31 +1149,6 @@ export class Engine {
|
|
|
1060
1149
|
this.device.queue.writeBuffer(this.lightUniformBuffer, 0, this.lightData)
|
|
1061
1150
|
}
|
|
1062
1151
|
|
|
1063
|
-
public async loadAnimation(url: string) {
|
|
1064
|
-
if (!this.currentModel) return
|
|
1065
|
-
await this.currentModel.loadVmd(url)
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
public playAnimation() {
|
|
1069
|
-
this.currentModel?.playAnimation()
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
public stopAnimation() {
|
|
1073
|
-
this.currentModel?.stopAnimation()
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
public pauseAnimation() {
|
|
1077
|
-
this.currentModel?.pauseAnimation()
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
public seekAnimation(time: number) {
|
|
1081
|
-
this.currentModel?.seekAnimation(time)
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
public getAnimationProgress() {
|
|
1085
|
-
return this.currentModel?.getAnimationProgress() ?? { current: 0, duration: 0, percentage: 0 }
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
1152
|
public getStats(): EngineStats {
|
|
1089
1153
|
return { ...this.stats }
|
|
1090
1154
|
}
|
|
@@ -1115,7 +1179,8 @@ export class Engine {
|
|
|
1115
1179
|
|
|
1116
1180
|
public dispose() {
|
|
1117
1181
|
this.stopRenderLoop()
|
|
1118
|
-
this.stopAnimation()
|
|
1182
|
+
this.currentModel?.stopAnimation()
|
|
1183
|
+
if (Engine.instance === this) Engine.instance = null
|
|
1119
1184
|
if (this.camera) this.camera.detachControl()
|
|
1120
1185
|
|
|
1121
1186
|
// Remove raycasting event listeners
|
|
@@ -1130,53 +1195,19 @@ export class Engine {
|
|
|
1130
1195
|
}
|
|
1131
1196
|
}
|
|
1132
1197
|
|
|
1133
|
-
//
|
|
1134
|
-
public async
|
|
1135
|
-
const pathParts =
|
|
1198
|
+
// Single active model; prefer Model.loadPmx() so load + register stay paired
|
|
1199
|
+
public async registerModel(model: Model, pmxPath: string): Promise<void> {
|
|
1200
|
+
const pathParts = pmxPath.split("/")
|
|
1136
1201
|
pathParts.pop()
|
|
1137
|
-
|
|
1138
|
-
this.modelDir = dir
|
|
1139
|
-
|
|
1140
|
-
const model = await PmxLoader.load(path)
|
|
1141
|
-
|
|
1142
|
-
// Clear cached skinned vertices when loading a new model
|
|
1202
|
+
this.modelDir = pathParts.join("/") + "/"
|
|
1143
1203
|
this.cachedSkinnedVertices = undefined
|
|
1144
1204
|
this.cachedSkinMatricesVersion = -1
|
|
1145
|
-
|
|
1146
1205
|
await this.setupModelBuffers(model)
|
|
1147
1206
|
}
|
|
1148
1207
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
// moveBones now takes relative translations (VMD-style) by default
|
|
1154
|
-
public moveBones(boneTranslations: Record<string, Vec3>, durationMs?: number) {
|
|
1155
|
-
this.currentModel?.moveBones(boneTranslations, durationMs)
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
public setPose(
|
|
1159
|
-
rotations?: Record<string, Quat>,
|
|
1160
|
-
translations?: Record<string, Vec3>,
|
|
1161
|
-
morphs?: Record<string, number>
|
|
1162
|
-
): void {
|
|
1163
|
-
this.currentModel?.setPose(rotations, translations, morphs)
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
public resetAllBones() {
|
|
1167
|
-
this.currentModel?.resetAllBones()
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
public resetAllMorphs(): void {
|
|
1171
|
-
this.currentModel?.resetAllMorphs()
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
public setMorphWeight(name: string, weight: number, durationMs?: number): void {
|
|
1175
|
-
if (!this.currentModel) return
|
|
1176
|
-
this.currentModel.setMorphWeight(name, weight, durationMs)
|
|
1177
|
-
if (!durationMs || durationMs === 0) {
|
|
1178
|
-
this.vertexBufferNeedsUpdate = true
|
|
1179
|
-
}
|
|
1208
|
+
// After morph/vertex edits, queues GPU vertex upload on next frame
|
|
1209
|
+
public markVertexBufferDirty(): void {
|
|
1210
|
+
this.vertexBufferNeedsUpdate = true
|
|
1180
1211
|
}
|
|
1181
1212
|
|
|
1182
1213
|
public setMaterialVisible(name: string, visible: boolean): void {
|
|
@@ -1199,18 +1230,6 @@ export class Engine {
|
|
|
1199
1230
|
return !this.hiddenMaterials.has(name)
|
|
1200
1231
|
}
|
|
1201
1232
|
|
|
1202
|
-
public getBones(): string[] {
|
|
1203
|
-
return this.currentModel?.getSkeleton().bones.map((bone) => bone.name) ?? []
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
public getMorphs(): string[] {
|
|
1207
|
-
return this.currentModel?.getMorphing().morphs.map((morph) => morph.name) ?? []
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
public getMaterials(): string[] {
|
|
1211
|
-
return this.currentModel?.getMaterials().map((material) => material.name) ?? []
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
1233
|
// IK control
|
|
1215
1234
|
public get disableIK(): boolean {
|
|
1216
1235
|
return this._disableIK
|
|
@@ -1394,34 +1413,25 @@ export class Engine {
|
|
|
1394
1413
|
this.device.queue.writeBuffer(this.groundIndexBuffer, 0, indices)
|
|
1395
1414
|
}
|
|
1396
1415
|
|
|
1397
|
-
private createGroundMaterialBuffer(
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
reflectionLevel, // reflectionLevel (4 bytes)
|
|
1408
|
-
fadeStart, // fadeStart (4 bytes)
|
|
1409
|
-
fadeEnd, // fadeEnd (4 bytes)
|
|
1410
|
-
0, // padding (4 bytes)
|
|
1411
|
-
0, // padding (4 bytes)
|
|
1412
|
-
0, // padding (4 bytes)
|
|
1413
|
-
0, // padding (4 bytes)
|
|
1414
|
-
])
|
|
1415
|
-
|
|
1416
|
+
private createGroundMaterialBuffer(diffuseColor: Vec3, reflectionLevel: number, fadeStart: number, fadeEnd: number) {
|
|
1417
|
+
const u = new Float32Array(8)
|
|
1418
|
+
u[0] = diffuseColor.x
|
|
1419
|
+
u[1] = diffuseColor.y
|
|
1420
|
+
u[2] = diffuseColor.z
|
|
1421
|
+
u[3] = reflectionLevel
|
|
1422
|
+
u[4] = fadeStart
|
|
1423
|
+
u[5] = fadeEnd
|
|
1424
|
+
u[6] = 0
|
|
1425
|
+
u[7] = 0
|
|
1416
1426
|
this.groundMaterialUniformBuffer = this.device.createBuffer({
|
|
1417
1427
|
label: "ground material uniform buffer",
|
|
1418
|
-
size:
|
|
1428
|
+
size: 64,
|
|
1419
1429
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1420
1430
|
})
|
|
1421
|
-
this.device.queue.writeBuffer(this.groundMaterialUniformBuffer, 0,
|
|
1431
|
+
this.device.queue.writeBuffer(this.groundMaterialUniformBuffer, 0, u)
|
|
1422
1432
|
}
|
|
1423
1433
|
|
|
1424
|
-
private createReflectionTexture(size: number
|
|
1434
|
+
private createReflectionTexture(size: number) {
|
|
1425
1435
|
this.groundReflectionTexture = this.device.createTexture({
|
|
1426
1436
|
label: "ground reflection texture",
|
|
1427
1437
|
size: [size, size],
|
|
@@ -1429,14 +1439,12 @@ export class Engine {
|
|
|
1429
1439
|
format: this.presentationFormat,
|
|
1430
1440
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1431
1441
|
})
|
|
1432
|
-
|
|
1433
1442
|
this.groundReflectionResolveTexture = this.device.createTexture({
|
|
1434
1443
|
label: "ground reflection resolve texture",
|
|
1435
1444
|
size: [size, size],
|
|
1436
1445
|
format: this.presentationFormat,
|
|
1437
1446
|
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1438
1447
|
})
|
|
1439
|
-
|
|
1440
1448
|
this.groundReflectionDepthTexture = this.device.createTexture({
|
|
1441
1449
|
label: "ground reflection depth texture",
|
|
1442
1450
|
size: [size, size],
|
|
@@ -1444,21 +1452,89 @@ export class Engine {
|
|
|
1444
1452
|
format: "depth24plus-stencil8",
|
|
1445
1453
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1446
1454
|
})
|
|
1447
|
-
|
|
1448
|
-
// Create a bind group for the reflection texture that can be used in the ground material
|
|
1449
1455
|
this.groundReflectionBindGroup = this.device.createBindGroup({
|
|
1450
1456
|
label: "ground reflection bind group",
|
|
1451
1457
|
layout: this.groundBindGroupLayout,
|
|
1452
1458
|
entries: [
|
|
1453
1459
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1454
1460
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1455
|
-
{ binding: 2, resource: this.groundReflectionResolveTexture
|
|
1461
|
+
{ binding: 2, resource: this.groundReflectionResolveTexture.createView() },
|
|
1456
1462
|
{ binding: 3, resource: this.materialSampler },
|
|
1457
1463
|
{ binding: 4, resource: { buffer: this.groundMaterialUniformBuffer! } },
|
|
1458
1464
|
],
|
|
1459
1465
|
})
|
|
1460
1466
|
}
|
|
1461
1467
|
|
|
1468
|
+
private createShadowGroundResources(
|
|
1469
|
+
shadowMapSize: number,
|
|
1470
|
+
diffuseColor: Vec3,
|
|
1471
|
+
fadeStart: number,
|
|
1472
|
+
fadeEnd: number,
|
|
1473
|
+
shadowStrength: number
|
|
1474
|
+
) {
|
|
1475
|
+
this.shadowMapTexture = this.device.createTexture({
|
|
1476
|
+
label: "shadow map",
|
|
1477
|
+
size: [shadowMapSize, shadowMapSize],
|
|
1478
|
+
format: "depth32float",
|
|
1479
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1480
|
+
})
|
|
1481
|
+
this.shadowMapDepthView = this.shadowMapTexture.createView()
|
|
1482
|
+
const gb = new Float32Array(8)
|
|
1483
|
+
gb[0] = diffuseColor.x
|
|
1484
|
+
gb[1] = diffuseColor.y
|
|
1485
|
+
gb[2] = diffuseColor.z
|
|
1486
|
+
gb[3] = fadeStart
|
|
1487
|
+
gb[4] = fadeEnd
|
|
1488
|
+
gb[5] = shadowStrength
|
|
1489
|
+
gb[6] = 1.2 / shadowMapSize
|
|
1490
|
+
gb[7] = 0
|
|
1491
|
+
this.groundShadowMaterialBuffer = this.device.createBuffer({ size: 64, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST })
|
|
1492
|
+
this.device.queue.writeBuffer(this.groundShadowMaterialBuffer, 0, gb)
|
|
1493
|
+
this.shadowBindGroup = this.device.createBindGroup({
|
|
1494
|
+
label: "shadow bind",
|
|
1495
|
+
layout: this.shadowDepthPipeline.getBindGroupLayout(0),
|
|
1496
|
+
entries: [
|
|
1497
|
+
{ binding: 0, resource: { buffer: this.shadowLightVPBuffer } },
|
|
1498
|
+
{ binding: 1, resource: { buffer: this.skinMatrixBuffer! } },
|
|
1499
|
+
],
|
|
1500
|
+
})
|
|
1501
|
+
this.groundShadowBindGroup = this.device.createBindGroup({
|
|
1502
|
+
label: "ground shadow bind",
|
|
1503
|
+
layout: this.groundShadowBindGroupLayout,
|
|
1504
|
+
entries: [
|
|
1505
|
+
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1506
|
+
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1507
|
+
{ binding: 2, resource: this.shadowMapDepthView },
|
|
1508
|
+
{ binding: 3, resource: this.shadowComparisonSampler },
|
|
1509
|
+
{ binding: 4, resource: { buffer: this.groundShadowMaterialBuffer } },
|
|
1510
|
+
{ binding: 5, resource: { buffer: this.shadowLightVPBuffer } },
|
|
1511
|
+
],
|
|
1512
|
+
})
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
private updateShadowLightVP() {
|
|
1516
|
+
const lx = this.lightData[4]
|
|
1517
|
+
const ly = this.lightData[5]
|
|
1518
|
+
const lz = this.lightData[6]
|
|
1519
|
+
if (lx === this.shadowVPLightX && ly === this.shadowVPLightY && lz === this.shadowVPLightZ) return
|
|
1520
|
+
this.shadowVPLightX = lx
|
|
1521
|
+
this.shadowVPLightY = ly
|
|
1522
|
+
this.shadowVPLightZ = lz
|
|
1523
|
+
const dir = new Vec3(lx, ly, lz)
|
|
1524
|
+
if (dir.length() < 1e-6) {
|
|
1525
|
+
dir.x = 0.35
|
|
1526
|
+
dir.y = -1
|
|
1527
|
+
dir.z = 0.2
|
|
1528
|
+
} else dir.normalize()
|
|
1529
|
+
const target = new Vec3(0, 11, 0)
|
|
1530
|
+
const eye = new Vec3(target.x - dir.x * 72, target.y - dir.y * 72, target.z - dir.z * 72)
|
|
1531
|
+
const view = Mat4.lookAt(eye, target, new Vec3(0, 1, 0))
|
|
1532
|
+
const proj = Mat4.orthographicLh(-72, 72, -72, 72, 1, 140)
|
|
1533
|
+
const vp = proj.multiply(view)
|
|
1534
|
+
this.shadowLightVPMatrix.set(vp.values)
|
|
1535
|
+
this.device.queue.writeBuffer(this.shadowLightVPBuffer, 0, this.shadowLightVPMatrix)
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1462
1538
|
private async setupMaterials(model: Model) {
|
|
1463
1539
|
const materials = model.getMaterials()
|
|
1464
1540
|
if (materials.length === 0) {
|
|
@@ -1633,6 +1709,11 @@ export class Engine {
|
|
|
1633
1709
|
|
|
1634
1710
|
currentIndexOffset += indexCount
|
|
1635
1711
|
}
|
|
1712
|
+
this.shadowDrawCalls.length = 0
|
|
1713
|
+
for (const d of this.drawCalls) {
|
|
1714
|
+
if (d.type === "opaque" || d.type === "hair-over-eyes" || d.type === "hair-over-non-eyes")
|
|
1715
|
+
this.shadowDrawCalls.push(d)
|
|
1716
|
+
}
|
|
1636
1717
|
}
|
|
1637
1718
|
|
|
1638
1719
|
private createMaterialUniformBuffer(
|
|
@@ -1742,26 +1823,17 @@ export class Engine {
|
|
|
1742
1823
|
}
|
|
1743
1824
|
|
|
1744
1825
|
private renderGround(pass: GPURenderPassEncoder) {
|
|
1745
|
-
if (!this.groundHasReflections || !this.groundVertexBuffer || !this.groundIndexBuffer)
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
if (this.groundReflectionTexture) {
|
|
1750
|
-
this.renderReflectionTexture()
|
|
1751
|
-
}
|
|
1752
|
-
pass.setPipeline(this.groundPipeline)
|
|
1826
|
+
if (!this.groundHasReflections || !this.groundVertexBuffer || !this.groundIndexBuffer) return
|
|
1827
|
+
if (this.groundMode === "reflection" && this.groundReflectionTexture) this.renderReflectionTexture()
|
|
1828
|
+
pass.setPipeline(this.groundMode === "reflection" ? this.groundPipeline : this.groundShadowPipeline)
|
|
1753
1829
|
pass.setVertexBuffer(0, this.groundVertexBuffer)
|
|
1754
1830
|
pass.setIndexBuffer(this.groundIndexBuffer, "uint16")
|
|
1755
|
-
|
|
1756
1831
|
for (const draw of this.drawCalls) {
|
|
1757
1832
|
if (draw.type === "ground" && this.shouldRenderDrawCall(draw)) {
|
|
1758
1833
|
pass.setBindGroup(0, draw.bindGroup)
|
|
1759
1834
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
1760
1835
|
}
|
|
1761
1836
|
}
|
|
1762
|
-
|
|
1763
|
-
// // Restore model index buffer for subsequent rendering
|
|
1764
|
-
// pass.setIndexBuffer(this.indexBuffer!, "uint32")
|
|
1765
1837
|
}
|
|
1766
1838
|
|
|
1767
1839
|
private renderReflectionTexture() {
|
|
@@ -2178,11 +2250,37 @@ export class Engine {
|
|
|
2178
2250
|
this.vertexBufferNeedsUpdate = false
|
|
2179
2251
|
}
|
|
2180
2252
|
|
|
2181
|
-
// Update skin matrices buffer
|
|
2182
2253
|
this.updateSkinMatrices()
|
|
2254
|
+
if (this.groundMode === "shadow") this.updateShadowLightVP()
|
|
2183
2255
|
|
|
2184
|
-
// Use single encoder for render
|
|
2185
2256
|
const encoder = this.device.createCommandEncoder()
|
|
2257
|
+
if (
|
|
2258
|
+
this.groundMode === "shadow" &&
|
|
2259
|
+
this.currentModel &&
|
|
2260
|
+
this.shadowMapDepthView &&
|
|
2261
|
+
this.shadowBindGroup
|
|
2262
|
+
) {
|
|
2263
|
+
const sp = encoder.beginRenderPass({
|
|
2264
|
+
colorAttachments: [],
|
|
2265
|
+
depthStencilAttachment: {
|
|
2266
|
+
view: this.shadowMapDepthView,
|
|
2267
|
+
depthClearValue: 1.0,
|
|
2268
|
+
depthLoadOp: "clear",
|
|
2269
|
+
depthStoreOp: "store",
|
|
2270
|
+
},
|
|
2271
|
+
})
|
|
2272
|
+
sp.setPipeline(this.shadowDepthPipeline)
|
|
2273
|
+
sp.setBindGroup(0, this.shadowBindGroup)
|
|
2274
|
+
sp.setVertexBuffer(0, this.vertexBuffer)
|
|
2275
|
+
sp.setVertexBuffer(1, this.jointsBuffer)
|
|
2276
|
+
sp.setVertexBuffer(2, this.weightsBuffer)
|
|
2277
|
+
sp.setIndexBuffer(this.indexBuffer!, "uint32")
|
|
2278
|
+
for (const draw of this.shadowDrawCalls) {
|
|
2279
|
+
if (this.shouldRenderDrawCall(draw))
|
|
2280
|
+
sp.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
2281
|
+
}
|
|
2282
|
+
sp.end()
|
|
2283
|
+
}
|
|
2186
2284
|
|
|
2187
2285
|
const pass = encoder.beginRenderPass(this.renderPassDescriptor)
|
|
2188
2286
|
|
|
@@ -2221,7 +2319,6 @@ export class Engine {
|
|
|
2221
2319
|
this.drawOutlines(pass, true)
|
|
2222
2320
|
}
|
|
2223
2321
|
|
|
2224
|
-
// Pass 4: Ground (with reflections)
|
|
2225
2322
|
if (this.groundHasReflections) {
|
|
2226
2323
|
this.renderGround(pass)
|
|
2227
2324
|
}
|