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;