A Fast and Stable Feature-Aware Motion Blur Filterの疑似コード。
float cone(float dist, float r) {
return saturate(1.0f - abs(dist) / r);
}
float cylinder(float dist, float r) {
return sign(r - abs(dist)) * 0.5f + 0.5f;
}
// linear depth.
float zCompare(float za, float zb) {
const float SOFT_Z_EXTENT = 0.1f;
return saturate(1.0 - (za - ab) / SOFT_Z_EXTENT);
}
float3 MotionBlur(float2 p)
{
// parameter setting (see. 5. Implementation and Results).
const auto N = 35; // sample count.
const auto eta = 0.95; // a larger maximum jitter value (in pixel units). (see p.6)
const auto phi = 27; // user-determined constant which affects the "baseline2 jitter level. (see p.6)
const auto kappa = 40; // use-parameter to bais its importance. (see p.6)
const auto r = 40; // a maximum image-space blur radius. (see p.2)
const auto gamma = 1.5; // minimum user threshold (see p.4)
auto j = Halton(-1, 1);
// sOffset jitters a tile lookup (but never into a diagonal tile).
auto vmax = FetchNeighborMax(p/r + sOffset(p, j));
auto mag_vmax = length(vmax);
if (mag_vmax <= 0.5f)
{
return FetchColor(p);
}
auto wn = vmax / mag_vmax;
auto vc = FetchVelocity(p);
auto wp = (-wn.y, wn.x); // vmax⊥.
if (dot(wp, vc) < 0.0)
{
wp = -wp;
}
auto mag_vc = length(vc);
auto wc = normalize(lerp(normalize(vc), wp, (mag_vc - 0.5) / gamma); // Eq. (1).
// First integration samples: p with center weight
auto totalWeight = N / (kappa * mag_vc);
auto result = FetchColor(p) * totalWeight;
auto j_dash = j * eta * phi / N;
auto z_p = FetchDepth(p);
for(int i=0; i<N; ++i)
{
auto t = lerp(-1.0, 1.0, (i+j_dash + 1)/(N+1)); // jitter sampler
// Compute point S; split samples between {vmax, vc}
auto d = (i & 0x1) ? vc : vmax; // iが奇数なら vc, iが偶数なら vmax.
auto T = t * mag_vmax;
auto S = int2(t * d) + p;
// Compute S's velocity and color
auto vs = FetchVelocity(S);
auto colorSmaple = FetchColor(S);
auto z_S = FetchDepth(S);
// Fore-vs. background classification Y w.r.t p
auto f = zCompare(z_p, z_S);
auto b = zCompare(z_S, z_p);
// Sample weight and velocity-aware factors (Sec.4.1)
// The length of v_s is clamped to 0.5 minimum during normalization
auto weight = 0;
auto wA = dot(wc, d);
auto wB = dot(normalize(vs), d);
auto mag_vs = length(vs);
// 3 phenomenological cases (Sec. 3, 4.1): Object
// moving over p, p's blurred motion, & their blending.
weight += dot(f, cone(T, 1 / mag_vs)) * wB;
weight += dot(b, cone(T, 1 / mag_vc)) * wA;
weight += cylinder(T, min(mag_vs, mag_vc)) * max(wA, wB) * 2;
totalWeight += weight; // For normalization
result += colorSample * weight;
}
return result / totalWeight;
}
McGuireのG3Dエンジンでは論文実装よりも変更がある。以下のように内積計算部分がガッツリなくなっている。
https://sourceforge.net/p/g3d/code/HEAD/tree/G3D10/data-files/shader/MotionBlur/MotionBlur_gather.pix
auto f = zCompare(z_p, z_S);
auto b = zCompare(z_S, z_p);
auto weight = 0.0f;
weight += b * cone(T, 1 / mag_vs);
weight += f * cone(T, 1 / mag_vc);
weight += cylinder(T, min(mag_vs, mag_vc)) * 2.0f;
totalWeight += weight;
result += colorSample * weight;