reze-engine 0.64.0 → 0.65.0
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 +146 -54
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +734 -582
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +7 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/model.d.ts +10 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +15 -1
- package/dist/outline-normals.d.ts +12 -0
- package/dist/outline-normals.d.ts.map +1 -0
- package/dist/outline-normals.js +100 -0
- package/dist/pmx-loader.d.ts +3 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +7 -2
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +4 -2
- package/dist/shaders/passes/bloom.d.ts +4 -3
- package/dist/shaders/passes/bloom.d.ts.map +1 -1
- package/dist/shaders/passes/bloom.js +58 -87
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +118 -72
- package/dist/shaders/passes/mirror.d.ts +18 -12
- package/dist/shaders/passes/mirror.d.ts.map +1 -1
- package/dist/shaders/passes/mirror.js +59 -13
- package/dist/shaders/passes/outline.d.ts.map +1 -1
- package/dist/shaders/passes/outline.js +71 -14
- package/dist/unity/host.d.ts +15 -0
- package/dist/unity/host.d.ts.map +1 -1
- package/dist/unity/host.js +103 -63
- package/dist/unity/looks.d.ts +5 -0
- package/dist/unity/looks.d.ts.map +1 -1
- package/dist/unity/looks.js +19 -2
- package/dist/unity/skin.d.ts.map +1 -1
- package/dist/unity/skin.js +5 -2
- package/dist/unity/stage.d.ts +7 -0
- package/dist/unity/stage.d.ts.map +1 -1
- package/dist/unity/stage.js +37 -2
- package/package.json +1 -1
- package/src/engine.ts +1042 -798
- package/src/graph/slots.ts +7 -1
- package/src/index.ts +1 -0
- package/src/model.ts +18 -1
- package/src/outline-normals.ts +110 -0
- package/src/pmx-loader.ts +8 -2
- package/src/shaders/materials/common.ts +4 -2
- package/src/shaders/passes/bloom.ts +59 -87
- package/src/shaders/passes/composite.ts +118 -72
- package/src/shaders/passes/mirror.ts +60 -13
- package/src/shaders/passes/outline.ts +71 -14
- package/src/unity/host.ts +57 -14
- package/src/unity/looks.ts +21 -2
- package/src/unity/skin.ts +5 -2
- package/src/unity/stage.ts +38 -2
- package/src/shaders/agx-lut.ts +0 -30
package/src/graph/slots.ts
CHANGED
|
@@ -191,7 +191,13 @@ function epilogue(renderClass: RenderClass, alphaMode: AlphaMode, hasOpacity: bo
|
|
|
191
191
|
// painted sheet — is Blender's Emission shader, which no lamp reaches; adding
|
|
192
192
|
// the lamps to it turned X340's floor shadow, a white-RGB picture at a soft
|
|
193
193
|
// alpha, into a glowing disc under the spot above it.
|
|
194
|
-
|
|
194
|
+
//
|
|
195
|
+
// The model's fill (Engine.setModelFill) rides here too, as brightness: the
|
|
196
|
+
// surface colour times the fill, on every pixel of her alike. In the graph's
|
|
197
|
+
// ambient an NPR ramp would read it as LIGHT — sliding it would move her
|
|
198
|
+
// shadows, and under a hard step flip whole regions at once. After the graph
|
|
199
|
+
// it lifts her evenly and leaves every shadow where it is.
|
|
200
|
+
const LIT = takesLight ? ` + (rzLightsDiffuseOnce(input.worldPos, n) + objectLight.fill.rgb) * albedo` : ""
|
|
195
201
|
|
|
196
202
|
const ALBEDO = ` let albedo = tex_color;
|
|
197
203
|
`
|
package/src/index.ts
CHANGED
package/src/model.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Engine } from "./engine"
|
|
|
3
3
|
import { joinAssetPath, type AssetReader } from "./asset-reader"
|
|
4
4
|
import { Rigidbody, Joint } from "./physics"
|
|
5
5
|
import { IKSolverSystem } from "./ik-solver"
|
|
6
|
+
import { buildOutlineVertices } from "./outline-normals"
|
|
6
7
|
import { VMDLoader, type VMDKeyFrame } from "./vmd-loader"
|
|
7
8
|
import { VMDWriter, type VmdTrackSelection } from "./vmd-writer"
|
|
8
9
|
import {
|
|
@@ -564,8 +565,10 @@ export class Model {
|
|
|
564
565
|
morphing: Morphing,
|
|
565
566
|
rigidbodies: Rigidbody[] = [],
|
|
566
567
|
joints: Joint[] = [],
|
|
567
|
-
loadWarnings: string[] = []
|
|
568
|
+
loadWarnings: string[] = [],
|
|
569
|
+
edgeScales: Float32Array | null = null
|
|
568
570
|
) {
|
|
571
|
+
this.edgeScales = edgeScales
|
|
569
572
|
// Store base vertex data (original positions before morphing)
|
|
570
573
|
this.baseVertexData = new Float32Array(vertexData)
|
|
571
574
|
this.vertexData = vertexData
|
|
@@ -1001,6 +1004,20 @@ export class Model {
|
|
|
1001
1004
|
return this.vertexData
|
|
1002
1005
|
}
|
|
1003
1006
|
|
|
1007
|
+
/** PMX per-vertex edge scale, when the source had one. */
|
|
1008
|
+
private edgeScales: Float32Array | null = null
|
|
1009
|
+
private outlineVertices: Float32Array<ArrayBuffer> | null = null
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* The outline hull's own vertex stream: per vertex, the smoothed rest-pose
|
|
1013
|
+
* normal (xyz) and the PMX edge scale (w). See outline-normals.ts. Built on
|
|
1014
|
+
* first ask — only models with an edge-flagged material ever ask.
|
|
1015
|
+
*/
|
|
1016
|
+
getOutlineVertices(): Float32Array<ArrayBuffer> {
|
|
1017
|
+
this.outlineVertices ??= buildOutlineVertices(this.baseVertexData, VERTEX_STRIDE, this.indexData, this.edgeScales ?? undefined)
|
|
1018
|
+
return this.outlineVertices
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1004
1021
|
getTextures(): Texture[] {
|
|
1005
1022
|
return this.textures
|
|
1006
1023
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// The inverted hull's own push direction, one per vertex, built once at load.
|
|
2
|
+
//
|
|
3
|
+
// The hull is the model pushed out along a normal. Pushed along the MESH's
|
|
4
|
+
// normals it tears wherever the mesh carries two vertices at one position —
|
|
5
|
+
// every hard edge and every UV seam — because the two copies go two ways and
|
|
6
|
+
// the gap between them shows the body through the line: the jaw, the finger
|
|
7
|
+
// joints, the parting in the hair. Aether Gazer bakes a smoothed normal into
|
|
8
|
+
// vertex colour for exactly this (Character/Debug.shader, pass "Outline"); a
|
|
9
|
+
// PMX has nowhere to carry one, so it is computed here instead: the normals of
|
|
10
|
+
// every vertex at one position, averaged, so the copies agree and the hull
|
|
11
|
+
// stays closed.
|
|
12
|
+
//
|
|
13
|
+
// Each vertex's vote is weighted by the corner angles it owns, which is what
|
|
14
|
+
// makes a hard corner come out as the corner's true bisector rather than
|
|
15
|
+
// leaning toward whichever side happened to be cut into more triangles.
|
|
16
|
+
//
|
|
17
|
+
// Rest pose only, as the game's is: a morph moves the positions and leaves this
|
|
18
|
+
// alone, and skinning turns it exactly as it turns the normal.
|
|
19
|
+
|
|
20
|
+
/** Positions closer than this (model units) are one position. */
|
|
21
|
+
export const OUTLINE_WELD_EPSILON = 1e-4
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Smoothed outline normals, packed with the PMX per-vertex edge scale:
|
|
25
|
+
* xyz = unit direction, w = edge scale — four floats per vertex.
|
|
26
|
+
*
|
|
27
|
+
* @param vertexData interleaved [pos3, normal3, uv2] per vertex
|
|
28
|
+
* @param indices triangle list
|
|
29
|
+
* @param edgeScales per-vertex PMX edge scale, or omitted for 1
|
|
30
|
+
*/
|
|
31
|
+
export function buildOutlineVertices(
|
|
32
|
+
vertexData: ArrayLike<number>,
|
|
33
|
+
stride: number,
|
|
34
|
+
indices: ArrayLike<number>,
|
|
35
|
+
edgeScales?: ArrayLike<number>,
|
|
36
|
+
): Float32Array<ArrayBuffer> {
|
|
37
|
+
const n = Math.floor(vertexData.length / stride)
|
|
38
|
+
const out = new Float32Array(n * 4)
|
|
39
|
+
|
|
40
|
+
// Corner angles each vertex owns. A vertex no triangle uses keeps a token
|
|
41
|
+
// weight, so it still has a direction of its own.
|
|
42
|
+
const weight = new Float32Array(n).fill(1e-6)
|
|
43
|
+
for (let t = 0; t + 2 < indices.length; t += 3) {
|
|
44
|
+
const a = indices[t], b = indices[t + 1], c = indices[t + 2]
|
|
45
|
+
if (a >= n || b >= n || c >= n) continue
|
|
46
|
+
weight[a] += cornerAngle(vertexData, stride, a, b, c)
|
|
47
|
+
weight[b] += cornerAngle(vertexData, stride, b, c, a)
|
|
48
|
+
weight[c] += cornerAngle(vertexData, stride, c, a, b)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Weld by quantized position.
|
|
52
|
+
const inv = 1 / OUTLINE_WELD_EPSILON
|
|
53
|
+
const group = new Int32Array(n)
|
|
54
|
+
const ids = new Map<string, number>()
|
|
55
|
+
let groups = 0
|
|
56
|
+
for (let i = 0; i < n; i++) {
|
|
57
|
+
const o = i * stride
|
|
58
|
+
const key = `${Math.round(vertexData[o] * inv)},${Math.round(vertexData[o + 1] * inv)},${Math.round(vertexData[o + 2] * inv)}`
|
|
59
|
+
let g = ids.get(key)
|
|
60
|
+
if (g === undefined) {
|
|
61
|
+
g = groups++
|
|
62
|
+
ids.set(key, g)
|
|
63
|
+
}
|
|
64
|
+
group[i] = g
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const sum = new Float32Array(groups * 3)
|
|
68
|
+
for (let i = 0; i < n; i++) {
|
|
69
|
+
const o = i * stride
|
|
70
|
+
const w = weight[i]
|
|
71
|
+
const g = group[i] * 3
|
|
72
|
+
sum[g] += vertexData[o + 3] * w
|
|
73
|
+
sum[g + 1] += vertexData[o + 4] * w
|
|
74
|
+
sum[g + 2] += vertexData[o + 5] * w
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < n; i++) {
|
|
78
|
+
const o = i * stride
|
|
79
|
+
const nx = vertexData[o + 3], ny = vertexData[o + 4], nz = vertexData[o + 5]
|
|
80
|
+
const g = group[i] * 3
|
|
81
|
+
let x = sum[g], y = sum[g + 1], z = sum[g + 2]
|
|
82
|
+
const len = Math.hypot(x, y, z)
|
|
83
|
+
// Two sheets back to back at one position (a double-sided skirt, a shell
|
|
84
|
+
// and its lining) cancel to nothing, or to a direction pointing INTO one of
|
|
85
|
+
// them. Either way the welded answer is wrong for this vertex, and its own
|
|
86
|
+
// normal is the honest one.
|
|
87
|
+
if (len < 1e-6 || x * nx + y * ny + z * nz <= 0) {
|
|
88
|
+
x = nx
|
|
89
|
+
y = ny
|
|
90
|
+
z = nz
|
|
91
|
+
}
|
|
92
|
+
const l = Math.hypot(x, y, z) || 1
|
|
93
|
+
out[i * 4] = x / l
|
|
94
|
+
out[i * 4 + 1] = y / l
|
|
95
|
+
out[i * 4 + 2] = z / l
|
|
96
|
+
out[i * 4 + 3] = edgeScales ? edgeScales[i] : 1
|
|
97
|
+
}
|
|
98
|
+
return out
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The angle at vertex `a` of triangle (a, b, c), in radians. */
|
|
102
|
+
function cornerAngle(v: ArrayLike<number>, stride: number, a: number, b: number, c: number): number {
|
|
103
|
+
const oa = a * stride, ob = b * stride, oc = c * stride
|
|
104
|
+
const ux = v[ob] - v[oa], uy = v[ob + 1] - v[oa + 1], uz = v[ob + 2] - v[oa + 2]
|
|
105
|
+
const wx = v[oc] - v[oa], wy = v[oc + 1] - v[oa + 1], wz = v[oc + 2] - v[oa + 2]
|
|
106
|
+
const lu = Math.hypot(ux, uy, uz), lw = Math.hypot(wx, wy, wz)
|
|
107
|
+
if (lu < 1e-12 || lw < 1e-12) return 0
|
|
108
|
+
const cos = (ux * wx + uy * wy + uz * wz) / (lu * lw)
|
|
109
|
+
return Math.acos(Math.max(-1, Math.min(1, cos)))
|
|
110
|
+
}
|
package/src/pmx-loader.ts
CHANGED
|
@@ -36,6 +36,9 @@ export class PmxLoader {
|
|
|
36
36
|
private inverseBindMatrices: Float32Array | null = null
|
|
37
37
|
private joints0: Uint16Array | null = null
|
|
38
38
|
private weights0: Uint8Array | null = null
|
|
39
|
+
/** PMX per-vertex edge scale — the outline's width multiplier, as the
|
|
40
|
+
* game's outline takes vertex colour alpha. */
|
|
41
|
+
private edgeScales: Float32Array | null = null
|
|
39
42
|
private morphs: Morph[] = []
|
|
40
43
|
private vertexCount: number = 0
|
|
41
44
|
private rigidbodies: Rigidbody[] = []
|
|
@@ -150,6 +153,7 @@ export class PmxLoader {
|
|
|
150
153
|
// Prepare skinning arrays (4 influences per vertex)
|
|
151
154
|
const joints = new Uint16Array(count * 4)
|
|
152
155
|
const weights = new Uint8Array(count * 4) // UNORM8, will be normalized to 255
|
|
156
|
+
const edgeScales = new Float32Array(count)
|
|
153
157
|
|
|
154
158
|
for (let i = 0; i < count; i++) {
|
|
155
159
|
const px = this.getFloat32()
|
|
@@ -238,11 +242,12 @@ export class PmxLoader {
|
|
|
238
242
|
} else {
|
|
239
243
|
throw new Error(`Invalid bone weight type: ${type}`)
|
|
240
244
|
}
|
|
241
|
-
this.
|
|
245
|
+
edgeScales[i] = this.getFloat32()
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
this.joints0 = joints
|
|
245
249
|
this.weights0 = weights
|
|
250
|
+
this.edgeScales = edgeScales
|
|
246
251
|
return { positions, normals, uvs }
|
|
247
252
|
}
|
|
248
253
|
|
|
@@ -1136,7 +1141,8 @@ export class PmxLoader {
|
|
|
1136
1141
|
morphing,
|
|
1137
1142
|
this.rigidbodies,
|
|
1138
1143
|
this.joints,
|
|
1139
|
-
this.warnings
|
|
1144
|
+
this.warnings,
|
|
1145
|
+
this.edgeScales
|
|
1140
1146
|
)
|
|
1141
1147
|
}
|
|
1142
1148
|
|
|
@@ -195,8 +195,10 @@ fn _rzCastShadow(wp: vec3f) -> f32 {
|
|
|
195
195
|
// layers, as the game lights them;
|
|
196
196
|
// ambient.x 1 while it has an ambient of its own (Engine.setModelAmbient),
|
|
197
197
|
// 0 for the world's;
|
|
198
|
-
// sh that ambient's irradiance, folded as the world's (ibl.ts)
|
|
199
|
-
|
|
198
|
+
// sh that ambient's irradiance, folded as the world's (ibl.ts);
|
|
199
|
+
// fill.rgb its fill (Engine.setModelFill), added after its graph times its
|
|
200
|
+
// surface colour — zero for a model that has none.
|
|
201
|
+
struct ObjectLight { layers: vec4u, ambient: vec4f, sh: array<vec4f, 9>, fill: vec4f }
|
|
200
202
|
@group(1) @binding(1) var<uniform> objectLight: ObjectLight;
|
|
201
203
|
|
|
202
204
|
/** The light arriving at a surface of this object facing n: its own ambient
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Bloom — Aether Gazer's own chain (AGSimPostFX.Bloom, URP-style): a soft-knee
|
|
2
|
+
// prefilter to half size, a separable Gaussian down to a few pixels, then back
|
|
3
|
+
// up level by level, each blend leaning toward the coarser level by `scatter`.
|
|
4
|
+
// The composite adds the result × tint × intensity before the view transform.
|
|
5
5
|
|
|
6
6
|
const FULLSCREEN_VS = /* wgsl */ `
|
|
7
7
|
@vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
|
|
@@ -11,114 +11,86 @@ const FULLSCREEN_VS = /* wgsl */ `
|
|
|
11
11
|
}
|
|
12
12
|
`
|
|
13
13
|
|
|
14
|
-
// Full-res HDR → half-res.
|
|
15
|
-
|
|
14
|
+
// Full-res HDR → half-res. The game takes ONE bilinear sample at the half-res
|
|
15
|
+
// pixel's centre, which is the 2×2 box of the block under it; here the four
|
|
16
|
+
// texels are loaded so each can be un-premultiplied (the scene target is
|
|
17
|
+
// premultiplied; its alpha lives in the aux target's .g) and gated by the bloom
|
|
18
|
+
// mask (.r) first.
|
|
19
|
+
export const BLOOM_PREFILTER_SHADER_WGSL = `${FULLSCREEN_VS}
|
|
16
20
|
@group(0) @binding(0) var hdrTex: texture_2d<f32>;
|
|
17
|
-
@group(0) @binding(1) var<uniform>
|
|
21
|
+
@group(0) @binding(1) var<uniform> params: vec4<f32>; // threshold, knee, clamp, scatter
|
|
18
22
|
@group(0) @binding(2) var maskTex: texture_2d<f32>;
|
|
19
23
|
|
|
20
|
-
fn
|
|
21
|
-
return dot(max(c, vec3f(0.0)), vec3f(0.2126, 0.7152, 0.0722));
|
|
22
|
-
}
|
|
23
|
-
fn fetch(c: vec2<i32>, clampV: f32) -> vec3f {
|
|
24
|
+
fn fetch(c: vec2<i32>) -> vec3f {
|
|
24
25
|
let d = vec2<i32>(textureDimensions(hdrTex));
|
|
25
26
|
let cc = clamp(c, vec2<i32>(0), d - vec2<i32>(1));
|
|
26
|
-
let s = textureLoad(hdrTex, cc, 0);
|
|
27
|
-
// hdrTex is rg11b10ufloat (no alpha channel). Alpha lives in maskTex.g, written
|
|
28
|
-
// alongside the bloom mask in .r by the scene pass. Scene uses src-alpha blend
|
|
29
|
-
// with clear alpha 0 → hdr rgb is premultiplied; divide by the aux alpha to
|
|
30
|
-
// recover straight color before the Karis luminance weighting.
|
|
31
27
|
let aux = textureLoad(maskTex, cc, 0);
|
|
32
|
-
|
|
33
|
-
let rgb = max(s.rgb / max(aux.g, 1e-6), vec3f(0.0));
|
|
34
|
-
let masked = rgb * mask;
|
|
35
|
-
// Blender clamps each tap BEFORE Karis average (eevee_bloom: color = min(clampIntensity, color)).
|
|
36
|
-
return select(masked, min(masked, vec3f(clampV)), clampV > 0.0);
|
|
28
|
+
return max(textureLoad(hdrTex, cc, 0).rgb / max(aux.g, 1e-6), vec3f(0.0)) * aux.r;
|
|
37
29
|
}
|
|
38
30
|
|
|
39
31
|
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
40
|
-
let
|
|
41
|
-
let
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
let
|
|
45
|
-
let
|
|
46
|
-
let
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
let wb = 1.0 / (1.0 + luminance(b));
|
|
50
|
-
let wc = 1.0 / (1.0 + luminance(c));
|
|
51
|
-
let wd = 1.0 / (1.0 + luminance(d));
|
|
52
|
-
let avg = (a * wa + b * wb + c * wc + d * wd) / max(wa + wb + wc + wd, 1e-6);
|
|
53
|
-
// EEVEE quadratic threshold (brightness = max-channel, then soft-knee curve).
|
|
54
|
-
let bright = max(avg.r, max(avg.g, avg.b));
|
|
55
|
-
let soft = clamp(bright - prefilter.x + prefilter.y, 0.0, 2.0 * prefilter.y);
|
|
56
|
-
let q = (soft * soft) / (4.0 * max(prefilter.y, 1e-4) + 1e-6);
|
|
57
|
-
let contrib = max(q, bright - prefilter.x) / max(bright, 1e-4);
|
|
58
|
-
return vec4f(max(avg * contrib, vec3f(0.0)), 1.0);
|
|
32
|
+
let base = vec2<i32>(p.xy - vec2f(0.5)) * 2;
|
|
33
|
+
let avg = (fetch(base) + fetch(base + vec2<i32>(1, 0)) + fetch(base + vec2<i32>(0, 1)) + fetch(base + vec2<i32>(1, 1))) * 0.25;
|
|
34
|
+
let c = min(avg, vec3f(params.z));
|
|
35
|
+
// URP's quadratic soft knee on the brightest channel.
|
|
36
|
+
let bright = max(c.r, max(c.g, c.b));
|
|
37
|
+
let knee = params.y;
|
|
38
|
+
let soft = clamp(bright - params.x + knee, 0.0, 2.0 * knee);
|
|
39
|
+
let curve = soft * soft / (4.0 * knee + 1e-4);
|
|
40
|
+
return vec4f(c * (max(curve, bright - params.x) / max(bright, 1e-4)), 1.0);
|
|
59
41
|
}
|
|
60
42
|
`
|
|
61
43
|
|
|
62
|
-
//
|
|
63
|
-
|
|
44
|
+
// Horizontal 9-tap Gaussian (σ ≈ 2 at the destination's scale), its taps two
|
|
45
|
+
// SOURCE texels apart: the destination is half the source, so this both halves
|
|
46
|
+
// and blurs. Each tap lands on a texel boundary, so bilinear averages pairs.
|
|
47
|
+
export const BLOOM_BLUR_H_SHADER_WGSL = `${FULLSCREEN_VS}
|
|
64
48
|
@group(0) @binding(0) var srcTex: texture_2d<f32>;
|
|
65
49
|
@group(0) @binding(1) var srcSamp: sampler;
|
|
66
50
|
|
|
67
|
-
fn
|
|
68
|
-
|
|
51
|
+
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
52
|
+
let srcDims = vec2f(textureDimensions(srcTex));
|
|
53
|
+
let dstDims = max(floor(srcDims * 0.5), vec2f(1.0));
|
|
54
|
+
let uv = p.xy / dstDims;
|
|
55
|
+
let dx = vec2f(2.0 / srcDims.x, 0.0);
|
|
56
|
+
var w = array<f32, 5>(0.22702703, 0.19459459, 0.12162162, 0.05405405, 0.01621622);
|
|
57
|
+
var o = textureSampleLevel(srcTex, srcSamp, uv, 0.0).rgb * w[0];
|
|
58
|
+
for (var i = 1; i < 5; i++) {
|
|
59
|
+
let off = dx * f32(i);
|
|
60
|
+
o += (textureSampleLevel(srcTex, srcSamp, uv - off, 0.0).rgb + textureSampleLevel(srcTex, srcSamp, uv + off, 0.0).rgb) * w[i];
|
|
61
|
+
}
|
|
62
|
+
return vec4f(o, 1.0);
|
|
69
63
|
}
|
|
64
|
+
`
|
|
65
|
+
|
|
66
|
+
// Vertical pass at the same size: the same 9-tap Gaussian as five bilinear taps.
|
|
67
|
+
export const BLOOM_BLUR_V_SHADER_WGSL = `${FULLSCREEN_VS}
|
|
68
|
+
@group(0) @binding(0) var srcTex: texture_2d<f32>;
|
|
69
|
+
@group(0) @binding(1) var srcSamp: sampler;
|
|
70
70
|
|
|
71
71
|
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
72
|
-
let
|
|
73
|
-
let
|
|
74
|
-
|
|
75
|
-
let
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
let C = samp(uv, t * vec2f( 2.0, -2.0));
|
|
80
|
-
let D = samp(uv, t * vec2f(-1.0, -1.0));
|
|
81
|
-
let E = samp(uv, t * vec2f( 1.0, -1.0));
|
|
82
|
-
let F = samp(uv, t * vec2f(-2.0, 0.0));
|
|
83
|
-
let G = samp(uv, t * vec2f( 0.0, 0.0));
|
|
84
|
-
let H = samp(uv, t * vec2f( 2.0, 0.0));
|
|
85
|
-
let I = samp(uv, t * vec2f(-1.0, 1.0));
|
|
86
|
-
let J = samp(uv, t * vec2f( 1.0, 1.0));
|
|
87
|
-
let K = samp(uv, t * vec2f(-2.0, 2.0));
|
|
88
|
-
let L = samp(uv, t * vec2f( 0.0, 2.0));
|
|
89
|
-
let M = samp(uv, t * vec2f( 2.0, 2.0));
|
|
90
|
-
var o = (D + E + I + J) * (0.5 / 4.0);
|
|
91
|
-
o = o + (A + B + G + F) * (0.125 / 4.0);
|
|
92
|
-
o = o + (B + C + H + G) * (0.125 / 4.0);
|
|
93
|
-
o = o + (F + G + L + K) * (0.125 / 4.0);
|
|
94
|
-
o = o + (G + H + M + L) * (0.125 / 4.0);
|
|
72
|
+
let dims = vec2f(textureDimensions(srcTex));
|
|
73
|
+
let uv = p.xy / dims;
|
|
74
|
+
let near = vec2f(0.0, 1.3846154 / dims.y);
|
|
75
|
+
let far = vec2f(0.0, 3.2307692 / dims.y);
|
|
76
|
+
var o = textureSampleLevel(srcTex, srcSamp, uv, 0.0).rgb * 0.22702703;
|
|
77
|
+
o += (textureSampleLevel(srcTex, srcSamp, uv - near, 0.0).rgb + textureSampleLevel(srcTex, srcSamp, uv + near, 0.0).rgb) * 0.31621623;
|
|
78
|
+
o += (textureSampleLevel(srcTex, srcSamp, uv - far, 0.0).rgb + textureSampleLevel(srcTex, srcSamp, uv + far, 0.0).rgb) * 0.07027027;
|
|
95
79
|
return vec4f(o, 1.0);
|
|
96
80
|
}
|
|
97
81
|
`
|
|
98
82
|
|
|
99
|
-
//
|
|
83
|
+
// One level up: this level's blur leaned toward the coarser one by scatter.
|
|
100
84
|
export const BLOOM_UPSAMPLE_SHADER_WGSL = `${FULLSCREEN_VS}
|
|
101
|
-
@group(0) @binding(0) var
|
|
102
|
-
@group(0) @binding(1) var
|
|
85
|
+
@group(0) @binding(0) var highTex: texture_2d<f32>; // down[i]
|
|
86
|
+
@group(0) @binding(1) var lowTex: texture_2d<f32>; // coarser: down[i+1] at the top, up[i+1] after
|
|
103
87
|
@group(0) @binding(2) var srcSamp: sampler;
|
|
104
|
-
@group(0) @binding(3) var<uniform>
|
|
88
|
+
@group(0) @binding(3) var<uniform> params: vec4<f32>; // threshold, knee, clamp, scatter
|
|
105
89
|
|
|
106
90
|
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
107
|
-
let
|
|
108
|
-
let
|
|
109
|
-
let
|
|
110
|
-
|
|
111
|
-
var o = textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, -1.0), 0.0).rgb * 1.0;
|
|
112
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, -1.0), 0.0).rgb * 2.0;
|
|
113
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, -1.0), 0.0).rgb * 1.0;
|
|
114
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, 0.0), 0.0).rgb * 2.0;
|
|
115
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, 0.0), 0.0).rgb * 4.0;
|
|
116
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, 0.0), 0.0).rgb * 2.0;
|
|
117
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f(-1.0, 1.0), 0.0).rgb * 1.0;
|
|
118
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 0.0, 1.0), 0.0).rgb * 2.0;
|
|
119
|
-
o = o + textureSampleLevel(srcTex, srcSamp, uv + t * vec2f( 1.0, 1.0), 0.0).rgb * 1.0;
|
|
120
|
-
o = o * (1.0 / 16.0);
|
|
121
|
-
let base = textureSampleLevel(baseTex, srcSamp, uv, 0.0).rgb;
|
|
122
|
-
return vec4f(o + base, 1.0);
|
|
91
|
+
let uv = p.xy / vec2f(textureDimensions(highTex));
|
|
92
|
+
let high = textureSampleLevel(highTex, srcSamp, uv, 0.0).rgb;
|
|
93
|
+
let low = textureSampleLevel(lowTex, srcSamp, uv, 0.0).rgb;
|
|
94
|
+
return vec4f(mix(high, low, params.w), 1.0);
|
|
123
95
|
}
|
|
124
96
|
`
|