reze-engine 0.8.3 → 0.8.4
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 +3 -3
- package/dist/engine.d.ts +22 -25
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +109 -325
- package/dist/model.d.ts +1 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +3 -28
- package/package.json +1 -1
- package/src/engine.ts +129 -348
- package/src/model.ts +3 -35
package/dist/engine.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Camera } from "./camera";
|
|
2
2
|
import { Mat4, Vec3 } from "./math";
|
|
3
|
+
import { PmxLoader } from "./pmx-loader";
|
|
3
4
|
import { Physics } from "./physics";
|
|
4
5
|
export const DEFAULT_ENGINE_OPTIONS = {
|
|
5
6
|
ambientColor: new Vec3(0.88, 0.88, 0.88),
|
|
@@ -10,12 +11,11 @@ export const DEFAULT_ENGINE_OPTIONS = {
|
|
|
10
11
|
cameraTarget: new Vec3(0, 12.5, 0),
|
|
11
12
|
cameraFov: Math.PI / 4,
|
|
12
13
|
onRaycast: undefined,
|
|
13
|
-
multisampleCount: 4,
|
|
14
14
|
};
|
|
15
15
|
export class Engine {
|
|
16
16
|
static getInstance() {
|
|
17
17
|
if (!Engine.instance) {
|
|
18
|
-
throw new Error("Engine not ready: create Engine, await init(), then load models via
|
|
18
|
+
throw new Error("Engine not ready: create Engine, await init(), then load models via engine.loadModel().");
|
|
19
19
|
}
|
|
20
20
|
return Engine.instance;
|
|
21
21
|
}
|
|
@@ -24,11 +24,9 @@ export class Engine {
|
|
|
24
24
|
this.lightData = new Float32Array(64);
|
|
25
25
|
this.lightCount = 0;
|
|
26
26
|
this.resizeObserver = null;
|
|
27
|
-
|
|
28
|
-
// Constants
|
|
27
|
+
// Post-alpha eye: eyes write stencil, hair-over-eyes reads it for see-through bangs (MMD-style).
|
|
29
28
|
this.STENCIL_EYE_VALUE = 1;
|
|
30
|
-
this.
|
|
31
|
-
this.groundMode = "reflection";
|
|
29
|
+
this.hasGround = false;
|
|
32
30
|
this.shadowLightVPMatrix = new Float32Array(16);
|
|
33
31
|
this.groundDrawCall = null;
|
|
34
32
|
this.shadowVPLightX = Number.NaN;
|
|
@@ -39,7 +37,14 @@ export class Engine {
|
|
|
39
37
|
this.DOUBLE_TAP_DELAY = 300; // ms
|
|
40
38
|
this.modelInstances = new Map();
|
|
41
39
|
this.textureCache = new Map();
|
|
42
|
-
this.
|
|
40
|
+
this._nextDefaultModelId = 0;
|
|
41
|
+
// IK and physics enabled at engine level (same for all models)
|
|
42
|
+
this.ikEnabled = true;
|
|
43
|
+
this.physicsEnabled = true;
|
|
44
|
+
// Camera target binding (Babylon/Three style: camera follows model)
|
|
45
|
+
this.cameraTargetModel = null;
|
|
46
|
+
this.cameraTargetBoneName = "全ての親";
|
|
47
|
+
this.cameraTargetOffset = new Vec3(0, 0, 0);
|
|
43
48
|
this.lastFpsUpdate = performance.now();
|
|
44
49
|
this.framesSinceLastUpdate = 0;
|
|
45
50
|
this.lastFrameTime = performance.now();
|
|
@@ -83,7 +88,6 @@ export class Engine {
|
|
|
83
88
|
}
|
|
84
89
|
};
|
|
85
90
|
this.canvas = canvas;
|
|
86
|
-
this.sampleCount = options?.multisampleCount ?? DEFAULT_ENGINE_OPTIONS.multisampleCount;
|
|
87
91
|
if (options) {
|
|
88
92
|
this.ambientColor = options.ambientColor ?? DEFAULT_ENGINE_OPTIONS.ambientColor;
|
|
89
93
|
this.directionalLightIntensity =
|
|
@@ -138,7 +142,7 @@ export class Engine {
|
|
|
138
142
|
: undefined,
|
|
139
143
|
primitive: { cullMode: config.cullMode ?? "none" },
|
|
140
144
|
depthStencil: config.depthStencil,
|
|
141
|
-
multisample: config.multisample ?? { count:
|
|
145
|
+
multisample: config.multisample ?? { count: Engine.MULTISAMPLE_COUNT },
|
|
142
146
|
});
|
|
143
147
|
}
|
|
144
148
|
createPipelines() {
|
|
@@ -378,70 +382,6 @@ export class Engine {
|
|
|
378
382
|
depthCompare: "less-equal",
|
|
379
383
|
},
|
|
380
384
|
});
|
|
381
|
-
this.groundBindGroupLayout = this.device.createBindGroupLayout({
|
|
382
|
-
label: "ground bind group layout",
|
|
383
|
-
entries: [
|
|
384
|
-
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
385
|
-
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
386
|
-
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
387
|
-
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
388
|
-
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
389
|
-
],
|
|
390
|
-
});
|
|
391
|
-
const groundPipelineLayout = this.device.createPipelineLayout({
|
|
392
|
-
label: "ground pipeline layout",
|
|
393
|
-
bindGroupLayouts: [this.groundBindGroupLayout],
|
|
394
|
-
});
|
|
395
|
-
const groundShaderModule = this.device.createShaderModule({
|
|
396
|
-
label: "ground shaders",
|
|
397
|
-
code: /* wgsl */ `
|
|
398
|
-
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
399
|
-
struct Light { direction: vec4f, color: vec4f, };
|
|
400
|
-
struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
|
|
401
|
-
struct GroundMaterialUniforms { diffuseColor: vec3f, reflectionLevel: f32, fadeStart: f32, fadeEnd: f32, _a: f32, _b: f32, };
|
|
402
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
403
|
-
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
404
|
-
@group(0) @binding(2) var reflectionTexture: texture_2d<f32>;
|
|
405
|
-
@group(0) @binding(3) var reflectionSampler: sampler;
|
|
406
|
-
@group(0) @binding(4) var<uniform> material: GroundMaterialUniforms;
|
|
407
|
-
struct VertexOutput {
|
|
408
|
-
@builtin(position) position: vec4f, @location(0) normal: vec3f, @location(1) uv: vec2f, @location(2) worldPos: vec3f,
|
|
409
|
-
};
|
|
410
|
-
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VertexOutput {
|
|
411
|
-
var o: VertexOutput;
|
|
412
|
-
o.worldPos = position;
|
|
413
|
-
o.position = camera.projection * camera.view * vec4f(position, 1.0);
|
|
414
|
-
o.normal = normal; o.uv = uv; return o;
|
|
415
|
-
}
|
|
416
|
-
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
417
|
-
let n = normalize(input.normal);
|
|
418
|
-
let centerDist = length(input.worldPos.xz);
|
|
419
|
-
let t = clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0);
|
|
420
|
-
let edgeFade = 1.0 - smoothstep(0.0, 1.0, t);
|
|
421
|
-
let clipPos = camera.projection * camera.view * vec4f(input.worldPos, 1.0);
|
|
422
|
-
let ndcPos = clipPos.xyz / clipPos.w;
|
|
423
|
-
let reflectionUV = vec2f(ndcPos.x * 0.5 + 0.5, 0.5 - ndcPos.y * 0.5);
|
|
424
|
-
let sampledReflectionColor = textureSample(reflectionTexture, reflectionSampler, reflectionUV).rgb;
|
|
425
|
-
let isValidReflection = clipPos.w > 0.0 && all(reflectionUV >= vec2f(0.0)) && all(reflectionUV <= vec2f(1.0));
|
|
426
|
-
let reflectionColor = select(vec3f(1.0, 1.0, 1.0), sampledReflectionColor, isValidReflection);
|
|
427
|
-
let fadeFactor = clamp((length(input.worldPos - camera.viewPos) - 15.0) / 20.0, 0.0, 1.0);
|
|
428
|
-
let refl = reflectionColor * (1.0 - fadeFactor * 0.3);
|
|
429
|
-
var finalColor = mix(material.diffuseColor, refl, material.reflectionLevel) * edgeFade;
|
|
430
|
-
let l = -light.lights[0].direction.xyz;
|
|
431
|
-
let lightAccum = light.ambientColor.xyz + light.lights[0].color.xyz * light.lights[0].color.w * max(dot(n, l), 0.0);
|
|
432
|
-
return vec4f(finalColor * lightAccum, edgeFade);
|
|
433
|
-
}
|
|
434
|
-
`,
|
|
435
|
-
});
|
|
436
|
-
this.groundPipeline = this.createRenderPipeline({
|
|
437
|
-
label: "ground pipeline",
|
|
438
|
-
layout: groundPipelineLayout,
|
|
439
|
-
shaderModule: groundShaderModule,
|
|
440
|
-
vertexBuffers: fullVertexBuffers,
|
|
441
|
-
fragmentTarget: standardBlend,
|
|
442
|
-
cullMode: "back",
|
|
443
|
-
depthStencil: { format: "depth24plus-stencil8", depthWriteEnabled: true, depthCompare: "less-equal" },
|
|
444
|
-
});
|
|
445
385
|
this.shadowLightVPBuffer = this.device.createBuffer({
|
|
446
386
|
size: 64,
|
|
447
387
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
@@ -557,24 +497,6 @@ export class Engine {
|
|
|
557
497
|
cullMode: "back",
|
|
558
498
|
depthStencil: { format: "depth24plus-stencil8", depthWriteEnabled: true, depthCompare: "less-equal" },
|
|
559
499
|
});
|
|
560
|
-
// Create reflection pipeline (multisampled version for higher quality)
|
|
561
|
-
this.reflectionPipeline = this.createRenderPipeline({
|
|
562
|
-
label: "reflection pipeline",
|
|
563
|
-
layout: mainPipelineLayout,
|
|
564
|
-
shaderModule,
|
|
565
|
-
vertexBuffers: fullVertexBuffers,
|
|
566
|
-
fragmentTarget: {
|
|
567
|
-
format: this.presentationFormat,
|
|
568
|
-
blend: standardBlend.blend,
|
|
569
|
-
},
|
|
570
|
-
multisample: { count: this.sampleCount }, // Use same multisampling as main render
|
|
571
|
-
cullMode: "none",
|
|
572
|
-
depthStencil: {
|
|
573
|
-
format: "depth24plus-stencil8",
|
|
574
|
-
depthWriteEnabled: true,
|
|
575
|
-
depthCompare: "less-equal",
|
|
576
|
-
},
|
|
577
|
-
});
|
|
578
500
|
// Create bind group layout for outline pipelines
|
|
579
501
|
this.outlineBindGroupLayout = this.device.createBindGroupLayout({
|
|
580
502
|
label: "outline bind group layout",
|
|
@@ -837,33 +759,25 @@ export class Engine {
|
|
|
837
759
|
this.multisampleTexture = this.device.createTexture({
|
|
838
760
|
label: "multisample render target",
|
|
839
761
|
size: [width, height],
|
|
840
|
-
sampleCount:
|
|
762
|
+
sampleCount: Engine.MULTISAMPLE_COUNT,
|
|
841
763
|
format: this.presentationFormat,
|
|
842
764
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
843
765
|
});
|
|
844
766
|
this.depthTexture = this.device.createTexture({
|
|
845
767
|
label: "depth texture",
|
|
846
768
|
size: [width, height],
|
|
847
|
-
sampleCount:
|
|
769
|
+
sampleCount: Engine.MULTISAMPLE_COUNT,
|
|
848
770
|
format: "depth24plus-stencil8",
|
|
849
771
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
850
772
|
});
|
|
851
773
|
const depthTextureView = this.depthTexture.createView();
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
storeOp: "store",
|
|
860
|
-
}
|
|
861
|
-
: {
|
|
862
|
-
view: this.context.getCurrentTexture().createView(),
|
|
863
|
-
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
864
|
-
loadOp: "clear",
|
|
865
|
-
storeOp: "store",
|
|
866
|
-
};
|
|
774
|
+
const colorAttachment = {
|
|
775
|
+
view: this.multisampleTexture.createView(),
|
|
776
|
+
resolveTarget: this.context.getCurrentTexture().createView(),
|
|
777
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
778
|
+
loadOp: "clear",
|
|
779
|
+
storeOp: "store",
|
|
780
|
+
};
|
|
867
781
|
this.renderPassDescriptor = {
|
|
868
782
|
label: "renderPass",
|
|
869
783
|
colorAttachments: [colorAttachment],
|
|
@@ -891,6 +805,24 @@ export class Engine {
|
|
|
891
805
|
this.camera.aspect = this.canvas.width / this.canvas.height;
|
|
892
806
|
this.camera.attachControl(this.canvas);
|
|
893
807
|
}
|
|
808
|
+
setCameraTarget(modelOrVec, boneName, offset) {
|
|
809
|
+
if (modelOrVec === null) {
|
|
810
|
+
this.cameraTargetModel = null;
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if ("x" in modelOrVec && "y" in modelOrVec && "z" in modelOrVec) {
|
|
814
|
+
this.cameraTargetModel = null;
|
|
815
|
+
this.camera.target.x = modelOrVec.x;
|
|
816
|
+
this.camera.target.y = modelOrVec.y;
|
|
817
|
+
this.camera.target.z = modelOrVec.z;
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
this.cameraTargetModel = modelOrVec;
|
|
821
|
+
this.cameraTargetBoneName = boneName ?? "";
|
|
822
|
+
this.cameraTargetOffset.x = offset?.x ?? 0;
|
|
823
|
+
this.cameraTargetOffset.y = offset?.y ?? 0;
|
|
824
|
+
this.cameraTargetOffset.z = offset?.z ?? 0;
|
|
825
|
+
}
|
|
894
826
|
// Step 5: Create lighting buffers
|
|
895
827
|
setupLighting() {
|
|
896
828
|
this.lightUniformBuffer = this.device.createBuffer({
|
|
@@ -943,30 +875,20 @@ export class Engine {
|
|
|
943
875
|
width: 100,
|
|
944
876
|
height: 100,
|
|
945
877
|
diffuseColor: new Vec3(1, 1, 1),
|
|
946
|
-
reflectionLevel: 0.5,
|
|
947
|
-
reflectionTextureSize: 1024,
|
|
948
878
|
fadeStart: 5.0,
|
|
949
879
|
fadeEnd: 60.0,
|
|
950
|
-
mode: "reflection",
|
|
951
880
|
shadowMapSize: 4096,
|
|
952
881
|
shadowStrength: 1.0,
|
|
953
882
|
...options,
|
|
954
883
|
};
|
|
955
|
-
this.groundMode = opts.mode;
|
|
956
884
|
this.createGroundGeometry(opts.width, opts.height);
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
this.createReflectionTexture(opts.reflectionTextureSize);
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
this.createShadowGroundResources(opts.shadowMapSize, opts.diffuseColor, opts.fadeStart, opts.fadeEnd, opts.shadowStrength);
|
|
963
|
-
}
|
|
964
|
-
this.groundHasReflections = true;
|
|
885
|
+
this.createShadowGroundResources(opts.shadowMapSize, opts.diffuseColor, opts.fadeStart, opts.fadeEnd, opts.shadowStrength);
|
|
886
|
+
this.hasGround = true;
|
|
965
887
|
this.groundDrawCall = {
|
|
966
888
|
type: "ground",
|
|
967
889
|
count: 6,
|
|
968
890
|
firstIndex: 0,
|
|
969
|
-
bindGroup:
|
|
891
|
+
bindGroup: this.groundShadowBindGroup,
|
|
970
892
|
materialName: "Ground",
|
|
971
893
|
};
|
|
972
894
|
}
|
|
@@ -1011,6 +933,14 @@ export class Engine {
|
|
|
1011
933
|
this.resizeObserver = null;
|
|
1012
934
|
}
|
|
1013
935
|
}
|
|
936
|
+
async loadModel(nameOrPath, path) {
|
|
937
|
+
const pmxPath = path === undefined ? nameOrPath : path;
|
|
938
|
+
const name = path === undefined ? "model_" + (this._nextDefaultModelId++) : nameOrPath;
|
|
939
|
+
const model = await PmxLoader.load(pmxPath);
|
|
940
|
+
model.setName(name);
|
|
941
|
+
await this.addModel(model, pmxPath, name);
|
|
942
|
+
return model;
|
|
943
|
+
}
|
|
1014
944
|
async addModel(model, pmxPath, name) {
|
|
1015
945
|
const requested = name ?? model.name;
|
|
1016
946
|
let key = requested;
|
|
@@ -1074,11 +1004,17 @@ export class Engine {
|
|
|
1074
1004
|
const inst = this.modelInstances.get(modelName);
|
|
1075
1005
|
return inst ? !inst.hiddenMaterials.has(materialName) : false;
|
|
1076
1006
|
}
|
|
1077
|
-
|
|
1078
|
-
this.
|
|
1007
|
+
setIKEnabled(enabled) {
|
|
1008
|
+
this.ikEnabled = enabled;
|
|
1079
1009
|
}
|
|
1080
|
-
|
|
1081
|
-
this.
|
|
1010
|
+
getIKEnabled() {
|
|
1011
|
+
return this.ikEnabled;
|
|
1012
|
+
}
|
|
1013
|
+
setPhysicsEnabled(enabled) {
|
|
1014
|
+
this.physicsEnabled = enabled;
|
|
1015
|
+
}
|
|
1016
|
+
getPhysicsEnabled() {
|
|
1017
|
+
return this.physicsEnabled;
|
|
1082
1018
|
}
|
|
1083
1019
|
resetPhysics() {
|
|
1084
1020
|
this.forEachInstance((inst) => {
|
|
@@ -1097,10 +1033,10 @@ export class Engine {
|
|
|
1097
1033
|
}
|
|
1098
1034
|
updateInstances(deltaTime) {
|
|
1099
1035
|
this.forEachInstance((inst) => {
|
|
1100
|
-
const verticesChanged = inst.model.update(deltaTime);
|
|
1036
|
+
const verticesChanged = inst.model.update(deltaTime, this.ikEnabled);
|
|
1101
1037
|
if (verticesChanged)
|
|
1102
1038
|
inst.vertexBufferNeedsUpdate = true;
|
|
1103
|
-
if (inst.physics &&
|
|
1039
|
+
if (inst.physics && this.physicsEnabled) {
|
|
1104
1040
|
inst.physics.step(deltaTime, inst.model.getWorldMatrices(), inst.model.getBoneInverseBindMatrices());
|
|
1105
1041
|
}
|
|
1106
1042
|
if (inst.vertexBufferNeedsUpdate)
|
|
@@ -1245,56 +1181,6 @@ export class Engine {
|
|
|
1245
1181
|
});
|
|
1246
1182
|
this.device.queue.writeBuffer(this.groundIndexBuffer, 0, indices);
|
|
1247
1183
|
}
|
|
1248
|
-
createGroundMaterialBuffer(diffuseColor, reflectionLevel, fadeStart, fadeEnd) {
|
|
1249
|
-
const u = new Float32Array(8);
|
|
1250
|
-
u[0] = diffuseColor.x;
|
|
1251
|
-
u[1] = diffuseColor.y;
|
|
1252
|
-
u[2] = diffuseColor.z;
|
|
1253
|
-
u[3] = reflectionLevel;
|
|
1254
|
-
u[4] = fadeStart;
|
|
1255
|
-
u[5] = fadeEnd;
|
|
1256
|
-
u[6] = 0;
|
|
1257
|
-
u[7] = 0;
|
|
1258
|
-
this.groundMaterialUniformBuffer = this.device.createBuffer({
|
|
1259
|
-
label: "ground material uniform buffer",
|
|
1260
|
-
size: 64,
|
|
1261
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1262
|
-
});
|
|
1263
|
-
this.device.queue.writeBuffer(this.groundMaterialUniformBuffer, 0, u);
|
|
1264
|
-
}
|
|
1265
|
-
createReflectionTexture(size) {
|
|
1266
|
-
this.groundReflectionTexture = this.device.createTexture({
|
|
1267
|
-
label: "ground reflection texture",
|
|
1268
|
-
size: [size, size],
|
|
1269
|
-
sampleCount: this.sampleCount,
|
|
1270
|
-
format: this.presentationFormat,
|
|
1271
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1272
|
-
});
|
|
1273
|
-
this.groundReflectionResolveTexture = this.device.createTexture({
|
|
1274
|
-
label: "ground reflection resolve texture",
|
|
1275
|
-
size: [size, size],
|
|
1276
|
-
format: this.presentationFormat,
|
|
1277
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
1278
|
-
});
|
|
1279
|
-
this.groundReflectionDepthTexture = this.device.createTexture({
|
|
1280
|
-
label: "ground reflection depth texture",
|
|
1281
|
-
size: [size, size],
|
|
1282
|
-
sampleCount: this.sampleCount,
|
|
1283
|
-
format: "depth24plus-stencil8",
|
|
1284
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1285
|
-
});
|
|
1286
|
-
this.groundReflectionBindGroup = this.device.createBindGroup({
|
|
1287
|
-
label: "ground reflection bind group",
|
|
1288
|
-
layout: this.groundBindGroupLayout,
|
|
1289
|
-
entries: [
|
|
1290
|
-
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1291
|
-
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1292
|
-
{ binding: 2, resource: this.groundReflectionResolveTexture.createView() },
|
|
1293
|
-
{ binding: 3, resource: this.materialSampler },
|
|
1294
|
-
{ binding: 4, resource: { buffer: this.groundMaterialUniformBuffer } },
|
|
1295
|
-
],
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
1184
|
createShadowGroundResources(shadowMapSize, diffuseColor, fadeStart, fadeEnd, shadowStrength) {
|
|
1299
1185
|
this.shadowMapTexture = this.device.createTexture({
|
|
1300
1186
|
label: "shadow map",
|
|
@@ -1526,83 +1412,28 @@ export class Engine {
|
|
|
1526
1412
|
return null;
|
|
1527
1413
|
}
|
|
1528
1414
|
}
|
|
1529
|
-
//
|
|
1530
|
-
renderEyes(pass, inst
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
else {
|
|
1541
|
-
pass.setPipeline(this.eyePipeline);
|
|
1542
|
-
pass.setStencilReference(this.STENCIL_EYE_VALUE);
|
|
1543
|
-
for (const draw of inst.drawCalls) {
|
|
1544
|
-
if (draw.type === "eye" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1545
|
-
pass.setBindGroup(0, draw.bindGroup);
|
|
1546
|
-
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1547
|
-
}
|
|
1415
|
+
// Post-alpha eye: render eye draws; main pass writes stencil so hair-over-eyes can use it for see-through bangs.
|
|
1416
|
+
renderEyes(pass, inst) {
|
|
1417
|
+
pass.setPipeline(this.eyePipeline);
|
|
1418
|
+
pass.setStencilReference(this.STENCIL_EYE_VALUE);
|
|
1419
|
+
for (const draw of inst.drawCalls) {
|
|
1420
|
+
if (draw.type === "eye" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1421
|
+
pass.setBindGroup(0, draw.bindGroup);
|
|
1422
|
+
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1548
1423
|
}
|
|
1549
1424
|
}
|
|
1550
1425
|
}
|
|
1551
1426
|
renderGround(pass) {
|
|
1552
|
-
if (!this.
|
|
1427
|
+
if (!this.hasGround || !this.groundVertexBuffer || !this.groundIndexBuffer || !this.groundDrawCall)
|
|
1553
1428
|
return;
|
|
1554
|
-
|
|
1555
|
-
this.renderReflectionTexture();
|
|
1556
|
-
pass.setPipeline(this.groundMode === "reflection" ? this.groundPipeline : this.groundShadowPipeline);
|
|
1429
|
+
pass.setPipeline(this.groundShadowPipeline);
|
|
1557
1430
|
pass.setVertexBuffer(0, this.groundVertexBuffer);
|
|
1558
1431
|
pass.setIndexBuffer(this.groundIndexBuffer, "uint16");
|
|
1559
1432
|
pass.setBindGroup(0, this.groundDrawCall.bindGroup);
|
|
1560
1433
|
pass.drawIndexed(this.groundDrawCall.count, 1, this.groundDrawCall.firstIndex, 0, 0);
|
|
1561
1434
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
return;
|
|
1565
|
-
const mirrorMatrix = this.createMirrorMatrix(new Vec3(0, 1, 0), 0);
|
|
1566
|
-
this.updateCameraUniforms();
|
|
1567
|
-
const reflectionEncoder = this.device.createCommandEncoder();
|
|
1568
|
-
const reflectionPassDescriptor = {
|
|
1569
|
-
label: "reflection render pass",
|
|
1570
|
-
colorAttachments: [
|
|
1571
|
-
{
|
|
1572
|
-
view: this.groundReflectionTexture.createView(),
|
|
1573
|
-
resolveTarget: this.groundReflectionResolveTexture.createView(),
|
|
1574
|
-
clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, // White
|
|
1575
|
-
loadOp: "clear",
|
|
1576
|
-
storeOp: "store",
|
|
1577
|
-
},
|
|
1578
|
-
],
|
|
1579
|
-
depthStencilAttachment: {
|
|
1580
|
-
view: this.groundReflectionDepthTexture.createView(),
|
|
1581
|
-
depthClearValue: 1.0,
|
|
1582
|
-
depthLoadOp: "clear",
|
|
1583
|
-
depthStoreOp: "store",
|
|
1584
|
-
stencilClearValue: 0,
|
|
1585
|
-
stencilLoadOp: "clear",
|
|
1586
|
-
stencilStoreOp: "discard",
|
|
1587
|
-
},
|
|
1588
|
-
};
|
|
1589
|
-
const reflectionPass = reflectionEncoder.beginRenderPass(reflectionPassDescriptor);
|
|
1590
|
-
this.forEachInstance((inst) => this.renderOneModel(reflectionPass, inst, true, mirrorMatrix));
|
|
1591
|
-
reflectionPass.end();
|
|
1592
|
-
this.device.queue.submit([reflectionEncoder.finish()]);
|
|
1593
|
-
this.updateSkinMatrices();
|
|
1594
|
-
}
|
|
1595
|
-
renderHair(pass, inst, useReflectionPipeline = false) {
|
|
1596
|
-
if (useReflectionPipeline) {
|
|
1597
|
-
pass.setPipeline(this.reflectionPipeline);
|
|
1598
|
-
for (const draw of inst.drawCalls) {
|
|
1599
|
-
if (draw.type === "hair-over-eyes" || draw.type === "hair-over-non-eyes") {
|
|
1600
|
-
pass.setBindGroup(0, draw.bindGroup);
|
|
1601
|
-
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
return;
|
|
1605
|
-
}
|
|
1435
|
+
// Post-alpha eye: hair-over-eyes uses stencil (from renderEyes) for 50% alpha; hair-over-non-eyes uses inverse stencil.
|
|
1436
|
+
renderHair(pass, inst) {
|
|
1606
1437
|
const hasHair = inst.drawCalls.some((d) => (d.type === "hair-over-eyes" || d.type === "hair-over-non-eyes") && this.shouldRenderDrawCall(inst, d));
|
|
1607
1438
|
if (hasHair) {
|
|
1608
1439
|
pass.setPipeline(this.hairDepthPipeline);
|
|
@@ -1751,17 +1582,27 @@ export class Engine {
|
|
|
1751
1582
|
const currentTime = performance.now();
|
|
1752
1583
|
const deltaTime = this.lastFrameTime > 0 ? (currentTime - this.lastFrameTime) / 1000 : 0.016;
|
|
1753
1584
|
this.lastFrameTime = currentTime;
|
|
1754
|
-
this.updateCameraUniforms();
|
|
1755
1585
|
this.updateRenderTarget();
|
|
1756
1586
|
const hasModels = this.modelInstances.size > 0;
|
|
1757
1587
|
if (hasModels) {
|
|
1758
1588
|
this.updateInstances(deltaTime);
|
|
1759
1589
|
this.updateSkinMatrices();
|
|
1590
|
+
// Update camera target from bound model (bone not found → 0,0,0 + offset)
|
|
1591
|
+
if (this.cameraTargetModel) {
|
|
1592
|
+
const pos = this.cameraTargetModel.getBoneWorldPosition(this.cameraTargetBoneName);
|
|
1593
|
+
const px = pos?.x ?? 0;
|
|
1594
|
+
const py = pos?.y ?? 0;
|
|
1595
|
+
const pz = pos?.z ?? 0;
|
|
1596
|
+
this.camera.target.x = px + this.cameraTargetOffset.x;
|
|
1597
|
+
this.camera.target.y = py + this.cameraTargetOffset.y;
|
|
1598
|
+
this.camera.target.z = pz + this.cameraTargetOffset.z;
|
|
1599
|
+
}
|
|
1760
1600
|
}
|
|
1761
|
-
|
|
1601
|
+
this.updateCameraUniforms();
|
|
1602
|
+
if (this.hasGround)
|
|
1762
1603
|
this.updateShadowLightVP();
|
|
1763
1604
|
const encoder = this.device.createCommandEncoder();
|
|
1764
|
-
if (hasModels && this.
|
|
1605
|
+
if (hasModels && this.hasGround && this.shadowMapDepthView) {
|
|
1765
1606
|
const sp = encoder.beginRenderPass({
|
|
1766
1607
|
colorAttachments: [],
|
|
1767
1608
|
depthStencilAttachment: {
|
|
@@ -1777,8 +1618,8 @@ export class Engine {
|
|
|
1777
1618
|
}
|
|
1778
1619
|
const pass = encoder.beginRenderPass(this.renderPassDescriptor);
|
|
1779
1620
|
if (hasModels)
|
|
1780
|
-
this.forEachInstance((inst) => this.renderOneModel(pass, inst
|
|
1781
|
-
if (this.
|
|
1621
|
+
this.forEachInstance((inst) => this.renderOneModel(pass, inst));
|
|
1622
|
+
if (this.hasGround)
|
|
1782
1623
|
this.renderGround(pass);
|
|
1783
1624
|
pass.end();
|
|
1784
1625
|
this.device.queue.submit([encoder.finish()]);
|
|
@@ -1795,48 +1636,34 @@ export class Engine {
|
|
|
1795
1636
|
sp.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1796
1637
|
}
|
|
1797
1638
|
}
|
|
1798
|
-
|
|
1799
|
-
pass.
|
|
1800
|
-
pass.setVertexBuffer(1, inst.jointsBuffer);
|
|
1801
|
-
pass.setVertexBuffer(2, inst.weightsBuffer);
|
|
1802
|
-
pass.setIndexBuffer(inst.indexBuffer, "uint32");
|
|
1803
|
-
if (useReflection && mirrorMatrix) {
|
|
1804
|
-
this.writeMirrorTransformedSkinMatrices(inst, mirrorMatrix);
|
|
1805
|
-
pass.setPipeline(this.reflectionPipeline);
|
|
1806
|
-
for (const draw of inst.drawCalls) {
|
|
1807
|
-
if (draw.type === "opaque" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1808
|
-
pass.setBindGroup(0, draw.bindGroup);
|
|
1809
|
-
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
this.renderEyes(pass, inst, true);
|
|
1813
|
-
this.renderHair(pass, inst, true);
|
|
1814
|
-
for (const draw of inst.drawCalls) {
|
|
1815
|
-
if (draw.type === "transparent" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1816
|
-
pass.setBindGroup(0, draw.bindGroup);
|
|
1817
|
-
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
this.drawOutlines(pass, inst, true, true);
|
|
1821
|
-
return;
|
|
1822
|
-
}
|
|
1823
|
-
pass.setPipeline(this.modelPipeline);
|
|
1639
|
+
drawOpaque(pass, inst, pipeline) {
|
|
1640
|
+
pass.setPipeline(pipeline);
|
|
1824
1641
|
for (const draw of inst.drawCalls) {
|
|
1825
1642
|
if (draw.type === "opaque" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1826
1643
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1827
1644
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1828
1645
|
}
|
|
1829
1646
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
pass.setPipeline(this.modelPipeline);
|
|
1647
|
+
}
|
|
1648
|
+
drawTransparent(pass, inst, pipeline) {
|
|
1649
|
+
pass.setPipeline(pipeline);
|
|
1834
1650
|
for (const draw of inst.drawCalls) {
|
|
1835
1651
|
if (draw.type === "transparent" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1836
1652
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1837
1653
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1838
1654
|
}
|
|
1839
1655
|
}
|
|
1656
|
+
}
|
|
1657
|
+
renderOneModel(pass, inst) {
|
|
1658
|
+
pass.setVertexBuffer(0, inst.vertexBuffer);
|
|
1659
|
+
pass.setVertexBuffer(1, inst.jointsBuffer);
|
|
1660
|
+
pass.setVertexBuffer(2, inst.weightsBuffer);
|
|
1661
|
+
pass.setIndexBuffer(inst.indexBuffer, "uint32");
|
|
1662
|
+
this.drawOpaque(pass, inst, this.modelPipeline);
|
|
1663
|
+
this.renderEyes(pass, inst);
|
|
1664
|
+
this.drawOutlines(pass, inst, false);
|
|
1665
|
+
this.renderHair(pass, inst);
|
|
1666
|
+
this.drawTransparent(pass, inst, this.modelPipeline);
|
|
1840
1667
|
this.drawOutlines(pass, inst, true);
|
|
1841
1668
|
}
|
|
1842
1669
|
updateCameraUniforms() {
|
|
@@ -1851,14 +1678,8 @@ export class Engine {
|
|
|
1851
1678
|
this.device.queue.writeBuffer(this.cameraUniformBuffer, 0, this.cameraMatrixData);
|
|
1852
1679
|
}
|
|
1853
1680
|
updateRenderTarget() {
|
|
1854
|
-
// Update render target to use current canvas texture
|
|
1855
1681
|
const colorAttachment = this.renderPassDescriptor.colorAttachments[0];
|
|
1856
|
-
|
|
1857
|
-
colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
|
|
1858
|
-
}
|
|
1859
|
-
else {
|
|
1860
|
-
colorAttachment.view = this.context.getCurrentTexture().createView();
|
|
1861
|
-
}
|
|
1682
|
+
colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
|
|
1862
1683
|
}
|
|
1863
1684
|
updateSkinMatrices() {
|
|
1864
1685
|
this.forEachInstance((inst) => {
|
|
@@ -1866,9 +1687,7 @@ export class Engine {
|
|
|
1866
1687
|
this.device.queue.writeBuffer(inst.skinMatrixBuffer, 0, skinMatrices.buffer, skinMatrices.byteOffset, skinMatrices.byteLength);
|
|
1867
1688
|
});
|
|
1868
1689
|
}
|
|
1869
|
-
drawOutlines(pass, inst, transparent
|
|
1870
|
-
if (useReflectionPipeline)
|
|
1871
|
-
return;
|
|
1690
|
+
drawOutlines(pass, inst, transparent) {
|
|
1872
1691
|
pass.setPipeline(this.outlinePipeline);
|
|
1873
1692
|
const outlineType = transparent ? "transparent-outline" : "opaque-outline";
|
|
1874
1693
|
for (const draw of inst.drawCalls) {
|
|
@@ -1900,41 +1719,6 @@ export class Engine {
|
|
|
1900
1719
|
this.lastFpsUpdate = now;
|
|
1901
1720
|
}
|
|
1902
1721
|
}
|
|
1903
|
-
createMirrorMatrix(planeNormal, planeDistance) {
|
|
1904
|
-
// Create reflection matrix across a plane
|
|
1905
|
-
const n = planeNormal.normalize();
|
|
1906
|
-
return new Mat4(new Float32Array([
|
|
1907
|
-
1 - 2 * n.x * n.x,
|
|
1908
|
-
-2 * n.x * n.y,
|
|
1909
|
-
-2 * n.x * n.z,
|
|
1910
|
-
0,
|
|
1911
|
-
-2 * n.y * n.x,
|
|
1912
|
-
1 - 2 * n.y * n.y,
|
|
1913
|
-
-2 * n.y * n.z,
|
|
1914
|
-
0,
|
|
1915
|
-
-2 * n.z * n.x,
|
|
1916
|
-
-2 * n.z * n.y,
|
|
1917
|
-
1 - 2 * n.z * n.z,
|
|
1918
|
-
0,
|
|
1919
|
-
-2 * planeDistance * n.x,
|
|
1920
|
-
-2 * planeDistance * n.y,
|
|
1921
|
-
-2 * planeDistance * n.z,
|
|
1922
|
-
1,
|
|
1923
|
-
]));
|
|
1924
|
-
}
|
|
1925
|
-
writeMirrorTransformedSkinMatrices(inst, mirrorMatrix) {
|
|
1926
|
-
const originalMatrices = inst.model.getSkinMatrices();
|
|
1927
|
-
const transformedMatrices = new Float32Array(originalMatrices.length);
|
|
1928
|
-
for (let i = 0; i < originalMatrices.length; i += 16) {
|
|
1929
|
-
const boneMatrixValues = new Float32Array(16);
|
|
1930
|
-
for (let j = 0; j < 16; j++)
|
|
1931
|
-
boneMatrixValues[j] = originalMatrices[i + j];
|
|
1932
|
-
const boneMatrix = new Mat4(boneMatrixValues);
|
|
1933
|
-
const transformed = mirrorMatrix.multiply(boneMatrix);
|
|
1934
|
-
for (let j = 0; j < 16; j++)
|
|
1935
|
-
transformedMatrices[i + j] = transformed.values[j];
|
|
1936
|
-
}
|
|
1937
|
-
this.device.queue.writeBuffer(inst.skinMatrixBuffer, 0, transformedMatrices);
|
|
1938
|
-
}
|
|
1939
1722
|
}
|
|
1940
1723
|
Engine.instance = null;
|
|
1724
|
+
Engine.MULTISAMPLE_COUNT = 4;
|
package/dist/model.d.ts
CHANGED
|
@@ -95,10 +95,6 @@ export interface MorphRuntime {
|
|
|
95
95
|
weights: Float32Array;
|
|
96
96
|
}
|
|
97
97
|
export declare class Model {
|
|
98
|
-
private static _nextId;
|
|
99
|
-
private static nextDefaultName;
|
|
100
|
-
static loadFrom(path: string): Promise<Model>;
|
|
101
|
-
static loadFrom(name: string, path: string): Promise<Model>;
|
|
102
98
|
private _name;
|
|
103
99
|
get name(): string;
|
|
104
100
|
setName(value: string): void;
|
|
@@ -125,8 +121,6 @@ export declare class Model {
|
|
|
125
121
|
private boneTrackIndices;
|
|
126
122
|
private morphTrackIndices;
|
|
127
123
|
private lastAppliedClip;
|
|
128
|
-
private ikEnabled;
|
|
129
|
-
private physicsEnabled;
|
|
130
124
|
constructor(vertexData: Float32Array<ArrayBuffer>, indexData: Uint32Array<ArrayBuffer>, textures: Texture[], materials: Material[], skeleton: Skeleton, skinning: Skinning, morphing: Morphing, rigidbodies?: Rigidbody[], joints?: Joint[]);
|
|
131
125
|
private initializeRuntimeSkeleton;
|
|
132
126
|
private initializeIKRuntime;
|
|
@@ -157,9 +151,6 @@ export declare class Model {
|
|
|
157
151
|
loadAnimation(animationName: string, vmdUrl: string): Promise<void>;
|
|
158
152
|
resetAllBones(): void;
|
|
159
153
|
resetAllMorphs(): void;
|
|
160
|
-
setIKEnabled(enabled: boolean): void;
|
|
161
|
-
setPhysicsEnabled(enabled: boolean): void;
|
|
162
|
-
getPhysicsEnabled(): boolean;
|
|
163
154
|
getAnimationState(): AnimationState;
|
|
164
155
|
play(): void;
|
|
165
156
|
play(name: string): boolean;
|
|
@@ -180,7 +171,7 @@ export declare class Model {
|
|
|
180
171
|
private static upperBound;
|
|
181
172
|
private findKeyframeIndex;
|
|
182
173
|
private applyPoseFromClip;
|
|
183
|
-
update(deltaTime: number): boolean;
|
|
174
|
+
update(deltaTime: number, ikEnabled: boolean): boolean;
|
|
184
175
|
private solveIKChains;
|
|
185
176
|
private ikComputedSet;
|
|
186
177
|
private computeSingleBoneWorldMatrix;
|