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 sceneTexture = b.textures['scene_texture'];
  if (sceneTexture == null) return Vector4(0.0, 0.0, 0.0, 1.0);
  final center = sceneTexture.sample(v[0], v[1]);

  final lens = b.vec4('DofInfo', 'lens', Vector4.zero());
  final samples = (lens.w + 0.5).floor();
  if (samples < 1) return center;

  final surfaceTexture = b.textures['surface_texture'];
  if (surfaceTexture == null) return center;

  final params = b.vec4('DofInfo', 'params', Vector4.zero());

  double circleAt(double depth) => circleOfConfusion(
    depth,
    focusDistance: lens.x,
    focalLength: lens.y,
    aperture: lens.z,
    maxRadius: params.z,
    texelsPerMeter: params.w,
  );

  final centreDepth = surfaceTexture.sample(v[0], v[1]).w;
  final radius = circleAt(centreDepth);
  // As far as anything nearby could spread — the tile neighbourhood's
  // largest circle — and never less than this pixel's own.
  final tiles = b.textures['coc_tile_texture'];
  final gather = math.max(
    tiles == null ? 0.0 : tiles.sample(v[0], v[1]).x,
    radius,
  );
  if (gather < 0.5) return center;

  var totalX = center.x;
  var totalY = center.y;
  var totalZ = center.z;
  var weight = 1.0;
  var nearX = 0.0;
  var nearY = 0.0;
  var nearZ = 0.0;
  var nearWeight = 0.0;
  var nearCover = 0.0;

  final centreFar = centreDepth <= 0.0 ? 1e9 : centreDepth;
  final turn = 6.2831853 * bayerCell(c.coord.x, c.coord.y);

  for (var i = 1; i <= samples && i <= 64; i++) {
    final t = (i - 0.5) / samples;
    final r = math.sqrt(t) * gather;
    final angle = i * _golden + turn;
    final atU = v[0] + math.cos(angle) * r * params.x;
    final atV = v[1] + math.sin(angle) * r * params.y;

    final tap = sceneTexture.sample(atU, atV);
    final tapDepth = surfaceTexture.sample(atU, atV).w;
    final tapRadius = circleAt(tapDepth);
    final tapFar = tapDepth <= 0.0 ? 1e9 : tapDepth;

    // Would this sample's own disc have reached here? See the GLSL.
    final tapReach = tapFar > centreFar
        ? math.min(tapRadius, radius)
        : tapRadius;
    final reach = (tapReach - r + 0.5).clamp(0.0, 1.0);

    if (tapFar < centreFar && tapRadius > radius) {
      // In front and more blurred: the share of this pixel its disc
      // covers, 1 / (pi c^2) of it per unit of the gather's area.
      final spread = gather / math.max(tapRadius, 0.5);
      nearX += tap.x * reach;
      nearY += tap.y * reach;
      nearZ += tap.z * reach;
      nearWeight += reach;
      nearCover += reach * spread * spread;
    } else {
      totalX += tap.x * reach;
      totalY += tap.y * reach;
      totalZ += tap.z * reach;
      weight += reach;
    }
  }

  final nearScale = 1.0 / math.max(nearWeight, 1e-5);
  final cover = (nearCover / samples).clamp(0.0, 1.0);
  double mix(double far, double near) => far + (near - far) * cover;
  return Vector4(
    mix(totalX / weight, nearX * nearScale),
    mix(totalY / weight, nearY * nearScale),
    mix(totalZ / weight, nearZ * nearScale),
    center.w,
  );
}