reze-engine 0.58.0 → 0.58.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/effect-points.d.ts +30 -0
- package/dist/effect-points.d.ts.map +1 -0
- package/dist/effect-points.js +49 -0
- package/dist/engine.d.ts +30 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +149 -19
- package/dist/light-grid.d.ts +25 -0
- package/dist/light-grid.d.ts.map +1 -0
- package/dist/light-grid.js +91 -0
- package/dist/model.d.ts +9 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +11 -6
- package/dist/shaders/cast-api.d.ts +1 -1
- package/dist/shaders/cast-layout.d.ts +5 -3
- package/dist/shaders/cast-layout.d.ts.map +1 -1
- package/dist/shaders/cast-layout.js +5 -3
- package/dist/shaders/directives.d.ts +3 -0
- package/dist/shaders/directives.d.ts.map +1 -1
- package/dist/shaders/directives.js +5 -0
- package/dist/shaders/lights.d.ts +16 -19
- package/dist/shaders/lights.d.ts.map +1 -1
- package/dist/shaders/lights.js +121 -88
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +3 -17
- package/dist/shaders/passes/composite.d.ts +1 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +4 -0
- package/dist/shaders/passes/grid.d.ts.map +1 -1
- package/dist/shaders/passes/grid.js +4 -0
- package/dist/shaders/passes/particles.d.ts +4 -0
- package/dist/shaders/passes/particles.d.ts.map +1 -1
- package/dist/shaders/passes/particles.js +13 -1
- package/dist/shaders/passes/trails.d.ts.map +1 -1
- package/dist/shaders/passes/trails.js +3 -1
- package/dist/shaders/points-api.d.ts +11 -0
- package/dist/shaders/points-api.d.ts.map +1 -0
- package/dist/shaders/points-api.js +58 -0
- package/dist/shaders/scene-light-api.d.ts +42 -0
- package/dist/shaders/scene-light-api.d.ts.map +1 -0
- package/dist/shaders/scene-light-api.js +144 -0
- package/dist/shaders/shadow-api.d.ts +29 -0
- package/dist/shaders/shadow-api.d.ts.map +1 -0
- package/dist/shaders/shadow-api.js +112 -0
- package/package.json +1 -1
- package/src/effect-points.ts +67 -0
- package/src/engine.ts +163 -18
- package/src/light-grid.ts +139 -0
- package/src/model.ts +12 -0
- package/src/pmx-loader.ts +13 -6
- package/src/shaders/cast-layout.ts +5 -3
- package/src/shaders/directives.ts +8 -0
- package/src/shaders/lights.ts +121 -88
- package/src/shaders/materials/common.ts +3 -17
- package/src/shaders/passes/composite.ts +4 -0
- package/src/shaders/passes/grid.ts +4 -0
- package/src/shaders/passes/particles.ts +14 -1
- package/src/shaders/passes/trails.ts +3 -1
- package/src/shaders/points-api.ts +66 -0
- package/src/shaders/scene-light-api.ts +148 -0
|
@@ -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":"AA6BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAgC+B;AAC/B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,CAIhG;AAID,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAEzG,KAAK,qBAAqB,GAAG;IAC3B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,aAAa,EAAE,OAAO,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAA;IACtB;6DACyD;IACzD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB;mFAC+E;IAC/E,GAAG,CAAC,EAAE,OAAO,CAAA;IACb;;;yEAGqE;IACrE,UAAU,EAAE,MAAM,CAAA;IAClB;;8EAE0E;IAC1E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AA+LD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,+ufAsI5B,CAAA;AA8MD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAclF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CA0GtE;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,QAA6B,CAAA"}
|
|
@@ -5,6 +5,8 @@ import { CAST_API, CAST_MASK_ALL, subjectMaskApi } from "../cast-api";
|
|
|
5
5
|
import { clockApi, EFFECT_MATH_API, PARTICLE_STRUCT_WGSL, trailSlotsApi, viewportApi } from "./hosted-api";
|
|
6
6
|
import { audioApi } from "../audio-api";
|
|
7
7
|
import { idApi } from "../id-api";
|
|
8
|
+
import { sceneLightApi } from "../scene-light-api";
|
|
9
|
+
import { pointsApi } from "../points-api";
|
|
8
10
|
import { castDistanceApi, CAST_FIELD_DIV } from "./cast-distance";
|
|
9
11
|
import { lyricsApi, lyricsTextApi } from "../lyrics-api";
|
|
10
12
|
import { midiApi } from "../midi-api";
|
|
@@ -681,6 +683,8 @@ export function buildFieldShader(effect) {
|
|
|
681
683
|
viewportApi("viewU[6].w") +
|
|
682
684
|
trailSlotsApi(effect.trailCount) +
|
|
683
685
|
idApi(effect.ids === true, 0, 23) +
|
|
686
|
+
sceneLightApi(false, 0, 0) +
|
|
687
|
+
pointsApi(false) +
|
|
684
688
|
// Distance to the cast, in SCREEN pixels: the field is half-res, so a field
|
|
685
689
|
// texel is CAST_FIELD_DIV of them and the accessor scales on the way out.
|
|
686
690
|
// An author writes the width they mean and never learns how it is built.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/grid.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/grid.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAoC7C,eAAO,MAAM,QAAQ,OAAO,CAAA;AAC5B,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,gBAAgC,CAAA;AAEzD,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA6B1F;AAgBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,SAAK,GAAG,MAAM,CAsFpG"}
|
|
@@ -7,6 +7,8 @@ import { subjectMaskApi } from "../cast-api";
|
|
|
7
7
|
import { SCENE_TAP_STUB } from "../scene-tap";
|
|
8
8
|
import { clockApi, trailSlotsApi, viewportApi } from "./hosted-api";
|
|
9
9
|
import { idApi } from "../id-api";
|
|
10
|
+
import { sceneLightApi } from "../scene-light-api";
|
|
11
|
+
import { pointsApi } from "../points-api";
|
|
10
12
|
// A persistent grid an effect can step and read: the one thing an effect could
|
|
11
13
|
// not have before, which is MEMORY.
|
|
12
14
|
//
|
|
@@ -160,6 +162,8 @@ fn rzGrid(uv: vec2f) -> vec4f { return rzGridPrev(uv); }
|
|
|
160
162
|
// Stubbed: this module cannot read an attachment the scene pass writes — see
|
|
161
163
|
// id-api.ts. The author's whole file compiles here, so the names must exist.
|
|
162
164
|
idApi(false, 0, 0) +
|
|
165
|
+
sceneLightApi(false, 0, 0) +
|
|
166
|
+
pointsApi(false) +
|
|
163
167
|
// Inlined rather than imported from cast-distance, and NOT for tidiness.
|
|
164
168
|
//
|
|
165
169
|
// This module and composite.ts import each other — grid takes
|
|
@@ -42,6 +42,10 @@ type ParticleSource = {
|
|
|
42
42
|
* recover, which is memory, and memory is what the grid is for. */
|
|
43
43
|
gridSize: number;
|
|
44
44
|
};
|
|
45
|
+
/** Where the shading stage's five scene-light bindings start — see scene-light-api.ts. */
|
|
46
|
+
export declare const PARTICLE_LIGHT_BINDING = 10;
|
|
47
|
+
/** The effect's named points (`#points`), in both stages — see points-api.ts. */
|
|
48
|
+
export declare const PARTICLE_POINTS_BINDING = 15;
|
|
45
49
|
/** Bytes per particle. Explicitly padded — see the struct below. */
|
|
46
50
|
export declare const PARTICLE_STRIDE = 48;
|
|
47
51
|
/** Does the source define the particle contract? All three are required
|
|
@@ -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":"AA+BA,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;AAkCD,qDAAqD;AACrD;0EAC0E;AAC1E,KAAK,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAA;AAEpD,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;IACd;;+BAE2B;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;iEAK6D;IAC7D,KAAK,EAAE,OAAO,CAAA;IACd;;;wEAGoE;IACpE,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,KAAK,CAAA;AACxC,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC,oEAAoE;AACpE,eAAO,MAAM,eAAe,KAAK,CAAA;AAwDjC;yEACyE;AACzE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAOlH;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAqDxF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA2LvF"}
|
|
@@ -10,6 +10,8 @@ import { EFFECT_SUBJECT_VEC4S } from "../cast-layout";
|
|
|
10
10
|
import { clockApi, EFFECT_MATH_API, PARTICLE_STRUCT_WGSL, trailSlotsApi, viewportApi } from "./hosted-api";
|
|
11
11
|
import { sceneIdFieldWgsl, sceneIdPadWgsl } from "./scene-contract";
|
|
12
12
|
import { idApi } from "../id-api";
|
|
13
|
+
import { sceneLightApi } from "../scene-light-api";
|
|
14
|
+
import { pointsApi } from "../points-api";
|
|
13
15
|
/**
|
|
14
16
|
* The trail accessors, in the PARTICLE module.
|
|
15
17
|
*
|
|
@@ -40,6 +42,10 @@ fn _rzCastLive() -> i32 {
|
|
|
40
42
|
trailSlotsApi(cast.trailCount) +
|
|
41
43
|
anchorAliasWgsl(cast.alias));
|
|
42
44
|
}
|
|
45
|
+
/** Where the shading stage's five scene-light bindings start — see scene-light-api.ts. */
|
|
46
|
+
export const PARTICLE_LIGHT_BINDING = 10;
|
|
47
|
+
/** The effect's named points (`#points`), in both stages — see points-api.ts. */
|
|
48
|
+
export const PARTICLE_POINTS_BINDING = 15;
|
|
43
49
|
/** Bytes per particle. Explicitly padded — see the struct below. */
|
|
44
50
|
export const PARTICLE_STRIDE = 48;
|
|
45
51
|
const CAMERA_STRUCT = /* wgsl */ `
|
|
@@ -134,7 +140,9 @@ ${src.paramsDecl}
|
|
|
134
140
|
PRELUDE +
|
|
135
141
|
// Stubbed: this module cannot read an attachment the scene pass writes — see
|
|
136
142
|
// id-api.ts. The author's whole file compiles here, so the names must exist.
|
|
137
|
-
idApi(false, 0, 0) + castDistanceStub() +
|
|
143
|
+
idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) +
|
|
144
|
+
// The named points, for real: particleInit is where a flame is put on a wick.
|
|
145
|
+
pointsApi(true, 0, PARTICLE_POINTS_BINDING) +
|
|
138
146
|
"\n// ── user effect ──\n" +
|
|
139
147
|
src.wgsl +
|
|
140
148
|
/* wgsl */ `
|
|
@@ -206,6 +214,10 @@ ${src.paramsDecl}
|
|
|
206
214
|
// Stubbed: this module cannot read an attachment the scene pass writes — see
|
|
207
215
|
// id-api.ts. The author's whole file compiles here, so the names must exist.
|
|
208
216
|
idApi(false, 0, 0) + castDistanceStub() +
|
|
217
|
+
// The sun's shadow and the world's ambient, for real: the shadow pass has
|
|
218
|
+
// run by the time this draws, inside the scene pass. See scene-light-api.ts.
|
|
219
|
+
sceneLightApi(true, 0, PARTICLE_LIGHT_BINDING) +
|
|
220
|
+
pointsApi(true, 0, PARTICLE_POINTS_BINDING) +
|
|
209
221
|
"\n// ── user effect ──\n" +
|
|
210
222
|
src.wgsl +
|
|
211
223
|
/* wgsl */ `
|
|
@@ -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":"AA+BA,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,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;IACd;sFACkF;IAClF,QAAQ,EAAE,MAAM,CAAA;CACjB,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,CA6ZR"}
|
|
@@ -10,6 +10,8 @@ import { EFFECT_SUBJECT_VEC4S } from "../cast-layout";
|
|
|
10
10
|
import { clockApi, EFFECT_MATH_API, PARTICLE_STRUCT_WGSL, trailSlotsApi, viewportApi } from "./hosted-api";
|
|
11
11
|
import { sceneIdFieldWgsl, sceneIdPadWgsl } from "./scene-contract";
|
|
12
12
|
import { idApi } from "../id-api";
|
|
13
|
+
import { sceneLightApi } from "../scene-light-api";
|
|
14
|
+
import { pointsApi } from "../points-api";
|
|
13
15
|
/**
|
|
14
16
|
* Sub-segments drawn between each pair of recorded samples.
|
|
15
17
|
*
|
|
@@ -247,7 +249,7 @@ fn rzTurnRadius(a: vec3f, b: vec3f, c: vec3f) -> f32 {
|
|
|
247
249
|
gridReadApi(0, 8, 9, src.gridSize) +
|
|
248
250
|
// Stubbed: this module cannot read an attachment the scene pass writes — see
|
|
249
251
|
// id-api.ts. The author's whole file compiles here, so the names must exist.
|
|
250
|
-
idApi(false, 0, 0) + castDistanceStub() +
|
|
252
|
+
idApi(false, 0, 0) + castDistanceStub() + sceneLightApi(false, 0, 0) + pointsApi(false) +
|
|
251
253
|
"\n// ── user effect ──\n" +
|
|
252
254
|
src.wgsl +
|
|
253
255
|
/* wgsl */ `
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** How many points one effect can read. A candlelit hall is fifty. */
|
|
2
|
+
export declare const MAX_EFFECT_POINTS = 256;
|
|
3
|
+
/** Floats in a points buffer: a vec4 header (count), then two vec4s a point. */
|
|
4
|
+
export declare const POINTS_FLOATS: number;
|
|
5
|
+
/**
|
|
6
|
+
* rzPointCount / rzPoint. `bound` is false in every module that cannot read the
|
|
7
|
+
* buffer — the author's whole file is spliced into each module it has a mount
|
|
8
|
+
* in, so the names must resolve there too, and there they read as no points.
|
|
9
|
+
*/
|
|
10
|
+
export declare function pointsApi(bound: boolean, group?: number, binding?: number): string;
|
|
11
|
+
//# sourceMappingURL=points-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"points-api.d.ts","sourceRoot":"","sources":["../../src/shaders/points-api.ts"],"names":[],"mappings":"AAmBA,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,MAAM,CAAA;AAEpC,gFAAgF;AAChF,eAAO,MAAM,aAAa,QAA4B,CAAA;AAUtD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,EAAE,OAAO,SAAI,GAAG,MAAM,CA2BxE"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Named points on the models, for an effect that puts something at each one.
|
|
2
|
+
//
|
|
3
|
+
// #points flame
|
|
4
|
+
//
|
|
5
|
+
// gives the effect every bone whose name starts with `flame`, on every model in
|
|
6
|
+
// the scene — a stage, its parts, a prop, a character — as RzPoint: where the
|
|
7
|
+
// bone stands and where its tail points, in world space, this frame. A candle
|
|
8
|
+
// stage carries a bone per wick; a flame effect draws one flame on each, sized
|
|
9
|
+
// and stood up by the tail.
|
|
10
|
+
//
|
|
11
|
+
// BONES, because a bone is MMD's own word for a named point in a model. A stage
|
|
12
|
+
// that brings its candles as bones needs no side file, a hand-made stage can
|
|
13
|
+
// add them in any PMX editor, and a candle a character carries moves with the
|
|
14
|
+
// hand that holds it.
|
|
15
|
+
//
|
|
16
|
+
// Not the cast's anchors: those name up to sixteen bones on four characters,
|
|
17
|
+
// one bone per name. A stage is not a character, and fifty wicks share a
|
|
18
|
+
// prefix rather than each having a name worth declaring.
|
|
19
|
+
/** How many points one effect can read. A candlelit hall is fifty. */
|
|
20
|
+
export const MAX_EFFECT_POINTS = 256;
|
|
21
|
+
/** Floats in a points buffer: a vec4 header (count), then two vec4s a point. */
|
|
22
|
+
export const POINTS_FLOATS = 4 + MAX_EFFECT_POINTS * 8;
|
|
23
|
+
const STRUCT = /* wgsl */ `
|
|
24
|
+
/** One named point: where the bone stands, and where its tail points to. */
|
|
25
|
+
struct RzPoint {
|
|
26
|
+
pos: vec3f,
|
|
27
|
+
tip: vec3f,
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
/**
|
|
31
|
+
* rzPointCount / rzPoint. `bound` is false in every module that cannot read the
|
|
32
|
+
* buffer — the author's whole file is spliced into each module it has a mount
|
|
33
|
+
* in, so the names must resolve there too, and there they read as no points.
|
|
34
|
+
*/
|
|
35
|
+
export function pointsApi(bound, group = 0, binding = 0) {
|
|
36
|
+
if (!bound) {
|
|
37
|
+
return (STRUCT +
|
|
38
|
+
/* wgsl */ `
|
|
39
|
+
fn rzPointCount() -> u32 { return 0u; }
|
|
40
|
+
fn rzPoint(i: u32) -> RzPoint { var p: RzPoint; return p; }
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
return (STRUCT +
|
|
44
|
+
/* wgsl */ `
|
|
45
|
+
@group(${group}) @binding(${binding}) var<storage, read> _rzPoints: array<vec4f>;
|
|
46
|
+
|
|
47
|
+
/** How many named points the scene has for this effect, this frame. */
|
|
48
|
+
fn rzPointCount() -> u32 { return min(u32(_rzPoints[0].x), ${MAX_EFFECT_POINTS}u); }
|
|
49
|
+
|
|
50
|
+
/** Point i, world space. */
|
|
51
|
+
fn rzPoint(i: u32) -> RzPoint {
|
|
52
|
+
var p: RzPoint;
|
|
53
|
+
p.pos = _rzPoints[1u + i * 2u].xyz;
|
|
54
|
+
p.tip = _rzPoints[2u + i * 2u].xyz;
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The world's light at a surface facing n — the flat colour, or the installed
|
|
3
|
+
* HDRI's irradiance (sh[0].w = 1), evaluated from folded SH coefficients (see
|
|
4
|
+
* ibl.ts for the folding; the shader is a plain polynomial in the normal).
|
|
5
|
+
*
|
|
6
|
+
* Written against whichever name the module gives the light uniform, so the
|
|
7
|
+
* materials, the ground and an effect all evaluate one polynomial.
|
|
8
|
+
*/
|
|
9
|
+
export declare const worldAmbientWgsl: (u: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
* 3×3 PCF over one cascade's map, for a point already in that cascade's NDC.
|
|
12
|
+
*
|
|
13
|
+
* Shared with the materials, so an effect's shadow and her own shadow soften
|
|
14
|
+
* alike. The sampler is linear-filtered, which makes each tap a 2×2 bilinear
|
|
15
|
+
* compare. Unrolled — Safari's Metal backend doesn't unroll nested shadow
|
|
16
|
+
* loops reliably. The body expects `ndc` in scope and returns.
|
|
17
|
+
*/
|
|
18
|
+
export declare const pcf9: (map: string, ts: string, sampler?: string) => string;
|
|
19
|
+
/** Bindings the real accessors take, from `binding` upward. */
|
|
20
|
+
export declare const SCENE_LIGHT_API_BINDINGS = 5;
|
|
21
|
+
/**
|
|
22
|
+
* `rzShadow(p)`: how much of the sun reaches world point `p` — 1 lit, 0 in a
|
|
23
|
+
* caster's shadow, soft in between.
|
|
24
|
+
*
|
|
25
|
+
* `rzWorldAmbient(n)`: the world's light arriving at a surface facing `n` —
|
|
26
|
+
* the materials' own ambient, so an effect's shade can match hers.
|
|
27
|
+
*
|
|
28
|
+
* With `on`, declares five bindings from `binding`:
|
|
29
|
+
* +0 the light uniform (ambient, the sun — direction, and in .w the shadow
|
|
30
|
+
* switch — and the HDRI's irradiance)
|
|
31
|
+
* +1 the cascade view-projections, then the caster sphere
|
|
32
|
+
* +2 the near cascade's map +3 the far cascade's map
|
|
33
|
+
* +4 the comparison sampler
|
|
34
|
+
*
|
|
35
|
+
* It takes a POINT and no normal. The materials' sampleShadow also rejects a
|
|
36
|
+
* surface facing away from the sun, which is right for skin and wrong for a
|
|
37
|
+
* blade of grass or a petal: those are thin, lit through, and shaded by their
|
|
38
|
+
* own N·L. The point is nudged toward the sun instead, which lifts a blade's
|
|
39
|
+
* root off a stage floor that casts into the same map.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sceneLightApi(on: boolean, group: number, binding: number): string;
|
|
42
|
+
//# sourceMappingURL=scene-light-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-light-api.d.ts","sourceRoot":"","sources":["../../src/shaders/scene-light-api.ts"],"names":[],"mappings":"AAkBA;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,GAAG,MAAM,WAazC,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,gBAAyB,WActE,CAAA;AAED,+DAA+D;AAC/D,eAAO,MAAM,wBAAwB,IAAI,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA2DjF"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { SHADOW_CASCADES } from "../shadow-cascades";
|
|
2
|
+
// The scene's own light, as an effect author sees it: the sun's shadow and the
|
|
3
|
+
// world's ambient.
|
|
4
|
+
//
|
|
5
|
+
// A lawn standing in a character's shadow has to darken where the floor under
|
|
6
|
+
// it does, or the figure floats: the ground catcher draws her shadow, and the
|
|
7
|
+
// blades drawn over it hide it. And the shade has to be the SCENE'S shade — her
|
|
8
|
+
// shaded side takes the world's colour, and a lawn shaded by its own guess
|
|
9
|
+
// reads as a patch of different flowers beside her. Both are already computed
|
|
10
|
+
// for the materials every frame; this hands the same lookups to effects.
|
|
11
|
+
//
|
|
12
|
+
// Bound for real in the particle SHADING stage only. That is where geometry an
|
|
13
|
+
// effect draws inside the scene pass is shaded, and the shadow pass has already
|
|
14
|
+
// run by then. Every other module that compiles the author's file gets stubs:
|
|
15
|
+
// rzShadow answers 1, lit, which is what a point with no occlusion information
|
|
16
|
+
// is — the same answer sampleShadow gives outside every cascade — and
|
|
17
|
+
// rzWorldAmbient answers black, no light.
|
|
18
|
+
/**
|
|
19
|
+
* The world's light at a surface facing n — the flat colour, or the installed
|
|
20
|
+
* HDRI's irradiance (sh[0].w = 1), evaluated from folded SH coefficients (see
|
|
21
|
+
* ibl.ts for the folding; the shader is a plain polynomial in the normal).
|
|
22
|
+
*
|
|
23
|
+
* Written against whichever name the module gives the light uniform, so the
|
|
24
|
+
* materials, the ground and an effect all evaluate one polynomial.
|
|
25
|
+
*/
|
|
26
|
+
export const worldAmbientWgsl = (u) => /* wgsl */ `
|
|
27
|
+
fn rzWorldAmbient(n: vec3f) -> vec3f {
|
|
28
|
+
if (${u}.sh[0].w < 0.5) { return ${u}.ambientColor.xyz; }
|
|
29
|
+
let x = n.x;
|
|
30
|
+
let y = n.y;
|
|
31
|
+
let z = n.z;
|
|
32
|
+
let c = ${u}.sh[0].xyz
|
|
33
|
+
+ ${u}.sh[1].xyz * y + ${u}.sh[2].xyz * z + ${u}.sh[3].xyz * x
|
|
34
|
+
+ ${u}.sh[4].xyz * (x * y) + ${u}.sh[5].xyz * (y * z)
|
|
35
|
+
+ ${u}.sh[6].xyz * (3.0 * z * z - 1.0) + ${u}.sh[7].xyz * (x * z)
|
|
36
|
+
+ ${u}.sh[8].xyz * (x * x - y * y);
|
|
37
|
+
return max(c, vec3f(0.0));
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
/**
|
|
41
|
+
* 3×3 PCF over one cascade's map, for a point already in that cascade's NDC.
|
|
42
|
+
*
|
|
43
|
+
* Shared with the materials, so an effect's shadow and her own shadow soften
|
|
44
|
+
* alike. The sampler is linear-filtered, which makes each tap a 2×2 bilinear
|
|
45
|
+
* compare. Unrolled — Safari's Metal backend doesn't unroll nested shadow
|
|
46
|
+
* loops reliably. The body expects `ndc` in scope and returns.
|
|
47
|
+
*/
|
|
48
|
+
export const pcf9 = (map, ts, sampler = "shadowSampler") => /* wgsl */ `
|
|
49
|
+
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
50
|
+
let cmpZ = ndc.z - 0.001;
|
|
51
|
+
let ts = ${ts};
|
|
52
|
+
let s00 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, -ts), cmpZ);
|
|
53
|
+
let s10 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(0.0, -ts), cmpZ);
|
|
54
|
+
let s20 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, -ts), cmpZ);
|
|
55
|
+
let s01 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, 0.0), cmpZ);
|
|
56
|
+
let s11 = textureSampleCompareLevel(${map}, ${sampler}, suv, cmpZ);
|
|
57
|
+
let s21 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, 0.0), cmpZ);
|
|
58
|
+
let s02 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, ts), cmpZ);
|
|
59
|
+
let s12 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(0.0, ts), cmpZ);
|
|
60
|
+
let s22 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, ts), cmpZ);
|
|
61
|
+
return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
|
|
62
|
+
`;
|
|
63
|
+
/** Bindings the real accessors take, from `binding` upward. */
|
|
64
|
+
export const SCENE_LIGHT_API_BINDINGS = 5;
|
|
65
|
+
/**
|
|
66
|
+
* `rzShadow(p)`: how much of the sun reaches world point `p` — 1 lit, 0 in a
|
|
67
|
+
* caster's shadow, soft in between.
|
|
68
|
+
*
|
|
69
|
+
* `rzWorldAmbient(n)`: the world's light arriving at a surface facing `n` —
|
|
70
|
+
* the materials' own ambient, so an effect's shade can match hers.
|
|
71
|
+
*
|
|
72
|
+
* With `on`, declares five bindings from `binding`:
|
|
73
|
+
* +0 the light uniform (ambient, the sun — direction, and in .w the shadow
|
|
74
|
+
* switch — and the HDRI's irradiance)
|
|
75
|
+
* +1 the cascade view-projections, then the caster sphere
|
|
76
|
+
* +2 the near cascade's map +3 the far cascade's map
|
|
77
|
+
* +4 the comparison sampler
|
|
78
|
+
*
|
|
79
|
+
* It takes a POINT and no normal. The materials' sampleShadow also rejects a
|
|
80
|
+
* surface facing away from the sun, which is right for skin and wrong for a
|
|
81
|
+
* blade of grass or a petal: those are thin, lit through, and shaded by their
|
|
82
|
+
* own N·L. The point is nudged toward the sun instead, which lifts a blade's
|
|
83
|
+
* root off a stage floor that casts into the same map.
|
|
84
|
+
*/
|
|
85
|
+
export function sceneLightApi(on, group, binding) {
|
|
86
|
+
if (!on) {
|
|
87
|
+
return /* wgsl */ `
|
|
88
|
+
fn rzShadow(p: vec3f) -> f32 { return 1.0; }
|
|
89
|
+
fn rzWorldAmbient(n: vec3f) -> vec3f { return vec3f(0.0); }
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
const n = SHADOW_CASCADES.length;
|
|
93
|
+
return /* wgsl */ `
|
|
94
|
+
// The light uniform, as the materials lay it out; lights[0] is the sun.
|
|
95
|
+
struct _RzSceneLight { direction: vec4f, color: vec4f, }
|
|
96
|
+
struct _RzSceneLightU { ambientColor: vec4f, lights: array<_RzSceneLight, 4>, sh: array<vec4f, 9>, }
|
|
97
|
+
// The cascades' matrices, inner to outer, then every caster in one sphere
|
|
98
|
+
// (x, y, z, radius): radius 0 = nothing casts, negative = do not test.
|
|
99
|
+
struct _RzShadowBlock { viewProj: array<mat4x4f, ${n}>, casters: vec4f, }
|
|
100
|
+
@group(${group}) @binding(${binding}) var<uniform> _rzLight: _RzSceneLightU;
|
|
101
|
+
@group(${group}) @binding(${binding + 1}) var<uniform> _rzShadowVP: _RzShadowBlock;
|
|
102
|
+
@group(${group}) @binding(${binding + 2}) var _rzShadowNear: texture_depth_2d;
|
|
103
|
+
@group(${group}) @binding(${binding + 3}) var _rzShadowFar: texture_depth_2d;
|
|
104
|
+
@group(${group}) @binding(${binding + 4}) var _rzShadowCmp: sampler_comparison;
|
|
105
|
+
|
|
106
|
+
fn _rzShadowNearTaps(ndc: vec3f) -> f32 {
|
|
107
|
+
${pcf9("_rzShadowNear", `1.0 / ${SHADOW_CASCADES[0].mapSize}.0`, "_rzShadowCmp")}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn _rzShadowFarTaps(ndc: vec3f) -> f32 {
|
|
111
|
+
${pcf9("_rzShadowFar", `1.0 / ${SHADOW_CASCADES[n - 1].mapSize}.0`, "_rzShadowCmp")}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn rzShadow(p: vec3f) -> f32 {
|
|
115
|
+
// The scene's one shadow switch, on the sun — see sampleShadow.
|
|
116
|
+
let castAmt = _rzLight.lights[0].direction.w;
|
|
117
|
+
if (castAmt <= 0.0) { return 1.0; }
|
|
118
|
+
let toSun = normalize(-_rzLight.lights[0].direction.xyz);
|
|
119
|
+
// Can anything cast onto this point at all? The ground's test, for the same
|
|
120
|
+
// two reasons: most of a lawn is nowhere near her, and a scene with no
|
|
121
|
+
// models never renders the map, whose untouched zeroes compare as shadowed.
|
|
122
|
+
let cs = _rzShadowVP.casters;
|
|
123
|
+
if (cs.w == 0.0) { return 1.0; }
|
|
124
|
+
if (cs.w > 0.0) {
|
|
125
|
+
let toCaster = cs.xyz - p;
|
|
126
|
+
let along = dot(toCaster, toSun);
|
|
127
|
+
let perp = length(toCaster - toSun * along);
|
|
128
|
+
if (along <= -cs.w || perp > cs.w) { return 1.0; }
|
|
129
|
+
}
|
|
130
|
+
let q = p + toSun * 0.08;
|
|
131
|
+
let c0 = _rzShadowVP.viewProj[0] * vec4f(q, 1.0);
|
|
132
|
+
let n0 = c0.xyz / max(c0.w, 1e-6);
|
|
133
|
+
if (all(abs(n0.xy) < vec2f(0.98)) && n0.z > 0.0 && n0.z < 1.0) {
|
|
134
|
+
return mix(1.0, _rzShadowNearTaps(n0), castAmt);
|
|
135
|
+
}
|
|
136
|
+
let c1 = _rzShadowVP.viewProj[${n - 1}] * vec4f(q, 1.0);
|
|
137
|
+
let n1 = c1.xyz / max(c1.w, 1e-6);
|
|
138
|
+
if (all(abs(n1.xy) < vec2f(0.98)) && n1.z > 0.0 && n1.z < 1.0) {
|
|
139
|
+
return mix(1.0, _rzShadowFarTaps(n1), castAmt);
|
|
140
|
+
}
|
|
141
|
+
return 1.0;
|
|
142
|
+
}
|
|
143
|
+
${worldAmbientWgsl("_rzLight")}`;
|
|
144
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 3×3 PCF over one cascade's map, for a point already in that cascade's NDC.
|
|
3
|
+
*
|
|
4
|
+
* Shared with the materials, so an effect's shadow and her own shadow soften
|
|
5
|
+
* alike. The sampler is linear-filtered, which makes each tap a 2×2 bilinear
|
|
6
|
+
* compare. Unrolled — Safari's Metal backend doesn't unroll nested shadow
|
|
7
|
+
* loops reliably. The body expects `ndc` in scope and returns.
|
|
8
|
+
*/
|
|
9
|
+
export declare const pcf9: (map: string, ts: string, sampler?: string) => string;
|
|
10
|
+
/** Bindings the real accessor takes, from `binding` upward. */
|
|
11
|
+
export declare const SHADOW_API_BINDINGS = 5;
|
|
12
|
+
/**
|
|
13
|
+
* `rzShadow(p)`: how much of the sun reaches world point `p` — 1 lit, 0 in a
|
|
14
|
+
* caster's shadow, soft in between.
|
|
15
|
+
*
|
|
16
|
+
* With `on`, declares five bindings from `binding`:
|
|
17
|
+
* +0 the light uniform (the sun's direction, and in .w the shadow switch)
|
|
18
|
+
* +1 the cascade view-projections, then the caster sphere
|
|
19
|
+
* +2 the near cascade's map +3 the far cascade's map
|
|
20
|
+
* +4 the comparison sampler
|
|
21
|
+
*
|
|
22
|
+
* It takes a POINT and no normal. The materials' sampleShadow also rejects a
|
|
23
|
+
* surface facing away from the sun, which is right for skin and wrong for a
|
|
24
|
+
* blade of grass or a petal: those are thin, lit through, and shaded by their
|
|
25
|
+
* own N·L. The point is nudged toward the sun instead, which lifts a blade's
|
|
26
|
+
* root off a stage floor that casts into the same map.
|
|
27
|
+
*/
|
|
28
|
+
export declare function shadowApi(on: boolean, group: number, binding: number): string;
|
|
29
|
+
//# sourceMappingURL=shadow-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-api.d.ts","sourceRoot":"","sources":["../../src/shaders/shadow-api.ts"],"names":[],"mappings":"AAcA;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,gBAAyB,WActE,CAAA;AAED,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAyD7E"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { SHADOW_CASCADES } from "../shadow-cascades";
|
|
2
|
+
// The sun's shadow, as an effect author sees it.
|
|
3
|
+
//
|
|
4
|
+
// A lawn standing in a character's shadow has to darken where the floor under
|
|
5
|
+
// it does, or the figure floats: the ground catcher draws her shadow, and the
|
|
6
|
+
// blades drawn over it hide it. The map is already there — the materials and
|
|
7
|
+
// the ground read it every frame — so this hands the same lookup to effects.
|
|
8
|
+
//
|
|
9
|
+
// Bound for real in the particle SHADING stage only. That is where geometry an
|
|
10
|
+
// effect draws inside the scene pass is shaded, and the shadow pass has already
|
|
11
|
+
// run by then. Every other module that compiles the author's file gets the
|
|
12
|
+
// stub, which answers 1: lit is what a point with no occlusion information is,
|
|
13
|
+
// the same answer sampleShadow gives outside every cascade.
|
|
14
|
+
/**
|
|
15
|
+
* 3×3 PCF over one cascade's map, for a point already in that cascade's NDC.
|
|
16
|
+
*
|
|
17
|
+
* Shared with the materials, so an effect's shadow and her own shadow soften
|
|
18
|
+
* alike. The sampler is linear-filtered, which makes each tap a 2×2 bilinear
|
|
19
|
+
* compare. Unrolled — Safari's Metal backend doesn't unroll nested shadow
|
|
20
|
+
* loops reliably. The body expects `ndc` in scope and returns.
|
|
21
|
+
*/
|
|
22
|
+
export const pcf9 = (map, ts, sampler = "shadowSampler") => /* wgsl */ `
|
|
23
|
+
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
24
|
+
let cmpZ = ndc.z - 0.001;
|
|
25
|
+
let ts = ${ts};
|
|
26
|
+
let s00 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, -ts), cmpZ);
|
|
27
|
+
let s10 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(0.0, -ts), cmpZ);
|
|
28
|
+
let s20 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, -ts), cmpZ);
|
|
29
|
+
let s01 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, 0.0), cmpZ);
|
|
30
|
+
let s11 = textureSampleCompareLevel(${map}, ${sampler}, suv, cmpZ);
|
|
31
|
+
let s21 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, 0.0), cmpZ);
|
|
32
|
+
let s02 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, ts), cmpZ);
|
|
33
|
+
let s12 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(0.0, ts), cmpZ);
|
|
34
|
+
let s22 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, ts), cmpZ);
|
|
35
|
+
return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
|
|
36
|
+
`;
|
|
37
|
+
/** Bindings the real accessor takes, from `binding` upward. */
|
|
38
|
+
export const SHADOW_API_BINDINGS = 5;
|
|
39
|
+
/**
|
|
40
|
+
* `rzShadow(p)`: how much of the sun reaches world point `p` — 1 lit, 0 in a
|
|
41
|
+
* caster's shadow, soft in between.
|
|
42
|
+
*
|
|
43
|
+
* With `on`, declares five bindings from `binding`:
|
|
44
|
+
* +0 the light uniform (the sun's direction, and in .w the shadow switch)
|
|
45
|
+
* +1 the cascade view-projections, then the caster sphere
|
|
46
|
+
* +2 the near cascade's map +3 the far cascade's map
|
|
47
|
+
* +4 the comparison sampler
|
|
48
|
+
*
|
|
49
|
+
* It takes a POINT and no normal. The materials' sampleShadow also rejects a
|
|
50
|
+
* surface facing away from the sun, which is right for skin and wrong for a
|
|
51
|
+
* blade of grass or a petal: those are thin, lit through, and shaded by their
|
|
52
|
+
* own N·L. The point is nudged toward the sun instead, which lifts a blade's
|
|
53
|
+
* root off a stage floor that casts into the same map.
|
|
54
|
+
*/
|
|
55
|
+
export function shadowApi(on, group, binding) {
|
|
56
|
+
if (!on) {
|
|
57
|
+
return /* wgsl */ `
|
|
58
|
+
fn rzShadow(p: vec3f) -> f32 { return 1.0; }
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
const n = SHADOW_CASCADES.length;
|
|
62
|
+
return /* wgsl */ `
|
|
63
|
+
// The first two vec4s of the light uniform: world ambient, then the sun.
|
|
64
|
+
struct _RzShadowSun { ambient: vec4f, direction: vec4f, }
|
|
65
|
+
// The cascades' matrices, inner to outer, then every caster in one sphere
|
|
66
|
+
// (x, y, z, radius): radius 0 = nothing casts, negative = do not test.
|
|
67
|
+
struct _RzShadowBlock { viewProj: array<mat4x4f, ${n}>, casters: vec4f, }
|
|
68
|
+
@group(${group}) @binding(${binding}) var<uniform> _rzShadowSun: _RzShadowSun;
|
|
69
|
+
@group(${group}) @binding(${binding + 1}) var<uniform> _rzShadowVP: _RzShadowBlock;
|
|
70
|
+
@group(${group}) @binding(${binding + 2}) var _rzShadowNear: texture_depth_2d;
|
|
71
|
+
@group(${group}) @binding(${binding + 3}) var _rzShadowFar: texture_depth_2d;
|
|
72
|
+
@group(${group}) @binding(${binding + 4}) var _rzShadowCmp: sampler_comparison;
|
|
73
|
+
|
|
74
|
+
fn _rzShadowNearTaps(ndc: vec3f) -> f32 {
|
|
75
|
+
${pcf9("_rzShadowNear", `1.0 / ${SHADOW_CASCADES[0].mapSize}.0`, "_rzShadowCmp")}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fn _rzShadowFarTaps(ndc: vec3f) -> f32 {
|
|
79
|
+
${pcf9("_rzShadowFar", `1.0 / ${SHADOW_CASCADES[n - 1].mapSize}.0`, "_rzShadowCmp")}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn rzShadow(p: vec3f) -> f32 {
|
|
83
|
+
// The scene's one shadow switch, on the sun — see sampleShadow.
|
|
84
|
+
let castAmt = _rzShadowSun.direction.w;
|
|
85
|
+
if (castAmt <= 0.0) { return 1.0; }
|
|
86
|
+
let toSun = normalize(-_rzShadowSun.direction.xyz);
|
|
87
|
+
// Can anything cast onto this point at all? The ground's test, for the same
|
|
88
|
+
// two reasons: most of a lawn is nowhere near her, and a scene with no
|
|
89
|
+
// models never renders the map, whose untouched zeroes compare as shadowed.
|
|
90
|
+
let cs = _rzShadowVP.casters;
|
|
91
|
+
if (cs.w == 0.0) { return 1.0; }
|
|
92
|
+
if (cs.w > 0.0) {
|
|
93
|
+
let toCaster = cs.xyz - p;
|
|
94
|
+
let along = dot(toCaster, toSun);
|
|
95
|
+
let perp = length(toCaster - toSun * along);
|
|
96
|
+
if (along <= -cs.w || perp > cs.w) { return 1.0; }
|
|
97
|
+
}
|
|
98
|
+
let q = p + toSun * 0.08;
|
|
99
|
+
let c0 = _rzShadowVP.viewProj[0] * vec4f(q, 1.0);
|
|
100
|
+
let n0 = c0.xyz / max(c0.w, 1e-6);
|
|
101
|
+
if (all(abs(n0.xy) < vec2f(0.98)) && n0.z > 0.0 && n0.z < 1.0) {
|
|
102
|
+
return mix(1.0, _rzShadowNearTaps(n0), castAmt);
|
|
103
|
+
}
|
|
104
|
+
let c1 = _rzShadowVP.viewProj[${n - 1}] * vec4f(q, 1.0);
|
|
105
|
+
let n1 = c1.xyz / max(c1.w, 1e-6);
|
|
106
|
+
if (all(abs(n1.xy) < vec2f(0.98)) && n1.z > 0.0 && n1.z < 1.0) {
|
|
107
|
+
return mix(1.0, _rzShadowFarTaps(n1), castAmt);
|
|
108
|
+
}
|
|
109
|
+
return 1.0;
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { POINTS_FLOATS } from "./shaders/points-api"
|
|
2
|
+
|
|
3
|
+
// The CPU half of `#points`: which bones match, and where they are this frame.
|
|
4
|
+
|
|
5
|
+
/** Indices of the bones whose names start with `prefix`, in rig order. By
|
|
6
|
+
* index, because fifty wicks may share one name and a name lookup keeps one. */
|
|
7
|
+
export function bonesWithPrefix(bones: { name: string }[], prefix: string): number[] {
|
|
8
|
+
const out: number[] = []
|
|
9
|
+
bones.forEach((b, i) => {
|
|
10
|
+
if (b.name.startsWith(prefix)) out.push(i)
|
|
11
|
+
})
|
|
12
|
+
return out
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Where a model stands: the same scale, rotation and position the cast's
|
|
16
|
+
* anchors are placed by. */
|
|
17
|
+
export type PointPlacement = {
|
|
18
|
+
scale: number
|
|
19
|
+
rotation: { x: number; y: number; z: number; w: number }
|
|
20
|
+
position: { x: number; y: number; z: number }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function place(v: [number, number, number], at: PointPlacement): [number, number, number] {
|
|
24
|
+
const s = at.scale
|
|
25
|
+
const x = v[0] * s
|
|
26
|
+
const y = v[1] * s
|
|
27
|
+
const z = v[2] * s
|
|
28
|
+
// v + 2w(q×v) + 2q×(q×v)
|
|
29
|
+
const q = at.rotation
|
|
30
|
+
const tx = 2 * (q.y * z - q.z * y)
|
|
31
|
+
const ty = 2 * (q.z * x - q.x * z)
|
|
32
|
+
const tz = 2 * (q.x * y - q.y * x)
|
|
33
|
+
return [
|
|
34
|
+
x + q.w * tx + (q.y * tz - q.z * ty) + at.position.x,
|
|
35
|
+
y + q.w * ty + (q.z * tx - q.x * tz) + at.position.y,
|
|
36
|
+
z + q.w * tz + (q.x * ty - q.y * tx) + at.position.z,
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Point `slot`: the bone's head and its tail's end, posed by the bone's world
|
|
42
|
+
* matrix (model space, column-major) and placed by the model. A bone with no
|
|
43
|
+
* tail is a point with no length — its tip is its head.
|
|
44
|
+
*/
|
|
45
|
+
export function writeBonePoint(
|
|
46
|
+
out: Float32Array,
|
|
47
|
+
slot: number,
|
|
48
|
+
world: Float32Array,
|
|
49
|
+
tail: [number, number, number] | undefined,
|
|
50
|
+
at: PointPlacement,
|
|
51
|
+
): void {
|
|
52
|
+
const head: [number, number, number] = [world[12], world[13], world[14]]
|
|
53
|
+
const t = tail ?? [0, 0, 0]
|
|
54
|
+
const tip: [number, number, number] = [
|
|
55
|
+
head[0] + world[0] * t[0] + world[4] * t[1] + world[8] * t[2],
|
|
56
|
+
head[1] + world[1] * t[0] + world[5] * t[1] + world[9] * t[2],
|
|
57
|
+
head[2] + world[2] * t[0] + world[6] * t[1] + world[10] * t[2],
|
|
58
|
+
]
|
|
59
|
+
const b = 4 + slot * 8
|
|
60
|
+
out.set(place(head, at), b)
|
|
61
|
+
out.set(place(tip, at), b + 4)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A zeroed points buffer's CPU copy. */
|
|
65
|
+
export function pointsData(): Float32Array {
|
|
66
|
+
return new Float32Array(POINTS_FLOATS)
|
|
67
|
+
}
|