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