@aethervtt/shard-render 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/assets.d.ts +104 -0
- package/dist/assets.js +213 -0
- package/dist/atmosphere-model.d.ts +96 -0
- package/dist/atmosphere-model.js +370 -0
- package/dist/atmosphere-nodes.d.ts +55 -0
- package/dist/atmosphere-nodes.js +780 -0
- package/dist/atmosphere-plugin.d.ts +5 -0
- package/dist/atmosphere-plugin.js +42 -0
- package/dist/atmosphere-shaders.d.ts +7 -0
- package/dist/atmosphere-shaders.js +595 -0
- package/dist/atmosphere-state.d.ts +72 -0
- package/dist/atmosphere-state.js +18 -0
- package/dist/atmosphere.d.ts +191 -0
- package/dist/atmosphere.js +950 -0
- package/dist/camera.d.ts +83 -0
- package/dist/camera.js +163 -0
- package/dist/clusters.d.ts +68 -0
- package/dist/clusters.js +288 -0
- package/dist/culling-shaders.d.ts +2 -0
- package/dist/culling-shaders.js +203 -0
- package/dist/culling.d.ts +84 -0
- package/dist/culling.js +498 -0
- package/dist/debug-shaders.d.ts +3 -0
- package/dist/debug-shaders.js +207 -0
- package/dist/debug-views.d.ts +60 -0
- package/dist/debug-views.js +197 -0
- package/dist/deferred-plugin.d.ts +5 -0
- package/dist/deferred-plugin.js +20 -0
- package/dist/deferred-shaders.d.ts +2 -0
- package/dist/deferred-shaders.js +151 -0
- package/dist/deferred.d.ts +22 -0
- package/dist/deferred.js +296 -0
- package/dist/deform.d.ts +80 -0
- package/dist/deform.js +213 -0
- package/dist/describe-plugin.d.ts +5 -0
- package/dist/describe-plugin.js +19 -0
- package/dist/display-nodes.d.ts +5 -0
- package/dist/display-nodes.js +189 -0
- package/dist/dynamic-resolution.d.ts +47 -0
- package/dist/dynamic-resolution.js +191 -0
- package/dist/environment-plugin.d.ts +6 -0
- package/dist/environment-plugin.js +44 -0
- package/dist/environment-shaders.d.ts +2 -0
- package/dist/environment-shaders.js +260 -0
- package/dist/environment-state.d.ts +71 -0
- package/dist/environment-state.js +144 -0
- package/dist/environment.d.ts +70 -0
- package/dist/environment.js +529 -0
- package/dist/forward.d.ts +123 -0
- package/dist/forward.js +1216 -0
- package/dist/fxaa.d.ts +2 -0
- package/dist/fxaa.js +65 -0
- package/dist/gizmo-font.d.ts +21 -0
- package/dist/gizmo-font.js +132 -0
- package/dist/gizmos-plugin.d.ts +5 -0
- package/dist/gizmos-plugin.js +34 -0
- package/dist/gizmos.d.ts +156 -0
- package/dist/gizmos.js +772 -0
- package/dist/gpu-assets.d.ts +114 -0
- package/dist/gpu-assets.js +494 -0
- package/dist/graph.d.ts +204 -0
- package/dist/graph.js +417 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +540 -0
- package/dist/instances.d.ts +349 -0
- package/dist/instances.js +1335 -0
- package/dist/large-world.d.ts +13 -0
- package/dist/large-world.js +20 -0
- package/dist/lights.d.ts +160 -0
- package/dist/lights.js +468 -0
- package/dist/material-noise.d.ts +19 -0
- package/dist/material-noise.js +7 -0
- package/dist/material-pipelines.d.ts +46 -0
- package/dist/material-pipelines.js +166 -0
- package/dist/materials.d.ts +108 -0
- package/dist/materials.js +219 -0
- package/dist/noise.d.ts +76 -0
- package/dist/noise.js +286 -0
- package/dist/overlay-registry.d.ts +18 -0
- package/dist/overlay-registry.js +30 -0
- package/dist/overlays.d.ts +35 -0
- package/dist/overlays.js +344 -0
- package/dist/paths.d.ts +20 -0
- package/dist/paths.js +46 -0
- package/dist/picking-plugin.d.ts +2 -0
- package/dist/picking-plugin.js +20 -0
- package/dist/picking.d.ts +94 -0
- package/dist/picking.js +456 -0
- package/dist/pixel-perfect-plugin.d.ts +9 -0
- package/dist/pixel-perfect-plugin.js +135 -0
- package/dist/pixel-perfect.d.ts +27 -0
- package/dist/pixel-perfect.js +70 -0
- package/dist/plugin.d.ts +111 -0
- package/dist/plugin.js +182 -0
- package/dist/pool.d.ts +19 -0
- package/dist/pool.js +43 -0
- package/dist/post-common.d.ts +51 -0
- package/dist/post-common.js +148 -0
- package/dist/post-nodes.d.ts +39 -0
- package/dist/post-nodes.js +849 -0
- package/dist/post-plugin.d.ts +5 -0
- package/dist/post-plugin.js +26 -0
- package/dist/post-shaders.d.ts +8 -0
- package/dist/post-shaders.js +698 -0
- package/dist/post.d.ts +175 -0
- package/dist/post.js +453 -0
- package/dist/readback.d.ts +4 -0
- package/dist/readback.js +95 -0
- package/dist/render-scale.d.ts +53 -0
- package/dist/render-scale.js +63 -0
- package/dist/shaders.d.ts +122 -0
- package/dist/shaders.js +1168 -0
- package/dist/shadows.d.ts +119 -0
- package/dist/shadows.js +562 -0
- package/dist/skin-asset.d.ts +26 -0
- package/dist/skin-asset.js +25 -0
- package/dist/skinning-plugin.d.ts +5 -0
- package/dist/skinning-plugin.js +16 -0
- package/dist/skinning.d.ts +19 -0
- package/dist/skinning.js +343 -0
- package/dist/standard-fields.d.ts +67 -0
- package/dist/standard-fields.js +73 -0
- package/dist/standard.d.ts +7 -0
- package/dist/standard.js +37 -0
- package/dist/stats.d.ts +25 -0
- package/dist/stats.js +18 -0
- package/dist/target.d.ts +55 -0
- package/dist/target.js +70 -0
- package/dist/testing.d.ts +34 -0
- package/dist/testing.js +126 -0
- package/dist/timer.d.ts +28 -0
- package/dist/timer.js +136 -0
- package/dist/view.d.ts +117 -0
- package/dist/view.js +389 -0
- package/dist/visibility.d.ts +10 -0
- package/dist/visibility.js +84 -0
- package/package.json +49 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
const POST_SHADERS = {
|
|
2
|
+
"shard::prepass::common": `
|
|
3
|
+
import shard::view::view;
|
|
4
|
+
import shard::pbr::types::VertexOutput;
|
|
5
|
+
import shard::mesh::{ instance_at, mesh_vertex, deform_vertex, previous_world, vertex_position };
|
|
6
|
+
import shard::pbr::gbuffer::oct_encode;
|
|
7
|
+
|
|
8
|
+
struct PrepassVertex {
|
|
9
|
+
@builtin(position) clip: vec4f,
|
|
10
|
+
@location(0) world_position: vec3f,
|
|
11
|
+
@location(1) world_normal: vec3f,
|
|
12
|
+
@location(2) uv: vec2f,
|
|
13
|
+
@location(3) uv1: vec2f,
|
|
14
|
+
@location(4) world_tangent: vec4f,
|
|
15
|
+
@location(5) @interpolate(flat) flags: u32,
|
|
16
|
+
@location(6) current: vec4f,
|
|
17
|
+
@location(7) previous: vec4f,
|
|
18
|
+
@location(8) extra: vec4f,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
struct PrepassOutput {
|
|
22
|
+
/** Octahedral world normal (xy), like gbuffer1. */
|
|
23
|
+
@location(0) normal: vec4f,
|
|
24
|
+
/** Screen motion since last frame, in uv units (+x right, +y down). */
|
|
25
|
+
@location(1) velocity: vec2f,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn prepass_vertex(instance_index: u32, vertex_index: u32, position: vec3f, normal: vec3f, uv: vec2f, uv1: vec2f, tangent: vec4f) -> PrepassVertex {
|
|
29
|
+
// Motion vectors use this frame's pose with last frame's transform: deformation adds none.
|
|
30
|
+
let d = deform_vertex(instance_index, vertex_index, position, normal, tangent);
|
|
31
|
+
let m = mesh_vertex(instance_at(instance_index), d.position, d.normal, uv, uv1, d.tangent);
|
|
32
|
+
var out: PrepassVertex;
|
|
33
|
+
out.clip = view.viewProj * vec4f(m.world_position, 1.0);
|
|
34
|
+
out.world_position = m.world_position;
|
|
35
|
+
out.world_normal = m.world_normal;
|
|
36
|
+
out.uv = m.uv;
|
|
37
|
+
out.uv1 = m.uv1;
|
|
38
|
+
out.world_tangent = m.world_tangent;
|
|
39
|
+
out.flags = m.flags;
|
|
40
|
+
out.extra = m.extra;
|
|
41
|
+
out.current = view.viewProjNoJitter * vec4f(m.world_position, 1.0);
|
|
42
|
+
let before = previous_world(instance_index, vertex_position(d.position, d.normal, uv));
|
|
43
|
+
out.previous = view.prevViewProj * vec4f(before, 1.0);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn surface_of(in: PrepassVertex) -> VertexOutput {
|
|
48
|
+
var v: VertexOutput;
|
|
49
|
+
v.clip = in.clip;
|
|
50
|
+
v.world_position = in.world_position;
|
|
51
|
+
v.world_normal = in.world_normal;
|
|
52
|
+
v.uv = in.uv;
|
|
53
|
+
v.uv1 = in.uv1;
|
|
54
|
+
v.world_tangent = in.world_tangent;
|
|
55
|
+
v.flags = in.flags;
|
|
56
|
+
v.extra = in.extra;
|
|
57
|
+
return v;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn prepass_output(in: PrepassVertex, n: vec3f) -> PrepassOutput {
|
|
61
|
+
var out: PrepassOutput;
|
|
62
|
+
out.normal = vec4f(oct_encode(n), 0.0, 0.0);
|
|
63
|
+
let a = in.current.xy / in.current.w;
|
|
64
|
+
let b = in.previous.xy / in.previous.w;
|
|
65
|
+
out.velocity = (a - b) * vec2f(0.5, -0.5);
|
|
66
|
+
return out;
|
|
67
|
+
}`,
|
|
68
|
+
"shard::prepass": `
|
|
69
|
+
import shard::prepass::common::{ PrepassVertex, PrepassOutput, prepass_vertex, prepass_output, surface_of };
|
|
70
|
+
import shard::pbr::material::pbr_input;
|
|
71
|
+
import shard::pbr::standard::material;
|
|
72
|
+
|
|
73
|
+
@vertex fn vs(
|
|
74
|
+
@builtin(instance_index) instance_index: u32,
|
|
75
|
+
@builtin(vertex_index) vertex_index: u32,
|
|
76
|
+
@location(0) position: vec3f,
|
|
77
|
+
@location(1) normal: vec3f,
|
|
78
|
+
@location(2) uv: vec2f,
|
|
79
|
+
@location(3) uv1: vec2f,
|
|
80
|
+
@location(4) tangent: vec4f,
|
|
81
|
+
) -> PrepassVertex {
|
|
82
|
+
return prepass_vertex(instance_index, vertex_index, position, normal, uv, uv1, tangent);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Depth, the shaded normal (normal maps included), and velocity. */
|
|
86
|
+
@fragment fn fs(in: PrepassVertex) -> PrepassOutput {
|
|
87
|
+
let p = pbr_input(surface_of(in));
|
|
88
|
+
@if(MASK) if (p.alpha < material.alphaCutoff) { discard; }
|
|
89
|
+
return prepass_output(in, p.normal);
|
|
90
|
+
}`,
|
|
91
|
+
"shard::prepass::plain": `
|
|
92
|
+
import shard::prepass::common::{ PrepassVertex, PrepassOutput, prepass_vertex, prepass_output };
|
|
93
|
+
|
|
94
|
+
@vertex fn vs(
|
|
95
|
+
@builtin(instance_index) instance_index: u32,
|
|
96
|
+
@builtin(vertex_index) vertex_index: u32,
|
|
97
|
+
@location(0) position: vec3f,
|
|
98
|
+
@location(1) normal: vec3f,
|
|
99
|
+
@location(2) uv: vec2f,
|
|
100
|
+
@location(3) uv1: vec2f,
|
|
101
|
+
@location(4) tangent: vec4f,
|
|
102
|
+
) -> PrepassVertex {
|
|
103
|
+
return prepass_vertex(instance_index, vertex_index, position, normal, uv, uv1, tangent);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Materials with their own shading: the geometric normal. */
|
|
107
|
+
@fragment fn fs(in: PrepassVertex) -> PrepassOutput {
|
|
108
|
+
return prepass_output(in, normalize(in.world_normal));
|
|
109
|
+
}`,
|
|
110
|
+
"shard::post::fog": `
|
|
111
|
+
import shard::view::view;
|
|
112
|
+
import shard::pbr::lights::directional;
|
|
113
|
+
import shard::pbr::environment::env_irradiance;
|
|
114
|
+
import shard::post::common::{ uv_of, world_at };
|
|
115
|
+
|
|
116
|
+
struct FogParams {
|
|
117
|
+
color: vec4f,
|
|
118
|
+
density: f32,
|
|
119
|
+
falloff: f32,
|
|
120
|
+
start: f32,
|
|
121
|
+
sun: f32,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@group(1) @binding(0) var input: texture_2d<f32>;
|
|
125
|
+
@group(1) @binding(1) var depth_texture: texture_depth_2d;
|
|
126
|
+
@group(1) @binding(2) var<uniform> fog: FogParams;
|
|
127
|
+
|
|
128
|
+
const PI: f32 = 3.14159265;
|
|
129
|
+
|
|
130
|
+
/** Optical depth of exponential height fog along the ray from t0 to t1 (meters). */
|
|
131
|
+
fn optical_depth(y0: f32, dir_y: f32, t0: f32, t1: f32) -> f32 {
|
|
132
|
+
let base = fog.density * exp(-fog.falloff * y0);
|
|
133
|
+
let k = fog.falloff * dir_y;
|
|
134
|
+
if (abs(k) < 1e-5) { return base * (t1 - t0); }
|
|
135
|
+
return base * (exp(-k * t0) - exp(-k * t1)) / k;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
139
|
+
let px = vec2i(frag.xy);
|
|
140
|
+
let c = textureLoad(input, px, 0);
|
|
141
|
+
let depth = textureLoad(depth_texture, px, 0);
|
|
142
|
+
let uv = uv_of(frag.xy);
|
|
143
|
+
// The background is far away, not infinitely: a floor keeps the ray finite.
|
|
144
|
+
let world = world_at(uv, max(depth, 1e-7));
|
|
145
|
+
let to = world - view.cameraPosition;
|
|
146
|
+
let dist = length(to);
|
|
147
|
+
let dir = to / max(dist, 1e-6);
|
|
148
|
+
let tau = optical_depth(view.cameraPosition.y, dir.y, fog.start, max(dist, fog.start));
|
|
149
|
+
let transmittance = exp(-tau);
|
|
150
|
+
// In-scattering: the sky (environment or ambient), plus the sun through a forward-peaked phase.
|
|
151
|
+
var sky = view.ambient;
|
|
152
|
+
if (view.envParams.w > 0.5) { sky = env_irradiance(vec3f(0.0, 1.0, 0.0)) * view.envParams.x; }
|
|
153
|
+
var sun = vec3f(0.0);
|
|
154
|
+
if (directional.count > 0u) {
|
|
155
|
+
let light = directional.lights[0];
|
|
156
|
+
let mu = dot(dir, normalize(light.direction));
|
|
157
|
+
let g = 0.76;
|
|
158
|
+
let hg = (1.0 - g * g) / (4.0 * PI * pow(1.0 + g * g - 2.0 * g * mu, 1.5));
|
|
159
|
+
sun = light.color * view.sunTransmittance[0].rgb * mix(1.0 / (4.0 * PI), hg, fog.sun);
|
|
160
|
+
}
|
|
161
|
+
let inscatter = fog.color.rgb * (sky + sun) * view.exposure;
|
|
162
|
+
return vec4f(c.rgb * transmittance + inscatter * (1.0 - transmittance), c.a);
|
|
163
|
+
}`,
|
|
164
|
+
"shard::post::bloom": `
|
|
165
|
+
import shard::color::luminance;
|
|
166
|
+
|
|
167
|
+
struct BloomParams {
|
|
168
|
+
/** Threshold (pre-exposed), knee (pre-exposed), texel size of the source (zw). */
|
|
169
|
+
threshold: vec4f,
|
|
170
|
+
/** intensity, level count, energy-conserving mix (1) or additive (0), 0. */
|
|
171
|
+
mix: vec4f,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@group(0) @binding(0) var source: texture_2d<f32>;
|
|
175
|
+
@group(0) @binding(1) var source_sampler: sampler;
|
|
176
|
+
@group(0) @binding(2) var<uniform> params: BloomParams;
|
|
177
|
+
@group(0) @binding(3) var scene: texture_2d<f32>;
|
|
178
|
+
|
|
179
|
+
fn tap(uv: vec2f, dx: f32, dy: f32) -> vec3f {
|
|
180
|
+
return textureSampleLevel(source, source_sampler, uv + vec2f(dx, dy) * params.threshold.zw, 0.0).rgb;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Jimenez's 13-tap downsample: four overlapping 2\xD72 boxes and a center one. */
|
|
184
|
+
fn downsample13(uv: vec2f) -> array<vec3f, 5> {
|
|
185
|
+
let a = tap(uv, -2.0, -2.0); let b = tap(uv, 0.0, -2.0); let c = tap(uv, 2.0, -2.0);
|
|
186
|
+
let d = tap(uv, -1.0, -1.0); let e = tap(uv, 1.0, -1.0);
|
|
187
|
+
let f = tap(uv, -2.0, 0.0); let g = tap(uv, 0.0, 0.0); let h = tap(uv, 2.0, 0.0);
|
|
188
|
+
let i = tap(uv, -1.0, 1.0); let j = tap(uv, 1.0, 1.0);
|
|
189
|
+
let k = tap(uv, -2.0, 2.0); let l = tap(uv, 0.0, 2.0); let m = tap(uv, 2.0, 2.0);
|
|
190
|
+
return array<vec3f, 5>((d + e + i + j) * 0.25, (a + b + f + g) * 0.25, (b + c + g + h) * 0.25, (f + g + k + l) * 0.25, (g + h + l + m) * 0.25);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fn karis(c: vec3f) -> f32 { return 1.0 / (1.0 + luminance(c)); }
|
|
194
|
+
|
|
195
|
+
/** Soft threshold: a quadratic knee below the threshold, linear above. */
|
|
196
|
+
fn threshold(c: vec3f) -> vec3f {
|
|
197
|
+
let t = params.threshold.x;
|
|
198
|
+
if (t <= 0.0) { return c; }
|
|
199
|
+
let knee = max(params.threshold.y, 1e-5);
|
|
200
|
+
let b = max(max(c.r, c.g), c.b);
|
|
201
|
+
let soft = clamp(b - t + knee, 0.0, 2.0 * knee);
|
|
202
|
+
let w = max(soft * soft / (4.0 * knee), b - t) / max(b, 1e-5);
|
|
203
|
+
return c * w;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
struct FullscreenInput { @builtin(position) clip: vec4f, @location(0) uv: vec2f }
|
|
207
|
+
|
|
208
|
+
/** First level: threshold, and Karis-weighted boxes so single bright pixels don't flicker. */
|
|
209
|
+
@fragment fn prefilter(in: FullscreenInput) -> @location(0) vec4f {
|
|
210
|
+
let s = downsample13(in.uv);
|
|
211
|
+
let w0 = karis(s[0]); let w1 = karis(s[1]); let w2 = karis(s[2]); let w3 = karis(s[3]); let w4 = karis(s[4]);
|
|
212
|
+
let c = (s[0] * w0 * 0.5 + (s[1] * w1 + s[2] * w2 + s[3] * w3 + s[4] * w4) * 0.125)
|
|
213
|
+
/ (w0 * 0.5 + (w1 + w2 + w3 + w4) * 0.125);
|
|
214
|
+
return vec4f(threshold(c), 1.0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@fragment fn downsample(in: FullscreenInput) -> @location(0) vec4f {
|
|
218
|
+
let s = downsample13(in.uv);
|
|
219
|
+
return vec4f(s[0] * 0.5 + (s[1] + s[2] + s[3] + s[4]) * 0.125, 1.0);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** 3\xD73 tent upsample, added onto the level below. */
|
|
223
|
+
@fragment fn upsample(in: FullscreenInput) -> @location(0) vec4f {
|
|
224
|
+
let c = tap(in.uv, 0.0, 0.0) * 4.0
|
|
225
|
+
+ (tap(in.uv, -1.0, 0.0) + tap(in.uv, 1.0, 0.0) + tap(in.uv, 0.0, -1.0) + tap(in.uv, 0.0, 1.0)) * 2.0
|
|
226
|
+
+ tap(in.uv, -1.0, -1.0) + tap(in.uv, 1.0, -1.0) + tap(in.uv, -1.0, 1.0) + tap(in.uv, 1.0, 1.0);
|
|
227
|
+
return vec4f(c / 16.0, 1.0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** The scene plus the glow: mixed (energy-conserving) without a threshold, added with one. */
|
|
231
|
+
@fragment fn composite(in: FullscreenInput) -> @location(0) vec4f {
|
|
232
|
+
let s = textureLoad(scene, vec2i(in.clip.xy), 0);
|
|
233
|
+
let glow = textureSampleLevel(source, source_sampler, in.uv, 0.0).rgb / params.mix.y;
|
|
234
|
+
let c = select(s.rgb + glow * params.mix.x, mix(s.rgb, glow, params.mix.x), params.mix.z > 0.5);
|
|
235
|
+
return vec4f(c, s.a);
|
|
236
|
+
}`,
|
|
237
|
+
"shard::post::exposure": `
|
|
238
|
+
import shard::view::view;
|
|
239
|
+
import shard::color::luminance;
|
|
240
|
+
|
|
241
|
+
struct MeterParams {
|
|
242
|
+
/** EV of bin 0, bins per EV, metering mode, 0. */
|
|
243
|
+
range: vec4f,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@group(0) @binding(1) var input: texture_2d<f32>;
|
|
247
|
+
@group(0) @binding(2) var<storage, read_write> histogram: array<atomic<u32>, 256>;
|
|
248
|
+
@group(0) @binding(3) var<uniform> params: MeterParams;
|
|
249
|
+
|
|
250
|
+
var<workgroup> local: array<atomic<u32>, 256>;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* A 256-bin histogram of EV100 (log2 of scene luminance \xD7 100 / 12.5, the reflected-light meter),
|
|
254
|
+
* weighted by the metering mode.
|
|
255
|
+
*/
|
|
256
|
+
@compute @workgroup_size(16, 16)
|
|
257
|
+
fn meter(@builtin(global_invocation_id) id: vec3u, @builtin(local_invocation_index) li: u32) {
|
|
258
|
+
atomicStore(&local[li], 0u);
|
|
259
|
+
workgroupBarrier();
|
|
260
|
+
// One pixel per 4\xD74 block: plenty for a meter, and 16\xD7 less work.
|
|
261
|
+
let size = textureDimensions(input);
|
|
262
|
+
let px = id.xy * 4u + 2u;
|
|
263
|
+
if (px.x < size.x && px.y < size.y) {
|
|
264
|
+
let c = textureLoad(input, vec2i(px), 0).rgb;
|
|
265
|
+
let l = luminance(c) / max(view.exposure, 1e-20);
|
|
266
|
+
let ev = log2(max(l, 1e-10) * 8.0);
|
|
267
|
+
let bin = u32(clamp((ev - params.range.x) * params.range.y, 0.0, 255.0));
|
|
268
|
+
let p = vec2f(px) / vec2f(size) * 2.0 - 1.0;
|
|
269
|
+
let r = length(p * vec2f(f32(size.x) / f32(size.y), 1.0)) / 1.4142;
|
|
270
|
+
var w = 16u;
|
|
271
|
+
if (params.range.z == 1.0) { w = u32(round(16.0 * exp(-r * r / 0.18))); }
|
|
272
|
+
if (params.range.z == 2.0) { w = select(0u, 16u, r < 0.1); }
|
|
273
|
+
if (w > 0u) { atomicAdd(&local[bin], w); }
|
|
274
|
+
}
|
|
275
|
+
workgroupBarrier();
|
|
276
|
+
let n = atomicLoad(&local[li]);
|
|
277
|
+
if (n > 0u) { atomicAdd(&histogram[li], n); }
|
|
278
|
+
}`,
|
|
279
|
+
"shard::post::dof": `
|
|
280
|
+
import shard::view::view;
|
|
281
|
+
import shard::post::common::{ uv_of, world_at, view_depth };
|
|
282
|
+
import shard::color::luminance;
|
|
283
|
+
|
|
284
|
+
struct DofParams {
|
|
285
|
+
/** Signed CoC radius in pixels = x / depth + y, capped at z; w: mode (0 gaussian, 1 bokeh). */
|
|
286
|
+
coc: vec4f,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
@group(0) @binding(1) var input: texture_2d<f32>;
|
|
290
|
+
@group(0) @binding(2) var depth_texture: texture_depth_2d;
|
|
291
|
+
@group(0) @binding(3) var<uniform> params: DofParams;
|
|
292
|
+
@group(0) @binding(4) var half_texture: texture_2d<f32>;
|
|
293
|
+
@group(0) @binding(5) var linear_sampler: sampler;
|
|
294
|
+
|
|
295
|
+
/** Signed circle of confusion radius in full-resolution pixels: negative in front of focus. */
|
|
296
|
+
fn coc_at(px: vec2i) -> f32 {
|
|
297
|
+
let depth = textureLoad(depth_texture, px, 0);
|
|
298
|
+
if (depth <= 0.0) { return params.coc.z; }
|
|
299
|
+
let uv = (vec2f(px) + 0.5) * view.viewport.zw;
|
|
300
|
+
let d = view_depth(world_at(uv, depth));
|
|
301
|
+
let c = params.coc.x / d + params.coc.y;
|
|
302
|
+
return clamp(c, -params.coc.z, params.coc.z);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Half resolution: color and signed CoC, taking the largest CoC of the 2\xD72 block. */
|
|
306
|
+
@fragment fn prepare(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
307
|
+
let base = vec2i(frag.xy) * 2;
|
|
308
|
+
var color = vec3f(0.0);
|
|
309
|
+
var coc = 0.0;
|
|
310
|
+
for (var k = 0; k < 4; k++) {
|
|
311
|
+
let px = base + vec2i(k & 1, k >> 1);
|
|
312
|
+
color += textureLoad(input, px, 0).rgb * 0.25;
|
|
313
|
+
let c = coc_at(px);
|
|
314
|
+
if (abs(c) > abs(coc)) { coc = c; }
|
|
315
|
+
}
|
|
316
|
+
return vec4f(color, coc);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const GOLDEN: f32 = 2.39996323;
|
|
320
|
+
const TAPS: i32 = 48;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Gather over a disc the size of this pixel's CoC. A tap counts if its own blur reaches this
|
|
324
|
+
* pixel, so sharp foreground doesn't smear over blurred background.
|
|
325
|
+
*/
|
|
326
|
+
@fragment fn gather(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
327
|
+
let size = vec2f(textureDimensions(half_texture));
|
|
328
|
+
let uv = frag.xy / size;
|
|
329
|
+
let center = textureSampleLevel(half_texture, linear_sampler, uv, 0.0);
|
|
330
|
+
// Half-resolution pixels.
|
|
331
|
+
let radius = abs(center.a) * 0.5;
|
|
332
|
+
if (radius < 0.5) { return center; }
|
|
333
|
+
var sum = vec3f(0.0);
|
|
334
|
+
var weight = 0.0;
|
|
335
|
+
for (var i = 0; i < TAPS; i++) {
|
|
336
|
+
let r = sqrt((f32(i) + 0.5) / f32(TAPS));
|
|
337
|
+
let a = f32(i) * GOLDEN;
|
|
338
|
+
let o = vec2f(cos(a), sin(a)) * r * radius;
|
|
339
|
+
let s = textureSampleLevel(half_texture, linear_sampler, uv + o / size, 0.0);
|
|
340
|
+
let d = r * radius;
|
|
341
|
+
var w = clamp(abs(s.a) * 0.5 - d + 1.0, 0.0, 1.0);
|
|
342
|
+
if (s.a < 0.0) { w = 1.0; } // foreground always bleeds over what's behind it
|
|
343
|
+
// Gaussian falls off toward the rim; bokeh is a flat disc, with highlights weighted up so
|
|
344
|
+
// bright points spread into visible discs.
|
|
345
|
+
if (params.coc.w < 0.5) {
|
|
346
|
+
w *= exp(-2.0 * r * r);
|
|
347
|
+
} else {
|
|
348
|
+
w *= 1.0 + clamp(luminance(s.rgb) - 1.0, 0.0, 8.0);
|
|
349
|
+
}
|
|
350
|
+
sum += s.rgb * w;
|
|
351
|
+
weight += w;
|
|
352
|
+
}
|
|
353
|
+
return vec4f(sum / max(weight, 1e-5), center.a);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Full resolution: the sharp image, or the blur where the CoC is large. */
|
|
357
|
+
@fragment fn composite(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
358
|
+
let px = vec2i(frag.xy);
|
|
359
|
+
let sharp = textureLoad(input, px, 0);
|
|
360
|
+
let blurred = textureSampleLevel(half_texture, linear_sampler, uv_of(frag.xy), 0.0);
|
|
361
|
+
let t = smoothstep(0.5, 1.5, abs(coc_at(px)));
|
|
362
|
+
return vec4f(mix(sharp.rgb, blurred.rgb, t), sharp.a);
|
|
363
|
+
}`,
|
|
364
|
+
"shard::post::motion_blur": `
|
|
365
|
+
import shard::view::view;
|
|
366
|
+
import shard::post::common::{ uv_of, motion };
|
|
367
|
+
|
|
368
|
+
struct MotionParams {
|
|
369
|
+
/** Shutter fraction of a frame, max blur in pixels, samples, 0. */
|
|
370
|
+
params: vec4f,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@group(0) @binding(1) var input: texture_2d<f32>;
|
|
374
|
+
@group(0) @binding(2) var depth_texture: texture_depth_2d;
|
|
375
|
+
@group(0) @binding(3) var velocity: texture_2d<f32>;
|
|
376
|
+
@group(0) @binding(4) var<uniform> motion_params: MotionParams;
|
|
377
|
+
@group(0) @binding(5) var linear_sampler: sampler;
|
|
378
|
+
|
|
379
|
+
@fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
380
|
+
let px = vec2i(frag.xy);
|
|
381
|
+
let uv = uv_of(frag.xy);
|
|
382
|
+
let depth = textureLoad(depth_texture, px, 0);
|
|
383
|
+
var v = motion(uv, depth, textureLoad(velocity, px, 0).xy) * motion_params.params.x;
|
|
384
|
+
let pixels = length(v * view.viewport.xy);
|
|
385
|
+
let cap = motion_params.params.y;
|
|
386
|
+
if (pixels > cap) { v *= cap / pixels; }
|
|
387
|
+
let center = textureLoad(input, px, 0);
|
|
388
|
+
if (pixels < 0.5) { return center; }
|
|
389
|
+
let n = i32(motion_params.params.z);
|
|
390
|
+
var sum = vec3f(0.0);
|
|
391
|
+
for (var i = 0; i < n; i++) {
|
|
392
|
+
let t = (f32(i) + 0.5) / f32(n) - 0.5;
|
|
393
|
+
sum += textureSampleLevel(input, linear_sampler, uv + v * t, 0.0).rgb;
|
|
394
|
+
}
|
|
395
|
+
return vec4f(sum / f32(n), center.a);
|
|
396
|
+
}`,
|
|
397
|
+
"shard::post::taa": `
|
|
398
|
+
import shard::view::view;
|
|
399
|
+
import shard::post::common::{ uv_of, motion };
|
|
400
|
+
import shard::color::luminance;
|
|
401
|
+
|
|
402
|
+
struct TaaParams {
|
|
403
|
+
/** Current frame weight, reset (1: no history), 0, 0. */
|
|
404
|
+
params: vec4f,
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@group(0) @binding(1) var input: texture_2d<f32>;
|
|
408
|
+
@group(0) @binding(2) var depth_texture: texture_depth_2d;
|
|
409
|
+
@group(0) @binding(3) var velocity: texture_2d<f32>;
|
|
410
|
+
@group(0) @binding(4) var history: texture_2d<f32>;
|
|
411
|
+
@group(0) @binding(5) var linear_sampler: sampler;
|
|
412
|
+
@group(0) @binding(6) var<uniform> taa: TaaParams;
|
|
413
|
+
|
|
414
|
+
struct TaaOutput {
|
|
415
|
+
@location(0) color: vec4f,
|
|
416
|
+
@location(1) history: vec4f,
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
fn rgb_to_ycocg(c: vec3f) -> vec3f {
|
|
420
|
+
return vec3f(0.25 * c.r + 0.5 * c.g + 0.25 * c.b, 0.5 * c.r - 0.5 * c.b, -0.25 * c.r + 0.5 * c.g - 0.25 * c.b);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
fn ycocg_to_rgb(c: vec3f) -> vec3f {
|
|
424
|
+
return vec3f(c.x + c.y - c.z, c.x + c.z, c.x - c.y - c.z);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Tonemapped for blending, so one bright sample doesn't dominate (Karis). */
|
|
428
|
+
fn compress(c: vec3f) -> vec3f { return c / (1.0 + luminance(c)); }
|
|
429
|
+
fn expand(c: vec3f) -> vec3f { return c / max(1.0 - luminance(c), 1e-4); }
|
|
430
|
+
|
|
431
|
+
/** Catmull-Rom history sample in 5 bilinear taps (Jimenez). */
|
|
432
|
+
fn sample_history(uv: vec2f) -> vec3f {
|
|
433
|
+
let size = vec2f(textureDimensions(history));
|
|
434
|
+
let p = uv * size;
|
|
435
|
+
let t1 = floor(p - 0.5) + 0.5;
|
|
436
|
+
let f = p - t1;
|
|
437
|
+
let w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
|
|
438
|
+
let w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
|
|
439
|
+
let w2 = f * (0.5 + f * (2.0 - 1.5 * f));
|
|
440
|
+
let w3 = f * f * (-0.5 + 0.5 * f);
|
|
441
|
+
let w12 = w1 + w2;
|
|
442
|
+
let tc12 = (t1 + w2 / w12) / size;
|
|
443
|
+
let tc0 = (t1 - 1.0) / size;
|
|
444
|
+
let tc3 = (t1 + 2.0) / size;
|
|
445
|
+
var c = textureSampleLevel(history, linear_sampler, vec2f(tc12.x, tc0.y), 0.0).rgb * (w12.x * w0.y);
|
|
446
|
+
c += textureSampleLevel(history, linear_sampler, vec2f(tc0.x, tc12.y), 0.0).rgb * (w0.x * w12.y);
|
|
447
|
+
c += textureSampleLevel(history, linear_sampler, vec2f(tc12.x, tc12.y), 0.0).rgb * (w12.x * w12.y);
|
|
448
|
+
c += textureSampleLevel(history, linear_sampler, vec2f(tc3.x, tc12.y), 0.0).rgb * (w3.x * w12.y);
|
|
449
|
+
c += textureSampleLevel(history, linear_sampler, vec2f(tc12.x, tc3.y), 0.0).rgb * (w12.x * w3.y);
|
|
450
|
+
let w = w12.x * w0.y + w0.x * w12.y + w12.x * w12.y + w3.x * w12.y + w12.x * w3.y;
|
|
451
|
+
return max(c / w, vec3f(0.0));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
@fragment fn fs(@builtin(position) frag: vec4f) -> TaaOutput {
|
|
455
|
+
let px = vec2i(frag.xy);
|
|
456
|
+
let size = vec2i(textureDimensions(input));
|
|
457
|
+
let uv = uv_of(frag.xy);
|
|
458
|
+
// Neighborhood: its YCoCg box clamps the history, and its nearest depth picks the velocity, so
|
|
459
|
+
// edges move with the foreground.
|
|
460
|
+
var lo = vec3f(1e20);
|
|
461
|
+
var hi = vec3f(-1e20);
|
|
462
|
+
var nearest = 0.0;
|
|
463
|
+
var nearest_px = px;
|
|
464
|
+
for (var y = -1; y <= 1; y++) {
|
|
465
|
+
for (var x = -1; x <= 1; x++) {
|
|
466
|
+
let q = clamp(px + vec2i(x, y), vec2i(0), size - 1);
|
|
467
|
+
let c = rgb_to_ycocg(compress(textureLoad(input, q, 0).rgb));
|
|
468
|
+
lo = min(lo, c);
|
|
469
|
+
hi = max(hi, c);
|
|
470
|
+
let d = textureLoad(depth_texture, q, 0);
|
|
471
|
+
if (d > nearest) { nearest = d; nearest_px = q; }
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
let current = compress(textureLoad(input, px, 0).rgb);
|
|
475
|
+
let v = motion(uv, nearest, textureLoad(velocity, nearest_px, 0).xy);
|
|
476
|
+
let prev_uv = uv - v;
|
|
477
|
+
var alpha = taa.params.x;
|
|
478
|
+
if (taa.params.y > 0.5 || any(prev_uv < vec2f(0.0)) || any(prev_uv > vec2f(1.0))) { alpha = 1.0; }
|
|
479
|
+
var h = rgb_to_ycocg(compress(sample_history(prev_uv)));
|
|
480
|
+
// Clip toward the box center rather than clamping per channel: keeps hue.
|
|
481
|
+
let center = (lo + hi) * 0.5;
|
|
482
|
+
let extent = max((hi - lo) * 0.5, vec3f(1e-5));
|
|
483
|
+
let offset = h - center;
|
|
484
|
+
let units = abs(offset / extent);
|
|
485
|
+
let m = max(units.x, max(units.y, units.z));
|
|
486
|
+
if (m > 1.0) { h = center + offset / m; }
|
|
487
|
+
let blended = mix(ycocg_to_rgb(h), current, alpha);
|
|
488
|
+
let color = expand(max(blended, vec3f(0.0)));
|
|
489
|
+
var out: TaaOutput;
|
|
490
|
+
out.color = vec4f(color, 1.0);
|
|
491
|
+
out.history = vec4f(color, 1.0);
|
|
492
|
+
return out;
|
|
493
|
+
}`,
|
|
494
|
+
"shard::post::ssao": `
|
|
495
|
+
import shard::view::view;
|
|
496
|
+
import shard::pbr::gbuffer::oct_decode;
|
|
497
|
+
import shard::color::ign;
|
|
498
|
+
|
|
499
|
+
struct SsaoParams {
|
|
500
|
+
/** Radius (m), intensity, directions, steps. */
|
|
501
|
+
params: vec4f,
|
|
502
|
+
/** Pixels per meter at 1 m (projection scale), 0, 0, 0. */
|
|
503
|
+
scale: vec4f,
|
|
504
|
+
/** Clip to view space: one multiply per sample, not two. */
|
|
505
|
+
inv_proj: mat4x4f,
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
@group(0) @binding(1) var depth_texture: texture_depth_2d;
|
|
509
|
+
@group(0) @binding(2) var normal_texture: texture_2d<f32>;
|
|
510
|
+
@group(0) @binding(3) var<uniform> ssao: SsaoParams;
|
|
511
|
+
@group(0) @binding(4) var half_ao: texture_2d<f32>;
|
|
512
|
+
|
|
513
|
+
const PI: f32 = 3.14159265;
|
|
514
|
+
|
|
515
|
+
fn view_position(px: vec2i) -> vec3f {
|
|
516
|
+
let d = max(textureLoad(depth_texture, px, 0), 1e-7);
|
|
517
|
+
let uv = (vec2f(px) + 0.5) * view.viewport.zw;
|
|
518
|
+
let p = ssao.inv_proj * vec4f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, d, 1.0);
|
|
519
|
+
return p.xyz / p.w;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Ground-truth AO (Jimenez 2016), at half resolution: per slice direction, the horizon angles on
|
|
524
|
+
* both sides, integrated against the normal projected into the slice.
|
|
525
|
+
*/
|
|
526
|
+
@fragment fn gtao(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
527
|
+
let full = vec2i(textureDimensions(depth_texture));
|
|
528
|
+
let px = min(vec2i(frag.xy) * 2, full - 1);
|
|
529
|
+
let depth = textureLoad(depth_texture, px, 0);
|
|
530
|
+
if (depth <= 0.0) { return vec4f(1.0); }
|
|
531
|
+
let p = view_position(px);
|
|
532
|
+
let v = normalize(-p);
|
|
533
|
+
let n = normalize((view.view * vec4f(oct_decode(textureLoad(normal_texture, px, 0).xy), 0.0)).xyz);
|
|
534
|
+
let radius = ssao.params.x;
|
|
535
|
+
let dirs = i32(ssao.params.z);
|
|
536
|
+
let steps = i32(ssao.params.w);
|
|
537
|
+
// The radius on screen, in full-resolution pixels.
|
|
538
|
+
let screen_radius = radius * ssao.scale.x / max(-p.z, 1e-3);
|
|
539
|
+
if (screen_radius < 1.0) { return vec4f(1.0); }
|
|
540
|
+
let noise = ign(frag.xy);
|
|
541
|
+
let jitter = ign(frag.xy + vec2f(17.0, 29.0));
|
|
542
|
+
var visibility = 0.0;
|
|
543
|
+
for (var s = 0; s < dirs; s++) {
|
|
544
|
+
let phi = (f32(s) + noise) / f32(dirs) * PI;
|
|
545
|
+
let dir = vec2f(cos(phi), sin(phi));
|
|
546
|
+
// Slice plane: view direction and the screen direction (y flips from screen to view).
|
|
547
|
+
let dir3 = vec3f(dir.x, -dir.y, 0.0);
|
|
548
|
+
let ortho = dir3 - v * dot(dir3, v);
|
|
549
|
+
let axis = normalize(cross(ortho, v));
|
|
550
|
+
let np = n - axis * dot(n, axis);
|
|
551
|
+
let np_len = max(length(np), 1e-4);
|
|
552
|
+
let cos_n = clamp(dot(np, v) / np_len, -1.0, 1.0);
|
|
553
|
+
let angle_n = sign(dot(np, cross(v, axis))) * acos(cos_n);
|
|
554
|
+
var h = array<f32, 2>(-1.0, -1.0);
|
|
555
|
+
for (var side = 0; side < 2; side++) {
|
|
556
|
+
let sd = select(-dir, dir, side == 0);
|
|
557
|
+
for (var k = 0; k < steps; k++) {
|
|
558
|
+
let t = (f32(k) + jitter) / f32(steps);
|
|
559
|
+
let o = vec2i(round(sd * t * t * screen_radius));
|
|
560
|
+
if (o.x == 0 && o.y == 0) { continue; }
|
|
561
|
+
let q = clamp(px + o, vec2i(0), full - 1);
|
|
562
|
+
let delta = view_position(q) - p;
|
|
563
|
+
let len = length(delta);
|
|
564
|
+
let falloff = clamp(1.0 - len * len / (radius * radius), 0.0, 1.0);
|
|
565
|
+
let c = mix(-1.0, dot(delta / max(len, 1e-5), v), falloff);
|
|
566
|
+
h[side] = max(h[side], c);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
let h0 = angle_n + max(-acos(h[1]) - angle_n, -PI * 0.5);
|
|
570
|
+
let h1 = angle_n + min(acos(h[0]) - angle_n, PI * 0.5);
|
|
571
|
+
let sin_n = sin(angle_n);
|
|
572
|
+
let arc0 = (cos_n + 2.0 * h0 * sin_n - cos(2.0 * h0 - angle_n)) * 0.25;
|
|
573
|
+
let arc1 = (cos_n + 2.0 * h1 * sin_n - cos(2.0 * h1 - angle_n)) * 0.25;
|
|
574
|
+
visibility += np_len * (arc0 + arc1);
|
|
575
|
+
}
|
|
576
|
+
let ao = clamp(visibility / f32(dirs), 0.0, 1.0);
|
|
577
|
+
return vec4f(pow(ao, ssao.params.y), -p.z, 0.0, 1.0);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** Back to full resolution: the four nearest half-resolution texels, weighted by depth match. */
|
|
581
|
+
@fragment fn upsample(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
582
|
+
let px = vec2i(frag.xy);
|
|
583
|
+
let depth = textureLoad(depth_texture, px, 0);
|
|
584
|
+
if (depth <= 0.0) { return vec4f(1.0); }
|
|
585
|
+
let z = -view_position(px).z;
|
|
586
|
+
let half_size = vec2i(textureDimensions(half_ao));
|
|
587
|
+
let base = vec2i(floor((frag.xy - 1.0) * 0.5));
|
|
588
|
+
var sum = 0.0;
|
|
589
|
+
var weight = 0.0;
|
|
590
|
+
for (var k = 0; k < 4; k++) {
|
|
591
|
+
let q = clamp(base + vec2i(k & 1, k >> 1), vec2i(0), half_size - 1);
|
|
592
|
+
let s = textureLoad(half_ao, q, 0);
|
|
593
|
+
let w = 1.0 / (1e-3 + abs(s.y - z) / max(z, 1e-3));
|
|
594
|
+
sum += s.x * w;
|
|
595
|
+
weight += w;
|
|
596
|
+
}
|
|
597
|
+
return vec4f(sum / max(weight, 1e-6), 0.0, 0.0, 1.0);
|
|
598
|
+
}`
|
|
599
|
+
};
|
|
600
|
+
const FXAA_SHADERS = {
|
|
601
|
+
"shard::post::fxaa": `
|
|
602
|
+
import shard::color::{ linear_to_srgb, srgb_to_linear };
|
|
603
|
+
|
|
604
|
+
@group(0) @binding(0) var input: texture_2d<f32>;
|
|
605
|
+
@group(0) @binding(1) var input_sampler: sampler;
|
|
606
|
+
|
|
607
|
+
fn luma(c: vec3f) -> f32 {
|
|
608
|
+
// sRGB-format inputs sample as linear; luma is measured in display space either way.
|
|
609
|
+
@if(SRGB_TARGET) return dot(linear_to_srgb(c), vec3f(0.299, 0.587, 0.114));
|
|
610
|
+
@if(!SRGB_TARGET) return dot(c, vec3f(0.299, 0.587, 0.114));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
fn at(uv: vec2f, o: vec2f, texel: vec2f) -> vec3f {
|
|
614
|
+
return textureSampleLevel(input, input_sampler, uv + o * texel, 0.0).rgb;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** FXAA 3.11 (Lottes), quality preset 12: local contrast, edge direction, end search, blend. */
|
|
618
|
+
@fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
619
|
+
let size = vec2f(textureDimensions(input));
|
|
620
|
+
let texel = 1.0 / size;
|
|
621
|
+
let uv = frag.xy * texel;
|
|
622
|
+
let center = at(uv, vec2f(0.0), texel);
|
|
623
|
+
let m = luma(center);
|
|
624
|
+
let n = luma(at(uv, vec2f(0.0, -1.0), texel));
|
|
625
|
+
let s = luma(at(uv, vec2f(0.0, 1.0), texel));
|
|
626
|
+
let e = luma(at(uv, vec2f(1.0, 0.0), texel));
|
|
627
|
+
let w = luma(at(uv, vec2f(-1.0, 0.0), texel));
|
|
628
|
+
let lo = min(m, min(min(n, s), min(e, w)));
|
|
629
|
+
let hi = max(m, max(max(n, s), max(e, w)));
|
|
630
|
+
let range = hi - lo;
|
|
631
|
+
if (range < max(0.0312, hi * 0.125)) { return vec4f(center, 1.0); }
|
|
632
|
+
let nw = luma(at(uv, vec2f(-1.0, -1.0), texel));
|
|
633
|
+
let ne = luma(at(uv, vec2f(1.0, -1.0), texel));
|
|
634
|
+
let sw = luma(at(uv, vec2f(-1.0, 1.0), texel));
|
|
635
|
+
let se = luma(at(uv, vec2f(1.0, 1.0), texel));
|
|
636
|
+
// Sub-pixel aliasing amount.
|
|
637
|
+
let average = (2.0 * (n + s + e + w) + nw + ne + sw + se) / 12.0;
|
|
638
|
+
let sub = clamp(abs(average - m) / range, 0.0, 1.0);
|
|
639
|
+
let sub_blend = smoothstep(0.0, 1.0, sub) * smoothstep(0.0, 1.0, sub) * 0.75;
|
|
640
|
+
let horizontal = abs(nw + ne - 2.0 * n) + 2.0 * abs(w + e - 2.0 * m) + abs(sw + se - 2.0 * s)
|
|
641
|
+
>= abs(nw + sw - 2.0 * w) + 2.0 * abs(n + s - 2.0 * m) + abs(ne + se - 2.0 * e);
|
|
642
|
+
let p_luma = select(e, s, horizontal);
|
|
643
|
+
let n_luma = select(w, n, horizontal);
|
|
644
|
+
let p_grad = abs(p_luma - m);
|
|
645
|
+
let n_grad = abs(n_luma - m);
|
|
646
|
+
var step_len = select(texel.x, texel.y, horizontal);
|
|
647
|
+
var edge_luma = 0.0;
|
|
648
|
+
var gradient = 0.0;
|
|
649
|
+
if (p_grad < n_grad) {
|
|
650
|
+
step_len = -step_len;
|
|
651
|
+
edge_luma = (n_luma + m) * 0.5;
|
|
652
|
+
gradient = n_grad;
|
|
653
|
+
} else {
|
|
654
|
+
edge_luma = (p_luma + m) * 0.5;
|
|
655
|
+
gradient = p_grad;
|
|
656
|
+
}
|
|
657
|
+
var edge_uv = uv;
|
|
658
|
+
var along = vec2f(texel.x, 0.0);
|
|
659
|
+
if (horizontal) { edge_uv.y += step_len * 0.5; } else { edge_uv.x += step_len * 0.5; along = vec2f(0.0, texel.y); }
|
|
660
|
+
let threshold = gradient * 0.25;
|
|
661
|
+
// Walk both ways along the edge until the luma leaves the edge.
|
|
662
|
+
let steps = array<f32, 12>(1.0, 1.0, 1.0, 1.0, 1.0, 1.5, 2.0, 2.0, 2.0, 2.0, 4.0, 8.0);
|
|
663
|
+
var up = edge_uv + along;
|
|
664
|
+
var down = edge_uv - along;
|
|
665
|
+
var lu = luma(textureSampleLevel(input, input_sampler, up, 0.0).rgb) - edge_luma;
|
|
666
|
+
var ld = luma(textureSampleLevel(input, input_sampler, down, 0.0).rgb) - edge_luma;
|
|
667
|
+
var done_u = abs(lu) >= threshold;
|
|
668
|
+
var done_d = abs(ld) >= threshold;
|
|
669
|
+
for (var i = 1; i < 12; i++) {
|
|
670
|
+
if (done_u && done_d) { break; }
|
|
671
|
+
if (!done_u) {
|
|
672
|
+
up += along * steps[i];
|
|
673
|
+
lu = luma(textureSampleLevel(input, input_sampler, up, 0.0).rgb) - edge_luma;
|
|
674
|
+
done_u = abs(lu) >= threshold;
|
|
675
|
+
}
|
|
676
|
+
if (!done_d) {
|
|
677
|
+
down -= along * steps[i];
|
|
678
|
+
ld = luma(textureSampleLevel(input, input_sampler, down, 0.0).rgb) - edge_luma;
|
|
679
|
+
done_d = abs(ld) >= threshold;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
let du = select(up.y - uv.y, up.x - uv.x, horizontal);
|
|
683
|
+
let dd = select(uv.y - down.y, uv.x - down.x, horizontal);
|
|
684
|
+
let closer_up = du < dd;
|
|
685
|
+
let dist = min(du, dd);
|
|
686
|
+
let span = du + dd;
|
|
687
|
+
let ends_right = (select(ld, lu, closer_up) < 0.0) != ((m - edge_luma) < 0.0);
|
|
688
|
+
let edge_blend = select(0.0, 0.5 - dist / span, ends_right);
|
|
689
|
+
let blend = max(edge_blend, sub_blend);
|
|
690
|
+
var final_uv = uv;
|
|
691
|
+
if (horizontal) { final_uv.y += blend * step_len; } else { final_uv.x += blend * step_len; }
|
|
692
|
+
return vec4f(textureSampleLevel(input, input_sampler, final_uv, 0.0).rgb, 1.0);
|
|
693
|
+
}`
|
|
694
|
+
};
|
|
695
|
+
export {
|
|
696
|
+
FXAA_SHADERS,
|
|
697
|
+
POST_SHADERS
|
|
698
|
+
};
|