effectcraft/crates/gpu/src/fx_light.rs

127 lines
5.5 KiB
Rust

//! GPU effects, the CC light family (kernels in `shaders/fx_light.wgsl`, every entry point
//! prefixed `flt_`): CC Light Rays, CC Light Burst 2.5, CC Light Sweep and CC Light Wipe, with
//! the CPU effects' exact steps. Centres, radii, the wipe's corner distance and ramp are
//! computed on the CPU in f64 as the CPU effect does; the kernels work relative to the centre.
//! Light Sweep's softened alpha plane is the CPU's `gauss_plane` (box passes, edges repeated).
use effectcraft_effects::EffectCtx;
use crate::context::{Enc, GpuImage, Params};
use crate::effects::{GBuf, gaussian_blur};
/// Compute entry points in `fx_light.wgsl`.
pub(crate) const KERNELS: &[&str] = &["flt_rays", "flt_burst", "flt_sweep", "flt_wipe"];
/// Effect ids implemented here.
pub(crate) const IDS: &[&str] = &["ec.generate.cclightrays", "ec.generate.cclightburst", "ec.generate.cclightsweep", "ec.transition.cclightwipe"];
/// Run effect `id` (one of [`IDS`]); `None` = this parameter combination runs on the CPU.
pub(crate) fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
match id {
"ec.generate.cclightrays" => light_rays(e, ctx, b),
"ec.generate.cclightburst" => light_burst(e, ctx, b),
"ec.generate.cclightsweep" => light_sweep(e, ctx, b),
"ec.transition.cclightwipe" => light_wipe(e, ctx, b),
_ => None,
}
}
/// A same-size per-pixel kernel over the buffer.
fn run(e: &mut Enc, entry: &str, p: &Params, mut b: GBuf, aux: Option<&GpuImage>) -> Option<GBuf> {
let out = e.scratch(b.img.width, b.img.height);
e.pixels(entry, p, &b.img, aux, &out, None);
b.img = out;
Some(b)
}
fn rgb(c: [f32; 4]) -> [f32; 4] {
[c[0], c[1], c[2], 0.0]
}
// ---------------------------------------------------------------- CC Light Rays / Burst (generate2.rs)
fn light_rays(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let c = b.to_px(pr.v2("center"));
let k = (pr.f("intensity") / 100.0 * 0.5).clamp(0.0, 0.95);
let gain = (pr.f("intensity") / 100.0 * 2.0) as f32;
let rad = (pr.f("radius") * b.scale).max(0.5);
let ws = (pr.f("warpSoftness") / 50.0).max(0.0);
let mut p = Params::default();
p.u[0] = [(pr.e("shape") == 1) as u32, pr.b("colorFromSource") as u32, pr.e("transferMode"), 0];
p.f[0] = [c.0 as f32, c.1 as f32, k as f32, gain];
p.f[1] = [rad as f32, (rad * (1.0 + ws)) as f32 + 1e-3, 0.0, 0.0];
p.f[2] = rgb(pr.color("color"));
run(e, "flt_rays", &p, b, None)
}
fn light_burst(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let c = b.to_px(pr.v2("center"));
let inten = (pr.f("intensity") / 100.0) as f32;
let k = (pr.f("rayLength") / 100.0).clamp(0.0, 1.0);
let set_color = pr.b("setColor");
if k <= 0.0 && (inten - 1.0).abs() < 1e-6 && !set_color {
return Some(b);
}
let mut p = Params::default();
p.u[0] = [pr.e("burst"), set_color as u32, 0, 0];
p.f[0] = [c.0 as f32, c.1 as f32, k as f32, inten];
p.f[1] = rgb(pr.color("color"));
run(e, "flt_burst", &p, b, None)
}
// ---------------------------------------------------------------- CC Light Sweep (generate2.rs)
fn light_sweep(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let c = b.to_px(pr.v2("center"));
let dir = pr.f("direction").to_radians();
let half = (pr.f("width") * b.scale * 0.5).max(0.5);
let edge_t = pr.f("edgeThickness") * b.scale;
// The alpha plane (x), softened by util::gauss_plane (edges repeated) when thick enough.
let alpha = e.scratch(b.img.width, b.img.height);
e.pixels("fxe_alpha", &Params::default(), &b.img, None, &alpha, None);
let soft = if edge_t > 0.05 { gaussian_blur(e, &alpha, edge_t * 0.5, edge_t * 0.5, true) } else { alpha };
let mut p = Params::default();
p.u[0] = [pr.e("shape"), pr.e("lightReceptionMode"), 0, 0];
p.f[0] = [c.0 as f32, c.1 as f32, dir.cos() as f32, dir.sin() as f32];
p.f[1] = [half as f32, (pr.f("sweepIntensity") / 100.0) as f32, (pr.f("edgeIntensity") / 100.0) as f32, 0.0];
p.f[2] = rgb(pr.color("lightColor"));
run(e, "flt_sweep", &p, b, Some(&soft))
}
// ---------------------------------------------------------------- CC Light Wipe (transition.rs)
fn light_wipe(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let pr = ctx.params;
let done = (pr.f("completion") / 100.0).clamp(0.0, 1.0);
if done <= 0.0 {
return Some(b);
}
let c = b.to_px(pr.v2("center"));
let shape = pr.e("shape");
let (s, co) = pr.f("direction").to_radians().sin_cos();
let reverse = pr.b("reverse");
let metric = |dx: f64, dy: f64| {
let (qx, qy) = (dx * co + dy * s, -dx * s + dy * co);
match shape {
0 => qx.abs(),
2 => qx.abs().max(qy.abs()),
_ => qx.hypot(qy),
}
};
// transition::max_corner_dist.
let (w, h) = (b.img.width as f64, b.img.height as f64);
let maxd = [(0.0, 0.0), (w, 0.0), (0.0, h), (w, h)].iter().map(|(x, y)| metric(x - c.0, y - c.1)).fold(1e-6, f64::max);
let edge = if reverse { (1.0 - done) * maxd } else { done * maxd };
let band = (maxd * 0.06).max(2.0);
let ramp = (done * 20.0).min(1.0) as f32 * ((1.0 - done) * 20.0).min(1.0) as f32;
let mut p = Params::default();
p.u[0] = [shape, pr.b("colorFromSource") as u32, reverse as u32, 0];
p.f[0] = [c.0 as f32, c.1 as f32, s as f32, co as f32];
p.f[1] = [edge as f32, band as f32, pr.f("intensity") as f32 / 100.0 * ramp, 0.0];
p.f[2] = rgb(pr.color("color"));
run(e, "flt_wipe", &p, b, None)
}