reze-engine 0.50.2 → 0.50.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine.d.ts +294 -7
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +900 -109
- package/dist/shaders/cast-api.d.ts +1 -1
- package/dist/shaders/cast-api.d.ts.map +1 -1
- package/dist/shaders/cast-layout.d.ts +44 -1
- package/dist/shaders/cast-layout.d.ts.map +1 -1
- package/dist/shaders/cast-layout.js +44 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +7 -1
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +17 -9
- package/dist/shaders/passes/composite.d.ts +1 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/depth-prepass.d.ts +1 -1
- package/dist/shaders/passes/depth-prepass.d.ts.map +1 -1
- package/dist/shaders/passes/depth-prepass.js +52 -12
- package/dist/shaders/passes/ground.d.ts +16 -0
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +143 -49
- package/dist/shaders/passes/outline.d.ts +1 -1
- package/dist/shaders/passes/outline.d.ts.map +1 -1
- package/dist/shaders/passes/outline.js +12 -3
- package/dist/shaders/passes/particles.d.ts.map +1 -1
- package/dist/shaders/passes/particles.js +6 -2
- package/dist/shaders/passes/scene-contract.d.ts +38 -6
- package/dist/shaders/passes/scene-contract.d.ts.map +1 -1
- package/dist/shaders/passes/scene-contract.js +53 -16
- package/dist/shaders/passes/trails.d.ts.map +1 -1
- package/dist/shaders/passes/trails.js +36 -8
- package/package.json +2 -2
- package/src/engine.ts +952 -100
- package/src/shaders/cast-layout.ts +44 -1
- package/src/shaders/materials/common.ts +7 -1
- package/src/shaders/materials/nodes.ts +17 -9
- package/src/shaders/passes/depth-prepass.ts +53 -12
- package/src/shaders/passes/ground.ts +145 -49
- package/src/shaders/passes/outline.ts +14 -3
- package/src/shaders/passes/particles.ts +6 -2
- package/src/shaders/passes/scene-contract.ts +55 -16
- package/src/shaders/passes/trails.ts +36 -8
|
@@ -1,16 +1,34 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
1
|
+
// The depth-only prime, one module behind four pipelines.
|
|
2
|
+
//
|
|
3
|
+
// The oldest fps complaint the engine had — zoom close and the frame drops, in
|
|
4
|
+
// every material generation — was per-fragment shading TIMES layering: an MMD
|
|
5
|
+
// model at close-up is cloth over body over face under hair, author-order drawn,
|
|
6
|
+
// every buried layer fully shaded and then covered. This module lets depth go
|
|
7
|
+
// down FIRST so the colour passes shade each pixel once:
|
|
8
|
+
//
|
|
9
|
+
// · opaque prepass (CUTOFF 0.5) — plain auto-class opaque draws, before the
|
|
10
|
+
// opaque colour walk. See drawOpaqueDepthPrepass for who is in and why.
|
|
11
|
+
// · hair prime (CUTOFF 1.0, stencil not-equal) — between the non-hair and
|
|
12
|
+
// hair colour walks, fenced off the eye silhouette the see-through-hair
|
|
13
|
+
// stencil pass needs. See drawHairDepthPrime.
|
|
14
|
+
// · transparent solid prime (CUTOFF 1.0) — a translucent material's alpha-1
|
|
15
|
+
// texels, where over-blending is plain replacement and the buried work
|
|
16
|
+
// provably never shows. See drawTransparentSolidPrepass.
|
|
17
|
+
// · transparent depth prepass (CUTOFF 0.5, AFTER colour) — the original
|
|
18
|
+
// occupant, dormant: re-records sheer fabric's depth so outline hulls are
|
|
19
|
+
// occluded behind it. Kept for a future OIT path.
|
|
9
20
|
//
|
|
10
21
|
// Reuses mainPipelineLayout: camera g0b0, diffuseSampler g0b2, skinMats g1b0,
|
|
11
22
|
// diffuse texture g2b0, material uniforms g2b1 — the same bind groups the
|
|
12
23
|
// color draws already set, so drawing it costs no extra binding work.
|
|
13
|
-
|
|
24
|
+
//
|
|
25
|
+
// A FUNCTION rather than the constant it was, for the reason commonFsOutWgsl is
|
|
26
|
+
// one: the fragment outputs below depend on whether the device carries the id
|
|
27
|
+
// attachment, and that answer does not exist at import time. The constant could
|
|
28
|
+
// not have taken the outputs at all, which is most of why it did not have them.
|
|
29
|
+
import { sceneFsOutWgsl, sceneIdPadWgsl } from "./scene-contract";
|
|
30
|
+
export function transparentDepthPrepassWgsl() {
|
|
31
|
+
return /* wgsl */ `
|
|
14
32
|
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
15
33
|
struct MaterialUniforms {
|
|
16
34
|
diffuseColor: vec3f,
|
|
@@ -24,7 +42,9 @@ struct MaterialUniforms {
|
|
|
24
42
|
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
25
43
|
|
|
26
44
|
struct VSOut {
|
|
27
|
-
@
|
|
45
|
+
// @invariant, to the same end as the material VertexOutput: the colour pass
|
|
46
|
+
// must land on exactly the depths this wrote.
|
|
47
|
+
@builtin(position) @invariant position: vec4f,
|
|
28
48
|
@location(0) uv: vec2f,
|
|
29
49
|
};
|
|
30
50
|
|
|
@@ -49,8 +69,28 @@ struct VSOut {
|
|
|
49
69
|
return o;
|
|
50
70
|
}
|
|
51
71
|
|
|
52
|
-
|
|
72
|
+
// Every attachment the pass carries, declared and then not written: the
|
|
73
|
+
// pipeline takes all of them at writeMask 0 (see sceneTargets), so what this
|
|
74
|
+
// returns is discarded by the hardware and only the depth write survives — which
|
|
75
|
+
// is the entire purpose of the pass. Declaring them anyway is what keeps the
|
|
76
|
+
// pipeline valid on a browser that requires an output per target rather than
|
|
77
|
+
// per WRITTEN target. Costs one dead struct store on a fragment that already
|
|
78
|
+
// runs, because the alpha test below needs it to.
|
|
79
|
+
${sceneFsOutWgsl({ name: "PrepassOut", aux: "mask" })}
|
|
80
|
+
// The cutout threshold, per pipeline. 0.5 is the OPAQUE prime's "solid enough"
|
|
81
|
+
// — safe there because opaque colour replaces rather than blends. The
|
|
82
|
+
// TRANSPARENT prime overrides it to 1.0: a translucent fragment's blend reads
|
|
83
|
+
// what is behind it, so only a texel at EXACTLY alpha 1 — where over-blending
|
|
84
|
+
// collapses to plain replacement and the destination stops mattering — may
|
|
85
|
+
// claim depth ahead of its buried layers without changing the pixel.
|
|
86
|
+
override CUTOFF: f32 = 0.5;
|
|
87
|
+
@fragment fn fs(in: VSOut) -> PrepassOut {
|
|
53
88
|
let a = material.alpha * textureSample(diffuseTexture, diffuseSampler, in.uv).a;
|
|
54
|
-
if (a <
|
|
89
|
+
if (a < CUTOFF) { discard; }
|
|
90
|
+
var out: PrepassOut;
|
|
91
|
+
out.color = vec4f(0.0);
|
|
92
|
+
out.mask = vec4f(0.0);
|
|
93
|
+
${sceneIdPadWgsl("out")} return out;
|
|
55
94
|
}
|
|
56
95
|
`;
|
|
96
|
+
}
|
|
@@ -11,4 +11,20 @@ export declare function groundShaderWgsl(): string;
|
|
|
11
11
|
*/
|
|
12
12
|
export declare const GROUND_MATERIAL_ID = 65535;
|
|
13
13
|
export declare const GROUND_OBJECT_ID = 65535;
|
|
14
|
+
/** Texel edge of the baked frost tile. 1024 over a 64-p-unit period is two
|
|
15
|
+
* texels per finest-octave noise cell — the bake resolves everything the
|
|
16
|
+
* evaluation produced. r8unorm: the value lives in [0,1] and feeds a 0.05
|
|
17
|
+
* tint; eight bits is already 5x below what that tint can show. */
|
|
18
|
+
export declare const GROUND_NOISE_SIZE = 1024;
|
|
19
|
+
/**
|
|
20
|
+
* The frost fbm as a bake: identical math, periodic lattice.
|
|
21
|
+
*
|
|
22
|
+
* hash2/valueNoise/fbm are the ground's old helpers verbatim, with one change —
|
|
23
|
+
* each octave's lattice wraps at its own period (64 p-units at octave 0,
|
|
24
|
+
* doubling with frequency), so the tile is seamless under the repeat sampler.
|
|
25
|
+
* Wrapping only renames lattice cells on the seam; every interior cell hashes
|
|
26
|
+
* exactly as before, which is what makes the bake a relocation of the old
|
|
27
|
+
* pixels rather than a new look.
|
|
28
|
+
*/
|
|
29
|
+
export declare const GROUND_NOISE_BAKE_WGSL = "\n@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n let x = f32((vi & 1u) << 2u) - 1.0;\n let y = f32((vi & 2u) << 1u) - 1.0;\n return vec4f(x, y, 0.0, 1.0);\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 wrapCell(i: vec2f, period: f32) -> vec2f { return i - period * floor(i / period); }\nfn valueNoiseP(p: vec2f, period: f32) -> 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(wrapCell(i, period)), hash2(wrapCell(i + vec2f(1.0, 0.0), period)), u.x),\n mix(hash2(wrapCell(i + vec2f(0.0, 1.0), period)), hash2(wrapCell(i + vec2f(1.0, 1.0), period)), u.x), u.y);\n}\n@fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n let p = pos.xy * (64.0 / 1024.0);\n var v = 0.0;\n var a = 0.5;\n var pp = p;\n var period = 64.0;\n for (var o = 0; o < 4; o++) {\n v += a * valueNoiseP(pp, period);\n pp *= 2.0;\n period *= 2.0;\n a *= 0.5;\n }\n return vec4f(v, 0.0, 0.0, 1.0);\n}\n";
|
|
14
30
|
//# sourceMappingURL=ground.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAYA,wBAAgB,gBAAgB,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAYA,wBAAgB,gBAAgB,IAAI,MAAM,CAkUzC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,QAAS,CAAA;AACxC,eAAO,MAAM,gBAAgB,QAAS,CAAA;AAEtC;;;oEAGoE;AACpE,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAErC;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,+oCAiClC,CAAA"}
|
|
@@ -18,7 +18,13 @@ struct GroundShadowMat {
|
|
|
18
18
|
fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,
|
|
19
19
|
gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, opacity: f32,
|
|
20
20
|
gridLineColor: vec3f, mirror: f32,
|
|
21
|
-
|
|
21
|
+
// farCascade: 1 while a stage is loaded, 0 otherwise. See the branch below —
|
|
22
|
+
// with no stage the far map is never drawn into, so its taps are known.
|
|
23
|
+
mirrorBlur: f32, farCascade: f32, _mb1: f32, _mb2: f32,
|
|
24
|
+
// Every shadow caster in one sphere, refreshed per frame. w = radius; 0 means
|
|
25
|
+
// nothing casts, negative means "do not use this" (a rigid caster has no
|
|
26
|
+
// sphere, so a scene with a stage keeps the taps). See rzShadowPossible.
|
|
27
|
+
casterSphere: vec4f,
|
|
22
28
|
};
|
|
23
29
|
// One view-projection per shadow cascade, inner to outer — same buffer and
|
|
24
30
|
// same order the materials read.
|
|
@@ -40,33 +46,12 @@ struct MirrorVP { viewProj: mat4x4f, params: vec4f, };
|
|
|
40
46
|
@group(0) @binding(9) var mirrorTex: texture_2d<f32>;
|
|
41
47
|
@group(0) @binding(10) var linearSampler: sampler;
|
|
42
48
|
@group(0) @binding(11) var mirrorDepth: texture_depth_multisampled_2d;
|
|
49
|
+
// The frost noise, PRE-BAKED — see GROUND_NOISE_BAKE_WGSL below. Sampled with
|
|
50
|
+
// the repeat sampler already at binding 10.
|
|
51
|
+
@group(0) @binding(12) var noiseTex: texture_2d<f32>;
|
|
43
52
|
${WORLD_AMBIENT_WGSL}
|
|
44
53
|
${lightsApi(0, 6)}
|
|
45
54
|
|
|
46
|
-
fn hash2(p: vec2f) -> f32 {
|
|
47
|
-
var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
|
|
48
|
-
p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));
|
|
49
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
50
|
-
}
|
|
51
|
-
fn valueNoise(p: vec2f) -> f32 {
|
|
52
|
-
let i = floor(p);
|
|
53
|
-
let f = fract(p);
|
|
54
|
-
let u = f * f * (3.0 - 2.0 * f);
|
|
55
|
-
return mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x),
|
|
56
|
-
mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);
|
|
57
|
-
}
|
|
58
|
-
fn fbmNoise(p: vec2f) -> f32 {
|
|
59
|
-
var v = 0.0;
|
|
60
|
-
var a = 0.5;
|
|
61
|
-
var pp = p;
|
|
62
|
-
for (var i = 0; i < 4; i++) {
|
|
63
|
-
v += a * valueNoise(pp);
|
|
64
|
-
pp *= 2.0;
|
|
65
|
-
a *= 0.5;
|
|
66
|
-
}
|
|
67
|
-
return v;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
55
|
struct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };
|
|
71
56
|
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {
|
|
72
57
|
var o: VO; o.worldPos = position; o.normal = normal;
|
|
@@ -119,8 +104,63 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
119
104
|
// The far cascade's taps run ONLY where the near one is fading or absent
|
|
120
105
|
// (frustum < 1), so a pixel in the near core costs exactly what it did with
|
|
121
106
|
// one cascade — and that core is where the camera usually looks.
|
|
107
|
+
// Can anything cast a shadow ONTO this point at all?
|
|
108
|
+
//
|
|
109
|
+
// The ground is vastly larger than what stands on it, and the answer for most
|
|
110
|
+
// of it is no. A point is shadowed only if the ray from it toward the light
|
|
111
|
+
// meets a caster, so testing that ray against one bounding sphere decides in a
|
|
112
|
+
// few ALU ops what nine hardware-bilinear depth comparisons would otherwise be
|
|
113
|
+
// spent discovering. Same pixels: outside the sphere vis stays 1.0, which is
|
|
114
|
+
// exactly what the taps return.
|
|
115
|
+
//
|
|
116
|
+
// Conservative in all three directions that matter. The sphere is a union of
|
|
117
|
+
// per-model bounds built from POSED bones plus the skin margin, so a jump or a
|
|
118
|
+
// flying skirt is inside it. A negative radius disables the test outright. And
|
|
119
|
+
// the halfspace check uses -R rather than 0, so a caster the point is standing
|
|
120
|
+
// inside still counts.
|
|
121
|
+
var shadowPossible = material.casterSphere.w > 0.0;
|
|
122
|
+
if (shadowPossible) {
|
|
123
|
+
let toLight = normalize(-light.lights[0].direction.xyz);
|
|
124
|
+
let toCaster = material.casterSphere.xyz - i.worldPos;
|
|
125
|
+
let along = dot(toCaster, toLight);
|
|
126
|
+
let perp = length(toCaster - toLight * along);
|
|
127
|
+
shadowPossible = along > -material.casterSphere.w && perp <= material.casterSphere.w;
|
|
128
|
+
} else {
|
|
129
|
+
// Negative radius is the opt-out, not "nothing casts".
|
|
130
|
+
shadowPossible = material.casterSphere.w < 0.0;
|
|
131
|
+
}
|
|
132
|
+
|
|
122
133
|
var vis = 1.0;
|
|
123
|
-
|
|
134
|
+
// The whole cascade lookup, behind the strength dial.
|
|
135
|
+
//
|
|
136
|
+
// This is a FULL-COVERAGE draw and each branch below is nine comparison-sampler
|
|
137
|
+
// taps per pixel — eighteen where the cascades overlap — so on a retina canvas
|
|
138
|
+
// it is tens of millions of shadow-map fetches a frame. All of it was computed
|
|
139
|
+
// and then multiplied by material.shadowStrength, which is ZERO whenever the
|
|
140
|
+
// host turns the ground's shadow catcher off. Turning shadows off in the UI
|
|
141
|
+
// therefore cost exactly what leaving them on cost, which is what a report of
|
|
142
|
+
// "no fps gain from disabling shadows" looks like from the outside.
|
|
143
|
+
//
|
|
144
|
+
// shadowStrength is a uniform, so this branch is uniform across the draw:
|
|
145
|
+
// every invocation takes the same side and the taps are genuinely skipped
|
|
146
|
+
// rather than merely masked. The dark term below is (1.0 - vis) * strength, so
|
|
147
|
+
// with strength at zero the result is identical either way — a pure
|
|
148
|
+
// deletion of work, not a change of look.
|
|
149
|
+
//
|
|
150
|
+
// The same reasoning the noise tint below already got, applied to the term
|
|
151
|
+
// that costs a hundred times more.
|
|
152
|
+
if (material.shadowStrength > 0.0 && shadowPossible) {
|
|
153
|
+
// The far cascade's taps, skipped entirely when nothing ever drew into it.
|
|
154
|
+
//
|
|
155
|
+
// This branch is the expensive one on a wide floor: it runs wherever the NEAR
|
|
156
|
+
// cascade does not reach, and the near cascade is a 64-unit box around the
|
|
157
|
+
// camera target, so a floor receding to the horizon takes it almost
|
|
158
|
+
// everywhere. Nine comparison taps, per pixel, on the largest draw in the
|
|
159
|
+
// frame — against a map that is cleared unless a stage is in the scene.
|
|
160
|
+
//
|
|
161
|
+
// Cleared depth compares as "no occluder", so the skipped path leaves vis at
|
|
162
|
+
// 1.0, which is exactly what the taps returned. Free, not cheaper.
|
|
163
|
+
if (material.farCascade > 0.0 && frustum < 1.0 && frustum1 > 0.0) {
|
|
124
164
|
let suv1 = vec2f(ndc1.x * 0.5 + 0.5, 0.5 - ndc1.y * 0.5);
|
|
125
165
|
let suv1_c = clamp(suv1, vec2f(0.02), vec2f(0.98));
|
|
126
166
|
let st1 = ${1 / SHADOW_CASCADES[SHADOW_CASCADES.length - 1].mapSize} * 2.0;
|
|
@@ -150,12 +190,27 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
150
190
|
// cascade to cascade rather than snapping to lit mid-floor.
|
|
151
191
|
vis = mix(vis, acc * (1.0 / 9.0), frustum);
|
|
152
192
|
}
|
|
193
|
+
}
|
|
153
194
|
|
|
154
195
|
// Frosted/matte micro-texture. Scenes leaving it at zero were still paying
|
|
155
196
|
// sixteen hash rounds a pixel for a tint of exactly 1.
|
|
156
197
|
var noiseTint = 1.0;
|
|
157
198
|
if (material.noiseStrength != 0.0) {
|
|
158
|
-
|
|
199
|
+
// One texture sample where four octaves of value noise used to be
|
|
200
|
+
// EVALUATED — sixteen hash rounds per pixel, on the largest draw in the
|
|
201
|
+
// frame. Bisection on the device that was slow pinned the ground's whole
|
|
202
|
+
// cost here: flat colour was smooth, the shader minus its shadow taps was
|
|
203
|
+
// still slow, and the shader minus only this was smooth. (0.33.2 built
|
|
204
|
+
// this same bake and measured no win — the cost then was the PCF. The
|
|
205
|
+
// instrument was right both times; the term changed.)
|
|
206
|
+
//
|
|
207
|
+
// The tile is the SAME fbm, baked once at init over a 64-p-unit period
|
|
208
|
+
// (~21 world units before it repeats — invisible at frost strengths), and
|
|
209
|
+
// the interior of the tile is bit-identical to the old evaluation because
|
|
210
|
+
// only lattice cells on the seam are wrapped. Sampled at level 0 with the
|
|
211
|
+
// repeat sampler: the old evaluation had no filtering either, so distant
|
|
212
|
+
// sparkle is unchanged rather than quietly "improved".
|
|
213
|
+
let noiseVal = textureSampleLevel(noiseTex, linearSampler, i.worldPos.xz * ${3 / 64}, 0.0).r;
|
|
159
214
|
noiseTint = 1.0 + (noiseVal - 0.5) * material.noiseStrength;
|
|
160
215
|
}
|
|
161
216
|
|
|
@@ -184,8 +239,8 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
184
239
|
// reflection was rendered by the MIRROR camera, so projecting this
|
|
185
240
|
// fragment's world position through that camera yields exactly the texel
|
|
186
241
|
// where its reflection landed — projective mapping, no screen-space guess.
|
|
187
|
-
// There is deliberately NO strength dial
|
|
188
|
-
//
|
|
242
|
+
// There is deliberately NO strength dial: mirror is on or off, and how much
|
|
243
|
+
// of it shows is the SURFACE's own opacity covering it — the same
|
|
189
244
|
// independence the grid won earlier. The branch is on a uniform, so the
|
|
190
245
|
// whole cost vanishes for the scenes that leave it off.
|
|
191
246
|
var reflShadowed = vec3f(0.0);
|
|
@@ -248,28 +303,18 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
248
303
|
// and a catcher stacked on top would darken the same shadow twice.
|
|
249
304
|
let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity) * (1.0 - material.mirror);
|
|
250
305
|
|
|
251
|
-
// FOUR LAYERS, premultiplied, bottom to top: received shadow,
|
|
252
|
-
// surface,
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
//
|
|
261
|
-
// thing it is made of happens to be.
|
|
262
|
-
//
|
|
263
|
-
// The catcher's order against it no longer matters — catchA already carries
|
|
264
|
-
// (1 - mirror), so the two are never both present.
|
|
265
|
-
var pm = vec3f(0.0);
|
|
266
|
-
var cov = catchA;
|
|
267
|
-
// Surface over the received shadow.
|
|
306
|
+
// FOUR LAYERS, premultiplied, bottom to top: reflection, received shadow,
|
|
307
|
+
// ground surface, grid. Each with its own coverage, none scaling another —
|
|
308
|
+
// ground opacity REVEALS the mirror rather than the mirror carrying a
|
|
309
|
+
// strength of its own.
|
|
310
|
+
var pm = reflShadowed * mirrA;
|
|
311
|
+
var cov = mirrA;
|
|
312
|
+
// Shadow catcher over the reflection-or-nothing: black, it covers.
|
|
313
|
+
pm = pm * (1.0 - catchA);
|
|
314
|
+
cov = catchA + cov * (1.0 - catchA);
|
|
315
|
+
// Surface over shadow.
|
|
268
316
|
pm = baseColor * surfA + pm * (1.0 - surfA);
|
|
269
317
|
cov = surfA + cov * (1.0 - surfA);
|
|
270
|
-
// The reflection over the surface, at full strength whatever the opacity.
|
|
271
|
-
pm = reflShadowed * mirrA + pm * (1.0 - mirrA);
|
|
272
|
-
cov = mirrA + cov * (1.0 - mirrA);
|
|
273
318
|
// Grid over surface. Unshadowed, as it has always been — a painted line reads
|
|
274
319
|
// as paint, and darkening it was never what the mix did either.
|
|
275
320
|
pm = material.gridLineColor * gridA + pm * (1.0 - gridA);
|
|
@@ -298,3 +343,52 @@ ${sceneIdWriteWgsl("out", `${GROUND_MATERIAL_ID}u`, `${GROUND_OBJECT_ID}u`)} re
|
|
|
298
343
|
*/
|
|
299
344
|
export const GROUND_MATERIAL_ID = 0xffff;
|
|
300
345
|
export const GROUND_OBJECT_ID = 0xffff;
|
|
346
|
+
/** Texel edge of the baked frost tile. 1024 over a 64-p-unit period is two
|
|
347
|
+
* texels per finest-octave noise cell — the bake resolves everything the
|
|
348
|
+
* evaluation produced. r8unorm: the value lives in [0,1] and feeds a 0.05
|
|
349
|
+
* tint; eight bits is already 5x below what that tint can show. */
|
|
350
|
+
export const GROUND_NOISE_SIZE = 1024;
|
|
351
|
+
/**
|
|
352
|
+
* The frost fbm as a bake: identical math, periodic lattice.
|
|
353
|
+
*
|
|
354
|
+
* hash2/valueNoise/fbm are the ground's old helpers verbatim, with one change —
|
|
355
|
+
* each octave's lattice wraps at its own period (64 p-units at octave 0,
|
|
356
|
+
* doubling with frequency), so the tile is seamless under the repeat sampler.
|
|
357
|
+
* Wrapping only renames lattice cells on the seam; every interior cell hashes
|
|
358
|
+
* exactly as before, which is what makes the bake a relocation of the old
|
|
359
|
+
* pixels rather than a new look.
|
|
360
|
+
*/
|
|
361
|
+
export const GROUND_NOISE_BAKE_WGSL = /* wgsl */ `
|
|
362
|
+
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
363
|
+
let x = f32((vi & 1u) << 2u) - 1.0;
|
|
364
|
+
let y = f32((vi & 2u) << 1u) - 1.0;
|
|
365
|
+
return vec4f(x, y, 0.0, 1.0);
|
|
366
|
+
}
|
|
367
|
+
fn hash2(p: vec2f) -> f32 {
|
|
368
|
+
var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
|
|
369
|
+
p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));
|
|
370
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
371
|
+
}
|
|
372
|
+
fn wrapCell(i: vec2f, period: f32) -> vec2f { return i - period * floor(i / period); }
|
|
373
|
+
fn valueNoiseP(p: vec2f, period: f32) -> f32 {
|
|
374
|
+
let i = floor(p);
|
|
375
|
+
let f = fract(p);
|
|
376
|
+
let u = f * f * (3.0 - 2.0 * f);
|
|
377
|
+
return mix(mix(hash2(wrapCell(i, period)), hash2(wrapCell(i + vec2f(1.0, 0.0), period)), u.x),
|
|
378
|
+
mix(hash2(wrapCell(i + vec2f(0.0, 1.0), period)), hash2(wrapCell(i + vec2f(1.0, 1.0), period)), u.x), u.y);
|
|
379
|
+
}
|
|
380
|
+
@fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
|
|
381
|
+
let p = pos.xy * (64.0 / ${GROUND_NOISE_SIZE}.0);
|
|
382
|
+
var v = 0.0;
|
|
383
|
+
var a = 0.5;
|
|
384
|
+
var pp = p;
|
|
385
|
+
var period = 64.0;
|
|
386
|
+
for (var o = 0; o < 4; o++) {
|
|
387
|
+
v += a * valueNoiseP(pp, period);
|
|
388
|
+
pp *= 2.0;
|
|
389
|
+
period *= 2.0;
|
|
390
|
+
a *= 0.5;
|
|
391
|
+
}
|
|
392
|
+
return vec4f(v, 0.0, 0.0, 1.0);
|
|
393
|
+
}
|
|
394
|
+
`;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare function outlineShaderWgsl(): string;
|
|
2
2
|
//# sourceMappingURL=outline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outline.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/outline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outline.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/outline.ts"],"names":[],"mappings":"AAoBA,wBAAgB,iBAAiB,IAAI,MAAM,CAqG1C"}
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
// MODULATES the rim's alpha by it (discarding only near-zero cut-out
|
|
12
12
|
// margins) — sheer fabric gets a proportional rim, never a solid black
|
|
13
13
|
// hull, without dropping the author's edge flag.
|
|
14
|
-
|
|
14
|
+
// A FUNCTION rather than a constant, for the reason commonFsOutWgsl is one: the
|
|
15
|
+
// fragment outputs depend on whether the device carries the id attachment, and
|
|
16
|
+
// a string baked at import cannot know what the device said.
|
|
17
|
+
import { sceneFsOutWgsl, sceneIdPadWgsl } from "./scene-contract";
|
|
18
|
+
export function outlineShaderWgsl() {
|
|
19
|
+
return /* wgsl */ `
|
|
15
20
|
struct CameraUniforms {
|
|
16
21
|
view: mat4x4f,
|
|
17
22
|
projection: mat4x4f,
|
|
@@ -90,7 +95,10 @@ struct VertexOutput {
|
|
|
90
95
|
return output;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
// The id output is declared and padded, never written for real: a hull would
|
|
99
|
+
// overwrite the id of the body it traces, so the pipeline takes the id target at
|
|
100
|
+
// writeMask 0. Declared all the same — see sceneFsOutWgsl.
|
|
101
|
+
${sceneFsOutWgsl({ name: "FSOut", aux: "mask" })}
|
|
94
102
|
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
95
103
|
// Rim alpha FOLLOWS the fabric's texture alpha instead of a hard alpha test:
|
|
96
104
|
// MMD draws blend-material edges solid (only cutout materials alpha-test), so
|
|
@@ -105,6 +113,7 @@ struct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };
|
|
|
105
113
|
var out: FSOut;
|
|
106
114
|
out.color = vec4f(material.edgeColor.rgb, material.edgeColor.a * texA);
|
|
107
115
|
out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
|
|
108
|
-
return out;
|
|
116
|
+
${sceneIdPadWgsl("out")} return out;
|
|
109
117
|
}
|
|
110
118
|
`;
|
|
119
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/particles.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/particles.ts"],"names":[],"mappings":"AAyBA,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb;;mFAE+E;IAC/E,UAAU,EAAE,MAAM,CAAA;IAClB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAiCD,qDAAqD;AACrD,KAAK,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEzC,KAAK,cAAc,GAAG;IACpB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,aAAa,CAAA;IACpB,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,oEAAoE;AACpE,eAAO,MAAM,eAAe,KAAK,CAAA;AAkDjC,oDAAoD;AACpD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOpE;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAE9D;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAMlG;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA0CxF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAgHvF"}
|
|
@@ -5,6 +5,7 @@ import { anchorAliasWgsl } from "../anchor-table";
|
|
|
5
5
|
import { midiApi } from "../midi-api";
|
|
6
6
|
import { CAST_API } from "../cast-api";
|
|
7
7
|
import { clockApi, EFFECT_MATH_API, PARTICLE_STRUCT_WGSL, trailSlotsApi, viewportApi } from "./hosted-api";
|
|
8
|
+
import { sceneIdFieldWgsl, sceneIdPadWgsl } from "./scene-contract";
|
|
8
9
|
/**
|
|
9
10
|
* The trail accessors, in the PARTICLE module.
|
|
10
11
|
*
|
|
@@ -247,7 +248,10 @@ struct FSOut {
|
|
|
247
248
|
// rejected outright — "reading alpha but it is missing from fragment output".
|
|
248
249
|
// The material shaders declare vec4f here for the same reason.
|
|
249
250
|
@location(1) mask: vec4f,
|
|
250
|
-
|
|
251
|
+
// Declared whenever the pass carries the attachment, and padded rather than
|
|
252
|
+
// written: a particle is not a thing you would address by id, so its pipeline
|
|
253
|
+
// takes this target at writeMask 0. Declared anyway — see sceneFsOutWgsl.
|
|
254
|
+
${sceneIdFieldWgsl()}}
|
|
251
255
|
|
|
252
256
|
@fragment
|
|
253
257
|
fn fs(in: VSOut) -> FSOut {
|
|
@@ -273,7 +277,7 @@ fn fs(in: VSOut) -> FSOut {
|
|
|
273
277
|
// not geometry, was the banding that survived every geometry fix.
|
|
274
278
|
let mg = select(vec2f(select(0.0, 1.0, BLOOM), 1.0), vec2f(select(0.0, c.a, BLOOM), c.a), ADDITIVE);
|
|
275
279
|
out.mask = vec4f(mg.x, mg.y, 0.0, c.a);
|
|
276
|
-
return out;
|
|
280
|
+
${sceneIdPadWgsl("out")} return out;
|
|
277
281
|
}
|
|
278
282
|
`);
|
|
279
283
|
}
|
|
@@ -105,12 +105,19 @@ export declare function sceneColorFormats(formats: SceneFormats): GPUTextureForm
|
|
|
105
105
|
* author. The struct and the targets above have to agree on count and order,
|
|
106
106
|
* and they now disagree in one file rather than in five.
|
|
107
107
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
108
|
+
* EVERY scene-pass shader takes this — outline, particles, ribbons and the
|
|
109
|
+
* depth prepass included, none of which write a meaningful id (and the prepass
|
|
110
|
+
* no meaningful colour either). They did not, once. The reasoning was that a
|
|
111
|
+
* target at writeMask 0 needs no matching output: true of Dawn, and the reading
|
|
112
|
+
* of gpuweb#1918 this file used to assert. It is not a reading every browser
|
|
113
|
+
* shares, and the failure mode when a browser disagrees is the worst kind —
|
|
114
|
+
* createRenderPipeline does not throw, it returns a pipeline that is already
|
|
115
|
+
* invalid, and the pass that binds it is dropped whole. Missing geometry, clean
|
|
116
|
+
* console, and a bug that reproduces on one vendor's browser only.
|
|
117
|
+
*
|
|
118
|
+
* So the rule is now the strict one, and it costs nothing to hold: declare
|
|
119
|
+
* every output the pass has attachments for, and let writeMask decide what is
|
|
120
|
+
* kept. A shader with no id to write pads it — see sceneIdPadWgsl.
|
|
114
121
|
*/
|
|
115
122
|
export declare function sceneFsOutWgsl(opts?: {
|
|
116
123
|
name?: string;
|
|
@@ -124,5 +131,30 @@ export declare function sceneFsOutWgsl(opts?: {
|
|
|
124
131
|
* ask the device what it supports.
|
|
125
132
|
*/
|
|
126
133
|
export declare function sceneIdWriteWgsl(out: string, material: string, object: string): string;
|
|
134
|
+
/**
|
|
135
|
+
* Just the id FIELD, for a shader that keeps its own struct.
|
|
136
|
+
*
|
|
137
|
+
* Particles and ribbons declare their outputs by hand because the comments on
|
|
138
|
+
* their aux field are about their own blend and belong beside it — taking the
|
|
139
|
+
* whole struct from sceneFsOutWgsl would move that prose away from what it
|
|
140
|
+
* explains. They still need the id output when the pass has the attachment, so
|
|
141
|
+
* they splice this in and pad it with sceneIdPadWgsl.
|
|
142
|
+
*/
|
|
143
|
+
export declare function sceneIdFieldWgsl(): string;
|
|
144
|
+
/**
|
|
145
|
+
* The id assignment for a shader that has no id to give.
|
|
146
|
+
*
|
|
147
|
+
* The counterpart to the strict rule on sceneFsOutWgsl: outline hulls,
|
|
148
|
+
* particles, ribbons and the depth prepass all declare the output because the
|
|
149
|
+
* pass has the attachment, and all of them take writeMask 0, so what they
|
|
150
|
+
* assign is never stored. Zero, which is the reserved "nothing" id the pass
|
|
151
|
+
* clears to — so if one of these ever did reach the target, it would read as
|
|
152
|
+
* absence rather than as a plausible wrong answer pointing at material 0.
|
|
153
|
+
*
|
|
154
|
+
* A separate function from sceneIdWriteWgsl rather than a call to it with "0u"
|
|
155
|
+
* twice, because the two say different things: that one records an identity,
|
|
156
|
+
* this one satisfies a declaration.
|
|
157
|
+
*/
|
|
158
|
+
export declare function sceneIdPadWgsl(out: string): string;
|
|
127
159
|
export {};
|
|
128
160
|
//# sourceMappingURL=scene-contract.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-contract.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/scene-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;uDAEuD;AACvD,MAAM,MAAM,YAAY,GAAG;IACzB,+CAA+C;IAC/C,GAAG,EAAE,gBAAgB,CAAA;IACrB,iEAAiE;IACjE,GAAG,EAAE,gBAAgB,CAAA;CACtB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,gBAA6B,CAAA;AAkB3D,8DAA8D;AAC9D,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAE3C;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;;;GAMG;AACH,KAAK,gBAAgB;AACnB,2DAA2D;AACzD,UAAU;AACZ;;+CAE+C;GAC7C,QAAQ;AACV,uEAAuE;GACrE,SAAS;AACX,iEAAiE;GAC/D,UAAU;AACZ;;;mDAGmD;GACjD,mBAAmB;AACrB;;;oCAGoC;GAClC,OAAO;AACT;;;kDAGkD;GAChD,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"scene-contract.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/scene-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;uDAEuD;AACvD,MAAM,MAAM,YAAY,GAAG;IACzB,+CAA+C;IAC/C,GAAG,EAAE,gBAAgB,CAAA;IACrB,iEAAiE;IACjE,GAAG,EAAE,gBAAgB,CAAA;CACtB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,gBAA6B,CAAA;AAkB3D,8DAA8D;AAC9D,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAE3C;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;;;GAMG;AACH,KAAK,gBAAgB;AACnB,2DAA2D;AACzD,UAAU;AACZ;;+CAE+C;GAC7C,QAAQ;AACV,uEAAuE;GACrE,SAAS;AACX,iEAAiE;GAC/D,UAAU;AACZ;;;mDAGmD;GACjD,mBAAmB;AACrB;;;oCAGoC;GAClC,OAAO;AACT;;;kDAGkD;GAChD,eAAe,CAAA;AA0EnB;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,GAAG,mBAAmB,EAAE,CAoBhG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,gBAAgB,EAAE,CAE3E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAmB7E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -89,12 +89,14 @@ const ADD_PREMULTIPLIED = {
|
|
|
89
89
|
alpha: { srcFactor: "zero", dstFactor: "one", operation: "add" },
|
|
90
90
|
};
|
|
91
91
|
/**
|
|
92
|
-
* Which classes
|
|
92
|
+
* Which classes write a MEANINGFUL id.
|
|
93
93
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
94
|
+
* Not the same question as which shaders declare the output — every scene-pass
|
|
95
|
+
* shader declares all three now (see sceneFsOutWgsl). This set decides only
|
|
96
|
+
* whose id survives: writeMask 0xf here, writeMask 0 for everyone else, so the
|
|
97
|
+
* others compute an id nothing stores. Leaving the target off those pipelines
|
|
98
|
+
* is not available: every pipeline in a pass must agree with the pass's
|
|
99
|
+
* attachments.
|
|
98
100
|
*
|
|
99
101
|
* The ground is in because a mark placed by id needs the floor to have one.
|
|
100
102
|
* Transparent fabric writes ids too — dissolving a dress needs the dress's own
|
|
@@ -127,11 +129,10 @@ const BLENDS = {
|
|
|
127
129
|
*/
|
|
128
130
|
export function sceneTargets(cls, formats) {
|
|
129
131
|
const targets = cls === "depth-prepass"
|
|
130
|
-
? // Format only, and writeMask 0
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
// direction, and it is the only one this file ever uses.
|
|
132
|
+
? // Format only, and writeMask 0 — the pipeline writes no colour. It still
|
|
133
|
+
// DECLARES every output, which is the part that used to be missing; see
|
|
134
|
+
// the note on sceneFsOutWgsl about why writeMask 0 is not a licence to
|
|
135
|
+
// leave the output off.
|
|
135
136
|
[
|
|
136
137
|
{ format: formats.hdr, writeMask: 0 },
|
|
137
138
|
{ format: formats.aux, writeMask: 0 },
|
|
@@ -168,12 +169,19 @@ export function sceneColorFormats(formats) {
|
|
|
168
169
|
* author. The struct and the targets above have to agree on count and order,
|
|
169
170
|
* and they now disagree in one file rather than in five.
|
|
170
171
|
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
172
|
+
* EVERY scene-pass shader takes this — outline, particles, ribbons and the
|
|
173
|
+
* depth prepass included, none of which write a meaningful id (and the prepass
|
|
174
|
+
* no meaningful colour either). They did not, once. The reasoning was that a
|
|
175
|
+
* target at writeMask 0 needs no matching output: true of Dawn, and the reading
|
|
176
|
+
* of gpuweb#1918 this file used to assert. It is not a reading every browser
|
|
177
|
+
* shares, and the failure mode when a browser disagrees is the worst kind —
|
|
178
|
+
* createRenderPipeline does not throw, it returns a pipeline that is already
|
|
179
|
+
* invalid, and the pass that binds it is dropped whole. Missing geometry, clean
|
|
180
|
+
* console, and a bug that reproduces on one vendor's browser only.
|
|
181
|
+
*
|
|
182
|
+
* So the rule is now the strict one, and it costs nothing to hold: declare
|
|
183
|
+
* every output the pass has attachments for, and let writeMask decide what is
|
|
184
|
+
* kept. A shader with no id to write pads it — see sceneIdPadWgsl.
|
|
177
185
|
*/
|
|
178
186
|
export function sceneFsOutWgsl(opts) {
|
|
179
187
|
const name = opts?.name ?? "FSOut";
|
|
@@ -205,3 +213,32 @@ ${id}};
|
|
|
205
213
|
export function sceneIdWriteWgsl(out, material, object) {
|
|
206
214
|
return mrtIds ? ` ${out}.id = vec2u(${material}, ${object});\n` : "";
|
|
207
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Just the id FIELD, for a shader that keeps its own struct.
|
|
218
|
+
*
|
|
219
|
+
* Particles and ribbons declare their outputs by hand because the comments on
|
|
220
|
+
* their aux field are about their own blend and belong beside it — taking the
|
|
221
|
+
* whole struct from sceneFsOutWgsl would move that prose away from what it
|
|
222
|
+
* explains. They still need the id output when the pass has the attachment, so
|
|
223
|
+
* they splice this in and pad it with sceneIdPadWgsl.
|
|
224
|
+
*/
|
|
225
|
+
export function sceneIdFieldWgsl() {
|
|
226
|
+
return mrtIds ? ` @location(2) id: vec2u,\n` : "";
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The id assignment for a shader that has no id to give.
|
|
230
|
+
*
|
|
231
|
+
* The counterpart to the strict rule on sceneFsOutWgsl: outline hulls,
|
|
232
|
+
* particles, ribbons and the depth prepass all declare the output because the
|
|
233
|
+
* pass has the attachment, and all of them take writeMask 0, so what they
|
|
234
|
+
* assign is never stored. Zero, which is the reserved "nothing" id the pass
|
|
235
|
+
* clears to — so if one of these ever did reach the target, it would read as
|
|
236
|
+
* absence rather than as a plausible wrong answer pointing at material 0.
|
|
237
|
+
*
|
|
238
|
+
* A separate function from sceneIdWriteWgsl rather than a call to it with "0u"
|
|
239
|
+
* twice, because the two say different things: that one records an identity,
|
|
240
|
+
* this one satisfies a declaration.
|
|
241
|
+
*/
|
|
242
|
+
export function sceneIdPadWgsl(out) {
|
|
243
|
+
return mrtIds ? ` ${out}.id = vec2u(0u, 0u);\n` : "";
|
|
244
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"AAyBA,KAAK,WAAW,GAAG;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb;oEACgE;IAChE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAKjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;kFAI8E;IAC9E,SAAS,EAAE,OAAO,CAAA;CACnB,GACA,MAAM,CAwUR"}
|