reze-engine 0.11.2 → 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 +4 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +58 -426
- 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 +7 -28
- 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/eye.d.ts +1 -1
- package/dist/shaders/eye.d.ts.map +1 -1
- 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 +1 -1
- package/src/engine.ts +93 -438
- package/src/index.ts +3 -2
- package/src/shaders/{body.ts → materials/body.ts} +7 -28
- 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/{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/{default.ts → materials/default.ts} +0 -0
- /package/src/shaders/{eye.ts → materials/eye.ts} +0 -0
- /package/src/shaders/{stockings.ts → materials/stockings.ts} +0 -0
package/src/engine.ts
CHANGED
|
@@ -13,18 +13,51 @@ import {
|
|
|
13
13
|
normalizeAssetPath,
|
|
14
14
|
type AssetReader,
|
|
15
15
|
} from "./asset-reader"
|
|
16
|
-
import { DEFAULT_SHADER_WGSL } from "./shaders/default"
|
|
16
|
+
import { DEFAULT_SHADER_WGSL } from "./shaders/materials/default"
|
|
17
|
+
import { FACE_SHADER_WGSL } from "./shaders/materials/face"
|
|
18
|
+
import { HAIR_SHADER_WGSL } from "./shaders/materials/hair"
|
|
19
|
+
import { CLOTH_SMOOTH_SHADER_WGSL } from "./shaders/materials/cloth_smooth"
|
|
20
|
+
import { CLOTH_ROUGH_SHADER_WGSL } from "./shaders/materials/cloth_rough"
|
|
21
|
+
import { METAL_SHADER_WGSL } from "./shaders/materials/metal"
|
|
22
|
+
import { BODY_SHADER_WGSL } from "./shaders/materials/body"
|
|
23
|
+
import { EYE_SHADER_WGSL } from "./shaders/materials/eye"
|
|
24
|
+
import { STOCKINGS_SHADER_WGSL } from "./shaders/materials/stockings"
|
|
17
25
|
import { BRDF_LUT_SIZE, BRDF_LUT_BAKE_WGSL } from "./shaders/dfg_lut"
|
|
18
26
|
import { LTC_MAG_LUT_SIZE, LTC_MAG_LUT_DATA } from "./shaders/ltc_mag_lut"
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import {
|
|
27
|
+
import { SHADOW_DEPTH_SHADER_WGSL } from "./shaders/passes/shadow"
|
|
28
|
+
import { GROUND_SHADOW_SHADER_WGSL } from "./shaders/passes/ground"
|
|
29
|
+
import { OUTLINE_SHADER_WGSL } from "./shaders/passes/outline"
|
|
30
|
+
import {
|
|
31
|
+
BLOOM_BLIT_SHADER_WGSL,
|
|
32
|
+
BLOOM_DOWNSAMPLE_SHADER_WGSL,
|
|
33
|
+
BLOOM_UPSAMPLE_SHADER_WGSL,
|
|
34
|
+
} from "./shaders/passes/bloom"
|
|
35
|
+
import { COMPOSITE_SHADER_WGSL } from "./shaders/passes/composite"
|
|
36
|
+
import { PICK_SHADER_WGSL } from "./shaders/passes/pick"
|
|
37
|
+
import { MIPMAP_BLIT_SHADER_WGSL } from "./shaders/passes/mipmap"
|
|
38
|
+
|
|
39
|
+
// Material preset dispatch. Consumers supply a MaterialPresetMap assigning material names
|
|
40
|
+
// to presets; unmapped materials fall back to "default" (Principled BSDF).
|
|
41
|
+
export type MaterialPreset =
|
|
42
|
+
| "default"
|
|
43
|
+
| "face"
|
|
44
|
+
| "hair"
|
|
45
|
+
| "body"
|
|
46
|
+
| "eye"
|
|
47
|
+
| "stockings"
|
|
48
|
+
| "metal"
|
|
49
|
+
| "cloth_smooth"
|
|
50
|
+
| "cloth_rough"
|
|
51
|
+
|
|
52
|
+
export type MaterialPresetMap = Partial<Record<MaterialPreset, string[]>>
|
|
53
|
+
|
|
54
|
+
function resolvePreset(materialName: string, map: MaterialPresetMap | undefined): MaterialPreset {
|
|
55
|
+
if (!map) return "default"
|
|
56
|
+
for (const [preset, names] of Object.entries(map)) {
|
|
57
|
+
if (names && names.includes(materialName)) return preset as MaterialPreset
|
|
58
|
+
}
|
|
59
|
+
return "default"
|
|
60
|
+
}
|
|
28
61
|
|
|
29
62
|
export type RaycastCallback = (modelName: string, material: string | null, screenX: number, screenY: number) => void
|
|
30
63
|
|
|
@@ -223,11 +256,15 @@ export class Engine {
|
|
|
223
256
|
private maskResolveView!: GPUTextureView
|
|
224
257
|
private renderPassDescriptor!: GPURenderPassDescriptor
|
|
225
258
|
private compositePassDescriptor!: GPURenderPassDescriptor
|
|
226
|
-
|
|
259
|
+
// Two specialized composite pipelines via WGSL pipeline-override constants.
|
|
260
|
+
// Identity variant skips the gamma pow entirely at shader-compile time —
|
|
261
|
+
// Safari's Metal backend won't fold pow(x, 1) to identity.
|
|
262
|
+
private compositePipelineIdentity!: GPURenderPipeline
|
|
263
|
+
private compositePipelineGamma!: GPURenderPipeline
|
|
227
264
|
private compositeBindGroupLayout!: GPUBindGroupLayout
|
|
228
265
|
private compositeBindGroup!: GPUBindGroup
|
|
229
266
|
private compositeUniformBuffer!: GPUBuffer
|
|
230
|
-
// [exposure,
|
|
267
|
+
// [exposure, invGamma, _, _, bloomTint.x, bloomTint.y, bloomTint.z, bloomIntensity]
|
|
231
268
|
private readonly compositeUniformData = new Float32Array(8)
|
|
232
269
|
|
|
233
270
|
// EEVEE-style bloom pyramid (mirrors Blender 3.6 effect_bloom_frag.glsl):
|
|
@@ -403,7 +440,10 @@ export class Engine {
|
|
|
403
440
|
const effIntensity = b.enabled ? b.intensity : 0.0
|
|
404
441
|
const u = this.compositeUniformData
|
|
405
442
|
u[0] = v.exposure
|
|
406
|
-
|
|
443
|
+
// Store 1/gamma so the shader avoids a per-pixel divide. Safari's Metal
|
|
444
|
+
// compiler doesn't fold `pow(x, 1/g)` into identity when g=1, so also emit
|
|
445
|
+
// a uniform branch that skips the pow entirely in the common case.
|
|
446
|
+
u[1] = 1.0 / Math.max(v.gamma, 1e-4)
|
|
407
447
|
u[2] = 0.0
|
|
408
448
|
u[3] = 0.0
|
|
409
449
|
u[4] = b.color.x
|
|
@@ -893,21 +933,7 @@ export class Engine {
|
|
|
893
933
|
})
|
|
894
934
|
const shadowShader = this.device.createShaderModule({
|
|
895
935
|
label: "shadow depth",
|
|
896
|
-
code:
|
|
897
|
-
struct LightVP { viewProj: mat4x4f, };
|
|
898
|
-
@group(0) @binding(0) var<uniform> lp: LightVP;
|
|
899
|
-
@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;
|
|
900
|
-
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,
|
|
901
|
-
@location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) -> @builtin(position) vec4f {
|
|
902
|
-
let pos4 = vec4f(position, 1.0);
|
|
903
|
-
let ws = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
904
|
-
let inv = select(1.0, 1.0 / ws, ws > 0.0001);
|
|
905
|
-
let nw = select(vec4f(1.0,0.0,0.0,0.0), weights0 * inv, ws > 0.0001);
|
|
906
|
-
var sp = vec4f(0.0);
|
|
907
|
-
for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }
|
|
908
|
-
return lp.viewProj * vec4f(sp.xyz, 1.0);
|
|
909
|
-
}
|
|
910
|
-
`,
|
|
936
|
+
code: SHADOW_DEPTH_SHADER_WGSL,
|
|
911
937
|
})
|
|
912
938
|
this.shadowDepthPipeline = this.device.createRenderPipeline({
|
|
913
939
|
label: "shadow depth pipeline",
|
|
@@ -967,99 +993,7 @@ export class Engine {
|
|
|
967
993
|
})
|
|
968
994
|
const groundShadowShader = this.device.createShaderModule({
|
|
969
995
|
label: "ground shadow",
|
|
970
|
-
code:
|
|
971
|
-
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
972
|
-
struct Light { direction: vec4f, color: vec4f, };
|
|
973
|
-
struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
|
|
974
|
-
struct GroundShadowMat {
|
|
975
|
-
diffuseColor: vec3f, fadeStart: f32,
|
|
976
|
-
fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,
|
|
977
|
-
gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, _pad: f32,
|
|
978
|
-
gridLineColor: vec3f, _pad2: f32,
|
|
979
|
-
};
|
|
980
|
-
struct LightVP { viewProj: mat4x4f, };
|
|
981
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
982
|
-
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
983
|
-
@group(0) @binding(2) var shadowMap: texture_depth_2d;
|
|
984
|
-
@group(0) @binding(3) var shadowSampler: sampler_comparison;
|
|
985
|
-
@group(0) @binding(4) var<uniform> material: GroundShadowMat;
|
|
986
|
-
@group(0) @binding(5) var<uniform> lightVP: LightVP;
|
|
987
|
-
|
|
988
|
-
// Hash-based noise for frosted/matte surface
|
|
989
|
-
fn hash2(p: vec2f) -> f32 {
|
|
990
|
-
var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
|
|
991
|
-
p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));
|
|
992
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
993
|
-
}
|
|
994
|
-
fn valueNoise(p: vec2f) -> f32 {
|
|
995
|
-
let i = floor(p);
|
|
996
|
-
let f = fract(p);
|
|
997
|
-
let u = f * f * (3.0 - 2.0 * f);
|
|
998
|
-
return mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x),
|
|
999
|
-
mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);
|
|
1000
|
-
}
|
|
1001
|
-
fn fbmNoise(p: vec2f) -> f32 {
|
|
1002
|
-
var v = 0.0;
|
|
1003
|
-
var a = 0.5;
|
|
1004
|
-
var pp = p;
|
|
1005
|
-
for (var i = 0; i < 4; i++) {
|
|
1006
|
-
v += a * valueNoise(pp);
|
|
1007
|
-
pp *= 2.0;
|
|
1008
|
-
a *= 0.5;
|
|
1009
|
-
}
|
|
1010
|
-
return v;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
struct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };
|
|
1014
|
-
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {
|
|
1015
|
-
var o: VO; o.worldPos = position; o.normal = normal;
|
|
1016
|
-
o.position = camera.projection * camera.view * vec4f(position, 1.0); return o;
|
|
1017
|
-
}
|
|
1018
|
-
struct FSOut { @location(0) color: vec4f, @location(1) mask: f32 };
|
|
1019
|
-
@fragment fn fs(i: VO) -> FSOut {
|
|
1020
|
-
let n = normalize(i.normal);
|
|
1021
|
-
let centerDist = length(i.worldPos.xz);
|
|
1022
|
-
let edgeFade = 1.0 - smoothstep(0.0, 1.0, clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0));
|
|
1023
|
-
|
|
1024
|
-
// Shadow sampling
|
|
1025
|
-
let lclip = lightVP.viewProj * vec4f(i.worldPos, 1.0);
|
|
1026
|
-
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
1027
|
-
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
1028
|
-
let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));
|
|
1029
|
-
let st = material.pcfTexel;
|
|
1030
|
-
let compareZ = ndc.z - 0.0035;
|
|
1031
|
-
var vis = 0.0;
|
|
1032
|
-
for (var y = -2; y <= 2; y++) {
|
|
1033
|
-
for (var x = -2; x <= 2; x++) {
|
|
1034
|
-
vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(f32(x), f32(y)) * st, compareZ);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
vis *= 0.04;
|
|
1038
|
-
|
|
1039
|
-
// Frosted/matte micro-texture (磨砂)
|
|
1040
|
-
let noiseVal = fbmNoise(i.worldPos.xz * 3.0);
|
|
1041
|
-
let noiseTint = 1.0 + (noiseVal - 0.5) * material.noiseStrength;
|
|
1042
|
-
|
|
1043
|
-
// Grid lines — anti-aliased via screen-space derivatives
|
|
1044
|
-
let gp = i.worldPos.xz / material.gridSpacing;
|
|
1045
|
-
let gridFrac = abs(fract(gp - 0.5) - 0.5);
|
|
1046
|
-
let gridDeriv = fwidth(gp);
|
|
1047
|
-
let halfLine = material.gridLineWidth * 0.5;
|
|
1048
|
-
let gridLine = 1.0 - min(
|
|
1049
|
-
smoothstep(halfLine - gridDeriv.x, halfLine + gridDeriv.x, gridFrac.x),
|
|
1050
|
-
smoothstep(halfLine - gridDeriv.y, halfLine + gridDeriv.y, gridFrac.y)
|
|
1051
|
-
);
|
|
1052
|
-
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);
|
|
1053
|
-
let dark = (1.0 - vis) * material.shadowStrength;
|
|
1054
|
-
var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);
|
|
1055
|
-
baseColor *= noiseTint;
|
|
1056
|
-
let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);
|
|
1057
|
-
var out: FSOut;
|
|
1058
|
-
out.color = vec4f(finalColor * edgeFade, edgeFade);
|
|
1059
|
-
out.mask = 0.0;
|
|
1060
|
-
return out;
|
|
1061
|
-
}
|
|
1062
|
-
`,
|
|
996
|
+
code: GROUND_SHADOW_SHADER_WGSL,
|
|
1063
997
|
})
|
|
1064
998
|
this.groundShadowPipeline = this.createRenderPipeline({
|
|
1065
999
|
label: "ground shadow pipeline",
|
|
@@ -1103,90 +1037,7 @@ export class Engine {
|
|
|
1103
1037
|
|
|
1104
1038
|
const outlineShaderModule = this.device.createShaderModule({
|
|
1105
1039
|
label: "outline shaders",
|
|
1106
|
-
code:
|
|
1107
|
-
struct CameraUniforms {
|
|
1108
|
-
view: mat4x4f,
|
|
1109
|
-
projection: mat4x4f,
|
|
1110
|
-
viewPos: vec3f,
|
|
1111
|
-
_padding: f32,
|
|
1112
|
-
};
|
|
1113
|
-
|
|
1114
|
-
struct MaterialUniforms {
|
|
1115
|
-
edgeColor: vec4f,
|
|
1116
|
-
edgeSize: f32,
|
|
1117
|
-
_padding1: f32,
|
|
1118
|
-
_padding2: f32,
|
|
1119
|
-
_padding3: f32,
|
|
1120
|
-
};
|
|
1121
|
-
|
|
1122
|
-
// group 0: per-frame
|
|
1123
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
1124
|
-
// group 1: per-instance
|
|
1125
|
-
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
1126
|
-
// group 2: per-material
|
|
1127
|
-
@group(2) @binding(0) var<uniform> material: MaterialUniforms;
|
|
1128
|
-
|
|
1129
|
-
struct VertexOutput {
|
|
1130
|
-
@builtin(position) position: vec4f,
|
|
1131
|
-
};
|
|
1132
|
-
|
|
1133
|
-
@vertex fn vs(
|
|
1134
|
-
@location(0) position: vec3f,
|
|
1135
|
-
@location(1) normal: vec3f,
|
|
1136
|
-
@location(3) joints0: vec4<u32>,
|
|
1137
|
-
@location(4) weights0: vec4<f32>
|
|
1138
|
-
) -> VertexOutput {
|
|
1139
|
-
var output: VertexOutput;
|
|
1140
|
-
let pos4 = vec4f(position, 1.0);
|
|
1141
|
-
|
|
1142
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
1143
|
-
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
1144
|
-
let normalizedWeights = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
1145
|
-
|
|
1146
|
-
var skinnedPos = vec4f(0.0, 0.0, 0.0, 0.0);
|
|
1147
|
-
var skinnedNrm = vec3f(0.0, 0.0, 0.0);
|
|
1148
|
-
for (var i = 0u; i < 4u; i++) {
|
|
1149
|
-
let j = joints0[i];
|
|
1150
|
-
let w = normalizedWeights[i];
|
|
1151
|
-
let m = skinMats[j];
|
|
1152
|
-
skinnedPos += (m * pos4) * w;
|
|
1153
|
-
let r3 = mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz);
|
|
1154
|
-
skinnedNrm += (r3 * normal) * w;
|
|
1155
|
-
}
|
|
1156
|
-
let worldPos = skinnedPos.xyz;
|
|
1157
|
-
let worldNormal = normalize(skinnedNrm);
|
|
1158
|
-
|
|
1159
|
-
// Screen-space outline extrusion — MMD-style pixel-stable edge line.
|
|
1160
|
-
// 1. Project position and normal-as-direction to clip space.
|
|
1161
|
-
// 2. Normalize the 2D clip-space normal, aspect-compensated so "one pixel horizontally"
|
|
1162
|
-
// matches "one pixel vertically" (otherwise wide viewports squash the outline in X).
|
|
1163
|
-
// 3. Offset clip-space xy by (normal * edgeSize * edgeScale), then multiply by w
|
|
1164
|
-
// so the perspective divide cancels out → offset stays constant in NDC regardless
|
|
1165
|
-
// of depth, matching how MMD / babylon-mmd style outlines look identical when zooming.
|
|
1166
|
-
// 4. edgeScale is in NDC-y units per PMX edgeSize. ≈ 0.006 gives ~3px at 1080p; it's
|
|
1167
|
-
// tied to viewport HEIGHT so resizing the window keeps pixel thickness stable.
|
|
1168
|
-
let viewProj = camera.projection * camera.view;
|
|
1169
|
-
let clipPos = viewProj * vec4f(worldPos, 1.0);
|
|
1170
|
-
let clipNormal = (viewProj * vec4f(worldNormal, 0.0)).xy;
|
|
1171
|
-
// projection is column-major: proj[0][0] = 1/(aspect·tan(fov/2)), proj[1][1] = 1/tan(fov/2).
|
|
1172
|
-
// Ratio proj[1][1]/proj[0][0] recovers the viewport aspect (width/height).
|
|
1173
|
-
let aspect = camera.projection[1][1] / camera.projection[0][0];
|
|
1174
|
-
let pixelDir = normalize(vec2f(clipNormal.x * aspect, clipNormal.y));
|
|
1175
|
-
let ndcDir = vec2f(pixelDir.x / aspect, pixelDir.y);
|
|
1176
|
-
let edgeScale = 0.0016;
|
|
1177
|
-
let offset = ndcDir * material.edgeSize * edgeScale * clipPos.w;
|
|
1178
|
-
output.position = vec4f(clipPos.xy + offset, clipPos.z, clipPos.w);
|
|
1179
|
-
return output;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
struct FSOut { @location(0) color: vec4f, @location(1) mask: f32 };
|
|
1183
|
-
@fragment fn fs() -> FSOut {
|
|
1184
|
-
var out: FSOut;
|
|
1185
|
-
out.color = material.edgeColor;
|
|
1186
|
-
out.mask = 1.0;
|
|
1187
|
-
return out;
|
|
1188
|
-
}
|
|
1189
|
-
`,
|
|
1040
|
+
code: OUTLINE_SHADER_WGSL,
|
|
1190
1041
|
})
|
|
1191
1042
|
|
|
1192
1043
|
this.outlinePipeline = this.createRenderPipeline({
|
|
@@ -1251,130 +1102,19 @@ export class Engine {
|
|
|
1251
1102
|
],
|
|
1252
1103
|
})
|
|
1253
1104
|
|
|
1254
|
-
const bloomFullscreenVs = /* wgsl */ `
|
|
1255
|
-
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
1256
|
-
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
1257
|
-
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
1258
|
-
return vec4f(x, y, 0.0, 1.0);
|
|
1259
|
-
}
|
|
1260
|
-
`
|
|
1261
|
-
|
|
1262
|
-
// Blit: full-res HDR → half-res. Karis 4-tap firefly average + EEVEE quadratic knee threshold + clamp.
|
|
1263
1105
|
const bloomBlitShader = this.device.createShaderModule({
|
|
1264
1106
|
label: "bloom blit (Karis prefilter)",
|
|
1265
|
-
code:
|
|
1266
|
-
@group(0) @binding(0) var hdrTex: texture_2d<f32>;
|
|
1267
|
-
@group(0) @binding(1) var<uniform> prefilter: vec4<f32>; // threshold, knee, clamp, _unused
|
|
1268
|
-
@group(0) @binding(2) var maskTex: texture_2d<f32>;
|
|
1269
|
-
|
|
1270
|
-
fn luminance(c: vec3f) -> f32 {
|
|
1271
|
-
return dot(max(c, vec3f(0.0)), vec3f(0.2126, 0.7152, 0.0722));
|
|
1272
|
-
}
|
|
1273
|
-
fn fetch(c: vec2<i32>, clampV: f32) -> vec3f {
|
|
1274
|
-
let d = vec2<i32>(textureDimensions(hdrTex));
|
|
1275
|
-
let cc = clamp(c, vec2<i32>(0), d - vec2<i32>(1));
|
|
1276
|
-
let s = textureLoad(hdrTex, cc, 0);
|
|
1277
|
-
// Scene pass uses src-alpha blend with clear alpha 0 → premultiplied. Unpremultiply.
|
|
1278
|
-
let rgb = max(s.rgb / max(s.a, 1e-6), vec3f(0.0));
|
|
1279
|
-
// Bloom mask: MRT r8unorm written by material shaders (1.0 = bloom, 0.0 = skip).
|
|
1280
|
-
let mask = textureLoad(maskTex, cc, 0).r;
|
|
1281
|
-
let masked = rgb * mask;
|
|
1282
|
-
// Blender: clamp each tap BEFORE Karis average (eevee_bloom: color = min(clampIntensity, color)).
|
|
1283
|
-
return select(masked, min(masked, vec3f(clampV)), clampV > 0.0);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
1287
|
-
let dst = vec2<i32>(p.xy - vec2f(0.5));
|
|
1288
|
-
let base = dst * 2;
|
|
1289
|
-
let clampV = prefilter.z;
|
|
1290
|
-
let a = fetch(base + vec2<i32>(0, 0), clampV);
|
|
1291
|
-
let b = fetch(base + vec2<i32>(1, 0), clampV);
|
|
1292
|
-
let c = fetch(base + vec2<i32>(0, 1), clampV);
|
|
1293
|
-
let d = fetch(base + vec2<i32>(1, 1), clampV);
|
|
1294
|
-
// Karis partial average: weight each tap by 1/(1+luma) — suppresses fireflies.
|
|
1295
|
-
let wa = 1.0 / (1.0 + luminance(a));
|
|
1296
|
-
let wb = 1.0 / (1.0 + luminance(b));
|
|
1297
|
-
let wc = 1.0 / (1.0 + luminance(c));
|
|
1298
|
-
let wd = 1.0 / (1.0 + luminance(d));
|
|
1299
|
-
let avg = (a * wa + b * wb + c * wc + d * wd) / max(wa + wb + wc + wd, 1e-6);
|
|
1300
|
-
// EEVEE quadratic threshold (brightness = max-channel, then soft-knee curve).
|
|
1301
|
-
let bright = max(avg.r, max(avg.g, avg.b));
|
|
1302
|
-
let soft = clamp(bright - prefilter.x + prefilter.y, 0.0, 2.0 * prefilter.y);
|
|
1303
|
-
let q = (soft * soft) / (4.0 * max(prefilter.y, 1e-4) + 1e-6);
|
|
1304
|
-
let contrib = max(q, bright - prefilter.x) / max(bright, 1e-4);
|
|
1305
|
-
return vec4f(max(avg * contrib, vec3f(0.0)), 1.0);
|
|
1306
|
-
}
|
|
1307
|
-
`,
|
|
1107
|
+
code: BLOOM_BLIT_SHADER_WGSL,
|
|
1308
1108
|
})
|
|
1309
1109
|
|
|
1310
|
-
// Downsample: Jimenez/COD 13-tap dual-box — 5 weighted 2×2 averages, rejects nyquist ringing.
|
|
1311
1110
|
const bloomDownsampleShader = this.device.createShaderModule({
|
|
1312
1111
|
label: "bloom downsample 13-tap",
|
|
1313
|
-
code:
|
|
1314
|
-
@group(0) @binding(0) var srcTex: texture_2d<f32>;
|
|
1315
|
-
@group(0) @binding(1) var srcSamp: sampler;
|
|
1316
|
-
|
|
1317
|
-
fn samp(uv: vec2f, off: vec2f) -> vec3f {
|
|
1318
|
-
return textureSampleLevel(srcTex, srcSamp, uv + off, 0.0).rgb;
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
1322
|
-
let srcDims = vec2f(textureDimensions(srcTex));
|
|
1323
|
-
let t = 1.0 / srcDims;
|
|
1324
|
-
// fragCoord.xy reports pixel centers (e.g. 0.5,0.5 for first pixel) — divide by dst dims directly.
|
|
1325
|
-
let dstDims = srcDims * 0.5;
|
|
1326
|
-
let uv = p.xy / max(dstDims, vec2f(1.0));
|
|
1327
|
-
let A = samp(uv, t * vec2f(-2.0, -2.0));
|
|
1328
|
-
let B = samp(uv, t * vec2f( 0.0, -2.0));
|
|
1329
|
-
let C = samp(uv, t * vec2f( 2.0, -2.0));
|
|
1330
|
-
let D = samp(uv, t * vec2f(-1.0, -1.0));
|
|
1331
|
-
let E = samp(uv, t * vec2f( 1.0, -1.0));
|
|
1332
|
-
let F = samp(uv, t * vec2f(-2.0, 0.0));
|
|
1333
|
-
let G = samp(uv, t * vec2f( 0.0, 0.0));
|
|
1334
|
-
let H = samp(uv, t * vec2f( 2.0, 0.0));
|
|
1335
|
-
let I = samp(uv, t * vec2f(-1.0, 1.0));
|
|
1336
|
-
let J = samp(uv, t * vec2f( 1.0, 1.0));
|
|
1337
|
-
let K = samp(uv, t * vec2f(-2.0, 2.0));
|
|
1338
|
-
let L = samp(uv, t * vec2f( 0.0, 2.0));
|
|
1339
|
-
let M = samp(uv, t * vec2f( 2.0, 2.0));
|
|
1340
|
-
var o = (D + E + I + J) * (0.5 / 4.0);
|
|
1341
|
-
o = o + (A + B + G + F) * (0.125 / 4.0);
|
|
1342
|
-
o = o + (B + C + H + G) * (0.125 / 4.0);
|
|
1343
|
-
o = o + (F + G + L + K) * (0.125 / 4.0);
|
|
1344
|
-
o = o + (G + H + M + L) * (0.125 / 4.0);
|
|
1345
|
-
return vec4f(o, 1.0);
|
|
1346
|
-
}
|
|
1347
|
-
`,
|
|
1112
|
+
code: BLOOM_DOWNSAMPLE_SHADER_WGSL,
|
|
1348
1113
|
})
|
|
1349
1114
|
|
|
1350
|
-
// Upsample: 9-tap tent, progressively added to matching downsample mip. Blender radius = sample scale.
|
|
1351
1115
|
const bloomUpsampleShader = this.device.createShaderModule({
|
|
1352
1116
|
label: "bloom upsample 9-tap tent",
|
|
1353
|
-
code:
|
|
1354
|
-
@group(0) @binding(0) var srcTex: texture_2d<f32>; // coarser accumulator
|
|
1355
|
-
@group(0) @binding(1) var baseTex: texture_2d<f32>; // matching downsample mip
|
|
1356
|
-
@group(0) @binding(2) var srcSamp: sampler;
|
|
1357
|
-
@group(0) @binding(3) var<uniform> upU: vec4<f32>; // sampleScale, _, _, _
|
|
1358
|
-
|
|
1359
|
-
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
1360
|
-
let srcDims = vec2f(textureDimensions(srcTex));
|
|
1361
|
-
let baseDims = vec2f(textureDimensions(baseTex));
|
|
1362
|
-
let uv = p.xy / max(baseDims, vec2f(1.0));
|
|
1363
|
-
let t = upU.x / srcDims;
|
|
1364
|
-
var o = textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, -1.0), 0.0).rgb * 1.0;
|
|
1365
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, -1.0), 0.0).rgb * 2.0;
|
|
1366
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, -1.0), 0.0).rgb * 1.0;
|
|
1367
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, 0.0), 0.0).rgb * 2.0;
|
|
1368
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, 0.0), 0.0).rgb * 4.0;
|
|
1369
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, 0.0), 0.0).rgb * 2.0;
|
|
1370
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, 1.0), 0.0).rgb * 1.0;
|
|
1371
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, 1.0), 0.0).rgb * 2.0;
|
|
1372
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, 1.0), 0.0).rgb * 1.0;
|
|
1373
|
-
o = o * (1.0 / 16.0);
|
|
1374
|
-
let base = textureSampleLevel(baseTex, srcSamp, uv, 0.0).rgb;
|
|
1375
|
-
return vec4f(o + base, 1.0);
|
|
1376
|
-
}
|
|
1377
|
-
`,
|
|
1117
|
+
code: BLOOM_UPSAMPLE_SHADER_WGSL,
|
|
1378
1118
|
})
|
|
1379
1119
|
|
|
1380
1120
|
const bloomBlitLayout = this.device.createPipelineLayout({ bindGroupLayouts: [this.bloomBlitBindGroupLayout] })
|
|
@@ -1425,68 +1165,27 @@ export class Engine {
|
|
|
1425
1165
|
|
|
1426
1166
|
const compositeShader = this.device.createShaderModule({
|
|
1427
1167
|
label: "composite shader",
|
|
1428
|
-
code:
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
1452
|
-
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
1453
|
-
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
1454
|
-
return vec4f(x, y, 0.0, 1.0);
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
@fragment fn fs(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
|
|
1458
|
-
let hdr = textureLoad(hdrTex, vec2<i32>(fragCoord.xy), 0);
|
|
1459
|
-
let a = max(hdr.a, 1e-6);
|
|
1460
|
-
let straight = hdr.rgb / a;
|
|
1461
|
-
let fullSz = vec2f(textureDimensions(hdrTex));
|
|
1462
|
-
let bloomSz = vec2f(textureDimensions(bloomTex));
|
|
1463
|
-
// Bloom is at half-res (pyramid mip 0). Sampler interpolates back to full-res UVs.
|
|
1464
|
-
// fragCoord.xy is already at pixel center (e.g. 0.5, 0.5 for first pixel).
|
|
1465
|
-
let bloomUv = fragCoord.xy / max(fullSz, vec2f(1.0));
|
|
1466
|
-
let tint = viewU[1].xyz;
|
|
1467
|
-
let intensity = viewU[1].w;
|
|
1468
|
-
let bloom = textureSampleLevel(bloomTex, bloomSamp, bloomUv, 0.0).rgb * tint * intensity;
|
|
1469
|
-
let combined = straight + bloom;
|
|
1470
|
-
let exposed = combined * exp2(viewU[0].x);
|
|
1471
|
-
let tm = vec3f(filmic(exposed.r), filmic(exposed.g), filmic(exposed.b));
|
|
1472
|
-
let g = max(viewU[0].y, 1e-4);
|
|
1473
|
-
let disp = pow(max(tm, vec3f(0.0)), vec3f(1.0 / g));
|
|
1474
|
-
return vec4f(disp * hdr.a, hdr.a);
|
|
1475
|
-
}
|
|
1476
|
-
`,
|
|
1477
|
-
})
|
|
1478
|
-
|
|
1479
|
-
this.compositePipeline = this.device.createRenderPipeline({
|
|
1480
|
-
label: "composite pipeline",
|
|
1481
|
-
layout: this.device.createPipelineLayout({ bindGroupLayouts: [this.compositeBindGroupLayout] }),
|
|
1482
|
-
vertex: { module: compositeShader, entryPoint: "vs" },
|
|
1483
|
-
fragment: {
|
|
1484
|
-
module: compositeShader,
|
|
1485
|
-
entryPoint: "fs",
|
|
1486
|
-
targets: [{ format: this.presentationFormat }],
|
|
1487
|
-
},
|
|
1488
|
-
primitive: { topology: "triangle-list" },
|
|
1489
|
-
})
|
|
1168
|
+
code: COMPOSITE_SHADER_WGSL,
|
|
1169
|
+
})
|
|
1170
|
+
|
|
1171
|
+
const compositePipelineLayout = this.device.createPipelineLayout({
|
|
1172
|
+
bindGroupLayouts: [this.compositeBindGroupLayout],
|
|
1173
|
+
})
|
|
1174
|
+
const makeCompositePipeline = (applyGamma: boolean, label: string): GPURenderPipeline =>
|
|
1175
|
+
this.device.createRenderPipeline({
|
|
1176
|
+
label,
|
|
1177
|
+
layout: compositePipelineLayout,
|
|
1178
|
+
vertex: { module: compositeShader, entryPoint: "vs" },
|
|
1179
|
+
fragment: {
|
|
1180
|
+
module: compositeShader,
|
|
1181
|
+
entryPoint: "fs",
|
|
1182
|
+
constants: { APPLY_GAMMA: applyGamma ? 1 : 0 },
|
|
1183
|
+
targets: [{ format: this.presentationFormat }],
|
|
1184
|
+
},
|
|
1185
|
+
primitive: { topology: "triangle-list" },
|
|
1186
|
+
})
|
|
1187
|
+
this.compositePipelineIdentity = makeCompositePipeline(false, "composite pipeline (gamma=1)")
|
|
1188
|
+
this.compositePipelineGamma = makeCompositePipeline(true, "composite pipeline (gamma!=1)")
|
|
1490
1189
|
|
|
1491
1190
|
this.bloomPassDescriptor = {
|
|
1492
1191
|
label: "bloom pass",
|
|
@@ -1500,47 +1199,9 @@ export class Engine {
|
|
|
1500
1199
|
],
|
|
1501
1200
|
} as GPURenderPassDescriptor
|
|
1502
1201
|
|
|
1503
|
-
// GPU picking: encode (modelIndex, materialIndex) as color
|
|
1504
1202
|
const pickShaderModule = this.device.createShaderModule({
|
|
1505
1203
|
label: "pick shader",
|
|
1506
|
-
code:
|
|
1507
|
-
struct CameraUniforms {
|
|
1508
|
-
view: mat4x4f,
|
|
1509
|
-
projection: mat4x4f,
|
|
1510
|
-
viewPos: vec3f,
|
|
1511
|
-
_padding: f32,
|
|
1512
|
-
};
|
|
1513
|
-
struct PickId {
|
|
1514
|
-
modelId: f32,
|
|
1515
|
-
materialId: f32,
|
|
1516
|
-
_p1: f32,
|
|
1517
|
-
_p2: f32,
|
|
1518
|
-
};
|
|
1519
|
-
|
|
1520
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
1521
|
-
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
1522
|
-
@group(2) @binding(0) var<uniform> pickId: PickId;
|
|
1523
|
-
|
|
1524
|
-
@vertex fn vs(
|
|
1525
|
-
@location(0) position: vec3f,
|
|
1526
|
-
@location(1) normal: vec3f,
|
|
1527
|
-
@location(2) uv: vec2f,
|
|
1528
|
-
@location(3) joints0: vec4<u32>,
|
|
1529
|
-
@location(4) weights0: vec4<f32>
|
|
1530
|
-
) -> @builtin(position) vec4f {
|
|
1531
|
-
let pos4 = vec4f(position, 1.0);
|
|
1532
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
1533
|
-
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
1534
|
-
let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
1535
|
-
var sp = vec4f(0.0);
|
|
1536
|
-
for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }
|
|
1537
|
-
return camera.projection * camera.view * vec4f(sp.xyz, 1.0);
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
@fragment fn fs() -> @location(0) vec4f {
|
|
1541
|
-
return vec4f(pickId.modelId / 255.0, pickId.materialId / 255.0, 0.0, 1.0);
|
|
1542
|
-
}
|
|
1543
|
-
`,
|
|
1204
|
+
code: PICK_SHADER_WGSL,
|
|
1544
1205
|
})
|
|
1545
1206
|
|
|
1546
1207
|
this.pickPerFrameBindGroupLayout = this.device.createBindGroupLayout({
|
|
@@ -1691,12 +1352,16 @@ export class Engine {
|
|
|
1691
1352
|
|
|
1692
1353
|
const depthTextureView = this.depthTexture.createView()
|
|
1693
1354
|
|
|
1355
|
+
// storeOp="discard" on MSAA views keeps per-sample data in Apple TBDR tile memory —
|
|
1356
|
+
// only the resolveTarget (hdrResolveTexture / maskResolveView) gets written to RAM.
|
|
1357
|
+
// With storeOp="store" Safari's Metal backend spills the full MS buffer every frame
|
|
1358
|
+
// (rgba16f × 4 samples on a 4K canvas ≈ 256 MB/frame of dead bandwidth).
|
|
1694
1359
|
const colorAttachment: GPURenderPassColorAttachment = {
|
|
1695
1360
|
view: this.multisampleTexture.createView(),
|
|
1696
1361
|
resolveTarget: this.hdrResolveTexture.createView(),
|
|
1697
1362
|
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
1698
1363
|
loadOp: "clear",
|
|
1699
|
-
storeOp: "
|
|
1364
|
+
storeOp: "discard",
|
|
1700
1365
|
}
|
|
1701
1366
|
|
|
1702
1367
|
const maskAttachment: GPURenderPassColorAttachment = {
|
|
@@ -1704,7 +1369,7 @@ export class Engine {
|
|
|
1704
1369
|
resolveTarget: this.maskResolveView,
|
|
1705
1370
|
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
1706
1371
|
loadOp: "clear",
|
|
1707
|
-
storeOp: "
|
|
1372
|
+
storeOp: "discard",
|
|
1708
1373
|
}
|
|
1709
1374
|
|
|
1710
1375
|
this.renderPassDescriptor = {
|
|
@@ -1714,7 +1379,8 @@ export class Engine {
|
|
|
1714
1379
|
view: depthTextureView,
|
|
1715
1380
|
depthClearValue: 1.0,
|
|
1716
1381
|
depthLoadOp: "clear",
|
|
1717
|
-
|
|
1382
|
+
// Main-pass depth is not sampled later (shadow uses its own map, composite is depthless).
|
|
1383
|
+
depthStoreOp: "discard",
|
|
1718
1384
|
stencilClearValue: 0,
|
|
1719
1385
|
stencilLoadOp: "clear",
|
|
1720
1386
|
stencilStoreOp: "discard",
|
|
@@ -2635,20 +2301,7 @@ export class Engine {
|
|
|
2635
2301
|
})
|
|
2636
2302
|
const module = this.device.createShaderModule({
|
|
2637
2303
|
label: "mipmap blit",
|
|
2638
|
-
code:
|
|
2639
|
-
@group(0) @binding(0) var src: texture_2d<f32>;
|
|
2640
|
-
@group(0) @binding(1) var samp: sampler;
|
|
2641
|
-
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
2642
|
-
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
2643
|
-
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
2644
|
-
return vec4f(x, y, 0.0, 1.0);
|
|
2645
|
-
}
|
|
2646
|
-
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
2647
|
-
let dstDims = vec2f(textureDimensions(src)) * 0.5;
|
|
2648
|
-
let uv = p.xy / max(dstDims, vec2f(1.0));
|
|
2649
|
-
return textureSampleLevel(src, samp, uv, 0.0);
|
|
2650
|
-
}
|
|
2651
|
-
`,
|
|
2304
|
+
code: MIPMAP_BLIT_SHADER_WGSL,
|
|
2652
2305
|
})
|
|
2653
2306
|
this.mipBlitPipeline = this.device.createRenderPipeline({
|
|
2654
2307
|
label: "mipmap blit pipeline",
|
|
@@ -2919,7 +2572,9 @@ export class Engine {
|
|
|
2919
2572
|
const compositeAttachment = (this.compositePassDescriptor.colorAttachments as GPURenderPassColorAttachment[])[0]
|
|
2920
2573
|
compositeAttachment.view = this.context.getCurrentTexture().createView()
|
|
2921
2574
|
const cpass = encoder.beginRenderPass(this.compositePassDescriptor)
|
|
2922
|
-
|
|
2575
|
+
const compositePipeline =
|
|
2576
|
+
this.viewTransform.gamma === 1.0 ? this.compositePipelineIdentity : this.compositePipelineGamma
|
|
2577
|
+
cpass.setPipeline(compositePipeline)
|
|
2923
2578
|
cpass.setBindGroup(0, this.compositeBindGroup)
|
|
2924
2579
|
cpass.draw(3)
|
|
2925
2580
|
cpass.end()
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export {
|
|
|
7
7
|
type BloomOptions,
|
|
8
8
|
type ViewTransformOptions,
|
|
9
9
|
type LoadModelFromFilesOptions,
|
|
10
|
+
type MaterialPreset,
|
|
11
|
+
type MaterialPresetMap,
|
|
10
12
|
} from "./engine"
|
|
11
13
|
export { parsePmxFolderInput, pmxFileAtRelativePath, type PmxFolderInputResult } from "./folder-upload"
|
|
12
14
|
export { Model } from "./model"
|
|
@@ -21,5 +23,4 @@ export type {
|
|
|
21
23
|
ControlPoint,
|
|
22
24
|
} from "./animation"
|
|
23
25
|
export { FPS } from "./animation"
|
|
24
|
-
export { Physics, type PhysicsOptions } from "./physics"
|
|
25
|
-
export type { MaterialPreset, MaterialPresetMap } from "./shaders/classify"
|
|
26
|
+
export { Physics, type PhysicsOptions } from "./physics"
|