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'];
if (scene == null) return Vector4(0.0, 0.0, 0.0, 1.0);
final center = scene.sample(v[0], v[1]);
final velocity = b.textures['velocity_texture'];
final surface = b.textures['surface_texture'];
final neighbors = b.textures['neighbor_texture'];
if (velocity == null || surface == null || neighbors == null) {
return center;
}
final size = b.vec4('MotionBlurInfo', 'scene', Vector4.zero());
final params = b.vec4('MotionBlurInfo', 'params', Vector4.zero());
final tiles = b.vec4('MotionBlurInfo', 'tiles', Vector4.zero());
final hereX = v[0] * size.z;
final hereY = v[1] * size.w;
final tileWidth = math.max(tiles.z, 1.0);
final dominant = neighbors.sample(
((hereX / tileWidth).floorToDouble() + 0.5) / tiles.x,
((hereY / tileWidth).floorToDouble() + 0.5) / tiles.y,
);
final reach = math.sqrt(dominant.x * dominant.x + dominant.y * dominant.y);
final samples = (params.w + 0.5).floor();
if (reach <= 0.5 || samples < 1) {
return center;
}
double spanAt(double u, double w) {
final motion = velocity.sample(u, w);
return math.max(
_halfMotion(motion.x, motion.y, params, params.z).length,
0.5,
);
}
final ownSpan = spanAt(v[0], v[1]);
final ownDepth = _far(surface.sample(v[0], v[1]).w);
final extent = math.max(tiles.w, 1e-4);
final stride = 2.0 * reach / (samples + 1);
final ownShare = math.min(math.max(stride, 1.0) / (2.0 * ownSpan), 1.0);
final front = Vector3.zero();
var frontCover = 0.0;
final level = Vector3(center.x, center.y, center.z) * ownShare;
var levelCover = ownShare;
final back = Vector3.zero();
var backWeight = 0.0;
final jitter = pixelNoise(b, c.coord.x, c.coord.y) - 0.5;
final middle = (samples - 1) ~/ 2;
for (var i = 0; i < 64 && i < samples; i++) {
if (i == middle) continue;
final t = -1.0 + 2.0 * ((i + jitter + 1.0) / (samples + 1));
final thereX = (hereX + dominant.x * t).floorToDouble() + 0.5;
final thereY = (hereY + dominant.y * t).floorToDouble() + 0.5;
final atU = thereX * size.x;
final atW = thereY * size.y;
final gap = math.sqrt(
(thereX - hereX) * (thereX - hereX) +
(thereY - hereY) * (thereY - hereY),
);
final tapColor = scene.sample(atU, atW);
final tap = Vector3(tapColor.x, tapColor.y, tapColor.z);
final tapSpan = spanAt(atU, atW);
final tapDepth = _far(surface.sample(atU, atW).w);
final nearer = ((ownDepth - tapDepth) / extent).clamp(0.0, 1.0);
final behind = ((tapDepth - ownDepth) / extent).clamp(0.0, 1.0);
final share =
_reaches(gap, tapSpan) * math.min(stride / (2.0 * tapSpan), 1.0);
front.addScaled(tap, nearer * share);
frontCover += nearer * share;
level.addScaled(tap, (1.0 - nearer - behind) * share);
levelCover += (1.0 - nearer - behind) * share;
final nearness = behind / math.max(gap * gap, 1.0);
back.addScaled(tap, nearness);
backWeight += nearness;
}
final own = level / levelCover;
final behindColor = backWeight > 0.0 ? back / backWeight : own;
final under = _mix(behindColor, own, math.min(levelCover, 1.0));
final over = frontCover > 0.0 ? front / frontCover : under;
final blurred = _mix(under, over, math.min(frontCover, 1.0));
return Vector4(blurred.x, blurred.y, blurred.z, center.w);
}