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 src = bloomSource(b);
  if (src == null) return Vector4(0.0, 0.0, 0.0, 1.0);
  final u = v[0];
  final w = v[1];

  // A four-tap box at the corners of the source quad. A single tap aliases a
  // one-pixel highlight in and out of existence as the camera moves, which
  // reads as flickering rather than as bloom.
  //
  // Weighted by Karis's `1 / (1 + luma)`, as `bloom_threshold.frag`.
  var sum = Vector3.zero();
  var weights = 0.0;
  for (final d in const <List<double>>[
    <double>[-0.5, -0.5],
    <double>[0.5, -0.5],
    <double>[-0.5, 0.5],
    <double>[0.5, 0.5],
  ]) {
    final t = src.source.sample(u + src.tx * d[0], w + src.ty * d[1]);
    final tap = Vector3(t.x, t.y, t.z);
    final weight = 1.0 / (1.0 + _luminance(tap));
    sum += tap * weight;
    weights += weight;
  }
  final color = sum..scale(1.0 / weights);

  final threshold = src.params.z;
  final knee = math.max(src.params.w, 1e-4);
  // A soft knee rather than a hard step: a hard cut makes the bloom appear
  // along a visible contour as a highlight brightens through the threshold.
  final brightness = _luminance(color);
  var soft = (brightness - threshold + knee).clamp(0.0, 2.0 * knee);
  soft = soft * soft / (4.0 * knee);
  final contribution =
      math.max(soft, brightness - threshold) / math.max(brightness, 1e-4);

  color.scale(contribution);
  return Vector4(color.x, color.y, color.z, 1.0);
}