run method

  1. @override
Vector4? run(
  1. Float32List v,
  2. ShaderBindings b,
  3. FragmentContext c
)
override

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);
}