fix(effects): calibrate CC Blobbylize to the AE oracle

Use the measured Softness kernel (sigma = 0.42 * Softness), threshold the blurred height with a ~1 px antialias, and allow Cut Away 100 to empty the matte. Split the source-matte displacement from the height-field RGB refraction: the matte uses a z=1 bevel normal fitted to the T2 step edges, while RGB follows the measured z=height refraction. Align the documented AE defaults and add CPU regression tests for the cut threshold, Cut Away 100, and the inward matte warp. Update the GPU kernel and parameters to match, keeping CPU/GPU parity.
This commit is contained in:
Camille Moine
2026-10-21 13:06:40 +02:00
parent 9db7f3b217
commit 89d954a10b
3 changed files with 159 additions and 44 deletions
+4 -2
View File
@@ -586,17 +586,19 @@ fn blobbylize(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let soft = pr.f("softness").max(0.0) * b.scale;
let blob = layer_or_self(e, ctx, &b, "blobLayer")?;
let raw = plane(e, &blob, pr.e("property").min(10));
let h = gaussian_blur(e, &raw, soft, soft, true);
let h = gaussian_blur(e, &raw, soft * 0.42, soft * 0.42, true);
let lpos = b.to_px(pr.v2("lightPosition"));
let lheight = pr.f("lightHeight");
let (d, el) = (pr.f("lightDirection").to_radians(), (lheight / 100.0).clamp(0.0, 1.0) * std::f64::consts::FRAC_PI_2);
let ldir = [d.sin() * el.cos(), -d.cos() * el.cos(), el.sin()];
let span = (b.img.width.max(b.img.height) as f64).max(1.0);
let mut p = Params::default();
p.f[0] = f4([(soft * 0.5).max(1.0), (pr.f("cutAway") / 100.0).clamp(0.0, 0.99), pr.f("lightIntensity") / 100.0, pr.f("ambient") / 100.0]);
p.f[0] = f4([(pr.f("cutAway") / 100.0).clamp(0.0, 1.0), pr.f("lightIntensity") / 100.0, pr.f("ambient") / 100.0, soft * 1.5]);
p.f[1] = f4([pr.f("diffuse") / 100.0, pr.f("specular") / 100.0, (1.0 / pr.f("roughness").clamp(0.001, 1.0)).min(500.0), pr.f("metal") / 100.0]);
p.f[2] = f4([lpos.0, lpos.1, lheight / 100.0 * span, (pr.e("lightType") == 1) as u32 as f64]);
p.f[3] = f4([ldir[0], ldir[1], ldir[2], 0.0]);
p.f[4] = rgb(pr.color("lightColor"), 0.0);
p.f[5] = f4([if soft > 0.05 { span / 64.0 } else { 0.0 }, span * 0.34 * b.scale, (soft * 0.65).max(1.0), 1.25]);
p.f[6] = f4([span * 0.5, 0.0, 0.0, 0.0]);
run(e, "ftr_blob", &p, b, Some(&h), None)
}
+33 -14
View File
@@ -569,9 +569,11 @@ fn ftr_bender(@builtin(global_invocation_id) gid: vec3<u32>) {
}
// ---------------------------------------------------------------- CC Blobbylize (distort3::cc_blobbylize)
// aux = the blurred blob plane (x). f[0] = (depth, cut away, intensity, ambient);
// f[1] = (diffuse, specular, shininess, metal); f[2] = (light x, y, point light height,
// point light); f[3] = distant light direction; f[4] = light colour.
// aux = the Softness-blurred blob plane (x). f[0] = (cut away, intensity, ambient,
// matte slope); f[1] = (diffuse, specular, shininess, metal); f[2] = (light x, y,
// point light height, point); f[3] = distant light direction; f[4] = light colour;
// f[5] = (matte depth, RGB depth, light slope, matte antialias width);
// f[6].x = displacement clamp.
@compute @workgroup_size(16, 16)
fn ftr_blob(@builtin(global_invocation_id) gid: vec3<u32>) {
@@ -579,32 +581,49 @@ fn ftr_blob(@builtin(global_invocation_id) gid: vec3<u32>) {
if (!ftr_inside(p)) {
return;
}
let depth = P.f[0].x;
let cut = P.f[0].y;
let cut = P.f[0].x;
let hv = textureLoad(aux, p, 0).x;
let g = ftr_grad(p);
let grad = length(g);
var m = 0.0;
if (grad > 1e-9) {
let aa = max(P.f[5].w * grad, 5e-4);
m = clamp(0.5 + (hv - cut) / aa, 0.0, 1.0);
} else if (hv > cut) {
m = 1.0;
}
if (m <= 0.0) {
textureStore(out, p, vec4<f32>(0.0));
return;
}
let f = ftr_centre(p);
let s = sample_bilinear(src, f.x - g.x * depth * 2.0, f.y - g.y * depth * 2.0);
let c = ftr_unpremul(s);
let a = max(s.w, 0.0);
let m = clamp((hv - cut) / max((1.0 - cut) * 0.5, 1e-3), 0.0, 1.0);
let alpha = max(a, s.w) * m;
// Softness-scaled bevel warp of the source matte (normal z = 1).
let na = normalize(vec3<f32>(-g.x * P.f[0].w, -g.y * P.f[0].w, 1.0));
let sa = sample_bilinear(src, f.x + na.x * P.f[5].x, f.y + na.y * P.f[5].x);
let alpha = max(sa.w, 0.0) * m;
if (alpha <= 0.0) {
textureStore(out, p, vec4<f32>(0.0));
return;
}
let n = normalize(vec3<f32>(-g.x * depth, -g.y * depth, 1.0));
// Height-field refraction of the colour (normal z = height), independent of Softness.
let hz = max(hv, 1e-3);
let d = -g * P.f[5].y / hz;
let lim = P.f[6].x;
let disp = vec2<f32>(clamp(d.x, -lim, lim), clamp(d.y, -lim, lim));
let srgb = sample_bilinear(src, f.x + disp.x, f.y + disp.y);
let c = ftr_unpremul(srgb);
let n = normalize(vec3<f32>(-g.x * P.f[5].z, -g.y * P.f[5].z, 1.0));
var l = P.f[3].xyz;
if (P.f[2].w != 0.0) {
l = normalize(vec3<f32>(P.f[2].x - f32(p.x), P.f[2].y - f32(p.y), P.f[2].z));
l = normalize(vec3<f32>(P.f[2].x - f.x, P.f[2].y - f.y, P.f[2].z));
}
let intensity = P.f[0].z;
let intensity = P.f[0].y;
let ndl = max(dot(n, l), 0.0);
let h3 = normalize(vec3<f32>(l.x, l.y, l.z + 1.0));
let sp = powz(max(dot(n, h3), 0.0), P.f[1].z) * P.f[1].y;
let lc = P.f[4].xyz;
let metal = P.f[1].w;
let lit = (P.f[0].w + P.f[1].x * ndl * intensity) * lc;
let lit = (P.f[0].z + P.f[1].x * ndl * intensity) * lc;
let spec_col = (1.0 - metal) * lc + metal * c * lc;
let o = c * lit + (sp * intensity) * spec_col;
textureStore(out, p, vec4<f32>(o * alpha, alpha));