reze-engine 0.24.1 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/engine.d.ts +127 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +422 -34
- package/dist/gpu-profile.d.ts +19 -0
- package/dist/gpu-profile.d.ts.map +1 -0
- package/dist/gpu-profile.js +120 -0
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +10 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/physics/profile.d.ts +18 -0
- package/dist/physics/profile.d.ts.map +1 -0
- package/dist/physics/profile.js +44 -0
- package/dist/shaders/materials/common.d.ts +1 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +143 -141
- package/dist/shaders/passes/composite.d.ts +23 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +62 -16
- package/dist/shaders/passes/depth-prepass.d.ts +2 -0
- package/dist/shaders/passes/depth-prepass.d.ts.map +1 -0
- package/dist/shaders/passes/depth-prepass.js +56 -0
- package/dist/shaders/passes/ground.d.ts +1 -1
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +8 -0
- package/package.json +1 -1
- package/src/engine.ts +459 -34
- package/src/graph/slots.ts +11 -2
- package/src/index.ts +2 -0
- package/src/shaders/materials/common.ts +207 -205
- package/src/shaders/passes/composite.ts +89 -16
- package/src/shaders/passes/depth-prepass.ts +57 -0
- package/src/shaders/passes/ground.ts +8 -0
- package/dist/physics-debug.d.ts +0 -30
- package/dist/physics-debug.d.ts.map +0 -1
- package/dist/physics-debug.js +0 -526
- package/dist/shaders/passes/physics-debug.d.ts +0 -2
- package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
- package/dist/shaders/passes/physics-debug.js +0 -69
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;yEAayE;AACzE,MAAM,MAAM,qBAAqB,GAAG;IAClC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AA8HD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAWlF;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,QAA6B,CAAA"}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Composite: HDR scene + bloom pyramid → Filmic tone map → gamma → swapchain.
|
|
2
2
|
// Bloom tint/intensity applied at combine (EEVEE treats them as combine-stage params, not prefilter).
|
|
3
|
-
|
|
3
|
+
//
|
|
4
|
+
// The shader is a TEMPLATE: buildCompositeShader() emits either the base pass or
|
|
5
|
+
// a variant with a user background effect injected (setBackgroundEffect). The
|
|
6
|
+
// effect is background mode 3, a sibling of the 360 equirect (mode 2) — it reuses
|
|
7
|
+
// the same per-pixel view-ray reconstruction and composites in display space
|
|
8
|
+
// under the scene, so it never affects lighting, bloom, or tonemapping.
|
|
9
|
+
const COMPOSITE_HEAD = /* wgsl */ `
|
|
4
10
|
// Pipeline-override constant: the engine creates two composite pipelines, one
|
|
5
11
|
// with APPLY_GAMMA=false (gamma=1 fast path) and one with APPLY_GAMMA=true.
|
|
6
12
|
// The 'if (APPLY_GAMMA)' below is resolved at pipeline-compile time — the
|
|
@@ -11,7 +17,7 @@ override APPLY_GAMMA: bool = true;
|
|
|
11
17
|
@group(0) @binding(0) var hdrTex: texture_2d<f32>;
|
|
12
18
|
@group(0) @binding(1) var bloomTex: texture_2d<f32>; // bloomUpTexture mip 0 (full pyramid top)
|
|
13
19
|
@group(0) @binding(2) var bloomSamp: sampler;
|
|
14
|
-
@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>,
|
|
20
|
+
@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 7>;
|
|
15
21
|
// Aux mask/alpha texture. .r = bloom mask (unused here; bloom blit uses it).
|
|
16
22
|
// .g = accumulated canvas alpha (what hdr.a carried before the HDR format
|
|
17
23
|
// became rg11b10ufloat). We unpremultiply HDR by this alpha for tonemap, then
|
|
@@ -28,10 +34,12 @@ override APPLY_GAMMA: bool = true;
|
|
|
28
34
|
@group(0) @binding(5) var filmicLut: texture_2d<f32>;
|
|
29
35
|
// viewU[0] = (exposure, invGamma, _, _); viewU[1] = (tint.rgb, intensity)
|
|
30
36
|
// viewU[2] = (background.rgb, mode) — display-space sRGB, composited UNDER the
|
|
31
|
-
// scene post-tonemap. mode: 0 transparent (DOM shows),
|
|
32
|
-
// 2 = 360 equirect skybox sampled by view ray.
|
|
37
|
+
// scene post-tonemap. BASE-layer mode: 0 transparent (DOM shows),
|
|
38
|
+
// 1 solid color, 2 = 360 equirect skybox sampled by view ray. A user
|
|
39
|
+
// WGSL effect is a separate LAYER over the base (viewU[6].y flag).
|
|
33
40
|
// viewU[3] = (camera right, tanHalfFov·aspect); viewU[4] = (camera up, tanHalfFov);
|
|
34
|
-
// viewU[5] = (camera forward, _) — refreshed per frame while
|
|
41
|
+
// viewU[5] = (camera forward, _) — refreshed per frame while skybox/effect active.
|
|
42
|
+
// viewU[6] = (time seconds, effect on/off, canvas width, canvas height).
|
|
35
43
|
// invGamma = 1/gamma precomputed on CPU — avoids a per-pixel divide.
|
|
36
44
|
@group(0) @binding(6) var bgEquirect: texture_2d<f32>;
|
|
37
45
|
|
|
@@ -49,6 +57,10 @@ fn filmic(x: f32) -> f32 {
|
|
|
49
57
|
return textureSampleLevel(filmicLut, bloomSamp, vec2f(u, 0.5), 0.0).r;
|
|
50
58
|
}
|
|
51
59
|
|
|
60
|
+
/** Canvas size in pixels — for user background effects (aspect correction). */
|
|
61
|
+
fn bgResolution() -> vec2f { return viewU[6].zw; }
|
|
62
|
+
`;
|
|
63
|
+
const COMPOSITE_BODY = /* wgsl */ `
|
|
52
64
|
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
53
65
|
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
54
66
|
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
@@ -75,21 +87,55 @@ fn filmic(x: f32) -> f32 {
|
|
|
75
87
|
if (APPLY_GAMMA) {
|
|
76
88
|
disp = pow(disp, vec3f(viewU[0].y));
|
|
77
89
|
}
|
|
78
|
-
// Composite over the background in display space (premultiplied out).
|
|
90
|
+
// Composite over the background in display space (premultiplied out). The
|
|
91
|
+
// background is TWO layers: a base (transparent / solid color / 360 equirect)
|
|
92
|
+
// and an optional user WGSL effect over-composited onto it.
|
|
79
93
|
let bg = viewU[2];
|
|
80
|
-
var bgRgb = bg.rgb;
|
|
81
94
|
var bgA = select(0.0, 1.0, bg.w > 0.5);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
95
|
+
var bgPm = bg.rgb * bgA; // premultiplied accumulator
|
|
96
|
+
let fxOn = viewU[6].y > 0.5;
|
|
97
|
+
if (bg.w > 1.5 || fxOn) {
|
|
98
|
+
// The equirect and any effect both need this pixel's world-space view ray,
|
|
99
|
+
// rebuilt from the camera basis. The dome sits at infinity (no parallax) —
|
|
100
|
+
// PhotoDome-style, display-only.
|
|
86
101
|
let ndc = vec2f(fragCoord.x / fullSz.x * 2.0 - 1.0, 1.0 - fragCoord.y / fullSz.y * 2.0);
|
|
87
102
|
let dir = normalize(viewU[5].xyz + ndc.x * viewU[3].w * viewU[3].xyz + ndc.y * viewU[4].w * viewU[4].xyz);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
if (bg.w > 1.5) {
|
|
104
|
+
// LH world (+Z forward): longitude = atan2(x, z), Babylon-PhotoDome convention.
|
|
105
|
+
let su = 0.5 + atan2(dir.x, dir.z) * 0.15915494309; // 1/(2π)
|
|
106
|
+
let sv = 0.5 - asin(clamp(dir.y, -1.0, 1.0)) * 0.31830988618; // 1/π
|
|
107
|
+
bgPm = textureSampleLevel(bgEquirect, bloomSamp, vec2f(su, sv), 0.0).rgb;
|
|
108
|
+
}
|
|
109
|
+
BG_EFFECT_CALL
|
|
92
110
|
}
|
|
93
|
-
return vec4f(disp * alpha +
|
|
111
|
+
return vec4f(disp * alpha + bgPm * (1.0 - alpha), alpha + bgA * (1.0 - alpha));
|
|
94
112
|
}
|
|
95
113
|
`;
|
|
114
|
+
// Base variant: no effect installed, the flag is never set — the ray block only
|
|
115
|
+
// runs for the equirect, and there is nothing to add. (`dir` may go unused when
|
|
116
|
+
// this compiles with mode<2 shaders; WGSL is fine with an unused let.)
|
|
117
|
+
const NO_EFFECT_CALL = `_ = dir;`;
|
|
118
|
+
// uv flipped to bottom-left origin (shadertoy convention); clamped so a stray
|
|
119
|
+
// effect can't push negatives/NaN into the premultiplied composite. Standard
|
|
120
|
+
// OVER onto the base layer.
|
|
121
|
+
const EFFECT_CALL = /* wgsl */ `
|
|
122
|
+
if (fxOn) {
|
|
123
|
+
let bgUv = vec2f(fragCoord.x / fullSz.x, 1.0 - fragCoord.y / fullSz.y);
|
|
124
|
+
let fx = clamp(background(dir, bgUv, viewU[6].x), vec4f(0.0), vec4f(1.0));
|
|
125
|
+
bgPm = fx.rgb * fx.a + bgPm * (1.0 - fx.a);
|
|
126
|
+
bgA = fx.a + bgA * (1.0 - fx.a);
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
export function buildCompositeShader(effect) {
|
|
130
|
+
if (!effect)
|
|
131
|
+
return COMPOSITE_HEAD + COMPOSITE_BODY.replace("BG_EFFECT_CALL", NO_EFFECT_CALL);
|
|
132
|
+
return (COMPOSITE_HEAD +
|
|
133
|
+
"\n// ── user background effect (setBackgroundEffect) ──\n" +
|
|
134
|
+
effect.paramsDecl +
|
|
135
|
+
"\n" +
|
|
136
|
+
effect.wgsl +
|
|
137
|
+
"\n" +
|
|
138
|
+
COMPOSITE_BODY.replace("BG_EFFECT_CALL", EFFECT_CALL.trim()));
|
|
139
|
+
}
|
|
140
|
+
/** Kept for compatibility with existing imports (the base, no-effect shader). */
|
|
141
|
+
export const COMPOSITE_SHADER_WGSL = buildCompositeShader(null);
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const TRANSPARENT_DEPTH_PREPASS_WGSL = "\nstruct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };\nstruct MaterialUniforms {\n diffuseColor: vec3f,\n alpha: f32,\n};\n\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n@group(0) @binding(2) var diffuseSampler: sampler;\n@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;\n@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;\n@group(2) @binding(1) var<uniform> material: MaterialUniforms;\n\nstruct VSOut {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n};\n\n@vertex fn vs(\n @location(0) position: vec3f,\n @location(1) normal: vec3f,\n @location(2) uv: vec2f,\n @location(3) joints0: vec4<u32>,\n @location(4) weights0: vec4<f32>,\n) -> VSOut {\n let pos4 = vec4f(position, 1.0);\n let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;\n let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);\n let w = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);\n var skinned = vec4f(0.0);\n for (var i = 0u; i < 4u; i++) {\n skinned += (skinMats[joints0[i]] * pos4) * w[i];\n }\n var o: VSOut;\n o.position = camera.projection * camera.view * vec4f(skinned.xyz, 1.0);\n o.uv = uv;\n return o;\n}\n\n@fragment fn fs(in: VSOut) {\n let a = material.alpha * textureSample(diffuseTexture, diffuseSampler, in.uv).a;\n if (a < 0.5) { discard; }\n}\n";
|
|
2
|
+
//# sourceMappingURL=depth-prepass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"depth-prepass.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/depth-prepass.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,8BAA8B,04CA2C1C,CAAA"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Depth-only prepass for the TRANSPARENT bucket. Transparent color draws keep
|
|
2
|
+
// depth write OFF so self-overlapping sheer cloth blends both layers instead of
|
|
3
|
+
// a triangle-order patchwork — but that leaves no depth record, so anything
|
|
4
|
+
// drawn later (the outline hulls) shows straight through the fabric as black
|
|
5
|
+
// shapes. This pass re-draws the transparent geometry depth-only AFTER its
|
|
6
|
+
// color pass: solid-enough texels (alpha ≥ 0.5) write depth, so outlines get
|
|
7
|
+
// occluded behind fabric exactly like they are behind opaque cloth, while
|
|
8
|
+
// truly sheer texels (a veil) stay non-occluding.
|
|
9
|
+
//
|
|
10
|
+
// Reuses mainPipelineLayout: camera g0b0, diffuseSampler g0b2, skinMats g1b0,
|
|
11
|
+
// diffuse texture g2b0, material uniforms g2b1 — the same bind groups the
|
|
12
|
+
// color draws already set, so drawing it costs no extra binding work.
|
|
13
|
+
export const TRANSPARENT_DEPTH_PREPASS_WGSL = /* wgsl */ `
|
|
14
|
+
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
15
|
+
struct MaterialUniforms {
|
|
16
|
+
diffuseColor: vec3f,
|
|
17
|
+
alpha: f32,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
21
|
+
@group(0) @binding(2) var diffuseSampler: sampler;
|
|
22
|
+
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
23
|
+
@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
24
|
+
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
25
|
+
|
|
26
|
+
struct VSOut {
|
|
27
|
+
@builtin(position) position: vec4f,
|
|
28
|
+
@location(0) uv: vec2f,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
@vertex fn vs(
|
|
32
|
+
@location(0) position: vec3f,
|
|
33
|
+
@location(1) normal: vec3f,
|
|
34
|
+
@location(2) uv: vec2f,
|
|
35
|
+
@location(3) joints0: vec4<u32>,
|
|
36
|
+
@location(4) weights0: vec4<f32>,
|
|
37
|
+
) -> VSOut {
|
|
38
|
+
let pos4 = vec4f(position, 1.0);
|
|
39
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
40
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
41
|
+
let w = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
42
|
+
var skinned = vec4f(0.0);
|
|
43
|
+
for (var i = 0u; i < 4u; i++) {
|
|
44
|
+
skinned += (skinMats[joints0[i]] * pos4) * w[i];
|
|
45
|
+
}
|
|
46
|
+
var o: VSOut;
|
|
47
|
+
o.position = camera.projection * camera.view * vec4f(skinned.xyz, 1.0);
|
|
48
|
+
o.uv = uv;
|
|
49
|
+
return o;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@fragment fn fs(in: VSOut) {
|
|
53
|
+
let a = material.alpha * textureSample(diffuseTexture, diffuseSampler, in.uv).a;
|
|
54
|
+
if (a < 0.5) { discard; }
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const GROUND_SHADOW_SHADER_WGSL = "\nstruct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };\nstruct Light { direction: vec4f, color: vec4f, };\nstruct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };\nstruct GroundShadowMat {\n diffuseColor: vec3f, fadeStart: f32,\n fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,\n gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, opacity: f32,\n gridLineColor: vec3f, _pad2: f32,\n};\nstruct LightVP { viewProj: mat4x4f, };\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n@group(0) @binding(1) var<uniform> light: LightUniforms;\n@group(0) @binding(2) var shadowMap: texture_depth_2d;\n@group(0) @binding(3) var shadowSampler: sampler_comparison;\n@group(0) @binding(4) var<uniform> material: GroundShadowMat;\n@group(0) @binding(5) var<uniform> lightVP: LightVP;\n\nfn hash2(p: vec2f) -> f32 {\n var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);\n p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));\n return fract((p3.x + p3.y) * p3.z);\n}\nfn valueNoise(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x),\n mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);\n}\nfn fbmNoise(p: vec2f) -> f32 {\n var v = 0.0;\n var a = 0.5;\n var pp = p;\n for (var i = 0; i < 4; i++) {\n v += a * valueNoise(pp);\n pp *= 2.0;\n a *= 0.5;\n }\n return v;\n}\n\nstruct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };\n@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {\n var o: VO; o.worldPos = position; o.normal = normal;\n o.position = camera.projection * camera.view * vec4f(position, 1.0); return o;\n}\nstruct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };\n@fragment fn fs(i: VO) -> FSOut {\n let n = normalize(i.normal);\n let centerDist = length(i.worldPos.xz);\n let edgeFade = 1.0 - smoothstep(0.0, 1.0, clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0));\n\n let lclip = lightVP.viewProj * vec4f(i.worldPos, 1.0);\n let ndc = lclip.xyz / max(lclip.w, 1e-6);\n let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\n let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));\n let st = material.pcfTexel;\n let compareZ = ndc.z - 0.0035;\n var vis = 0.0;\n for (var y = -2; y <= 2; y++) {\n for (var x = -2; x <= 2; x++) {\n vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(f32(x), f32(y)) * st, compareZ);\n }\n }\n vis *= 0.04;\n\n // Frosted/matte micro-texture\n let noiseVal = fbmNoise(i.worldPos.xz * 3.0);\n let noiseTint = 1.0 + (noiseVal - 0.5) * material.noiseStrength;\n\n // Grid lines \u2014 anti-aliased via screen-space derivatives\n let gp = i.worldPos.xz / material.gridSpacing;\n let gridFrac = abs(fract(gp - 0.5) - 0.5);\n let gridDeriv = fwidth(gp);\n let halfLine = material.gridLineWidth * 0.5;\n let gridLine = 1.0 - min(\n smoothstep(halfLine - gridDeriv.x, halfLine + gridDeriv.x, gridFrac.x),\n smoothstep(halfLine - gridDeriv.y, halfLine + gridDeriv.y, gridFrac.y)\n );\n 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);\n let dark = (1.0 - vis) * material.shadowStrength;\n var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);\n baseColor *= noiseTint;\n let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);\n // Whole-ground opacity fades the SURFACE (color, grid) but the shadow stays \u2014\n // as opacity drops, the received shadow becomes a translucent dark layer\n // (Blender's Shadow Catcher), so models still feel grounded on a photo or\n // 360 backdrop. At opacity 1 this reduces exactly to the plain surface.\n let surfA = edgeFade * material.opacity;\n let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity);\n let outA = surfA + catchA;\n var out: FSOut;\n out.color = vec4f(finalColor * surfA, outA);\n // mask.r = 0: ground never contributes to bloom. mask.g = 1.0 with src.a =\n // outA turns the aux blend into alpha-over, so the drawable alpha goes\n // from outA at the center to 0 at the radial edge \u2014 letting the page\n // background show through under the premultiplied canvas alphaMode.\n out.mask = vec4f(0.0, 1.0, 0.0, outA);\n return out;\n}\n";
|
|
1
|
+
export declare const GROUND_SHADOW_SHADER_WGSL = "\nstruct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };\nstruct Light { direction: vec4f, color: vec4f, };\nstruct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };\nstruct GroundShadowMat {\n diffuseColor: vec3f, fadeStart: f32,\n fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,\n gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, opacity: f32,\n gridLineColor: vec3f, _pad2: f32,\n};\nstruct LightVP { viewProj: mat4x4f, };\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n@group(0) @binding(1) var<uniform> light: LightUniforms;\n@group(0) @binding(2) var shadowMap: texture_depth_2d;\n@group(0) @binding(3) var shadowSampler: sampler_comparison;\n@group(0) @binding(4) var<uniform> material: GroundShadowMat;\n@group(0) @binding(5) var<uniform> lightVP: LightVP;\n\nfn hash2(p: vec2f) -> f32 {\n var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);\n p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));\n return fract((p3.x + p3.y) * p3.z);\n}\nfn valueNoise(p: vec2f) -> f32 {\n let i = floor(p);\n let f = fract(p);\n let u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x),\n mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);\n}\nfn fbmNoise(p: vec2f) -> f32 {\n var v = 0.0;\n var a = 0.5;\n var pp = p;\n for (var i = 0; i < 4; i++) {\n v += a * valueNoise(pp);\n pp *= 2.0;\n a *= 0.5;\n }\n return v;\n}\n\nstruct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };\n@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {\n var o: VO; o.worldPos = position; o.normal = normal;\n o.position = camera.projection * camera.view * vec4f(position, 1.0); return o;\n}\nstruct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };\n@fragment fn fs(i: VO) -> FSOut {\n let n = normalize(i.normal);\n let centerDist = length(i.worldPos.xz);\n let edgeFade = 1.0 - smoothstep(0.0, 1.0, clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0));\n\n let lclip = lightVP.viewProj * vec4f(i.worldPos, 1.0);\n let ndc = lclip.xyz / max(lclip.w, 1e-6);\n let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\n let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));\n let st = material.pcfTexel;\n let compareZ = ndc.z - 0.0035;\n var vis = 0.0;\n for (var y = -2; y <= 2; y++) {\n for (var x = -2; x <= 2; x++) {\n vis += textureSampleCompare(shadowMap, shadowSampler, suv_c + vec2f(f32(x), f32(y)) * st, compareZ);\n }\n }\n vis *= 0.04;\n // Outside the light's frustum there IS no shadow information \u2014 the clamped\n // border samples above compare against unrelated depths and read \"shadowed\",\n // darkening the whole far plane with a visible band at the frustum edge\n // (masked by the opaque surface normally, glaring in green-screen mode where\n // only the shadow-catcher term renders). Fade to fully lit near the border.\n let inZ = select(0.0, 1.0, ndc.z > 0.0 && ndc.z < 1.0);\n let frustum = (1.0 - smoothstep(0.88, 0.96, abs(ndc.x))) * (1.0 - smoothstep(0.88, 0.96, abs(ndc.y))) * inZ;\n vis = mix(1.0, vis, frustum);\n\n // Frosted/matte micro-texture\n let noiseVal = fbmNoise(i.worldPos.xz * 3.0);\n let noiseTint = 1.0 + (noiseVal - 0.5) * material.noiseStrength;\n\n // Grid lines \u2014 anti-aliased via screen-space derivatives\n let gp = i.worldPos.xz / material.gridSpacing;\n let gridFrac = abs(fract(gp - 0.5) - 0.5);\n let gridDeriv = fwidth(gp);\n let halfLine = material.gridLineWidth * 0.5;\n let gridLine = 1.0 - min(\n smoothstep(halfLine - gridDeriv.x, halfLine + gridDeriv.x, gridFrac.x),\n smoothstep(halfLine - gridDeriv.y, halfLine + gridDeriv.y, gridFrac.y)\n );\n 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);\n let dark = (1.0 - vis) * material.shadowStrength;\n var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);\n baseColor *= noiseTint;\n let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);\n // Whole-ground opacity fades the SURFACE (color, grid) but the shadow stays \u2014\n // as opacity drops, the received shadow becomes a translucent dark layer\n // (Blender's Shadow Catcher), so models still feel grounded on a photo or\n // 360 backdrop. At opacity 1 this reduces exactly to the plain surface.\n let surfA = edgeFade * material.opacity;\n let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity);\n let outA = surfA + catchA;\n var out: FSOut;\n out.color = vec4f(finalColor * surfA, outA);\n // mask.r = 0: ground never contributes to bloom. mask.g = 1.0 with src.a =\n // outA turns the aux blend into alpha-over, so the drawable alpha goes\n // from outA at the center to 0 at the radial edge \u2014 letting the page\n // background show through under the premultiplied canvas alphaMode.\n out.mask = vec4f(0.0, 1.0, 0.0, outA);\n return out;\n}\n";
|
|
2
2
|
//# sourceMappingURL=ground.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,
|
|
1
|
+
{"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,2kKA6GrC,CAAA"}
|
|
@@ -66,6 +66,14 @@ struct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
vis *= 0.04;
|
|
69
|
+
// Outside the light's frustum there IS no shadow information — the clamped
|
|
70
|
+
// border samples above compare against unrelated depths and read "shadowed",
|
|
71
|
+
// darkening the whole far plane with a visible band at the frustum edge
|
|
72
|
+
// (masked by the opaque surface normally, glaring in green-screen mode where
|
|
73
|
+
// only the shadow-catcher term renders). Fade to fully lit near the border.
|
|
74
|
+
let inZ = select(0.0, 1.0, ndc.z > 0.0 && ndc.z < 1.0);
|
|
75
|
+
let frustum = (1.0 - smoothstep(0.88, 0.96, abs(ndc.x))) * (1.0 - smoothstep(0.88, 0.96, abs(ndc.y))) * inZ;
|
|
76
|
+
vis = mix(1.0, vis, frustum);
|
|
69
77
|
|
|
70
78
|
// Frosted/matte micro-texture
|
|
71
79
|
let noiseVal = fbmNoise(i.worldPos.xz * 3.0);
|