run method
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,
);
}