reze-engine 0.11.1 → 0.11.3
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 +5 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +72 -425
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/shaders/body.d.ts +1 -1
- package/dist/shaders/body.d.ts.map +1 -1
- package/dist/shaders/body.js +27 -60
- package/dist/shaders/cloth_rough.d.ts +1 -1
- package/dist/shaders/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/cloth_rough.js +4 -16
- package/dist/shaders/cloth_smooth.d.ts +1 -1
- package/dist/shaders/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/cloth_smooth.js +5 -17
- package/dist/shaders/default.d.ts +1 -1
- package/dist/shaders/default.d.ts.map +1 -1
- package/dist/shaders/default.js +20 -34
- package/dist/shaders/dfg_lut.d.ts +1 -1
- package/dist/shaders/dfg_lut.d.ts.map +1 -1
- package/dist/shaders/dfg_lut.js +1 -1
- package/dist/shaders/eye.d.ts +1 -1
- package/dist/shaders/eye.d.ts.map +1 -1
- package/dist/shaders/eye.js +22 -35
- package/dist/shaders/face.d.ts +1 -1
- package/dist/shaders/face.d.ts.map +1 -1
- package/dist/shaders/face.js +21 -57
- package/dist/shaders/hair.d.ts +1 -1
- package/dist/shaders/hair.d.ts.map +1 -1
- package/dist/shaders/hair.js +7 -27
- package/dist/shaders/materials/body.d.ts +2 -0
- package/dist/shaders/materials/body.d.ts.map +1 -0
- package/dist/shaders/materials/body.js +199 -0
- package/dist/shaders/materials/cloth_rough.d.ts +2 -0
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -0
- package/dist/shaders/materials/cloth_rough.js +178 -0
- package/dist/shaders/materials/cloth_smooth.d.ts +2 -0
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -0
- package/dist/shaders/materials/cloth_smooth.js +174 -0
- package/dist/shaders/materials/default.d.ts +2 -0
- package/dist/shaders/materials/default.d.ts.map +1 -0
- package/dist/shaders/materials/default.js +171 -0
- package/dist/shaders/materials/eye.d.ts +2 -0
- package/dist/shaders/materials/eye.d.ts.map +1 -0
- package/dist/shaders/materials/eye.js +146 -0
- package/dist/shaders/materials/face.d.ts +2 -0
- package/dist/shaders/materials/face.d.ts.map +1 -0
- package/dist/shaders/materials/face.js +199 -0
- package/dist/shaders/materials/hair.d.ts +2 -0
- package/dist/shaders/materials/hair.d.ts.map +1 -0
- package/dist/shaders/materials/hair.js +176 -0
- package/dist/shaders/materials/metal.d.ts +2 -0
- package/dist/shaders/materials/metal.d.ts.map +1 -0
- package/dist/shaders/materials/metal.js +183 -0
- package/dist/shaders/materials/nodes.d.ts +2 -0
- package/dist/shaders/materials/nodes.d.ts.map +1 -0
- package/{src/shaders/nodes.ts → dist/shaders/materials/nodes.js} +32 -16
- package/dist/shaders/materials/stockings.d.ts +2 -0
- package/dist/shaders/materials/stockings.d.ts.map +1 -0
- package/dist/shaders/materials/stockings.js +244 -0
- package/dist/shaders/metal.d.ts +1 -1
- package/dist/shaders/metal.d.ts.map +1 -1
- package/dist/shaders/metal.js +4 -17
- package/dist/shaders/nodes.d.ts +1 -1
- package/dist/shaders/nodes.d.ts.map +1 -1
- package/dist/shaders/nodes.js +0 -9
- package/dist/shaders/passes/bloom.d.ts +4 -0
- package/dist/shaders/passes/bloom.d.ts.map +1 -0
- package/dist/shaders/passes/bloom.js +117 -0
- package/dist/shaders/passes/composite.d.ts +2 -0
- package/dist/shaders/passes/composite.d.ts.map +1 -0
- package/dist/shaders/passes/composite.js +61 -0
- package/dist/shaders/passes/ground.d.ts +2 -0
- package/dist/shaders/passes/ground.d.ts.map +1 -0
- package/dist/shaders/passes/ground.js +93 -0
- package/dist/shaders/passes/mipmap.d.ts +2 -0
- package/dist/shaders/passes/mipmap.d.ts.map +1 -0
- package/dist/shaders/passes/mipmap.js +16 -0
- package/dist/shaders/passes/outline.d.ts +2 -0
- package/dist/shaders/passes/outline.d.ts.map +1 -0
- package/dist/shaders/passes/outline.js +83 -0
- package/dist/shaders/passes/pick.d.ts +2 -0
- package/dist/shaders/passes/pick.d.ts.map +1 -0
- package/dist/shaders/passes/pick.js +39 -0
- package/dist/shaders/passes/shadow.d.ts +2 -0
- package/dist/shaders/passes/shadow.d.ts.map +1 -0
- package/dist/shaders/passes/shadow.js +16 -0
- package/dist/shaders/stockings.d.ts +1 -1
- package/dist/shaders/stockings.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/engine.ts +112 -449
- package/src/index.ts +3 -2
- package/src/shaders/dfg_lut.ts +1 -1
- package/src/shaders/{body.ts → materials/body.ts} +27 -60
- package/src/shaders/{cloth_rough.ts → materials/cloth_rough.ts} +4 -16
- package/src/shaders/{cloth_smooth.ts → materials/cloth_smooth.ts} +5 -17
- package/src/shaders/{default.ts → materials/default.ts} +21 -34
- package/src/shaders/{eye.ts → materials/eye.ts} +23 -35
- package/src/shaders/{face.ts → materials/face.ts} +21 -57
- package/src/shaders/{hair.ts → materials/hair.ts} +7 -27
- package/src/shaders/{metal.ts → materials/metal.ts} +15 -19
- package/src/shaders/materials/nodes.ts +483 -0
- package/src/shaders/passes/bloom.ts +121 -0
- package/src/shaders/passes/composite.ts +62 -0
- package/src/shaders/passes/ground.ts +94 -0
- package/src/shaders/passes/mipmap.ts +17 -0
- package/src/shaders/passes/outline.ts +84 -0
- package/src/shaders/passes/pick.ts +40 -0
- package/src/shaders/passes/shadow.ts +17 -0
- package/src/shaders/classify.ts +0 -25
- /package/src/shaders/{stockings.ts → materials/stockings.ts} +0 -0
package/dist/engine.js
CHANGED
|
@@ -3,18 +3,33 @@ import { Mat4, Vec3 } from "./math";
|
|
|
3
3
|
import { PmxLoader } from "./pmx-loader";
|
|
4
4
|
import { Physics } from "./physics";
|
|
5
5
|
import { createFetchAssetReader, createFileMapAssetReader, deriveBasePathFromPmxPath, fileListToMap, findFirstPmxFileInList, joinAssetPath, normalizeAssetPath, } from "./asset-reader";
|
|
6
|
-
import { DEFAULT_SHADER_WGSL } from "./shaders/default";
|
|
6
|
+
import { DEFAULT_SHADER_WGSL } from "./shaders/materials/default";
|
|
7
|
+
import { FACE_SHADER_WGSL } from "./shaders/materials/face";
|
|
8
|
+
import { HAIR_SHADER_WGSL } from "./shaders/materials/hair";
|
|
9
|
+
import { CLOTH_SMOOTH_SHADER_WGSL } from "./shaders/materials/cloth_smooth";
|
|
10
|
+
import { CLOTH_ROUGH_SHADER_WGSL } from "./shaders/materials/cloth_rough";
|
|
11
|
+
import { METAL_SHADER_WGSL } from "./shaders/materials/metal";
|
|
12
|
+
import { BODY_SHADER_WGSL } from "./shaders/materials/body";
|
|
13
|
+
import { EYE_SHADER_WGSL } from "./shaders/materials/eye";
|
|
14
|
+
import { STOCKINGS_SHADER_WGSL } from "./shaders/materials/stockings";
|
|
7
15
|
import { BRDF_LUT_SIZE, BRDF_LUT_BAKE_WGSL } from "./shaders/dfg_lut";
|
|
8
16
|
import { LTC_MAG_LUT_SIZE, LTC_MAG_LUT_DATA } from "./shaders/ltc_mag_lut";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
import { SHADOW_DEPTH_SHADER_WGSL } from "./shaders/passes/shadow";
|
|
18
|
+
import { GROUND_SHADOW_SHADER_WGSL } from "./shaders/passes/ground";
|
|
19
|
+
import { OUTLINE_SHADER_WGSL } from "./shaders/passes/outline";
|
|
20
|
+
import { BLOOM_BLIT_SHADER_WGSL, BLOOM_DOWNSAMPLE_SHADER_WGSL, BLOOM_UPSAMPLE_SHADER_WGSL, } from "./shaders/passes/bloom";
|
|
21
|
+
import { COMPOSITE_SHADER_WGSL } from "./shaders/passes/composite";
|
|
22
|
+
import { PICK_SHADER_WGSL } from "./shaders/passes/pick";
|
|
23
|
+
import { MIPMAP_BLIT_SHADER_WGSL } from "./shaders/passes/mipmap";
|
|
24
|
+
function resolvePreset(materialName, map) {
|
|
25
|
+
if (!map)
|
|
26
|
+
return "default";
|
|
27
|
+
for (const [preset, names] of Object.entries(map)) {
|
|
28
|
+
if (names && names.includes(materialName))
|
|
29
|
+
return preset;
|
|
30
|
+
}
|
|
31
|
+
return "default";
|
|
32
|
+
}
|
|
18
33
|
export const DEFAULT_BLOOM_OPTIONS = {
|
|
19
34
|
enabled: true,
|
|
20
35
|
threshold: 0.5,
|
|
@@ -24,8 +39,10 @@ export const DEFAULT_BLOOM_OPTIONS = {
|
|
|
24
39
|
intensity: 0.05,
|
|
25
40
|
clamp: 0.0,
|
|
26
41
|
};
|
|
42
|
+
// Matches the reference Blender project: Filmic view, Medium High Contrast look,
|
|
43
|
+
// exposure 0.3, gamma 1.0, sRGB display, no curves.
|
|
27
44
|
export const DEFAULT_VIEW_TRANSFORM = {
|
|
28
|
-
exposure:
|
|
45
|
+
exposure: 0.6,
|
|
29
46
|
gamma: 1.0,
|
|
30
47
|
look: "medium_high_contrast",
|
|
31
48
|
};
|
|
@@ -48,7 +65,7 @@ export class Engine {
|
|
|
48
65
|
this.lightData = new Float32Array(64);
|
|
49
66
|
this.lightCount = 0;
|
|
50
67
|
this.resizeObserver = null;
|
|
51
|
-
// [exposure,
|
|
68
|
+
// [exposure, invGamma, _, _, bloomTint.x, bloomTint.y, bloomTint.z, bloomIntensity]
|
|
52
69
|
this.compositeUniformData = new Float32Array(8);
|
|
53
70
|
this.bloomBlitUniformData = new Float32Array(4);
|
|
54
71
|
this.bloomUpsampleUniformData = new Float32Array(4);
|
|
@@ -198,7 +215,10 @@ export class Engine {
|
|
|
198
215
|
const effIntensity = b.enabled ? b.intensity : 0.0;
|
|
199
216
|
const u = this.compositeUniformData;
|
|
200
217
|
u[0] = v.exposure;
|
|
201
|
-
|
|
218
|
+
// Store 1/gamma so the shader avoids a per-pixel divide. Safari's Metal
|
|
219
|
+
// compiler doesn't fold `pow(x, 1/g)` into identity when g=1, so also emit
|
|
220
|
+
// a uniform branch that skips the pow entirely in the common case.
|
|
221
|
+
u[1] = 1.0 / Math.max(v.gamma, 1e-4);
|
|
202
222
|
u[2] = 0.0;
|
|
203
223
|
u[3] = 0.0;
|
|
204
224
|
u[4] = b.color.x;
|
|
@@ -236,9 +256,12 @@ export class Engine {
|
|
|
236
256
|
writeBloomUniforms() {
|
|
237
257
|
const b = this.bloomSettings;
|
|
238
258
|
const bu = this.bloomBlitUniformData;
|
|
239
|
-
// EEVEE prefilter: threshold,
|
|
259
|
+
// EEVEE prefilter: threshold, knee_half, clamp (0 → disabled), _unused
|
|
260
|
+
// Blender halves the knee before passing to the shader (eevee_bloom.c: knee * 0.5f).
|
|
261
|
+
// The blit shader's quadratic soft-knee curve uses knee_half as the offset from threshold,
|
|
262
|
+
// so the soft ramp spans [threshold - knee/2 .. threshold + knee/2] — NOT [threshold - knee .. threshold + knee].
|
|
240
263
|
bu[0] = b.threshold;
|
|
241
|
-
bu[1] = b.knee;
|
|
264
|
+
bu[1] = b.knee * 0.5;
|
|
242
265
|
bu[2] = b.clamp;
|
|
243
266
|
bu[3] = 0.0;
|
|
244
267
|
this.device.queue.writeBuffer(this.bloomBlitUniformBuffer, 0, bu);
|
|
@@ -635,21 +658,7 @@ export class Engine {
|
|
|
635
658
|
});
|
|
636
659
|
const shadowShader = this.device.createShaderModule({
|
|
637
660
|
label: "shadow depth",
|
|
638
|
-
code:
|
|
639
|
-
struct LightVP { viewProj: mat4x4f, };
|
|
640
|
-
@group(0) @binding(0) var<uniform> lp: LightVP;
|
|
641
|
-
@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;
|
|
642
|
-
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,
|
|
643
|
-
@location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) -> @builtin(position) vec4f {
|
|
644
|
-
let pos4 = vec4f(position, 1.0);
|
|
645
|
-
let ws = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
646
|
-
let inv = select(1.0, 1.0 / ws, ws > 0.0001);
|
|
647
|
-
let nw = select(vec4f(1.0,0.0,0.0,0.0), weights0 * inv, ws > 0.0001);
|
|
648
|
-
var sp = vec4f(0.0);
|
|
649
|
-
for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }
|
|
650
|
-
return lp.viewProj * vec4f(sp.xyz, 1.0);
|
|
651
|
-
}
|
|
652
|
-
`,
|
|
661
|
+
code: SHADOW_DEPTH_SHADER_WGSL,
|
|
653
662
|
});
|
|
654
663
|
this.shadowDepthPipeline = this.device.createRenderPipeline({
|
|
655
664
|
label: "shadow depth pipeline",
|
|
@@ -706,99 +715,7 @@ export class Engine {
|
|
|
706
715
|
});
|
|
707
716
|
const groundShadowShader = this.device.createShaderModule({
|
|
708
717
|
label: "ground shadow",
|
|
709
|
-
code:
|
|
710
|
-
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
711
|
-
struct Light { direction: vec4f, color: vec4f, };
|
|
712
|
-
struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
|
|
713
|
-
struct GroundShadowMat {
|
|
714
|
-
diffuseColor: vec3f, fadeStart: f32,
|
|
715
|
-
fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,
|
|
716
|
-
gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, _pad: f32,
|
|
717
|
-
gridLineColor: vec3f, _pad2: f32,
|
|
718
|
-
};
|
|
719
|
-
struct LightVP { viewProj: mat4x4f, };
|
|
720
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
721
|
-
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
722
|
-
@group(0) @binding(2) var shadowMap: texture_depth_2d;
|
|
723
|
-
@group(0) @binding(3) var shadowSampler: sampler_comparison;
|
|
724
|
-
@group(0) @binding(4) var<uniform> material: GroundShadowMat;
|
|
725
|
-
@group(0) @binding(5) var<uniform> lightVP: LightVP;
|
|
726
|
-
|
|
727
|
-
// Hash-based noise for frosted/matte surface
|
|
728
|
-
fn hash2(p: vec2f) -> f32 {
|
|
729
|
-
var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
|
|
730
|
-
p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));
|
|
731
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
732
|
-
}
|
|
733
|
-
fn valueNoise(p: vec2f) -> f32 {
|
|
734
|
-
let i = floor(p);
|
|
735
|
-
let f = fract(p);
|
|
736
|
-
let u = f * f * (3.0 - 2.0 * f);
|
|
737
|
-
return mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x),
|
|
738
|
-
mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);
|
|
739
|
-
}
|
|
740
|
-
fn fbmNoise(p: vec2f) -> f32 {
|
|
741
|
-
var v = 0.0;
|
|
742
|
-
var a = 0.5;
|
|
743
|
-
var pp = p;
|
|
744
|
-
for (var i = 0; i < 4; i++) {
|
|
745
|
-
v += a * valueNoise(pp);
|
|
746
|
-
pp *= 2.0;
|
|
747
|
-
a *= 0.5;
|
|
748
|
-
}
|
|
749
|
-
return v;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
struct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };
|
|
753
|
-
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {
|
|
754
|
-
var o: VO; o.worldPos = position; o.normal = normal;
|
|
755
|
-
o.position = camera.projection * camera.view * vec4f(position, 1.0); return o;
|
|
756
|
-
}
|
|
757
|
-
struct FSOut { @location(0) color: vec4f, @location(1) mask: f32 };
|
|
758
|
-
@fragment fn fs(i: VO) -> FSOut {
|
|
759
|
-
let n = normalize(i.normal);
|
|
760
|
-
let centerDist = length(i.worldPos.xz);
|
|
761
|
-
let edgeFade = 1.0 - smoothstep(0.0, 1.0, clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0));
|
|
762
|
-
|
|
763
|
-
// Shadow sampling
|
|
764
|
-
let lclip = lightVP.viewProj * vec4f(i.worldPos, 1.0);
|
|
765
|
-
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
766
|
-
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
767
|
-
let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));
|
|
768
|
-
let st = material.pcfTexel;
|
|
769
|
-
let compareZ = ndc.z - 0.0035;
|
|
770
|
-
var vis = 0.0;
|
|
771
|
-
for (var y = -2; y <= 2; y++) {
|
|
772
|
-
for (var x = -2; x <= 2; x++) {
|
|
773
|
-
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(f32(x), f32(y)) * st, compareZ);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
vis *= 0.04;
|
|
777
|
-
|
|
778
|
-
// Frosted/matte micro-texture (磨砂)
|
|
779
|
-
let noiseVal = fbmNoise(i.worldPos.xz * 3.0);
|
|
780
|
-
let noiseTint = 1.0 + (noiseVal - 0.5) * material.noiseStrength;
|
|
781
|
-
|
|
782
|
-
// Grid lines — anti-aliased via screen-space derivatives
|
|
783
|
-
let gp = i.worldPos.xz / material.gridSpacing;
|
|
784
|
-
let gridFrac = abs(fract(gp - 0.5) - 0.5);
|
|
785
|
-
let gridDeriv = fwidth(gp);
|
|
786
|
-
let halfLine = material.gridLineWidth * 0.5;
|
|
787
|
-
let gridLine = 1.0 - min(
|
|
788
|
-
smoothstep(halfLine - gridDeriv.x, halfLine + gridDeriv.x, gridFrac.x),
|
|
789
|
-
smoothstep(halfLine - gridDeriv.y, halfLine + gridDeriv.y, gridFrac.y)
|
|
790
|
-
);
|
|
791
|
-
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);
|
|
792
|
-
let dark = (1.0 - vis) * material.shadowStrength;
|
|
793
|
-
var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);
|
|
794
|
-
baseColor *= noiseTint;
|
|
795
|
-
let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);
|
|
796
|
-
var out: FSOut;
|
|
797
|
-
out.color = vec4f(finalColor * edgeFade, edgeFade);
|
|
798
|
-
out.mask = 0.0;
|
|
799
|
-
return out;
|
|
800
|
-
}
|
|
801
|
-
`,
|
|
718
|
+
code: GROUND_SHADOW_SHADER_WGSL,
|
|
802
719
|
});
|
|
803
720
|
this.groundShadowPipeline = this.createRenderPipeline({
|
|
804
721
|
label: "ground shadow pipeline",
|
|
@@ -838,90 +755,7 @@ export class Engine {
|
|
|
838
755
|
});
|
|
839
756
|
const outlineShaderModule = this.device.createShaderModule({
|
|
840
757
|
label: "outline shaders",
|
|
841
|
-
code:
|
|
842
|
-
struct CameraUniforms {
|
|
843
|
-
view: mat4x4f,
|
|
844
|
-
projection: mat4x4f,
|
|
845
|
-
viewPos: vec3f,
|
|
846
|
-
_padding: f32,
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
struct MaterialUniforms {
|
|
850
|
-
edgeColor: vec4f,
|
|
851
|
-
edgeSize: f32,
|
|
852
|
-
_padding1: f32,
|
|
853
|
-
_padding2: f32,
|
|
854
|
-
_padding3: f32,
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
// group 0: per-frame
|
|
858
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
859
|
-
// group 1: per-instance
|
|
860
|
-
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
861
|
-
// group 2: per-material
|
|
862
|
-
@group(2) @binding(0) var<uniform> material: MaterialUniforms;
|
|
863
|
-
|
|
864
|
-
struct VertexOutput {
|
|
865
|
-
@builtin(position) position: vec4f,
|
|
866
|
-
};
|
|
867
|
-
|
|
868
|
-
@vertex fn vs(
|
|
869
|
-
@location(0) position: vec3f,
|
|
870
|
-
@location(1) normal: vec3f,
|
|
871
|
-
@location(3) joints0: vec4<u32>,
|
|
872
|
-
@location(4) weights0: vec4<f32>
|
|
873
|
-
) -> VertexOutput {
|
|
874
|
-
var output: VertexOutput;
|
|
875
|
-
let pos4 = vec4f(position, 1.0);
|
|
876
|
-
|
|
877
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
878
|
-
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
879
|
-
let normalizedWeights = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
880
|
-
|
|
881
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
882
|
-
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
883
|
-
for (var i = 0u; i < 4u; i++) {
|
|
884
|
-
let j = joints0[i];
|
|
885
|
-
let w = normalizedWeights[i];
|
|
886
|
-
let m = skinMats[j];
|
|
887
|
-
skinnedPos += (m * pos4) * w;
|
|
888
|
-
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
889
|
-
skinnedNrm += (r3 * normal) * w;
|
|
890
|
-
}
|
|
891
|
-
let worldPos = skinnedPos.xyz;
|
|
892
|
-
let worldNormal = normalize(skinnedNrm);
|
|
893
|
-
|
|
894
|
-
// Screen-space outline extrusion — MMD-style pixel-stable edge line.
|
|
895
|
-
// 1. Project position and normal-as-direction to clip space.
|
|
896
|
-
// 2. Normalize the 2D clip-space normal, aspect-compensated so "one pixel horizontally"
|
|
897
|
-
// matches "one pixel vertically" (otherwise wide viewports squash the outline in X).
|
|
898
|
-
// 3. Offset clip-space xy by (normal * edgeSize * edgeScale), then multiply by w
|
|
899
|
-
// so the perspective divide cancels out → offset stays constant in NDC regardless
|
|
900
|
-
// of depth, matching how MMD / babylon-mmd style outlines look identical when zooming.
|
|
901
|
-
// 4. edgeScale is in NDC-y units per PMX edgeSize. ≈ 0.006 gives ~3px at 1080p; it's
|
|
902
|
-
// tied to viewport HEIGHT so resizing the window keeps pixel thickness stable.
|
|
903
|
-
let viewProj = camera.projection * camera.view;
|
|
904
|
-
let clipPos = viewProj * vec4f(worldPos, 1.0);
|
|
905
|
-
let clipNormal = (viewProj * vec4f(worldNormal, 0.0)).xy;
|
|
906
|
-
// projection is column-major: proj[0][0] = 1/(aspect·tan(fov/2)), proj[1][1] = 1/tan(fov/2).
|
|
907
|
-
// Ratio proj[1][1]/proj[0][0] recovers the viewport aspect (width/height).
|
|
908
|
-
let aspect = camera.projection[1][1] / camera.projection[0][0];
|
|
909
|
-
let pixelDir = normalize(vec2f(clipNormal.x * aspect, clipNormal.y));
|
|
910
|
-
let ndcDir = vec2f(pixelDir.x / aspect, pixelDir.y);
|
|
911
|
-
let edgeScale = 0.0016;
|
|
912
|
-
let offset = ndcDir * material.edgeSize * edgeScale * clipPos.w;
|
|
913
|
-
output.position = vec4f(clipPos.xy + offset, clipPos.z, clipPos.w);
|
|
914
|
-
return output;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
struct FSOut { @location(0) color: vec4f, @location(1) mask: f32 };
|
|
918
|
-
@fragment fn fs() -> FSOut {
|
|
919
|
-
var out: FSOut;
|
|
920
|
-
out.color = material.edgeColor;
|
|
921
|
-
out.mask = 1.0;
|
|
922
|
-
return out;
|
|
923
|
-
}
|
|
924
|
-
`,
|
|
758
|
+
code: OUTLINE_SHADER_WGSL,
|
|
925
759
|
});
|
|
926
760
|
this.outlinePipeline = this.createRenderPipeline({
|
|
927
761
|
label: "outline pipeline",
|
|
@@ -982,130 +816,22 @@ export class Engine {
|
|
|
982
816
|
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
983
817
|
],
|
|
984
818
|
});
|
|
985
|
-
const bloomFullscreenVs = /* wgsl */ `
|
|
986
|
-
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
987
|
-
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
988
|
-
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
989
|
-
return vec4f(x, y, 0.0, 1.0);
|
|
990
|
-
}
|
|
991
|
-
`;
|
|
992
|
-
// Blit: full-res HDR → half-res. Karis 4-tap firefly average + EEVEE quadratic knee threshold + clamp.
|
|
993
819
|
const bloomBlitShader = this.device.createShaderModule({
|
|
994
820
|
label: "bloom blit (Karis prefilter)",
|
|
995
|
-
code:
|
|
996
|
-
@group(0) @binding(0) var hdrTex: texture_2d<f32>;
|
|
997
|
-
@group(0) @binding(1) var<uniform> prefilter: vec4<f32>; // threshold, knee, clamp, _unused
|
|
998
|
-
@group(0) @binding(2) var maskTex: texture_2d<f32>;
|
|
999
|
-
|
|
1000
|
-
fn luminance(c: vec3f) -> f32 {
|
|
1001
|
-
return dot(max(c, vec3f(0.0)), vec3f(0.2126, 0.7152, 0.0722));
|
|
1002
|
-
}
|
|
1003
|
-
fn fetch(c: vec2<i32>, clampV: f32) -> vec3f {
|
|
1004
|
-
let d = vec2<i32>(textureDimensions(hdrTex));
|
|
1005
|
-
let cc = clamp(c, vec2<i32>(0), d - vec2<i32>(1));
|
|
1006
|
-
let s = textureLoad(hdrTex, cc, 0);
|
|
1007
|
-
// Scene pass uses src-alpha blend with clear alpha 0 → premultiplied. Unpremultiply.
|
|
1008
|
-
let rgb = max(s.rgb / max(s.a, 1e-6), vec3f(0.0));
|
|
1009
|
-
// Bloom mask: MRT r8unorm written by material shaders (1.0 = bloom, 0.0 = skip).
|
|
1010
|
-
let mask = textureLoad(maskTex, cc, 0).r;
|
|
1011
|
-
let masked = rgb * mask;
|
|
1012
|
-
// Blender: clamp each tap BEFORE Karis average (eevee_bloom: color = min(clampIntensity, color)).
|
|
1013
|
-
return select(masked, min(masked, vec3f(clampV)), clampV > 0.0);
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
1017
|
-
let dst = vec2<i32>(p.xy - vec2f(0.5));
|
|
1018
|
-
let base = dst * 2;
|
|
1019
|
-
let clampV = prefilter.z;
|
|
1020
|
-
let a = fetch(base + vec2<i32>(0, 0), clampV);
|
|
1021
|
-
let b = fetch(base + vec2<i32>(1, 0), clampV);
|
|
1022
|
-
let c = fetch(base + vec2<i32>(0, 1), clampV);
|
|
1023
|
-
let d = fetch(base + vec2<i32>(1, 1), clampV);
|
|
1024
|
-
// Karis partial average: weight each tap by 1/(1+luma) — suppresses fireflies.
|
|
1025
|
-
let wa = 1.0 / (1.0 + luminance(a));
|
|
1026
|
-
let wb = 1.0 / (1.0 + luminance(b));
|
|
1027
|
-
let wc = 1.0 / (1.0 + luminance(c));
|
|
1028
|
-
let wd = 1.0 / (1.0 + luminance(d));
|
|
1029
|
-
let avg = (a * wa + b * wb + c * wc + d * wd) / max(wa + wb + wc + wd, 1e-6);
|
|
1030
|
-
// EEVEE quadratic threshold (brightness = max-channel, then soft-knee curve).
|
|
1031
|
-
let bright = max(avg.r, max(avg.g, avg.b));
|
|
1032
|
-
let soft = clamp(bright - prefilter.x + prefilter.y, 0.0, 2.0 * prefilter.y);
|
|
1033
|
-
let q = (soft * soft) / (4.0 * max(prefilter.y, 1e-4) + 1e-6);
|
|
1034
|
-
let contrib = max(q, bright - prefilter.x) / max(bright, 1e-4);
|
|
1035
|
-
return vec4f(max(avg * contrib, vec3f(0.0)), 1.0);
|
|
1036
|
-
}
|
|
1037
|
-
`,
|
|
821
|
+
code: BLOOM_BLIT_SHADER_WGSL,
|
|
1038
822
|
});
|
|
1039
|
-
// Downsample: Jimenez/COD 13-tap dual-box — 5 weighted 2×2 averages, rejects nyquist ringing.
|
|
1040
823
|
const bloomDownsampleShader = this.device.createShaderModule({
|
|
1041
824
|
label: "bloom downsample 13-tap",
|
|
1042
|
-
code:
|
|
1043
|
-
@group(0) @binding(0) var srcTex: texture_2d<f32>;
|
|
1044
|
-
@group(0) @binding(1) var srcSamp: sampler;
|
|
1045
|
-
|
|
1046
|
-
fn samp(uv: vec2f, off: vec2f) -> vec3f {
|
|
1047
|
-
return textureSampleLevel(srcTex, srcSamp, uv + off, 0.0).rgb;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
1051
|
-
let srcDims = vec2f(textureDimensions(srcTex));
|
|
1052
|
-
let t = 1.0 / srcDims;
|
|
1053
|
-
// fragCoord.xy reports pixel centers (e.g. 0.5,0.5 for first pixel) — divide by dst dims directly.
|
|
1054
|
-
let dstDims = srcDims * 0.5;
|
|
1055
|
-
let uv = p.xy / max(dstDims, vec2f(1.0));
|
|
1056
|
-
let A = samp(uv, t * vec2f(-2.0, -2.0));
|
|
1057
|
-
let B = samp(uv, t * vec2f( 0.0, -2.0));
|
|
1058
|
-
let C = samp(uv, t * vec2f( 2.0, -2.0));
|
|
1059
|
-
let D = samp(uv, t * vec2f(-1.0, -1.0));
|
|
1060
|
-
let E = samp(uv, t * vec2f( 1.0, -1.0));
|
|
1061
|
-
let F = samp(uv, t * vec2f(-2.0, 0.0));
|
|
1062
|
-
let G = samp(uv, t * vec2f( 0.0, 0.0));
|
|
1063
|
-
let H = samp(uv, t * vec2f( 2.0, 0.0));
|
|
1064
|
-
let I = samp(uv, t * vec2f(-1.0, 1.0));
|
|
1065
|
-
let J = samp(uv, t * vec2f( 1.0, 1.0));
|
|
1066
|
-
let K = samp(uv, t * vec2f(-2.0, 2.0));
|
|
1067
|
-
let L = samp(uv, t * vec2f( 0.0, 2.0));
|
|
1068
|
-
let M = samp(uv, t * vec2f( 2.0, 2.0));
|
|
1069
|
-
var o = (D + E + I + J) * (0.5 / 4.0);
|
|
1070
|
-
o = o + (A + B + G + F) * (0.125 / 4.0);
|
|
1071
|
-
o = o + (B + C + H + G) * (0.125 / 4.0);
|
|
1072
|
-
o = o + (F + G + L + K) * (0.125 / 4.0);
|
|
1073
|
-
o = o + (G + H + M + L) * (0.125 / 4.0);
|
|
1074
|
-
return vec4f(o, 1.0);
|
|
1075
|
-
}
|
|
1076
|
-
`,
|
|
825
|
+
code: BLOOM_DOWNSAMPLE_SHADER_WGSL,
|
|
1077
826
|
});
|
|
1078
|
-
// Upsample: 9-tap tent, progressively added to matching downsample mip. Blender radius = sample scale.
|
|
1079
827
|
const bloomUpsampleShader = this.device.createShaderModule({
|
|
1080
828
|
label: "bloom upsample 9-tap tent",
|
|
1081
|
-
code:
|
|
1082
|
-
@group(0) @binding(0) var srcTex: texture_2d<f32>; // coarser accumulator
|
|
1083
|
-
@group(0) @binding(1) var baseTex: texture_2d<f32>; // matching downsample mip
|
|
1084
|
-
@group(0) @binding(2) var srcSamp: sampler;
|
|
1085
|
-
@group(0) @binding(3) var<uniform> upU: vec4<f32>; // sampleScale, _, _, _
|
|
1086
|
-
|
|
1087
|
-
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
1088
|
-
let srcDims = vec2f(textureDimensions(srcTex));
|
|
1089
|
-
let baseDims = vec2f(textureDimensions(baseTex));
|
|
1090
|
-
let uv = p.xy / max(baseDims, vec2f(1.0));
|
|
1091
|
-
let t = upU.x / srcDims;
|
|
1092
|
-
var o = textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, -1.0), 0.0).rgb * 1.0;
|
|
1093
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, -1.0), 0.0).rgb * 2.0;
|
|
1094
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, -1.0), 0.0).rgb * 1.0;
|
|
1095
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, 0.0), 0.0).rgb * 2.0;
|
|
1096
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, 0.0), 0.0).rgb * 4.0;
|
|
1097
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, 0.0), 0.0).rgb * 2.0;
|
|
1098
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, 1.0), 0.0).rgb * 1.0;
|
|
1099
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, 1.0), 0.0).rgb * 2.0;
|
|
1100
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, 1.0), 0.0).rgb * 1.0;
|
|
1101
|
-
o = o * (1.0 / 16.0);
|
|
1102
|
-
let base = textureSampleLevel(baseTex, srcSamp, uv, 0.0).rgb;
|
|
1103
|
-
return vec4f(o + base, 1.0);
|
|
1104
|
-
}
|
|
1105
|
-
`,
|
|
829
|
+
code: BLOOM_UPSAMPLE_SHADER_WGSL,
|
|
1106
830
|
});
|
|
1107
831
|
const bloomBlitLayout = this.device.createPipelineLayout({ bindGroupLayouts: [this.bloomBlitBindGroupLayout] });
|
|
1108
|
-
const bloomDownLayout = this.device.createPipelineLayout({
|
|
832
|
+
const bloomDownLayout = this.device.createPipelineLayout({
|
|
833
|
+
bindGroupLayouts: [this.bloomDownsampleBindGroupLayout],
|
|
834
|
+
});
|
|
1109
835
|
const bloomUpLayout = this.device.createPipelineLayout({ bindGroupLayouts: [this.bloomUpsampleBindGroupLayout] });
|
|
1110
836
|
this.bloomBlitPipeline = this.device.createRenderPipeline({
|
|
1111
837
|
label: "bloom blit pipeline",
|
|
@@ -1147,61 +873,25 @@ export class Engine {
|
|
|
1147
873
|
});
|
|
1148
874
|
const compositeShader = this.device.createShaderModule({
|
|
1149
875
|
label: "composite shader",
|
|
1150
|
-
code:
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 2>;
|
|
1155
|
-
// viewU[0] = (exposure, gamma, _, _); viewU[1] = (tint.rgb, intensity)
|
|
1156
|
-
|
|
1157
|
-
fn filmic(x: f32) -> f32 {
|
|
1158
|
-
var lut = array<f32, 14>(
|
|
1159
|
-
0.0067, 0.0141, 0.0272, 0.0499, 0.0885, 0.1512, 0.2462,
|
|
1160
|
-
0.3753, 0.5273, 0.6776, 0.8031, 0.8929, 0.9495, 0.9814
|
|
1161
|
-
);
|
|
1162
|
-
let t = clamp(log2(max(x, 1e-10)) + 10.0, 0.0, 13.0);
|
|
1163
|
-
let i = u32(t);
|
|
1164
|
-
let j = min(i + 1u, 13u);
|
|
1165
|
-
return mix(lut[i], lut[j], t - f32(i));
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
1169
|
-
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
1170
|
-
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
1171
|
-
return vec4f(x, y, 0.0, 1.0);
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
@fragment fn fs(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
|
|
1175
|
-
let hdr = textureLoad(hdrTex, vec2<i32>(fragCoord.xy), 0);
|
|
1176
|
-
let a = max(hdr.a, 1e-6);
|
|
1177
|
-
let straight = hdr.rgb / a;
|
|
1178
|
-
let fullSz = vec2f(textureDimensions(hdrTex));
|
|
1179
|
-
let bloomSz = vec2f(textureDimensions(bloomTex));
|
|
1180
|
-
// Bloom is at half-res (pyramid mip 0). Sampler interpolates back to full-res UVs.
|
|
1181
|
-
let bloomUv = (fragCoord.xy + vec2f(0.5)) / max(fullSz, vec2f(1.0));
|
|
1182
|
-
let tint = viewU[1].xyz;
|
|
1183
|
-
let intensity = viewU[1].w;
|
|
1184
|
-
let bloom = textureSampleLevel(bloomTex, bloomSamp, bloomUv, 0.0).rgb * tint * intensity;
|
|
1185
|
-
let combined = straight + bloom;
|
|
1186
|
-
let exposed = combined * exp2(viewU[0].x);
|
|
1187
|
-
let tm = vec3f(filmic(exposed.r), filmic(exposed.g), filmic(exposed.b));
|
|
1188
|
-
let g = max(viewU[0].y, 1e-4);
|
|
1189
|
-
let disp = pow(max(tm, vec3f(0.0)), vec3f(1.0 / g));
|
|
1190
|
-
return vec4f(disp * hdr.a, hdr.a);
|
|
1191
|
-
}
|
|
1192
|
-
`,
|
|
876
|
+
code: COMPOSITE_SHADER_WGSL,
|
|
877
|
+
});
|
|
878
|
+
const compositePipelineLayout = this.device.createPipelineLayout({
|
|
879
|
+
bindGroupLayouts: [this.compositeBindGroupLayout],
|
|
1193
880
|
});
|
|
1194
|
-
|
|
1195
|
-
label
|
|
1196
|
-
layout:
|
|
881
|
+
const makeCompositePipeline = (applyGamma, label) => this.device.createRenderPipeline({
|
|
882
|
+
label,
|
|
883
|
+
layout: compositePipelineLayout,
|
|
1197
884
|
vertex: { module: compositeShader, entryPoint: "vs" },
|
|
1198
885
|
fragment: {
|
|
1199
886
|
module: compositeShader,
|
|
1200
887
|
entryPoint: "fs",
|
|
888
|
+
constants: { APPLY_GAMMA: applyGamma ? 1 : 0 },
|
|
1201
889
|
targets: [{ format: this.presentationFormat }],
|
|
1202
890
|
},
|
|
1203
891
|
primitive: { topology: "triangle-list" },
|
|
1204
892
|
});
|
|
893
|
+
this.compositePipelineIdentity = makeCompositePipeline(false, "composite pipeline (gamma=1)");
|
|
894
|
+
this.compositePipelineGamma = makeCompositePipeline(true, "composite pipeline (gamma!=1)");
|
|
1205
895
|
this.bloomPassDescriptor = {
|
|
1206
896
|
label: "bloom pass",
|
|
1207
897
|
colorAttachments: [
|
|
@@ -1213,47 +903,9 @@ export class Engine {
|
|
|
1213
903
|
},
|
|
1214
904
|
],
|
|
1215
905
|
};
|
|
1216
|
-
// GPU picking: encode (modelIndex, materialIndex) as color
|
|
1217
906
|
const pickShaderModule = this.device.createShaderModule({
|
|
1218
907
|
label: "pick shader",
|
|
1219
|
-
code:
|
|
1220
|
-
struct CameraUniforms {
|
|
1221
|
-
view: mat4x4f,
|
|
1222
|
-
projection: mat4x4f,
|
|
1223
|
-
viewPos: vec3f,
|
|
1224
|
-
_padding: f32,
|
|
1225
|
-
};
|
|
1226
|
-
struct PickId {
|
|
1227
|
-
modelId: f32,
|
|
1228
|
-
materialId: f32,
|
|
1229
|
-
_p1: f32,
|
|
1230
|
-
_p2: f32,
|
|
1231
|
-
};
|
|
1232
|
-
|
|
1233
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
1234
|
-
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
1235
|
-
@group(2) @binding(0) var<uniform> pickId: PickId;
|
|
1236
|
-
|
|
1237
|
-
@vertex fn vs(
|
|
1238
|
-
@location(0) position: vec3f,
|
|
1239
|
-
@location(1) normal: vec3f,
|
|
1240
|
-
@location(2) uv: vec2f,
|
|
1241
|
-
@location(3) joints0: vec4<u32>,
|
|
1242
|
-
@location(4) weights0: vec4<f32>
|
|
1243
|
-
) -> @builtin(position) vec4f {
|
|
1244
|
-
let pos4 = vec4f(position, 1.0);
|
|
1245
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
1246
|
-
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
1247
|
-
let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
1248
|
-
var sp = vec4f(0.0);
|
|
1249
|
-
for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }
|
|
1250
|
-
return camera.projection * camera.view * vec4f(sp.xyz, 1.0);
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
@fragment fn fs() -> @location(0) vec4f {
|
|
1254
|
-
return vec4f(pickId.modelId / 255.0, pickId.materialId / 255.0, 0.0, 1.0);
|
|
1255
|
-
}
|
|
1256
|
-
`,
|
|
908
|
+
code: PICK_SHADER_WGSL,
|
|
1257
909
|
});
|
|
1258
910
|
this.pickPerFrameBindGroupLayout = this.device.createBindGroupLayout({
|
|
1259
911
|
label: "pick per-frame layout",
|
|
@@ -1387,19 +1039,23 @@ export class Engine {
|
|
|
1387
1039
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1388
1040
|
});
|
|
1389
1041
|
const depthTextureView = this.depthTexture.createView();
|
|
1042
|
+
// storeOp="discard" on MSAA views keeps per-sample data in Apple TBDR tile memory —
|
|
1043
|
+
// only the resolveTarget (hdrResolveTexture / maskResolveView) gets written to RAM.
|
|
1044
|
+
// With storeOp="store" Safari's Metal backend spills the full MS buffer every frame
|
|
1045
|
+
// (rgba16f × 4 samples on a 4K canvas ≈ 256 MB/frame of dead bandwidth).
|
|
1390
1046
|
const colorAttachment = {
|
|
1391
1047
|
view: this.multisampleTexture.createView(),
|
|
1392
1048
|
resolveTarget: this.hdrResolveTexture.createView(),
|
|
1393
1049
|
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
1394
1050
|
loadOp: "clear",
|
|
1395
|
-
storeOp: "
|
|
1051
|
+
storeOp: "discard",
|
|
1396
1052
|
};
|
|
1397
1053
|
const maskAttachment = {
|
|
1398
1054
|
view: this.multisampleMaskTexture.createView(),
|
|
1399
1055
|
resolveTarget: this.maskResolveView,
|
|
1400
1056
|
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
1401
1057
|
loadOp: "clear",
|
|
1402
|
-
storeOp: "
|
|
1058
|
+
storeOp: "discard",
|
|
1403
1059
|
};
|
|
1404
1060
|
this.renderPassDescriptor = {
|
|
1405
1061
|
label: "renderPass",
|
|
@@ -1408,7 +1064,8 @@ export class Engine {
|
|
|
1408
1064
|
view: depthTextureView,
|
|
1409
1065
|
depthClearValue: 1.0,
|
|
1410
1066
|
depthLoadOp: "clear",
|
|
1411
|
-
|
|
1067
|
+
// Main-pass depth is not sampled later (shadow uses its own map, composite is depthless).
|
|
1068
|
+
depthStoreOp: "discard",
|
|
1412
1069
|
stencilClearValue: 0,
|
|
1413
1070
|
stencilLoadOp: "clear",
|
|
1414
1071
|
stencilStoreOp: "discard",
|
|
@@ -2200,20 +1857,7 @@ export class Engine {
|
|
|
2200
1857
|
});
|
|
2201
1858
|
const module = this.device.createShaderModule({
|
|
2202
1859
|
label: "mipmap blit",
|
|
2203
|
-
code:
|
|
2204
|
-
@group(0) @binding(0) var src: texture_2d<f32>;
|
|
2205
|
-
@group(0) @binding(1) var samp: sampler;
|
|
2206
|
-
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
2207
|
-
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
2208
|
-
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
2209
|
-
return vec4f(x, y, 0.0, 1.0);
|
|
2210
|
-
}
|
|
2211
|
-
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
2212
|
-
let dstDims = vec2f(textureDimensions(src)) * 0.5;
|
|
2213
|
-
let uv = p.xy / max(dstDims, vec2f(1.0));
|
|
2214
|
-
return textureSampleLevel(src, samp, uv, 0.0);
|
|
2215
|
-
}
|
|
2216
|
-
`,
|
|
1860
|
+
code: MIPMAP_BLIT_SHADER_WGSL,
|
|
2217
1861
|
});
|
|
2218
1862
|
this.mipBlitPipeline = this.device.createRenderPipeline({
|
|
2219
1863
|
label: "mipmap blit pipeline",
|
|
@@ -2235,7 +1879,9 @@ export class Engine {
|
|
|
2235
1879
|
],
|
|
2236
1880
|
});
|
|
2237
1881
|
const pass = encoder.beginRenderPass({
|
|
2238
|
-
colorAttachments: [
|
|
1882
|
+
colorAttachments: [
|
|
1883
|
+
{ view: dstView, clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: "clear", storeOp: "store" },
|
|
1884
|
+
],
|
|
2239
1885
|
});
|
|
2240
1886
|
pass.setPipeline(this.mipBlitPipeline);
|
|
2241
1887
|
pass.setBindGroup(0, bindGroup);
|
|
@@ -2426,7 +2072,8 @@ export class Engine {
|
|
|
2426
2072
|
const compositeAttachment = this.compositePassDescriptor.colorAttachments[0];
|
|
2427
2073
|
compositeAttachment.view = this.context.getCurrentTexture().createView();
|
|
2428
2074
|
const cpass = encoder.beginRenderPass(this.compositePassDescriptor);
|
|
2429
|
-
|
|
2075
|
+
const compositePipeline = this.viewTransform.gamma === 1.0 ? this.compositePipelineIdentity : this.compositePipelineGamma;
|
|
2076
|
+
cpass.setPipeline(compositePipeline);
|
|
2430
2077
|
cpass.setBindGroup(0, this.compositeBindGroup);
|
|
2431
2078
|
cpass.draw(3);
|
|
2432
2079
|
cpass.end();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export { Engine, DEFAULT_BLOOM_OPTIONS, DEFAULT_VIEW_TRANSFORM, type EngineStats, type EngineOptions, type BloomOptions, type ViewTransformOptions, type LoadModelFromFilesOptions, } from "./engine";
|
|
1
|
+
export { Engine, DEFAULT_BLOOM_OPTIONS, DEFAULT_VIEW_TRANSFORM, type EngineStats, type EngineOptions, type BloomOptions, type ViewTransformOptions, type LoadModelFromFilesOptions, type MaterialPreset, type MaterialPresetMap, } from "./engine";
|
|
2
2
|
export { parsePmxFolderInput, pmxFileAtRelativePath, type PmxFolderInputResult } from "./folder-upload";
|
|
3
3
|
export { Model } from "./model";
|
|
4
4
|
export { Vec3, Quat, Mat4 } from "./math";
|
|
5
5
|
export type { AnimationClip, AnimationPlayOptions, AnimationProgress, BoneKeyframe, MorphKeyframe, BoneInterpolation, ControlPoint, } from "./animation";
|
|
6
6
|
export { FPS } from "./animation";
|
|
7
7
|
export { Physics, type PhysicsOptions } from "./physics";
|
|
8
|
-
export type { MaterialPreset, MaterialPresetMap } from "./shaders/classify";
|
|
9
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACvG,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA"}
|