run method
Returns linear RGBA for attachment zero. Null discards the fragment.
Implementation
@override
Vector4? run(Float32List v, ShaderBindings b, FragmentContext c) {
final scene = b.textures['scene_texture'];
final history = b.textures['history_texture'];
final velocity = b.textures['velocity_texture'];
final surface = b.textures['surface_texture'];
if (scene == null ||
history == null ||
velocity == null ||
surface == null) {
return Vector4(0.0, 0.0, 0.0, 0.0);
}
final sceneTexel = b.vec4(_block, 'scene_texel', Vector4.zero());
final jitter = b.vec4(_block, 'jitter', Vector4.zero());
final params = b.vec4(_block, 'params', Vector4.zero());
final exposure = params.x;
final u = v[0] + jitter.x;
final w = v[1] + jitter.y;
final cu = ((u * sceneTexel.z).floorToDouble() + 0.5) * sceneTexel.x;
final cw = ((w * sceneTexel.w).floorToDouble() + 0.5) * sceneTexel.y;
final sum = Vector3.zero();
final sumSquares = Vector3.zero();
final lowest = Vector3.all(1e30);
final highest = Vector3.all(-1e30);
var nearest = 1e30;
var nearestU = cu;
var nearestW = cw;
final around = <Vector3>[];
for (var dy = -1; dy <= 1; dy++) {
for (var dx = -1; dx <= 1; dx++) {
final au = cu + dx * sceneTexel.x;
final aw = cw + dy * sceneTexel.y;
final col = _toYCoCg(_weigh(_rgb(scene.sample(au, aw)), exposure));
around.add(col);
sum.add(col);
sumSquares.add(Vector3(col.x * col.x, col.y * col.y, col.z * col.z));
Vector3.min(lowest, col, lowest);
Vector3.max(highest, col, highest);
final depth = surface.sample(au, aw).w;
if (depth > 0.0 && depth < nearest) {
nearest = depth;
nearestU = au;
nearestW = aw;
}
}
}
final current = _rgb(scene.sample(u, w));
// The nearest depth around the pixel, as the GLSL says why.
final depth = nearest < 1e30 ? nearest : 0.0;
final motion = velocity.sample(nearestU, nearestW);
final thenU = v[0] - motion.x;
final thenW = v[1] - motion.y;
if (jitter.w < 0.5 ||
thenU < 0.0 ||
thenU > 1.0 ||
thenW < 0.0 ||
thenW > 1.0) {
return Vector4(current.x, current.y, current.z, depth);
}
final trust = _depthTrust(history, depth, thenU, thenW, params);
final mean = sum / 9.0;
final variance =
sumSquares / 9.0 -
Vector3(mean.x * mean.x, mean.y * mean.y, mean.z * mean.z);
final sigma = Vector3(
math.sqrt(math.max(variance.x, 0.0)),
math.sqrt(math.max(variance.y, 0.0)),
math.sqrt(math.max(variance.z, 0.0)),
);
final lo = Vector3.zero();
final hi = Vector3.zero();
Vector3.max(lowest, mean - sigma * 1.25, lo);
Vector3.min(highest, mean + sigma * 1.25, hi);
final remembered = _toYCoCg(
_weigh(_historyAt(history, thenU, thenW, params), exposure),
);
final axes = b.vec4(_block, 'clip', Vector4.zero()).x;
final clipped = axes > 0.5
? _clipToDop(
_toYCoCg(_weigh(current, exposure)),
remembered,
around,
b,
axes.round(),
)
: _clipToBox(lo, hi, remembered);
final past = _unweigh(_fromYCoCg(clipped), exposure);
// `R4`: the reactive mark, at the pixel itself.
final reactive = velocity.sample(cu, cw).z.clamp(0.0, 1.0);
final keep = jitter.z * trust * (1.0 - reactive);
final wCurrent = (1.0 - keep) / (1.0 + _luma(current) * exposure);
final wHistory = keep / (1.0 + _luma(past) * exposure);
final resolved =
(current * wCurrent + past * wHistory) /
math.max(wCurrent + wHistory, 1e-6);
return Vector4(resolved.x, resolved.y, resolved.z, depth);
}