reze-engine 0.4.1 → 0.4.2

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 CHANGED
@@ -39,6 +39,9 @@ export declare class Engine {
39
39
  private hairPipelineOverEyes;
40
40
  private hairPipelineOverNonEyes;
41
41
  private hairDepthPipeline;
42
+ private groundPipeline;
43
+ private groundBindGroupLayout;
44
+ private reflectionPipeline;
42
45
  private outlinePipeline;
43
46
  private hairOutlinePipeline;
44
47
  private mainBindGroupLayout;
@@ -72,6 +75,14 @@ export declare class Engine {
72
75
  private bloomThreshold;
73
76
  private bloomIntensity;
74
77
  private rimLightIntensity;
78
+ private groundVertexBuffer?;
79
+ private groundIndexBuffer?;
80
+ private groundReflectionTexture?;
81
+ private groundReflectionResolveTexture?;
82
+ private groundReflectionDepthTexture?;
83
+ private groundReflectionBindGroup?;
84
+ private groundMaterialUniformBuffer?;
85
+ private groundHasReflections;
75
86
  private onRaycast?;
76
87
  private cachedSkinnedVertices?;
77
88
  private cachedSkinMatricesVersion;
@@ -106,6 +117,15 @@ export declare class Engine {
106
117
  private setAmbientColor;
107
118
  clearLights(): void;
108
119
  addLight(direction: Vec3, color: Vec3, intensity?: number): boolean;
120
+ addGround(options?: {
121
+ width?: number;
122
+ height?: number;
123
+ diffuseColor?: Vec3;
124
+ reflectionLevel?: number;
125
+ reflectionTextureSize?: number;
126
+ fadeStart?: number;
127
+ fadeEnd?: number;
128
+ }): void;
109
129
  private updateLightBuffer;
110
130
  loadAnimation(url: string): Promise<void>;
111
131
  playAnimation(): void;
@@ -132,12 +152,17 @@ export declare class Engine {
132
152
  getMaterials(): string[];
133
153
  private updateVertexBuffer;
134
154
  private setupModelBuffers;
155
+ private createGroundGeometry;
156
+ private createGroundMaterialBuffer;
157
+ private createReflectionTexture;
135
158
  private setupMaterials;
136
159
  private createMaterialUniformBuffer;
137
160
  private createUniformBuffer;
138
161
  private shouldRenderDrawCall;
139
162
  private createTextureFromPath;
140
163
  private renderEyes;
164
+ private renderGround;
165
+ private renderReflectionTexture;
141
166
  private renderHair;
142
167
  private handleCanvasDoubleClick;
143
168
  private handleCanvasTouch;
@@ -149,5 +174,7 @@ export declare class Engine {
149
174
  private updateSkinMatrices;
150
175
  private drawOutlines;
151
176
  private updateStats;
177
+ private createMirrorMatrix;
178
+ private writeMirrorTransformedSkinMatrices;
152
179
  }
153
180
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Camera } from "./camera";
2
- import { Vec3 } from "./math";
2
+ import { Mat4, Vec3 } from "./math";
3
3
  import { PmxLoader } from "./pmx-loader";
4
4
  export const DEFAULT_ENGINE_OPTIONS = {
5
5
  ambientColor: new Vec3(0.85, 0.85, 0.85),
@@ -21,6 +21,7 @@ export class Engine {
21
21
  // Constants
22
22
  this.STENCIL_EYE_VALUE = 1;
23
23
  this.BLOOM_DOWNSCALE_FACTOR = 2;
24
+ this.groundHasReflections = false;
24
25
  this.cachedSkinMatricesVersion = -1;
25
26
  this.skinMatricesVersion = 0;
26
27
  // Double-tap detection
@@ -352,6 +353,154 @@ export class Engine {
352
353
  depthCompare: "less-equal",
353
354
  },
354
355
  });
356
+ // Create ground/reflection pipeline with reflection texture support
357
+ this.groundBindGroupLayout = this.device.createBindGroupLayout({
358
+ label: "ground bind group layout",
359
+ entries: [
360
+ { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // camera
361
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // light
362
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: {} }, // reflectionTexture
363
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: {} }, // reflectionSampler
364
+ { binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // groundMaterial
365
+ ],
366
+ });
367
+ const groundPipelineLayout = this.device.createPipelineLayout({
368
+ label: "ground pipeline layout",
369
+ bindGroupLayouts: [this.groundBindGroupLayout],
370
+ });
371
+ const groundShaderModule = this.device.createShaderModule({
372
+ label: "ground shaders",
373
+ code: /* wgsl */ `
374
+ struct CameraUniforms {
375
+ view: mat4x4f,
376
+ projection: mat4x4f,
377
+ viewPos: vec3f,
378
+ _padding: f32,
379
+ };
380
+
381
+ struct LightUniforms {
382
+ ambientColor: vec4f,
383
+ lights: array<Light, 4>,
384
+ };
385
+
386
+ struct Light {
387
+ direction: vec4f,
388
+ color: vec4f,
389
+ };
390
+
391
+ struct GroundMaterialUniforms {
392
+ diffuseColor: vec3f,
393
+ reflectionLevel: f32,
394
+ fadeStart: f32,
395
+ fadeEnd: f32,
396
+ _padding1: f32,
397
+ _padding2: f32,
398
+ };
399
+
400
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
401
+ @group(0) @binding(1) var<uniform> light: LightUniforms;
402
+ @group(0) @binding(2) var reflectionTexture: texture_2d<f32>;
403
+ @group(0) @binding(3) var reflectionSampler: sampler;
404
+ @group(0) @binding(4) var<uniform> material: GroundMaterialUniforms;
405
+
406
+ struct VertexOutput {
407
+ @builtin(position) position: vec4f,
408
+ @location(0) normal: vec3f,
409
+ @location(1) uv: vec2f,
410
+ @location(2) worldPos: vec3f,
411
+ };
412
+
413
+ @vertex fn vs(
414
+ @location(0) position: vec3f,
415
+ @location(1) normal: vec3f,
416
+ @location(2) uv: vec2f,
417
+ ) -> VertexOutput {
418
+ var output: VertexOutput;
419
+ let worldPos = position;
420
+ output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
421
+ output.normal = normal;
422
+ output.uv = uv;
423
+ output.worldPos = worldPos;
424
+ return output;
425
+ }
426
+
427
+ @fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
428
+ let n = normalize(input.normal);
429
+
430
+ let clipPos = camera.projection * camera.view * vec4f(input.worldPos, 1.0);
431
+ let ndcPos = clipPos.xyz / clipPos.w;
432
+ var reflectionUV = vec2f(ndcPos.x * 0.5 + 0.5, 0.5 - ndcPos.y * 0.5);
433
+
434
+ let sampledReflectionColor = textureSample(reflectionTexture, reflectionSampler, reflectionUV).rgb;
435
+ let isValidReflection = clipPos.w > 0.0 &&
436
+ all(reflectionUV >= vec2f(0.0)) && all(reflectionUV <= vec2f(1.0));
437
+ var reflectionColor = select(vec3f(1.0, 1.0, 1.0), sampledReflectionColor, isValidReflection);
438
+
439
+ let distanceFromCamera = length(input.worldPos - camera.viewPos);
440
+ let fadeFactor = clamp((distanceFromCamera - 15.0) / 20.0, 0.0, 1.0);
441
+ reflectionColor *= (1.0 - fadeFactor * 0.3);
442
+
443
+ let diffuseColor = material.diffuseColor;
444
+ var finalColor = mix(diffuseColor, reflectionColor, material.reflectionLevel);
445
+
446
+ // Ground edge fade effect - smooth fade out at edges based on distance from center
447
+ let centerDist = length(input.worldPos.xz); // Distance from ground center in XZ plane
448
+
449
+ // Smoothstep for much smoother gradient transition
450
+ let t = clamp((centerDist - material.fadeStart) / (material.fadeEnd - material.fadeStart), 0.0, 1.0);
451
+ let edgeFade = 1.0 - smoothstep(0.0, 1.0, t);
452
+ finalColor *= edgeFade;
453
+
454
+ // Accumulate light contribution
455
+ var lightAccum = light.ambientColor.xyz;
456
+ for (var i = 0u; i < 4u; i++) {
457
+ let intensity = light.lights[i].color.w;
458
+ if (intensity > 0.0) {
459
+ let l = -light.lights[i].direction.xyz;
460
+ let nDotL = max(dot(n, l), 0.0);
461
+ let radiance = light.lights[i].color.xyz * intensity;
462
+ lightAccum += radiance * nDotL;
463
+ }
464
+ }
465
+
466
+ // Apply lighting to the blended color
467
+ let litColor = finalColor * lightAccum;
468
+
469
+ return vec4f(litColor, edgeFade);
470
+ }
471
+ `,
472
+ });
473
+ this.groundPipeline = this.createRenderPipeline({
474
+ label: "ground pipeline",
475
+ layout: groundPipelineLayout,
476
+ shaderModule: groundShaderModule,
477
+ vertexBuffers: fullVertexBuffers,
478
+ fragmentTarget: standardBlend,
479
+ cullMode: "back",
480
+ depthStencil: {
481
+ format: "depth24plus-stencil8",
482
+ depthWriteEnabled: true,
483
+ depthCompare: "less-equal",
484
+ },
485
+ });
486
+ // Create reflection pipeline (multisampled version for higher quality)
487
+ this.reflectionPipeline = this.createRenderPipeline({
488
+ label: "reflection pipeline",
489
+ layout: mainPipelineLayout,
490
+ shaderModule,
491
+ vertexBuffers: fullVertexBuffers,
492
+ fragmentTarget: {
493
+ format: this.presentationFormat,
494
+ blend: standardBlend.blend,
495
+ },
496
+ multisample: { count: this.sampleCount }, // Use same multisampling as main render
497
+ cullMode: "none",
498
+ depthStencil: {
499
+ format: "depth24plus-stencil8",
500
+ depthWriteEnabled: true,
501
+ depthCompare: "less-equal",
502
+ },
503
+ });
355
504
  // Create bind group layout for outline pipelines
356
505
  this.outlineBindGroupLayout = this.device.createBindGroupLayout({
357
506
  label: "outline bind group layout",
@@ -1009,6 +1158,30 @@ export class Engine {
1009
1158
  this.updateLightBuffer();
1010
1159
  return true;
1011
1160
  }
1161
+ addGround(options) {
1162
+ const opts = {
1163
+ width: 100,
1164
+ height: 100,
1165
+ diffuseColor: new Vec3(1, 1, 1),
1166
+ reflectionLevel: 0.5,
1167
+ reflectionTextureSize: 1024,
1168
+ fadeStart: 5.0,
1169
+ fadeEnd: 60.0,
1170
+ ...options,
1171
+ };
1172
+ // Create ground geometry
1173
+ this.createGroundGeometry(opts.width, opts.height);
1174
+ this.createGroundMaterialBuffer(opts.diffuseColor, opts.reflectionLevel, opts.fadeStart, opts.fadeEnd);
1175
+ this.createReflectionTexture(opts.reflectionTextureSize);
1176
+ this.groundHasReflections = true;
1177
+ this.drawCalls.push({
1178
+ type: "ground",
1179
+ count: 6, // 2 triangles, 3 indices each
1180
+ firstIndex: 0,
1181
+ bindGroup: this.groundReflectionBindGroup,
1182
+ materialName: "Ground",
1183
+ });
1184
+ }
1012
1185
  updateLightBuffer() {
1013
1186
  this.device.queue.writeBuffer(this.lightUniformBuffer, 0, this.lightData);
1014
1187
  }
@@ -1176,6 +1349,124 @@ export class Engine {
1176
1349
  }
1177
1350
  await this.setupMaterials(model);
1178
1351
  }
1352
+ createGroundGeometry(width = 100, height = 100) {
1353
+ const halfWidth = width / 2;
1354
+ const halfHeight = height / 2;
1355
+ const vertices = new Float32Array([
1356
+ // Bottom-left
1357
+ -halfWidth,
1358
+ 0,
1359
+ -halfHeight, // position
1360
+ 0,
1361
+ 1,
1362
+ 0, // normal (up)
1363
+ 0,
1364
+ 0, // uv
1365
+ // Bottom-right
1366
+ halfWidth,
1367
+ 0,
1368
+ -halfHeight, // position
1369
+ 0,
1370
+ 1,
1371
+ 0, // normal (up)
1372
+ 1,
1373
+ 0, // uv
1374
+ // Top-right
1375
+ halfWidth,
1376
+ 0,
1377
+ halfHeight, // position
1378
+ 0,
1379
+ 1,
1380
+ 0, // normal (up)
1381
+ 1,
1382
+ 1, // uv
1383
+ // Top-left
1384
+ -halfWidth,
1385
+ 0,
1386
+ halfHeight, // position
1387
+ 0,
1388
+ 1,
1389
+ 0, // normal (up)
1390
+ 0,
1391
+ 1, // uv
1392
+ ]);
1393
+ // Create indices for two triangles
1394
+ const indices = new Uint16Array([
1395
+ 0,
1396
+ 1,
1397
+ 2, // First triangle
1398
+ 0,
1399
+ 2,
1400
+ 3, // Second triangle
1401
+ ]);
1402
+ // Create vertex buffer
1403
+ this.groundVertexBuffer = this.device.createBuffer({
1404
+ label: "ground vertex buffer",
1405
+ size: vertices.byteLength,
1406
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
1407
+ });
1408
+ this.device.queue.writeBuffer(this.groundVertexBuffer, 0, vertices);
1409
+ this.groundIndexBuffer = this.device.createBuffer({
1410
+ label: "ground index buffer",
1411
+ size: indices.byteLength,
1412
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
1413
+ });
1414
+ this.device.queue.writeBuffer(this.groundIndexBuffer, 0, indices);
1415
+ }
1416
+ createGroundMaterialBuffer(diffuseColor = new Vec3(1, 1, 1), reflectionLevel = 0.5, fadeStart = 5.0, fadeEnd = 60.0) {
1417
+ const materialData = new Float32Array([
1418
+ diffuseColor.x,
1419
+ diffuseColor.y,
1420
+ diffuseColor.z, // diffuseColor (12 bytes)
1421
+ reflectionLevel, // reflectionLevel (4 bytes)
1422
+ fadeStart, // fadeStart (4 bytes)
1423
+ fadeEnd, // fadeEnd (4 bytes)
1424
+ 0, // padding (4 bytes)
1425
+ 0, // padding (4 bytes)
1426
+ 0, // padding (4 bytes)
1427
+ 0, // padding (4 bytes)
1428
+ ]);
1429
+ this.groundMaterialUniformBuffer = this.device.createBuffer({
1430
+ label: "ground material uniform buffer",
1431
+ size: materialData.byteLength,
1432
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
1433
+ });
1434
+ this.device.queue.writeBuffer(this.groundMaterialUniformBuffer, 0, materialData);
1435
+ }
1436
+ createReflectionTexture(size = 1024) {
1437
+ this.groundReflectionTexture = this.device.createTexture({
1438
+ label: "ground reflection texture",
1439
+ size: [size, size],
1440
+ sampleCount: this.sampleCount,
1441
+ format: this.presentationFormat,
1442
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
1443
+ });
1444
+ this.groundReflectionResolveTexture = this.device.createTexture({
1445
+ label: "ground reflection resolve texture",
1446
+ size: [size, size],
1447
+ format: this.presentationFormat,
1448
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
1449
+ });
1450
+ this.groundReflectionDepthTexture = this.device.createTexture({
1451
+ label: "ground reflection depth texture",
1452
+ size: [size, size],
1453
+ sampleCount: this.sampleCount,
1454
+ format: "depth24plus-stencil8",
1455
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
1456
+ });
1457
+ // Create a bind group for the reflection texture that can be used in the ground material
1458
+ this.groundReflectionBindGroup = this.device.createBindGroup({
1459
+ label: "ground reflection bind group",
1460
+ layout: this.groundBindGroupLayout,
1461
+ entries: [
1462
+ { binding: 0, resource: { buffer: this.cameraUniformBuffer } },
1463
+ { binding: 1, resource: { buffer: this.lightUniformBuffer } },
1464
+ { binding: 2, resource: this.groundReflectionResolveTexture.createView() }, // Use resolve texture for sampling
1465
+ { binding: 3, resource: this.materialSampler },
1466
+ { binding: 4, resource: { buffer: this.groundMaterialUniformBuffer } },
1467
+ ],
1468
+ });
1469
+ }
1179
1470
  async setupMaterials(model) {
1180
1471
  const materials = model.getMaterials();
1181
1472
  if (materials.length === 0) {
@@ -1369,18 +1660,119 @@ export class Engine {
1369
1660
  }
1370
1661
  }
1371
1662
  // Helper: Render eyes with stencil writing (for post-alpha-eye effect)
1372
- renderEyes(pass) {
1373
- pass.setPipeline(this.eyePipeline);
1374
- pass.setStencilReference(this.STENCIL_EYE_VALUE);
1663
+ renderEyes(pass, useReflectionPipeline = false) {
1664
+ if (useReflectionPipeline) {
1665
+ // For reflections, use the basic reflection pipeline instead of specialized eye pipeline
1666
+ pass.setPipeline(this.reflectionPipeline);
1667
+ for (const draw of this.drawCalls) {
1668
+ if (draw.type === "eye") {
1669
+ pass.setBindGroup(0, draw.bindGroup);
1670
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1671
+ }
1672
+ }
1673
+ }
1674
+ else {
1675
+ pass.setPipeline(this.eyePipeline);
1676
+ pass.setStencilReference(this.STENCIL_EYE_VALUE);
1677
+ for (const draw of this.drawCalls) {
1678
+ if (draw.type === "eye" && this.shouldRenderDrawCall(draw)) {
1679
+ pass.setBindGroup(0, draw.bindGroup);
1680
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1681
+ }
1682
+ }
1683
+ }
1684
+ }
1685
+ renderGround(pass) {
1686
+ if (!this.groundHasReflections || !this.groundVertexBuffer || !this.groundIndexBuffer) {
1687
+ return;
1688
+ }
1689
+ if (this.groundReflectionTexture) {
1690
+ this.renderReflectionTexture();
1691
+ }
1692
+ pass.setPipeline(this.groundPipeline);
1693
+ pass.setVertexBuffer(0, this.groundVertexBuffer);
1694
+ pass.setIndexBuffer(this.groundIndexBuffer, "uint16");
1375
1695
  for (const draw of this.drawCalls) {
1376
- if (draw.type === "eye" && this.shouldRenderDrawCall(draw)) {
1696
+ if (draw.type === "ground" && this.shouldRenderDrawCall(draw)) {
1377
1697
  pass.setBindGroup(0, draw.bindGroup);
1378
1698
  pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1379
1699
  }
1380
1700
  }
1381
1701
  }
1702
+ renderReflectionTexture() {
1703
+ if (!this.groundReflectionTexture)
1704
+ return;
1705
+ const mirrorMatrix = this.createMirrorMatrix(new Vec3(0, 1, 0), 0);
1706
+ this.updateCameraUniforms();
1707
+ const reflectionEncoder = this.device.createCommandEncoder();
1708
+ const reflectionPassDescriptor = {
1709
+ label: "reflection render pass",
1710
+ colorAttachments: [
1711
+ {
1712
+ view: this.groundReflectionTexture.createView(),
1713
+ resolveTarget: this.groundReflectionResolveTexture.createView(),
1714
+ clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, // White
1715
+ loadOp: "clear",
1716
+ storeOp: "store",
1717
+ },
1718
+ ],
1719
+ depthStencilAttachment: {
1720
+ view: this.groundReflectionDepthTexture.createView(),
1721
+ depthClearValue: 1.0,
1722
+ depthLoadOp: "clear",
1723
+ depthStoreOp: "store",
1724
+ stencilClearValue: 0,
1725
+ stencilLoadOp: "clear",
1726
+ stencilStoreOp: "discard",
1727
+ },
1728
+ };
1729
+ const reflectionPass = reflectionEncoder.beginRenderPass(reflectionPassDescriptor);
1730
+ if (this.currentModel) {
1731
+ reflectionPass.setVertexBuffer(0, this.vertexBuffer);
1732
+ reflectionPass.setVertexBuffer(1, this.jointsBuffer);
1733
+ reflectionPass.setVertexBuffer(2, this.weightsBuffer);
1734
+ reflectionPass.setIndexBuffer(this.indexBuffer, "uint32");
1735
+ this.writeMirrorTransformedSkinMatrices(mirrorMatrix);
1736
+ reflectionPass.setPipeline(this.reflectionPipeline);
1737
+ for (const draw of this.drawCalls) {
1738
+ if (draw.type === "opaque" && this.shouldRenderDrawCall(draw)) {
1739
+ reflectionPass.setBindGroup(0, draw.bindGroup);
1740
+ reflectionPass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1741
+ }
1742
+ }
1743
+ // Render eyes (using reflection pipeline)
1744
+ this.renderEyes(reflectionPass, true);
1745
+ // Render hair (using reflection pipeline)
1746
+ this.renderHair(reflectionPass, true);
1747
+ // Render transparent objects
1748
+ for (const draw of this.drawCalls) {
1749
+ if (draw.type === "transparent" && this.shouldRenderDrawCall(draw)) {
1750
+ reflectionPass.setBindGroup(0, draw.bindGroup);
1751
+ reflectionPass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1752
+ }
1753
+ }
1754
+ this.drawOutlines(reflectionPass, true, true);
1755
+ }
1756
+ reflectionPass.end();
1757
+ // Submit reflection rendering commands
1758
+ const reflectionCommandBuffer = reflectionEncoder.finish();
1759
+ this.device.queue.submit([reflectionCommandBuffer]);
1760
+ // Restore original skin matrices
1761
+ this.updateSkinMatrices();
1762
+ }
1382
1763
  // Helper: Render hair with post-alpha-eye effect (depth pre-pass + stencil-based shading + outlines)
1383
- renderHair(pass) {
1764
+ renderHair(pass, useReflectionPipeline = false) {
1765
+ if (useReflectionPipeline) {
1766
+ // For reflections, use the basic reflection pipeline for all hair
1767
+ pass.setPipeline(this.reflectionPipeline);
1768
+ for (const draw of this.drawCalls) {
1769
+ if (draw.type === "hair-over-eyes" || draw.type === "hair-over-non-eyes") {
1770
+ pass.setBindGroup(0, draw.bindGroup);
1771
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
1772
+ }
1773
+ }
1774
+ return;
1775
+ }
1384
1776
  // Hair depth pre-pass (reduces overdraw via early depth rejection)
1385
1777
  const hasHair = this.drawCalls.some((d) => (d.type === "hair-over-eyes" || d.type === "hair-over-non-eyes") && this.shouldRenderDrawCall(d));
1386
1778
  if (hasHair) {
@@ -1430,7 +1822,7 @@ export class Engine {
1430
1822
  // Get camera matrices
1431
1823
  const viewMatrix = this.camera.getViewMatrix();
1432
1824
  const projectionMatrix = this.camera.getProjectionMatrix();
1433
- // Convert screen coordinates to world space ray (like Babylon.js)
1825
+ // Convert screen coordinates to world space ray
1434
1826
  const canvas = this.canvas;
1435
1827
  const rect = canvas.getBoundingClientRect();
1436
1828
  // Convert to clip space (-1 to 1)
@@ -1636,7 +2028,11 @@ export class Engine {
1636
2028
  this.drawOutlines(pass, false);
1637
2029
  // Pass 3: Hair rendering (depth pre-pass + shading + outlines)
1638
2030
  this.renderHair(pass);
1639
- // Pass 4: Transparent
2031
+ // Pass 4: Ground (with reflections)
2032
+ if (this.groundHasReflections) {
2033
+ this.renderGround(pass);
2034
+ }
2035
+ // Pass 5: Transparent
1640
2036
  pass.setPipeline(this.modelPipeline);
1641
2037
  for (const draw of this.drawCalls) {
1642
2038
  if (draw.type === "transparent" && this.shouldRenderDrawCall(draw)) {
@@ -1767,7 +2163,11 @@ export class Engine {
1767
2163
  // Increment version to invalidate cached skinned vertices
1768
2164
  this.skinMatricesVersion++;
1769
2165
  }
1770
- drawOutlines(pass, transparent) {
2166
+ drawOutlines(pass, transparent, useReflectionPipeline = false) {
2167
+ if (useReflectionPipeline) {
2168
+ // Skip outlines for reflections - not critical for the effect
2169
+ return;
2170
+ }
1771
2171
  pass.setPipeline(this.outlinePipeline);
1772
2172
  const outlineType = transparent ? "transparent-outline" : "opaque-outline";
1773
2173
  for (const draw of this.drawCalls) {
@@ -1799,4 +2199,44 @@ export class Engine {
1799
2199
  this.lastFpsUpdate = now;
1800
2200
  }
1801
2201
  }
2202
+ createMirrorMatrix(planeNormal, planeDistance) {
2203
+ // Create reflection matrix across a plane
2204
+ const n = planeNormal.normalize();
2205
+ return new Mat4(new Float32Array([
2206
+ 1 - 2 * n.x * n.x,
2207
+ -2 * n.x * n.y,
2208
+ -2 * n.x * n.z,
2209
+ 0,
2210
+ -2 * n.y * n.x,
2211
+ 1 - 2 * n.y * n.y,
2212
+ -2 * n.y * n.z,
2213
+ 0,
2214
+ -2 * n.z * n.x,
2215
+ -2 * n.z * n.y,
2216
+ 1 - 2 * n.z * n.z,
2217
+ 0,
2218
+ -2 * planeDistance * n.x,
2219
+ -2 * planeDistance * n.y,
2220
+ -2 * planeDistance * n.z,
2221
+ 1,
2222
+ ]));
2223
+ }
2224
+ writeMirrorTransformedSkinMatrices(mirrorMatrix) {
2225
+ if (!this.currentModel || !this.skinMatrixBuffer)
2226
+ return;
2227
+ const originalMatrices = this.currentModel.getSkinMatrices();
2228
+ const transformedMatrices = new Float32Array(originalMatrices.length);
2229
+ for (let i = 0; i < originalMatrices.length; i += 16) {
2230
+ const boneMatrixValues = new Float32Array(16);
2231
+ for (let j = 0; j < 16; j++) {
2232
+ boneMatrixValues[j] = originalMatrices[i + j];
2233
+ }
2234
+ const boneMatrix = new Mat4(boneMatrixValues);
2235
+ const transformed = mirrorMatrix.multiply(boneMatrix);
2236
+ for (let j = 0; j < 16; j++) {
2237
+ transformedMatrices[i + j] = transformed.values[j];
2238
+ }
2239
+ }
2240
+ this.device.queue.writeBuffer(this.skinMatrixBuffer, 0, transformedMatrices);
2241
+ }
1802
2242
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "A WebGPU-based MMD model renderer",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/engine.ts CHANGED
@@ -40,6 +40,7 @@ type DrawCallType =
40
40
  | "hair-over-eyes"
41
41
  | "hair-over-non-eyes"
42
42
  | "transparent"
43
+ | "ground"
43
44
  | "opaque-outline"
44
45
  | "eye-outline"
45
46
  | "hair-outline"
@@ -77,6 +78,10 @@ export class Engine {
77
78
  private hairPipelineOverEyes!: GPURenderPipeline
78
79
  private hairPipelineOverNonEyes!: GPURenderPipeline
79
80
  private hairDepthPipeline!: GPURenderPipeline
81
+ // Ground/reflection pipeline
82
+ private groundPipeline!: GPURenderPipeline
83
+ private groundBindGroupLayout!: GPUBindGroupLayout
84
+ private reflectionPipeline!: GPURenderPipeline
80
85
  // Outline pipelines
81
86
  private outlinePipeline!: GPURenderPipeline
82
87
  private hairOutlinePipeline!: GPURenderPipeline
@@ -120,6 +125,16 @@ export class Engine {
120
125
  // Rim light settings
121
126
  private rimLightIntensity!: number
122
127
 
128
+ // Ground/reflection properties
129
+ private groundVertexBuffer?: GPUBuffer
130
+ private groundIndexBuffer?: GPUBuffer
131
+ private groundReflectionTexture?: GPUTexture
132
+ private groundReflectionResolveTexture?: GPUTexture // Resolve target for multisampled texture
133
+ private groundReflectionDepthTexture?: GPUTexture
134
+ private groundReflectionBindGroup?: GPUBindGroup
135
+ private groundMaterialUniformBuffer?: GPUBuffer
136
+ private groundHasReflections = false
137
+
123
138
  // Raycasting
124
139
  private onRaycast?: RaycastCallback
125
140
  private cachedSkinnedVertices?: Float32Array
@@ -452,6 +467,159 @@ export class Engine {
452
467
  },
453
468
  })
454
469
 
470
+ // Create ground/reflection pipeline with reflection texture support
471
+ this.groundBindGroupLayout = this.device.createBindGroupLayout({
472
+ label: "ground bind group layout",
473
+ entries: [
474
+ { binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // camera
475
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // light
476
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: {} }, // reflectionTexture
477
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: {} }, // reflectionSampler
478
+ { binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, // groundMaterial
479
+ ],
480
+ })
481
+
482
+ const groundPipelineLayout = this.device.createPipelineLayout({
483
+ label: "ground pipeline layout",
484
+ bindGroupLayouts: [this.groundBindGroupLayout],
485
+ })
486
+
487
+ const groundShaderModule = this.device.createShaderModule({
488
+ label: "ground shaders",
489
+ code: /* wgsl */ `
490
+ struct CameraUniforms {
491
+ view: mat4x4f,
492
+ projection: mat4x4f,
493
+ viewPos: vec3f,
494
+ _padding: f32,
495
+ };
496
+
497
+ struct LightUniforms {
498
+ ambientColor: vec4f,
499
+ lights: array<Light, 4>,
500
+ };
501
+
502
+ struct Light {
503
+ direction: vec4f,
504
+ color: vec4f,
505
+ };
506
+
507
+ struct GroundMaterialUniforms {
508
+ diffuseColor: vec3f,
509
+ reflectionLevel: f32,
510
+ fadeStart: f32,
511
+ fadeEnd: f32,
512
+ _padding1: f32,
513
+ _padding2: f32,
514
+ };
515
+
516
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
517
+ @group(0) @binding(1) var<uniform> light: LightUniforms;
518
+ @group(0) @binding(2) var reflectionTexture: texture_2d<f32>;
519
+ @group(0) @binding(3) var reflectionSampler: sampler;
520
+ @group(0) @binding(4) var<uniform> material: GroundMaterialUniforms;
521
+
522
+ struct VertexOutput {
523
+ @builtin(position) position: vec4f,
524
+ @location(0) normal: vec3f,
525
+ @location(1) uv: vec2f,
526
+ @location(2) worldPos: vec3f,
527
+ };
528
+
529
+ @vertex fn vs(
530
+ @location(0) position: vec3f,
531
+ @location(1) normal: vec3f,
532
+ @location(2) uv: vec2f,
533
+ ) -> VertexOutput {
534
+ var output: VertexOutput;
535
+ let worldPos = position;
536
+ output.position = camera.projection * camera.view * vec4f(worldPos, 1.0);
537
+ output.normal = normal;
538
+ output.uv = uv;
539
+ output.worldPos = worldPos;
540
+ return output;
541
+ }
542
+
543
+ @fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
544
+ let n = normalize(input.normal);
545
+
546
+ let clipPos = camera.projection * camera.view * vec4f(input.worldPos, 1.0);
547
+ let ndcPos = clipPos.xyz / clipPos.w;
548
+ var reflectionUV = vec2f(ndcPos.x * 0.5 + 0.5, 0.5 - ndcPos.y * 0.5);
549
+
550
+ let sampledReflectionColor = textureSample(reflectionTexture, reflectionSampler, reflectionUV).rgb;
551
+ let isValidReflection = clipPos.w > 0.0 &&
552
+ all(reflectionUV >= vec2f(0.0)) && all(reflectionUV <= vec2f(1.0));
553
+ var reflectionColor = select(vec3f(1.0, 1.0, 1.0), sampledReflectionColor, isValidReflection);
554
+
555
+ let distanceFromCamera = length(input.worldPos - camera.viewPos);
556
+ let fadeFactor = clamp((distanceFromCamera - 15.0) / 20.0, 0.0, 1.0);
557
+ reflectionColor *= (1.0 - fadeFactor * 0.3);
558
+
559
+ let diffuseColor = material.diffuseColor;
560
+ var finalColor = mix(diffuseColor, reflectionColor, material.reflectionLevel);
561
+
562
+ // Ground edge fade effect - smooth fade out at edges based on distance from center
563
+ let centerDist = length(input.worldPos.xz); // Distance from ground center in XZ plane
564
+
565
+ // Smoothstep for much smoother gradient transition
566
+ let t = clamp((centerDist - material.fadeStart) / (material.fadeEnd - material.fadeStart), 0.0, 1.0);
567
+ let edgeFade = 1.0 - smoothstep(0.0, 1.0, t);
568
+ finalColor *= edgeFade;
569
+
570
+ // Accumulate light contribution
571
+ var lightAccum = light.ambientColor.xyz;
572
+ for (var i = 0u; i < 4u; i++) {
573
+ let intensity = light.lights[i].color.w;
574
+ if (intensity > 0.0) {
575
+ let l = -light.lights[i].direction.xyz;
576
+ let nDotL = max(dot(n, l), 0.0);
577
+ let radiance = light.lights[i].color.xyz * intensity;
578
+ lightAccum += radiance * nDotL;
579
+ }
580
+ }
581
+
582
+ // Apply lighting to the blended color
583
+ let litColor = finalColor * lightAccum;
584
+
585
+ return vec4f(litColor, edgeFade);
586
+ }
587
+ `,
588
+ })
589
+
590
+ this.groundPipeline = this.createRenderPipeline({
591
+ label: "ground pipeline",
592
+ layout: groundPipelineLayout,
593
+ shaderModule: groundShaderModule,
594
+ vertexBuffers: fullVertexBuffers,
595
+ fragmentTarget: standardBlend,
596
+ cullMode: "back",
597
+ depthStencil: {
598
+ format: "depth24plus-stencil8",
599
+ depthWriteEnabled: true,
600
+ depthCompare: "less-equal",
601
+ },
602
+ })
603
+
604
+ // Create reflection pipeline (multisampled version for higher quality)
605
+ this.reflectionPipeline = this.createRenderPipeline({
606
+ label: "reflection pipeline",
607
+ layout: mainPipelineLayout,
608
+ shaderModule,
609
+ vertexBuffers: fullVertexBuffers,
610
+ fragmentTarget: {
611
+ format: this.presentationFormat,
612
+ blend: standardBlend.blend,
613
+ },
614
+ multisample: { count: this.sampleCount }, // Use same multisampling as main render
615
+ cullMode: "none",
616
+ depthStencil: {
617
+ format: "depth24plus-stencil8",
618
+ depthWriteEnabled: true,
619
+ depthCompare: "less-equal",
620
+ },
621
+ })
622
+
455
623
  // Create bind group layout for outline pipelines
456
624
  this.outlineBindGroupLayout = this.device.createBindGroupLayout({
457
625
  label: "outline bind group layout",
@@ -1161,6 +1329,42 @@ export class Engine {
1161
1329
  return true
1162
1330
  }
1163
1331
 
1332
+ public addGround(options?: {
1333
+ width?: number
1334
+ height?: number
1335
+ diffuseColor?: Vec3
1336
+ reflectionLevel?: number
1337
+ reflectionTextureSize?: number
1338
+ fadeStart?: number
1339
+ fadeEnd?: number
1340
+ }): void {
1341
+ const opts = {
1342
+ width: 100,
1343
+ height: 100,
1344
+ diffuseColor: new Vec3(1, 1, 1),
1345
+ reflectionLevel: 0.5,
1346
+ reflectionTextureSize: 1024,
1347
+ fadeStart: 5.0,
1348
+ fadeEnd: 60.0,
1349
+ ...options,
1350
+ }
1351
+
1352
+ // Create ground geometry
1353
+ this.createGroundGeometry(opts.width, opts.height)
1354
+
1355
+ this.createGroundMaterialBuffer(opts.diffuseColor, opts.reflectionLevel, opts.fadeStart, opts.fadeEnd)
1356
+ this.createReflectionTexture(opts.reflectionTextureSize)
1357
+ this.groundHasReflections = true
1358
+
1359
+ this.drawCalls.push({
1360
+ type: "ground",
1361
+ count: 6, // 2 triangles, 3 indices each
1362
+ firstIndex: 0,
1363
+ bindGroup: this.groundReflectionBindGroup!,
1364
+ materialName: "Ground",
1365
+ })
1366
+ }
1367
+
1164
1368
  private updateLightBuffer() {
1165
1369
  this.device.queue.writeBuffer(this.lightUniformBuffer, 0, this.lightData)
1166
1370
  }
@@ -1379,6 +1583,143 @@ export class Engine {
1379
1583
  await this.setupMaterials(model)
1380
1584
  }
1381
1585
 
1586
+ private createGroundGeometry(width: number = 100, height: number = 100) {
1587
+ const halfWidth = width / 2
1588
+ const halfHeight = height / 2
1589
+
1590
+ const vertices = new Float32Array([
1591
+ // Bottom-left
1592
+ -halfWidth,
1593
+ 0,
1594
+ -halfHeight, // position
1595
+ 0,
1596
+ 1,
1597
+ 0, // normal (up)
1598
+ 0,
1599
+ 0, // uv
1600
+
1601
+ // Bottom-right
1602
+ halfWidth,
1603
+ 0,
1604
+ -halfHeight, // position
1605
+ 0,
1606
+ 1,
1607
+ 0, // normal (up)
1608
+ 1,
1609
+ 0, // uv
1610
+
1611
+ // Top-right
1612
+ halfWidth,
1613
+ 0,
1614
+ halfHeight, // position
1615
+ 0,
1616
+ 1,
1617
+ 0, // normal (up)
1618
+ 1,
1619
+ 1, // uv
1620
+
1621
+ // Top-left
1622
+ -halfWidth,
1623
+ 0,
1624
+ halfHeight, // position
1625
+ 0,
1626
+ 1,
1627
+ 0, // normal (up)
1628
+ 0,
1629
+ 1, // uv
1630
+ ])
1631
+
1632
+ // Create indices for two triangles
1633
+ const indices = new Uint16Array([
1634
+ 0,
1635
+ 1,
1636
+ 2, // First triangle
1637
+ 0,
1638
+ 2,
1639
+ 3, // Second triangle
1640
+ ])
1641
+
1642
+ // Create vertex buffer
1643
+ this.groundVertexBuffer = this.device.createBuffer({
1644
+ label: "ground vertex buffer",
1645
+ size: vertices.byteLength,
1646
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
1647
+ })
1648
+ this.device.queue.writeBuffer(this.groundVertexBuffer, 0, vertices)
1649
+
1650
+ this.groundIndexBuffer = this.device.createBuffer({
1651
+ label: "ground index buffer",
1652
+ size: indices.byteLength,
1653
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
1654
+ })
1655
+ this.device.queue.writeBuffer(this.groundIndexBuffer, 0, indices)
1656
+ }
1657
+
1658
+ private createGroundMaterialBuffer(
1659
+ diffuseColor: Vec3 = new Vec3(1, 1, 1),
1660
+ reflectionLevel: number = 0.5,
1661
+ fadeStart: number = 5.0,
1662
+ fadeEnd: number = 60.0
1663
+ ) {
1664
+ const materialData = new Float32Array([
1665
+ diffuseColor.x,
1666
+ diffuseColor.y,
1667
+ diffuseColor.z, // diffuseColor (12 bytes)
1668
+ reflectionLevel, // reflectionLevel (4 bytes)
1669
+ fadeStart, // fadeStart (4 bytes)
1670
+ fadeEnd, // fadeEnd (4 bytes)
1671
+ 0, // padding (4 bytes)
1672
+ 0, // padding (4 bytes)
1673
+ 0, // padding (4 bytes)
1674
+ 0, // padding (4 bytes)
1675
+ ])
1676
+
1677
+ this.groundMaterialUniformBuffer = this.device.createBuffer({
1678
+ label: "ground material uniform buffer",
1679
+ size: materialData.byteLength,
1680
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
1681
+ })
1682
+ this.device.queue.writeBuffer(this.groundMaterialUniformBuffer, 0, materialData)
1683
+ }
1684
+
1685
+ private createReflectionTexture(size: number = 1024) {
1686
+ this.groundReflectionTexture = this.device.createTexture({
1687
+ label: "ground reflection texture",
1688
+ size: [size, size],
1689
+ sampleCount: this.sampleCount,
1690
+ format: this.presentationFormat,
1691
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
1692
+ })
1693
+
1694
+ this.groundReflectionResolveTexture = this.device.createTexture({
1695
+ label: "ground reflection resolve texture",
1696
+ size: [size, size],
1697
+ format: this.presentationFormat,
1698
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
1699
+ })
1700
+
1701
+ this.groundReflectionDepthTexture = this.device.createTexture({
1702
+ label: "ground reflection depth texture",
1703
+ size: [size, size],
1704
+ sampleCount: this.sampleCount,
1705
+ format: "depth24plus-stencil8",
1706
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
1707
+ })
1708
+
1709
+ // Create a bind group for the reflection texture that can be used in the ground material
1710
+ this.groundReflectionBindGroup = this.device.createBindGroup({
1711
+ label: "ground reflection bind group",
1712
+ layout: this.groundBindGroupLayout,
1713
+ entries: [
1714
+ { binding: 0, resource: { buffer: this.cameraUniformBuffer } },
1715
+ { binding: 1, resource: { buffer: this.lightUniformBuffer } },
1716
+ { binding: 2, resource: this.groundReflectionResolveTexture!.createView() }, // Use resolve texture for sampling
1717
+ { binding: 3, resource: this.materialSampler },
1718
+ { binding: 4, resource: { buffer: this.groundMaterialUniformBuffer! } },
1719
+ ],
1720
+ })
1721
+ }
1722
+
1382
1723
  private async setupMaterials(model: Model) {
1383
1724
  const materials = model.getMaterials()
1384
1725
  if (materials.length === 0) {
@@ -1598,19 +1939,135 @@ export class Engine {
1598
1939
  }
1599
1940
 
1600
1941
  // Helper: Render eyes with stencil writing (for post-alpha-eye effect)
1601
- private renderEyes(pass: GPURenderPassEncoder) {
1602
- pass.setPipeline(this.eyePipeline)
1603
- pass.setStencilReference(this.STENCIL_EYE_VALUE)
1942
+ private renderEyes(pass: GPURenderPassEncoder, useReflectionPipeline: boolean = false) {
1943
+ if (useReflectionPipeline) {
1944
+ // For reflections, use the basic reflection pipeline instead of specialized eye pipeline
1945
+ pass.setPipeline(this.reflectionPipeline)
1946
+ for (const draw of this.drawCalls) {
1947
+ if (draw.type === "eye") {
1948
+ pass.setBindGroup(0, draw.bindGroup)
1949
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1950
+ }
1951
+ }
1952
+ } else {
1953
+ pass.setPipeline(this.eyePipeline)
1954
+ pass.setStencilReference(this.STENCIL_EYE_VALUE)
1955
+ for (const draw of this.drawCalls) {
1956
+ if (draw.type === "eye" && this.shouldRenderDrawCall(draw)) {
1957
+ pass.setBindGroup(0, draw.bindGroup)
1958
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1959
+ }
1960
+ }
1961
+ }
1962
+ }
1963
+
1964
+ private renderGround(pass: GPURenderPassEncoder) {
1965
+ if (!this.groundHasReflections || !this.groundVertexBuffer || !this.groundIndexBuffer) {
1966
+ return
1967
+ }
1968
+
1969
+ if (this.groundReflectionTexture) {
1970
+ this.renderReflectionTexture()
1971
+ }
1972
+ pass.setPipeline(this.groundPipeline)
1973
+ pass.setVertexBuffer(0, this.groundVertexBuffer)
1974
+ pass.setIndexBuffer(this.groundIndexBuffer, "uint16")
1975
+
1604
1976
  for (const draw of this.drawCalls) {
1605
- if (draw.type === "eye" && this.shouldRenderDrawCall(draw)) {
1977
+ if (draw.type === "ground" && this.shouldRenderDrawCall(draw)) {
1606
1978
  pass.setBindGroup(0, draw.bindGroup)
1607
1979
  pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
1608
1980
  }
1609
1981
  }
1610
1982
  }
1611
1983
 
1984
+ private renderReflectionTexture() {
1985
+ if (!this.groundReflectionTexture) return
1986
+
1987
+ const mirrorMatrix = this.createMirrorMatrix(new Vec3(0, 1, 0), 0)
1988
+ this.updateCameraUniforms()
1989
+
1990
+ const reflectionEncoder = this.device.createCommandEncoder()
1991
+ const reflectionPassDescriptor: GPURenderPassDescriptor = {
1992
+ label: "reflection render pass",
1993
+ colorAttachments: [
1994
+ {
1995
+ view: this.groundReflectionTexture!.createView(),
1996
+ resolveTarget: this.groundReflectionResolveTexture!.createView(),
1997
+ clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, // White
1998
+ loadOp: "clear",
1999
+ storeOp: "store",
2000
+ },
2001
+ ],
2002
+ depthStencilAttachment: {
2003
+ view: this.groundReflectionDepthTexture!.createView(),
2004
+ depthClearValue: 1.0,
2005
+ depthLoadOp: "clear",
2006
+ depthStoreOp: "store",
2007
+ stencilClearValue: 0,
2008
+ stencilLoadOp: "clear",
2009
+ stencilStoreOp: "discard",
2010
+ },
2011
+ }
2012
+
2013
+ const reflectionPass = reflectionEncoder.beginRenderPass(reflectionPassDescriptor)
2014
+
2015
+ if (this.currentModel) {
2016
+ reflectionPass.setVertexBuffer(0, this.vertexBuffer)
2017
+ reflectionPass.setVertexBuffer(1, this.jointsBuffer)
2018
+ reflectionPass.setVertexBuffer(2, this.weightsBuffer)
2019
+ reflectionPass.setIndexBuffer(this.indexBuffer!, "uint32")
2020
+
2021
+ this.writeMirrorTransformedSkinMatrices(mirrorMatrix)
2022
+ reflectionPass.setPipeline(this.reflectionPipeline)
2023
+ for (const draw of this.drawCalls) {
2024
+ if (draw.type === "opaque" && this.shouldRenderDrawCall(draw)) {
2025
+ reflectionPass.setBindGroup(0, draw.bindGroup)
2026
+ reflectionPass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
2027
+ }
2028
+ }
2029
+
2030
+ // Render eyes (using reflection pipeline)
2031
+ this.renderEyes(reflectionPass, true)
2032
+
2033
+ // Render hair (using reflection pipeline)
2034
+ this.renderHair(reflectionPass, true)
2035
+
2036
+ // Render transparent objects
2037
+ for (const draw of this.drawCalls) {
2038
+ if (draw.type === "transparent" && this.shouldRenderDrawCall(draw)) {
2039
+ reflectionPass.setBindGroup(0, draw.bindGroup)
2040
+ reflectionPass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
2041
+ }
2042
+ }
2043
+
2044
+ this.drawOutlines(reflectionPass, true, true)
2045
+ }
2046
+
2047
+ reflectionPass.end()
2048
+
2049
+ // Submit reflection rendering commands
2050
+ const reflectionCommandBuffer = reflectionEncoder.finish()
2051
+ this.device.queue.submit([reflectionCommandBuffer])
2052
+
2053
+ // Restore original skin matrices
2054
+ this.updateSkinMatrices()
2055
+ }
2056
+
1612
2057
  // Helper: Render hair with post-alpha-eye effect (depth pre-pass + stencil-based shading + outlines)
1613
- private renderHair(pass: GPURenderPassEncoder) {
2058
+ private renderHair(pass: GPURenderPassEncoder, useReflectionPipeline: boolean = false) {
2059
+ if (useReflectionPipeline) {
2060
+ // For reflections, use the basic reflection pipeline for all hair
2061
+ pass.setPipeline(this.reflectionPipeline)
2062
+ for (const draw of this.drawCalls) {
2063
+ if (draw.type === "hair-over-eyes" || draw.type === "hair-over-non-eyes") {
2064
+ pass.setBindGroup(0, draw.bindGroup)
2065
+ pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
2066
+ }
2067
+ }
2068
+ return
2069
+ }
2070
+
1614
2071
  // Hair depth pre-pass (reduces overdraw via early depth rejection)
1615
2072
  const hasHair = this.drawCalls.some(
1616
2073
  (d) => (d.type === "hair-over-eyes" || d.type === "hair-over-non-eyes") && this.shouldRenderDrawCall(d)
@@ -1707,7 +2164,7 @@ export class Engine {
1707
2164
  const viewMatrix = this.camera.getViewMatrix()
1708
2165
  const projectionMatrix = this.camera.getProjectionMatrix()
1709
2166
 
1710
- // Convert screen coordinates to world space ray (like Babylon.js)
2167
+ // Convert screen coordinates to world space ray
1711
2168
  const canvas = this.canvas
1712
2169
  const rect = canvas.getBoundingClientRect()
1713
2170
 
@@ -1969,7 +2426,12 @@ export class Engine {
1969
2426
  // Pass 3: Hair rendering (depth pre-pass + shading + outlines)
1970
2427
  this.renderHair(pass)
1971
2428
 
1972
- // Pass 4: Transparent
2429
+ // Pass 4: Ground (with reflections)
2430
+ if (this.groundHasReflections) {
2431
+ this.renderGround(pass)
2432
+ }
2433
+
2434
+ // Pass 5: Transparent
1973
2435
  pass.setPipeline(this.modelPipeline)
1974
2436
  for (const draw of this.drawCalls) {
1975
2437
  if (draw.type === "transparent" && this.shouldRenderDrawCall(draw)) {
@@ -2127,7 +2589,12 @@ export class Engine {
2127
2589
  this.skinMatricesVersion++
2128
2590
  }
2129
2591
 
2130
- private drawOutlines(pass: GPURenderPassEncoder, transparent: boolean) {
2592
+ private drawOutlines(pass: GPURenderPassEncoder, transparent: boolean, useReflectionPipeline: boolean = false) {
2593
+ if (useReflectionPipeline) {
2594
+ // Skip outlines for reflections - not critical for the effect
2595
+ return
2596
+ }
2597
+
2131
2598
  pass.setPipeline(this.outlinePipeline)
2132
2599
  const outlineType: DrawCallType = transparent ? "transparent-outline" : "opaque-outline"
2133
2600
  for (const draw of this.drawCalls) {
@@ -2162,4 +2629,51 @@ export class Engine {
2162
2629
  this.lastFpsUpdate = now
2163
2630
  }
2164
2631
  }
2632
+
2633
+ private createMirrorMatrix(planeNormal: Vec3, planeDistance: number): Mat4 {
2634
+ // Create reflection matrix across a plane
2635
+ const n = planeNormal.normalize()
2636
+
2637
+ return new Mat4(
2638
+ new Float32Array([
2639
+ 1 - 2 * n.x * n.x,
2640
+ -2 * n.x * n.y,
2641
+ -2 * n.x * n.z,
2642
+ 0,
2643
+ -2 * n.y * n.x,
2644
+ 1 - 2 * n.y * n.y,
2645
+ -2 * n.y * n.z,
2646
+ 0,
2647
+ -2 * n.z * n.x,
2648
+ -2 * n.z * n.y,
2649
+ 1 - 2 * n.z * n.z,
2650
+ 0,
2651
+ -2 * planeDistance * n.x,
2652
+ -2 * planeDistance * n.y,
2653
+ -2 * planeDistance * n.z,
2654
+ 1,
2655
+ ])
2656
+ )
2657
+ }
2658
+
2659
+ private writeMirrorTransformedSkinMatrices(mirrorMatrix: Mat4) {
2660
+ if (!this.currentModel || !this.skinMatrixBuffer) return
2661
+
2662
+ const originalMatrices = this.currentModel.getSkinMatrices()
2663
+ const transformedMatrices = new Float32Array(originalMatrices.length)
2664
+
2665
+ for (let i = 0; i < originalMatrices.length; i += 16) {
2666
+ const boneMatrixValues = new Float32Array(16)
2667
+ for (let j = 0; j < 16; j++) {
2668
+ boneMatrixValues[j] = originalMatrices[i + j]
2669
+ }
2670
+ const boneMatrix = new Mat4(boneMatrixValues)
2671
+ const transformed = mirrorMatrix.multiply(boneMatrix)
2672
+ for (let j = 0; j < 16; j++) {
2673
+ transformedMatrices[i + j] = transformed.values[j]
2674
+ }
2675
+ }
2676
+
2677
+ this.device.queue.writeBuffer(this.skinMatrixBuffer, 0, transformedMatrices)
2678
+ }
2165
2679
  }