mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-26 11:07:54 -06:00
M13.22: GPU transitions and perspective (Block Dissolve, CC Glass/Grid/Image Wipe, CC Jaws, CC Line Sweep, CC Radial ScaleWipe, CC Scale Wipe, CC Twister, CC WarpoMatic, Radial Shadow, CC Bender, CC Blobbylize, CC Cylinder, CC Sphere, CC Spotlight, CC Environment, 3D Glasses) and text (Numbers, Timecode: CPU glyph coverage, GPU composite)
CC Environment now fades nearly transparent environment texels to black continuously (colour / max(alpha, 1e-3)) instead of a hard 1e-6 un-premultiply cut-off, which turned the filtered map's float noise into colour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f7927c8f9f
commit
d0b92948ee
@@ -94,6 +94,7 @@ pub use transition::place_layer;
|
||||
// effectcraft-gpu fx_noise.
|
||||
pub use noise::remove_grain_level;
|
||||
pub use noise3::{FractalGpu, fractal_gpu};
|
||||
pub use textfx::{TextLayer, text_layer};
|
||||
pub use time_fx::{posterized_time, time_frames};
|
||||
// effectcraft-gpu fx_tone.
|
||||
pub use ocio::color_stabilizer_maps;
|
||||
@@ -936,6 +937,28 @@ pub const GPU_EFFECTS: &[&str] = &[
|
||||
"ec.generate.cclightburst",
|
||||
"ec.generate.cclightsweep",
|
||||
"ec.transition.cclightwipe",
|
||||
// effectcraft-gpu fx_transition (transitions, perspective)
|
||||
"ec.transition.blockdissolve",
|
||||
"ec.transition.ccglasswipe",
|
||||
"ec.transition.ccgridwipe",
|
||||
"ec.transition.ccimagewipe",
|
||||
"ec.transition.ccjaws",
|
||||
"ec.transition.cclinesweep",
|
||||
"ec.transition.ccradialscalewipe",
|
||||
"ec.transition.ccscalewipe",
|
||||
"ec.transition.cctwister",
|
||||
"ec.transition.ccwarpomatic",
|
||||
"ec.perspective.radialshadow",
|
||||
"ec.distort.ccbender",
|
||||
"ec.distort.ccblobbylize",
|
||||
"ec.perspective.cccylinder",
|
||||
"ec.perspective.ccsphere",
|
||||
"ec.perspective.ccspotlight",
|
||||
"ec.perspective.ccenvironment",
|
||||
"ec.perspective.3dglasses",
|
||||
// effectcraft-gpu fx_text (glyph coverage rasterised on the CPU)
|
||||
"ec.text.numbers",
|
||||
"ec.text.timecode",
|
||||
];
|
||||
|
||||
/// Effects whose output depends on [`EffectCtx::time`] directly (not only through animated
|
||||
|
||||
@@ -90,8 +90,10 @@ fn cc_environment(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
||||
let ex = u.rem_euclid(1.0) * esz[0] * es + eo[0];
|
||||
let ey = v.clamp(0.0, 1.0) * esz[1] * es + eo[1];
|
||||
let e = env_img.sample_bilinear_clamped(ex, ey);
|
||||
let (ec, _) = unpremul(e);
|
||||
*px = premul(ec, a);
|
||||
// Straight colour, with nearly transparent texels fading to black continuously
|
||||
// (a hard 1e-6 cut-off would turn the float noise of the filtered map into colour).
|
||||
let k = 1.0 / e[3].max(1e-3);
|
||||
*px = premul([e[0] * k, e[1] * k, e[2] * k], a);
|
||||
}
|
||||
});
|
||||
b
|
||||
|
||||
+122
-46
@@ -192,9 +192,9 @@ fn tint(c: [f32; 4], a: f32) -> Px {
|
||||
[c[0] * a, c[1] * a, c[2] * a, a]
|
||||
}
|
||||
|
||||
/// Draw text polylines with the given look (`r` = glyph stroke radius in px).
|
||||
fn draw_text(b: &mut Buf, polys: &[Vec<[f64; 2]>], r: f64, look: &TextLook) {
|
||||
let (w, h) = (b.img.width as usize, b.img.height as usize);
|
||||
/// Coverage of text polylines (`r` = glyph stroke radius in px): the fill and, when the look
|
||||
/// strokes, the stroke ring.
|
||||
fn text_coverage(w: usize, h: usize, polys: &[Vec<[f64; 2]>], r: f64, look: &TextLook) -> (Plane, Option<Plane>) {
|
||||
let segs_at = |rad: f64| {
|
||||
let mut s = Vec::new();
|
||||
for pl in polys {
|
||||
@@ -211,24 +211,35 @@ fn draw_text(b: &mut Buf, polys: &[Vec<[f64; 2]>], r: f64, look: &TextLook) {
|
||||
let inner = if r - sw * 0.5 > 0.05 { raster_segs(w, h, &segs_at(r - sw * 0.5), 1.0) } else { Plane::new(w, h) };
|
||||
Some(outer.zip_map(&inner, |a, b| (a - b).max(0.0)))
|
||||
};
|
||||
(fill, ring)
|
||||
}
|
||||
|
||||
/// One pixel of drawn text: `px` under the fill / stroke coverages `f`, `s` (opacity applied).
|
||||
fn text_px(px: Px, f: f32, s: f32, look: &TextLook) -> Px {
|
||||
let mut out = if look.on_original { px } else { [0.0; 4] };
|
||||
match look.display {
|
||||
1 => out = over(out, tint(look.stroke, s)),
|
||||
2 => {
|
||||
out = over(out, tint(look.stroke, s));
|
||||
out = over(out, tint(look.fill, f));
|
||||
}
|
||||
3 => {
|
||||
out = over(out, tint(look.fill, f));
|
||||
out = over(out, tint(look.stroke, s));
|
||||
}
|
||||
_ => out = over(out, tint(look.fill, f)),
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Draw text polylines with the given look (`r` = glyph stroke radius in px).
|
||||
fn draw_text(b: &mut Buf, polys: &[Vec<[f64; 2]>], r: f64, look: &TextLook) {
|
||||
let (fill, ring) = text_coverage(b.img.width as usize, b.img.height as usize, polys, r, look);
|
||||
let op = look.opacity;
|
||||
b.img.data.par_iter_mut().enumerate().for_each(|(i, px)| {
|
||||
let mut out = if look.on_original { *px } else { [0.0; 4] };
|
||||
let f = fill.data[i] * op;
|
||||
let s = ring.as_ref().map(|r| r.data[i]).unwrap_or(0.0) * op;
|
||||
match look.display {
|
||||
1 => out = over(out, tint(look.stroke, s)),
|
||||
2 => {
|
||||
out = over(out, tint(look.stroke, s));
|
||||
out = over(out, tint(look.fill, f));
|
||||
}
|
||||
3 => {
|
||||
out = over(out, tint(look.fill, f));
|
||||
out = over(out, tint(look.stroke, s));
|
||||
}
|
||||
_ => out = over(out, tint(look.fill, f)),
|
||||
}
|
||||
*px = out;
|
||||
*px = text_px(*px, f, s, look);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -337,7 +348,18 @@ fn numbers_text(ctx: &EffectCtx) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn numbers(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
||||
/// What Numbers or Timecode draws: glyph polylines, stroke radius and look, whether the layer
|
||||
/// stays under the text, and Timecode's box (`[x0, x1, y0, y1]` in buffer pixels, colour,
|
||||
/// opacity × colour alpha).
|
||||
struct TextPlan {
|
||||
polys: Vec<Vec<[f64; 2]>>,
|
||||
r: f64,
|
||||
look: TextLook,
|
||||
keep: bool,
|
||||
boxed: Option<([f64; 4], [f32; 4], f32)>,
|
||||
}
|
||||
|
||||
fn numbers_plan(ctx: &EffectCtx, b: &Buf) -> TextPlan {
|
||||
let flat = ungroup(ctx.params);
|
||||
let ctx = &EffectCtx { params: &flat, time: ctx.time, layer_size: ctx.layer_size, seed: ctx.seed, adjustment: ctx.adjustment, env: ctx.env };
|
||||
let pr = ctx.params;
|
||||
@@ -349,11 +371,81 @@ fn numbers(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
||||
let wdt = line_width(&text, size, tracking, prop);
|
||||
let mut polys = Vec::new();
|
||||
layout_line(&text, [pos.0 - wdt * 0.5, pos.1 + unit(size) * 3.0], size, tracking, prop, &mut polys);
|
||||
let look = look_from(ctx, &b, "compositeOnOriginal");
|
||||
draw_text(&mut b, &polys, weight(size), &look);
|
||||
let mut look = look_from(ctx, b, "compositeOnOriginal");
|
||||
let keep = look.on_original;
|
||||
look.on_original = true;
|
||||
TextPlan { polys, r: weight(size), look, keep, boxed: None }
|
||||
}
|
||||
|
||||
/// Timecode's box over pixel (`x`, `y`).
|
||||
fn box_px(px: Px, x: usize, y: usize, (r, bc, a): ([f64; 4], [f32; 4], f32)) -> Px {
|
||||
let [x0, x1, y0, y1] = r;
|
||||
let fy = y as f64 + 0.5;
|
||||
let cy = ((fy - y0 + 0.5).clamp(0.0, 1.0) * (y1 - fy + 0.5).clamp(0.0, 1.0)) as f32;
|
||||
if cy <= 0.0 {
|
||||
return px;
|
||||
}
|
||||
let fx = x as f64 + 0.5;
|
||||
let cx = ((fx - x0 + 0.5).clamp(0.0, 1.0) * (x1 - fx + 0.5).clamp(0.0, 1.0)) as f32;
|
||||
let k = cx * cy * a;
|
||||
if k > 0.0 { over(px, [bc[0] * k, bc[1] * k, bc[2] * k, k]) } else { px }
|
||||
}
|
||||
|
||||
fn draw_plan(mut b: Buf, plan: &TextPlan) -> Buf {
|
||||
let w = b.img.width as usize;
|
||||
let (fill, ring) = text_coverage(w, b.img.height as usize, &plan.polys, plan.r, &plan.look);
|
||||
let op = plan.look.opacity;
|
||||
b.img.data.par_iter_mut().enumerate().for_each(|(i, px)| {
|
||||
let mut o = if plan.keep { *px } else { [0.0; 4] };
|
||||
if let Some(bx) = plan.boxed {
|
||||
o = box_px(o, i % w.max(1), i / w.max(1), bx);
|
||||
}
|
||||
let f = fill.data[i] * op;
|
||||
let s = ring.as_ref().map(|r| r.data[i]).unwrap_or(0.0) * op;
|
||||
*px = text_px(o, f, s, &plan.look);
|
||||
});
|
||||
b
|
||||
}
|
||||
|
||||
fn numbers(ctx: &EffectCtx, b: Buf) -> Buf {
|
||||
let plan = numbers_plan(ctx, &b);
|
||||
draw_plan(b, &plan)
|
||||
}
|
||||
|
||||
/// Numbers and Timecode for the GPU compositor (effectcraft-gpu `fx_text`): the glyph
|
||||
/// coverage is rasterised here (the fill in x, the stroke ring in y, opacity applied), the
|
||||
/// kernel composites it.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextLayer {
|
||||
pub coverage: Image,
|
||||
/// Display Options (0 fill only, 1 stroke only, 2 fill over stroke, 3 stroke over fill).
|
||||
pub display: u32,
|
||||
pub fill: [f32; 4],
|
||||
pub stroke: [f32; 4],
|
||||
/// The layer stays under the text (else the text is drawn on transparency).
|
||||
pub keep: bool,
|
||||
/// Timecode's box: `[x0, x1, y0, y1]` (buffer pixels), colour, opacity × colour alpha.
|
||||
pub boxed: Option<([f64; 4], [f32; 4], f32)>,
|
||||
}
|
||||
|
||||
/// [`TextLayer`] of Numbers (`ec.text.numbers`) or Timecode (`ec.text.timecode`) on a buffer
|
||||
/// of `b`'s geometry.
|
||||
pub fn text_layer(id: &str, ctx: &EffectCtx, b: &Buf) -> Option<TextLayer> {
|
||||
let plan = match id {
|
||||
"ec.text.numbers" => numbers_plan(ctx, b),
|
||||
"ec.text.timecode" => timecode_plan(ctx, b),
|
||||
_ => return None,
|
||||
};
|
||||
let (w, h) = (b.img.width as usize, b.img.height as usize);
|
||||
let (fill, ring) = text_coverage(w, h, &plan.polys, plan.r, &plan.look);
|
||||
let op = plan.look.opacity;
|
||||
let mut coverage = Image::new(b.img.width, b.img.height);
|
||||
coverage.data.par_iter_mut().enumerate().for_each(|(i, px)| {
|
||||
*px = [fill.data[i] * op, ring.as_ref().map(|r| r.data[i]).unwrap_or(0.0) * op, 0.0, 0.0];
|
||||
});
|
||||
Some(TextLayer { coverage, display: plan.look.display, fill: plan.look.fill, stroke: plan.look.stroke, keep: plan.keep, boxed: plan.boxed })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Timecode
|
||||
|
||||
/// SMPTE drop-frame label for nominal 30/60 fps rates.
|
||||
@@ -368,7 +460,7 @@ fn drop_frame(frames: i64, nominal: i64) -> String {
|
||||
format!("{:02};{:02};{:02};{:02}", adj / (nominal * 3600), (adj / (nominal * 60)) % 60, (adj / nominal) % 60, adj % nominal)
|
||||
}
|
||||
|
||||
fn timecode_fx(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
||||
fn timecode_plan(ctx: &EffectCtx, b: &Buf) -> TextPlan {
|
||||
let pr = ctx.params;
|
||||
let src = pr.e("timeSource");
|
||||
let (t, fps) = match src {
|
||||
@@ -397,36 +489,20 @@ fn timecode_fx(ctx: &EffectCtx, mut b: Buf) -> Buf {
|
||||
let pos = b.to_px(pr.v2("textPosition"));
|
||||
let wdt = line_width(&text, size, 0.0, false);
|
||||
let op = (pr.f("opacity") / 100.0) as f32;
|
||||
if !pr.b("renderOnOriginal") {
|
||||
b.img = Image::new(b.img.width, b.img.height);
|
||||
}
|
||||
if pr.b("showBox") {
|
||||
let boxed = pr.b("showBox").then(|| {
|
||||
let bc = pr.color("boxColor");
|
||||
let pad = size * 0.25;
|
||||
let (x0, x1) = (pos.0 - wdt * 0.5 - pad, pos.0 + wdt * 0.5 + pad);
|
||||
let (y0, y1) = (pos.1 - size * 0.5, pos.1 + size * 0.5);
|
||||
let a = op * bc[3];
|
||||
b.img.rows_mut().for_each(|(y, row)| {
|
||||
let fy = y as f64 + 0.5;
|
||||
let cy = ((fy - y0 + 0.5).clamp(0.0, 1.0) * (y1 - fy + 0.5).clamp(0.0, 1.0)) as f32;
|
||||
if cy <= 0.0 {
|
||||
return;
|
||||
}
|
||||
for (x, px) in row.iter_mut().enumerate() {
|
||||
let fx = x as f64 + 0.5;
|
||||
let cx = ((fx - x0 + 0.5).clamp(0.0, 1.0) * (x1 - fx + 0.5).clamp(0.0, 1.0)) as f32;
|
||||
let k = cx * cy * a;
|
||||
if k > 0.0 {
|
||||
*px = over(*px, [bc[0] * k, bc[1] * k, bc[2] * k, k]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
([pos.0 - wdt * 0.5 - pad, pos.0 + wdt * 0.5 + pad, pos.1 - size * 0.5, pos.1 + size * 0.5], bc, op * bc[3])
|
||||
});
|
||||
let mut polys = Vec::new();
|
||||
layout_line(&text, [pos.0 - wdt * 0.5, pos.1 + unit(size) * 3.0], size, 0.0, false, &mut polys);
|
||||
let look = TextLook { display: 0, fill: pr.color("textColor"), stroke: [0.0; 4], stroke_w: 0.0, on_original: true, opacity: op };
|
||||
draw_text(&mut b, &polys, weight(size), &look);
|
||||
b
|
||||
TextPlan { polys, r: weight(size), look, keep: pr.b("renderOnOriginal"), boxed }
|
||||
}
|
||||
|
||||
fn timecode_fx(ctx: &EffectCtx, b: Buf) -> Buf {
|
||||
let plan = timecode_plan(ctx, &b);
|
||||
draw_plan(b, &plan)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Basic Text
|
||||
|
||||
@@ -315,6 +315,8 @@ impl GpuContext {
|
||||
include_str!("shaders/fx_warp.wgsl"),
|
||||
include_str!("shaders/fx_extra.wgsl"),
|
||||
include_str!("shaders/fx_light.wgsl"),
|
||||
include_str!("shaders/fx_transition.wgsl"),
|
||||
include_str!("shaders/fx_text.wgsl"),
|
||||
]
|
||||
.concat();
|
||||
let module =
|
||||
@@ -375,6 +377,8 @@ impl GpuContext {
|
||||
.chain(crate::fx_warp::KERNELS)
|
||||
.chain(crate::fx_extra::KERNELS)
|
||||
.chain(crate::fx_light::KERNELS)
|
||||
.chain(crate::fx_transition::KERNELS)
|
||||
.chain(crate::fx_text::KERNELS)
|
||||
.map(|e| (e, &layout))
|
||||
.chain(EXT_ENTRIES.iter().map(|e| (e, &layout_ext)))
|
||||
.map(|(e, layout)| {
|
||||
|
||||
@@ -134,6 +134,8 @@ fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
_ if crate::fx_warp::IDS.contains(&id) => crate::fx_warp::apply(e, id, ctx, b),
|
||||
_ if crate::fx_extra::IDS.contains(&id) => crate::fx_extra::apply(e, id, ctx, b),
|
||||
_ if crate::fx_light::IDS.contains(&id) => crate::fx_light::apply(e, id, ctx, b),
|
||||
_ if crate::fx_transition::IDS.contains(&id) => crate::fx_transition::apply(e, id, ctx, b),
|
||||
_ if crate::fx_text::IDS.contains(&id) => crate::fx_text::apply(e, id, ctx, b),
|
||||
_ => pointwise(e, id, ctx, b),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//! GPU effects, Numbers and Timecode (kernel `ftx_text` in `shaders/fx_text.wgsl`). The text
|
||||
//! is laid out and its glyph strokes rasterised on the CPU (`effectcraft_effects::text_layer`,
|
||||
//! the CPU effect's own coverage: fill and stroke ring), uploaded as one image and composited
|
||||
//! on the GPU with the layer, Timecode's box and the fill / stroke colours.
|
||||
|
||||
use effectcraft_effects::{Buf, EffectCtx};
|
||||
use effectcraft_raster::Image;
|
||||
|
||||
use crate::context::{Enc, Params};
|
||||
use crate::effects::GBuf;
|
||||
|
||||
/// Compute entry points in `fx_text.wgsl`.
|
||||
pub(crate) const KERNELS: &[&str] = &["ftx_text"];
|
||||
|
||||
/// Effect ids implemented here.
|
||||
pub(crate) const IDS: &[&str] = &["ec.text.numbers", "ec.text.timecode"];
|
||||
|
||||
/// 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> {
|
||||
let shape = Buf { img: Image { width: b.img.width, height: b.img.height, data: vec![] }, offset: b.offset, scale: b.scale };
|
||||
let t = effectcraft_effects::text_layer(id, ctx, &shape)?;
|
||||
let cov = e.g.upload_image(&t.coverage)?;
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [t.display, t.keep as u32, t.boxed.is_some() as u32, 0];
|
||||
p.f[0] = t.fill;
|
||||
p.f[1] = t.stroke;
|
||||
if let Some((r, c, a)) = t.boxed {
|
||||
p.f[2] = r.map(|v| v as f32);
|
||||
p.f[3] = [c[0], c[1], c[2], a];
|
||||
}
|
||||
let out = e.scratch(b.img.width, b.img.height);
|
||||
e.pixels("ftx_text", &p, &b.img, Some(&cov), &out, None);
|
||||
Some(GBuf { img: out, ..b })
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
//! GPU effects, transitions and perspective (kernels in `shaders/fx_transition.wgsl`, every
|
||||
//! entry point prefixed `ftr_`): Block Dissolve, CC Glass Wipe, CC Grid Wipe, CC Image Wipe,
|
||||
//! CC Jaws, CC Line Sweep, CC Radial ScaleWipe, CC Scale Wipe, CC Twister, CC WarpoMatic,
|
||||
//! Radial Shadow, CC Bender, CC Blobbylize, CC Cylinder, CC Sphere, CC Spotlight, CC
|
||||
//! Environment and 3D Glasses, with the CPU effects' exact steps.
|
||||
//!
|
||||
//! Scalars (corner distances, projected extents, light vectors, rotation matrices) are computed
|
||||
//! on the CPU in f64 as the CPU effect does; Block Dissolve's block indices too (one per column
|
||||
//! and row sub-sample), so the hash sees the CPU's exact integers. Other layers (gradient,
|
||||
//! reveal, backside, environment, stereo views) are fetched and fitted on the CPU
|
||||
//! (`util::fit_layer`) and uploaded. Gradient planes are blurred with the CPU's
|
||||
//! `util::gauss_plane` steps (box passes, edges repeated). Card Wipe (a 3D card renderer
|
||||
//! shared with Card Dance and Shatter) stays on the CPU.
|
||||
|
||||
use effectcraft_effects::{Buf, EffectCtx};
|
||||
use effectcraft_raster::Image;
|
||||
|
||||
use crate::context::{Enc, GpuImage, Params};
|
||||
use crate::effects::{GBuf, gaussian_blur};
|
||||
|
||||
/// Compute entry points in `fx_transition.wgsl`.
|
||||
pub(crate) const KERNELS: &[&str] = &[
|
||||
"ftr_plane",
|
||||
"ftr_mul",
|
||||
"ftr_under",
|
||||
"ftr_blocks",
|
||||
"ftr_grid",
|
||||
"ftr_radial_scale",
|
||||
"ftr_scale",
|
||||
"ftr_glass",
|
||||
"ftr_image_wipe",
|
||||
"ftr_jaws",
|
||||
"ftr_line_sweep",
|
||||
"ftr_twister",
|
||||
"ftr_react_contrast",
|
||||
"ftr_react_local",
|
||||
"ftr_warpo",
|
||||
"ftr_rshadow",
|
||||
"ftr_bender",
|
||||
"ftr_blob",
|
||||
"ftr_cylinder",
|
||||
"ftr_sphere",
|
||||
"ftr_spot",
|
||||
"ftr_env",
|
||||
"ftr_shift",
|
||||
"ftr_glasses",
|
||||
];
|
||||
|
||||
/// Effect ids implemented here.
|
||||
pub(crate) const IDS: &[&str] = &[
|
||||
"ec.transition.blockdissolve",
|
||||
"ec.transition.ccglasswipe",
|
||||
"ec.transition.ccgridwipe",
|
||||
"ec.transition.ccimagewipe",
|
||||
"ec.transition.ccjaws",
|
||||
"ec.transition.cclinesweep",
|
||||
"ec.transition.ccradialscalewipe",
|
||||
"ec.transition.ccscalewipe",
|
||||
"ec.transition.cctwister",
|
||||
"ec.transition.ccwarpomatic",
|
||||
"ec.perspective.radialshadow",
|
||||
"ec.distort.ccbender",
|
||||
"ec.distort.ccblobbylize",
|
||||
"ec.perspective.cccylinder",
|
||||
"ec.perspective.ccsphere",
|
||||
"ec.perspective.ccspotlight",
|
||||
"ec.perspective.ccenvironment",
|
||||
"ec.perspective.3dglasses",
|
||||
];
|
||||
|
||||
/// 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.transition.blockdissolve" => block_dissolve(e, ctx, b),
|
||||
"ec.transition.ccglasswipe" => glass_wipe(e, ctx, b),
|
||||
"ec.transition.ccgridwipe" => grid_wipe(e, ctx, b),
|
||||
"ec.transition.ccimagewipe" => image_wipe(e, ctx, b),
|
||||
"ec.transition.ccjaws" => jaws(e, ctx, b),
|
||||
"ec.transition.cclinesweep" => line_sweep(e, ctx, b),
|
||||
"ec.transition.ccradialscalewipe" => radial_scale_wipe(e, ctx, b),
|
||||
"ec.transition.ccscalewipe" => scale_wipe(e, ctx, b),
|
||||
"ec.transition.cctwister" => twister(e, ctx, b),
|
||||
"ec.transition.ccwarpomatic" => warpomatic(e, ctx, b),
|
||||
"ec.perspective.radialshadow" => radial_shadow(e, ctx, b),
|
||||
"ec.distort.ccbender" => bender(e, ctx, b),
|
||||
"ec.distort.ccblobbylize" => blobbylize(e, ctx, b),
|
||||
"ec.perspective.cccylinder" => cylinder(e, ctx, b),
|
||||
"ec.perspective.ccsphere" => sphere(e, ctx, b),
|
||||
"ec.perspective.ccspotlight" => spotlight(e, ctx, b),
|
||||
"ec.perspective.ccenvironment" => environment(e, ctx, b),
|
||||
"ec.perspective.3dglasses" => glasses(e, ctx, b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- helpers
|
||||
|
||||
/// A same-size per-pixel kernel over `src` (the buffer's size).
|
||||
fn kernel(e: &mut Enc, entry: &str, p: &Params, src: &GpuImage, aux: Option<&GpuImage>, data: Option<&wgpu::Buffer>) -> GpuImage {
|
||||
let out = e.scratch(src.width, src.height);
|
||||
e.pixels(entry, p, src, aux, &out, data);
|
||||
out
|
||||
}
|
||||
|
||||
fn run(e: &mut Enc, entry: &str, p: &Params, mut b: GBuf, aux: Option<&GpuImage>, data: Option<&wgpu::Buffer>) -> Option<GBuf> {
|
||||
b.img = kernel(e, entry, p, &b.img, aux, data);
|
||||
Some(b)
|
||||
}
|
||||
|
||||
fn f4(v: [f64; 4]) -> [f32; 4] {
|
||||
v.map(|x| x as f32)
|
||||
}
|
||||
|
||||
fn rgb(c: [f32; 4], w: f32) -> [f32; 4] {
|
||||
[c[0], c[1], c[2], w]
|
||||
}
|
||||
|
||||
/// The CPU buffer geometry of `b` (no pixels: `fit_layer` reads only size, offset and scale).
|
||||
fn shape(b: &GBuf) -> Buf {
|
||||
Buf { img: Image { width: b.img.width, height: b.img.height, data: vec![] }, offset: b.offset, scale: b.scale }
|
||||
}
|
||||
|
||||
/// The layer in layer parameter `id` fitted to `b` (`util::fit_layer`) and uploaded; `Some(None)`
|
||||
/// when none is chosen.
|
||||
fn fitted(e: &mut Enc, ctx: &EffectCtx, b: &GBuf, id: &str, masks_and_effects: bool, stretch: bool) -> Option<Option<GpuImage>> {
|
||||
match ctx.layer_param(id, masks_and_effects) {
|
||||
Some(o) => Some(Some(e.g.upload_image(&effectcraft_effects::util::fit_layer(ctx, &shape(b), &o, stretch))?)),
|
||||
None => Some(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// `util::layer_or_self`: the fitted layer, or the buffer itself.
|
||||
fn layer_or_self(e: &mut Enc, ctx: &EffectCtx, b: &GBuf, id: &str) -> Option<GpuImage> {
|
||||
Some(fitted(e, ctx, b, id, true, false)?.unwrap_or_else(|| b.img.clone()))
|
||||
}
|
||||
|
||||
/// A plane (x) of `img`: `util::Src` index `src` times alpha (alpha itself unscaled); 11 is CC
|
||||
/// Environment's relief.
|
||||
fn plane(e: &mut Enc, img: &GpuImage, src: u32) -> GpuImage {
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = src;
|
||||
kernel(e, "ftr_plane", &p, img, None, None)
|
||||
}
|
||||
|
||||
/// transition2::prop_plane's property → `util::Src` index.
|
||||
fn prop_src(prop: u32) -> u32 {
|
||||
match prop {
|
||||
5 => 6,
|
||||
6 => 7,
|
||||
p => p.min(4),
|
||||
}
|
||||
}
|
||||
|
||||
/// `img` as RGBA rows for the kernels' third image (`P.u[3]` = stride, width, height).
|
||||
fn rows(e: &mut Enc, p: &mut Params, img: &GpuImage) -> wgpu::Buffer {
|
||||
let (buf, stride) = e.image_rows(img);
|
||||
p.u[3] = [stride, img.width, img.height, 0];
|
||||
buf
|
||||
}
|
||||
|
||||
fn completion(ctx: &EffectCtx) -> f64 {
|
||||
(ctx.params.f("completion") / 100.0).clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Block Dissolve (transition.rs)
|
||||
|
||||
fn block_dissolve(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let done = completion(ctx) as f32;
|
||||
if done <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let pr = ctx.params;
|
||||
let bw = (pr.f("blockWidth") * b.scale).max(1e-3);
|
||||
let bh = (pr.f("blockHeight") * b.scale).max(1e-3);
|
||||
let feather = pr.f("feather") * b.scale;
|
||||
let subs: Vec<f64> = if pr.b("softEdges") { (0..4).map(|k| (k as f64 + 0.5) / 4.0).collect() } else { vec![0.5] };
|
||||
// Block indices per column / row sub-sample, as the CPU computes them (f64, then u32).
|
||||
let index =
|
||||
|n: u32, o: f64, size: f64| (0..n).flat_map(|i| subs.iter().map(move |s| (((i as f64 + s - o) / size).floor() as i64) as u32)).collect::<Vec<_>>();
|
||||
let cols = index(b.img.width, b.offset[0], bw);
|
||||
let table: Vec<u8> = cols.iter().chain(&index(b.img.height, b.offset[1], bh)).flat_map(|v| v.to_le_bytes()).collect();
|
||||
let data = e.bytes(table);
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [subs.len() as u32, ctx.seed ^ 0xb10c, cols.len() as u32, 0];
|
||||
p.f[0][0] = done;
|
||||
let mut mask = kernel(e, "ftr_blocks", &p, &b.img, None, Some(&data));
|
||||
if feather > 0.0 {
|
||||
mask = gaussian_blur(e, &mask, feather * 0.5, feather * 0.5, true);
|
||||
}
|
||||
run(e, "ftr_mul", &Params::default(), b, Some(&mask), None)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Grid Wipe / Radial ScaleWipe / Scale Wipe (transition.rs)
|
||||
|
||||
/// transition::max_corner_dist.
|
||||
fn max_corner_dist(b: &GBuf, c: (f64, f64), metric: impl Fn(f64, f64) -> f64) -> f64 {
|
||||
let (w, h) = (b.img.width as f64, b.img.height as f64);
|
||||
[(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)
|
||||
}
|
||||
|
||||
fn grid_wipe(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let done = completion(ctx);
|
||||
if done <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let pr = ctx.params;
|
||||
let c = b.to_px(pr.v2("center"));
|
||||
let rot = pr.f("rotation").to_radians();
|
||||
let border = pr.f("border").max(0.0) * b.scale;
|
||||
let tiles = pr.f("tiles").round().clamp(1.0, 500.0);
|
||||
let shape = pr.e("shape");
|
||||
let cs = (ctx.layer_size[0] * b.scale / tiles).max(1.0);
|
||||
let (s, co) = rot.sin_cos();
|
||||
let metric = |dx: f64, dy: f64| match shape {
|
||||
0 => dx.abs(),
|
||||
2 => dx.abs().max(dy.abs()),
|
||||
_ => dx.hypot(dy),
|
||||
};
|
||||
let maxd = max_corner_dist(&b, c, |dx, dy| metric(dx * co + dy * s, -dx * s + dy * co));
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [shape, pr.b("reverse") as u32, 0, 0];
|
||||
p.f[0] = f4([c.0, c.1, s, co]);
|
||||
p.f[1] = f4([cs, maxd, done, border]);
|
||||
run(e, "ftr_grid", &p, b, None, None)
|
||||
}
|
||||
|
||||
fn radial_scale_wipe(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let done = completion(ctx);
|
||||
if done <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
let maxd = max_corner_dist(&b, c, f64::hypot);
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = ctx.params.b("reverse") as u32;
|
||||
p.f[0] = f4([c.0, c.1, maxd, done]);
|
||||
run(e, "ftr_radial_scale", &p, b, None, None)
|
||||
}
|
||||
|
||||
fn scale_wipe(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let stretch = ctx.params.f("stretch").max(0.0);
|
||||
if stretch <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let c = b.to_px(ctx.params.v2("center"));
|
||||
let a = ctx.params.f("direction").to_radians();
|
||||
let mut p = Params::default();
|
||||
p.f[0] = f4([c.0, c.1, a.sin(), -a.cos()]);
|
||||
p.f[1][0] = stretch as f32;
|
||||
run(e, "ftr_scale", &p, b, None, None)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Glass Wipe / Image Wipe / WarpoMatic (transition2.rs)
|
||||
|
||||
fn glass_wipe(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let c = completion(ctx) as f32;
|
||||
if c <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let pr = ctx.params;
|
||||
let gimg = layer_or_self(e, ctx, &b, "gradientLayer")?;
|
||||
let mut g = plane(e, &gimg, 4);
|
||||
let sm = pr.f("softness") * b.scale * 0.2;
|
||||
if sm > 0.05 {
|
||||
g = gaussian_blur(e, &g, sm, sm, true);
|
||||
}
|
||||
let soft = (pr.f("softness") / 100.0) as f32;
|
||||
let rev = fitted(e, ctx, &b, "layerToReveal", true, false)?;
|
||||
let mut p = Params::default();
|
||||
let data = rev.as_ref().map(|r| rows(e, &mut p, r));
|
||||
p.u[0][0] = rev.is_some() as u32;
|
||||
p.f[0] = [c, soft * 0.5 + 0.01, (pr.f("displacementAmount") * b.scale * 4.0) as f32, 0.0];
|
||||
run(e, "ftr_glass", &p, b, Some(&g), data.as_ref())
|
||||
}
|
||||
|
||||
fn image_wipe(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let c = completion(ctx) as f32;
|
||||
if c <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let pr = ctx.params;
|
||||
let gimg = layer_or_self(e, ctx, &b, "layer")?;
|
||||
let mut g = plane(e, &gimg, prop_src(pr.e("property")));
|
||||
let bl = pr.f("blur") * b.scale * 0.5;
|
||||
if bl > 0.05 {
|
||||
g = gaussian_blur(e, &g, bl, bl, true);
|
||||
}
|
||||
let mut w = (pr.f("borderSoftness") / 100.0) as f32 * 0.5;
|
||||
if pr.b("autoSoftness") {
|
||||
w = w.max(0.02);
|
||||
}
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = pr.b("inverseGradient") as u32;
|
||||
p.f[0] = [c, w, 0.0, 0.0];
|
||||
run(e, "ftr_image_wipe", &p, b, Some(&g), None)
|
||||
}
|
||||
|
||||
fn warpomatic(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let c = completion(ctx) as f32;
|
||||
if c <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let pr = ctx.params;
|
||||
let rimg = layer_or_self(e, ctx, &b, "reactorLayer")?;
|
||||
let mut m = plane(e, &rimg, 4);
|
||||
let sm = pr.f("smoothness") * b.scale * 0.5;
|
||||
if sm > 0.05 {
|
||||
m = gaussian_blur(e, &m, sm, sm, true);
|
||||
}
|
||||
m = match pr.e("reactor") {
|
||||
1 => {
|
||||
let mean = gaussian_blur(e, &m, 8.0 * b.scale, 8.0 * b.scale, true);
|
||||
kernel(e, "ftr_react_contrast", &Params::default(), &m, Some(&mean), None)
|
||||
}
|
||||
2 => kernel(e, "ftr_react_local", &Params::default(), &m, Some(&m), None),
|
||||
_ => m,
|
||||
};
|
||||
let rev = fitted(e, ctx, &b, "layerToReveal", true, false)?;
|
||||
let mut p = Params::default();
|
||||
let data = rev.as_ref().map(|r| rows(e, &mut p, r));
|
||||
p.u[0] = [pr.e("warpDirection"), rev.is_some() as u32, 0, 0];
|
||||
p.f[0] = [c, (pr.f("blendSpan") / 100.0) as f32 * 0.5, (pr.f("warpAmount") * b.scale) as f32 * 20.0, 0.0];
|
||||
run(e, "ftr_warpo", &p, b, Some(&m), data.as_ref())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Jaws / Line Sweep / Twister (transition2.rs)
|
||||
|
||||
fn jaws(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let c = completion(ctx);
|
||||
if c <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let pr = ctx.params;
|
||||
let (cx, cy) = b.to_px(pr.v2("center"));
|
||||
let (sn, cs) = pr.f("direction").to_radians().sin_cos();
|
||||
let width = (pr.f("width") * b.scale).max(1.0);
|
||||
let amp = pr.f("height") / 100.0 * width * 0.5;
|
||||
let (bw, bh) = (b.img.width as f64, b.img.height as f64);
|
||||
let vmax = [(0.0, 0.0), (bw, 0.0), (0.0, bh), (bw, bh)].iter().map(|&(x, y)| (-(x - cx) * sn + (y - cy) * cs).abs()).fold(0.0, f64::max);
|
||||
let d = c * (vmax + 2.0 * amp + 2.0);
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = pr.e("shape");
|
||||
p.f[0] = f4([cx, cy, sn, cs]);
|
||||
p.f[1] = f4([width, amp, d, 0.0]);
|
||||
run(e, "ftr_jaws", &p, b, None, None)
|
||||
}
|
||||
|
||||
fn line_sweep(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let c = completion(ctx);
|
||||
if c <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let pr = ctx.params;
|
||||
let (sn, cs) = pr.f("direction").to_radians().sin_cos();
|
||||
let thick = (pr.f("thickness") * b.scale).max(1.0);
|
||||
let slant = pr.f("slant") * b.scale;
|
||||
let (w, h) = (b.img.width as f64, b.img.height as f64);
|
||||
let (mut u0, mut u1, mut v0, mut v1) = (f64::MAX, f64::MIN, f64::MAX, f64::MIN);
|
||||
for (x, y) in [(0.0, 0.0), (w, 0.0), (0.0, h), (w, h)] {
|
||||
let (u, v) = (x * cs + y * sn, -x * sn + y * cs);
|
||||
u0 = u0.min(u);
|
||||
u1 = u1.max(u);
|
||||
v0 = v0.min(v);
|
||||
v1 = v1.max(v);
|
||||
}
|
||||
let nstr = ((v1 - v0) / thick).ceil().max(1.0);
|
||||
let total = (u1 - u0) + slant.abs() * nstr + 1.0;
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [pr.b("flipDirection") as u32, (slant >= 0.0) as u32, 0, 0];
|
||||
p.f[0] = f4([sn, cs, v0, u0]);
|
||||
p.f[1] = f4([thick, slant.abs(), nstr, total]);
|
||||
p.f[2][0] = c as f32;
|
||||
run(e, "ftr_line_sweep", &p, b, None, None)
|
||||
}
|
||||
|
||||
fn twister(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let c = completion(ctx);
|
||||
if c <= 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let pr = ctx.params;
|
||||
let (cx, cy) = b.to_px(pr.v2("center"));
|
||||
let (sn, cs) = pr.f("axis").to_radians().sin_cos();
|
||||
let half = (b.img.width as f64).hypot(b.img.height as f64) * 0.5;
|
||||
let back = fitted(e, ctx, &b, "backside", true, false)?;
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [back.is_some() as u32, pr.b("shading") as u32, 0, 0];
|
||||
p.f[0] = f4([cx, cy, sn, cs]);
|
||||
p.f[1] = f4([half, c, 0.0, 0.0]);
|
||||
run(e, "ftr_twister", &p, b, back.as_ref(), None)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Radial Shadow / CC Cylinder / CC Sphere / CC Spotlight (perspective.rs)
|
||||
|
||||
fn radial_shadow(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let color = pr.color("color");
|
||||
let soft = pr.f("softness").max(0.0) * b.scale;
|
||||
let k = 1.0 + pr.f("projectionDistance").max(0.0) / 100.0;
|
||||
let lp0 = pr.v2("lightSource");
|
||||
if pr.b("resizeLayer") && !ctx.adjustment {
|
||||
let l = b.to_px(lp0);
|
||||
let (bw, bh) = (b.img.width as f64, b.img.height as f64);
|
||||
let grow = [(0.0, 0.0), (bw, 0.0), (0.0, bh), (bw, bh)].iter().map(|(cx, cy)| ((cx - l.0).hypot(cy - l.1)) * (k - 1.0)).fold(0.0, f64::max);
|
||||
b.pad(e, (grow + soft * 1.5).ceil() as u32 + 1)?;
|
||||
}
|
||||
let l = b.to_px(lp0);
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = (pr.e("render") == 1) as u32;
|
||||
p.f[0] = [l.0 as f32, l.1 as f32, k as f32, pr.f("opacity") as f32 / 100.0 * color[3]];
|
||||
p.f[1] = rgb(color, pr.f("colorInfluence") as f32 / 100.0);
|
||||
let mut sh = kernel(e, "ftr_rshadow", &p, &b.img, None, None);
|
||||
if soft > 0.0 {
|
||||
sh = gaussian_blur(e, &sh, soft * 0.5, soft * 0.5, false);
|
||||
}
|
||||
if !pr.b("shadowOnly") {
|
||||
sh = kernel(e, "ftr_under", &Params::default(), &sh, Some(&b.img), None);
|
||||
}
|
||||
b.img = sh;
|
||||
Some(b)
|
||||
}
|
||||
|
||||
fn norm3(v: [f64; 3]) -> [f64; 3] {
|
||||
let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt().max(1e-12);
|
||||
[v[0] / l, v[1] / l, v[2] / l]
|
||||
}
|
||||
|
||||
/// perspective::Shading into f[3..7]; the layer rectangle into f[1].
|
||||
fn shading(ctx: &EffectCtx, b: &GBuf, p: &mut Params) {
|
||||
let pr = ctx.params;
|
||||
let (a, el) = (pr.f("lightDirection").to_radians(), pr.f("lightHeight").to_radians());
|
||||
let l = norm3([a.sin() * el.cos(), -a.cos() * el.cos(), el.sin()]);
|
||||
let hv = norm3([l[0], l[1], l[2] + 1.0]);
|
||||
p.f[1] = f4([b.offset[0], b.offset[1], ctx.layer_size[0] * b.scale, ctx.layer_size[1] * b.scale]);
|
||||
p.f[3] = [l[0] as f32, l[1] as f32, l[2] as f32, pr.f("lightIntensity") as f32 / 100.0];
|
||||
p.f[4] = rgb(pr.color("lightColor"), pr.f("ambient") as f32 / 100.0);
|
||||
p.f[5] = [pr.f("diffuse") as f32 / 100.0, pr.f("specular") as f32 / 100.0, 1.0 / (pr.f("roughness") as f32).max(0.001), 0.0];
|
||||
p.f[6] = [hv[0] as f32, hv[1] as f32, hv[2] as f32, 0.0];
|
||||
}
|
||||
|
||||
fn cylinder(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let (w, h) = (ctx.layer_size[0] * b.scale, ctx.layer_size[1] * b.scale);
|
||||
let r = (pr.f("radius") / 100.0 * w / std::f64::consts::TAU).max(0.5);
|
||||
let c = b.to_px(pr.v2("position"));
|
||||
let mut p = Params::default();
|
||||
shading(ctx, &b, &mut p);
|
||||
p.u[0][0] = pr.e("render");
|
||||
p.f[0] = f4([c.0, c.1, r, h]);
|
||||
p.f[2][0] = pr.f("rotation").to_radians() as f32;
|
||||
run(e, "ftr_cylinder", &p, b, None, None)
|
||||
}
|
||||
|
||||
/// perspective::rot_xyz (R = Rz · Ry · Rx).
|
||||
fn rot_xyz(rx: f64, ry: f64, rz: f64) -> [[f64; 3]; 3] {
|
||||
let (sx, cx) = rx.to_radians().sin_cos();
|
||||
let (sy, cy) = ry.to_radians().sin_cos();
|
||||
let (sz, cz) = rz.to_radians().sin_cos();
|
||||
let mx = [[1.0, 0.0, 0.0], [0.0, cx, -sx], [0.0, sx, cx]];
|
||||
let my = [[cy, 0.0, sy], [0.0, 1.0, 0.0], [-sy, 0.0, cy]];
|
||||
let mz = [[cz, -sz, 0.0], [sz, cz, 0.0], [0.0, 0.0, 1.0]];
|
||||
let mul = |a: [[f64; 3]; 3], b: [[f64; 3]; 3]| {
|
||||
let mut o = [[0.0; 3]; 3];
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
o[i][j] = (0..3).map(|k| a[i][k] * b[k][j]).sum();
|
||||
}
|
||||
}
|
||||
o
|
||||
};
|
||||
mul(mz, mul(my, mx))
|
||||
}
|
||||
|
||||
fn sphere(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let r = (pr.f("radius") * b.scale).max(0.5);
|
||||
let c = b.to_px(pr.v2("offset"));
|
||||
let m = rot_xyz(pr.f("rotationX"), pr.f("rotationY"), pr.f("rotationZ"));
|
||||
let mut p = Params::default();
|
||||
shading(ctx, &b, &mut p);
|
||||
p.u[0][0] = pr.e("render");
|
||||
p.f[0] = f4([c.0, c.1, r, 0.0]);
|
||||
// Rows of M: the kernel computes Mᵀ · q as q.x·row0 + q.y·row1 + q.z·row2.
|
||||
for (i, row) in m.iter().enumerate() {
|
||||
p.f[7 + i] = f4([row[0], row[1], row[2], 0.0]);
|
||||
}
|
||||
run(e, "ftr_sphere", &p, b, None, None)
|
||||
}
|
||||
|
||||
fn spotlight(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let (w, h) = (ctx.layer_size[0] * b.scale, ctx.layer_size[1] * b.scale);
|
||||
let from = b.to_px(pr.v2("from"));
|
||||
let to = b.to_px(pr.v2("to"));
|
||||
let height = (pr.f("height") / 100.0 * w.max(h)).max(1e-3);
|
||||
let cone = pr.f("coneAngle").clamp(0.1, 89.9).to_radians();
|
||||
let soft = (pr.f("edgeSoftness") / 100.0).clamp(0.0, 1.0);
|
||||
let axis = norm3([to.0 - from.0, to.1 - from.1, -height]);
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = (pr.e("render") == 1) as u32;
|
||||
p.f[0] = [from.0 as f32, from.1 as f32, height as f32, pr.f("intensity") as f32 / 100.0];
|
||||
p.f[1] = f4([axis[0], axis[1], axis[2], cone * (1.0 - soft)]);
|
||||
p.f[2] = rgb(pr.color("color"), cone as f32);
|
||||
run(e, "ftr_spot", &p, b, None, None)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Environment / 3D Glasses (perspective2.rs)
|
||||
|
||||
fn environment(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let (mut env, es, eo, esz) = match ctx.layer_param("environment", true) {
|
||||
Some(o) => (e.g.upload_image(&o.buf.img)?, o.buf.scale, o.buf.offset, o.size),
|
||||
None => (b.img.clone(), b.scale, b.offset, ctx.layer_size),
|
||||
};
|
||||
if pr.b("filterEnvironment") {
|
||||
env = gaussian_blur(e, &env, 2.0 * es, 2.0 * es, true);
|
||||
}
|
||||
let relief = plane(e, &b.img, 11);
|
||||
let soft = 1.5 * b.scale.max(0.25);
|
||||
let hmap = gaussian_blur(e, &relief, soft, soft, true);
|
||||
let mut p = Params::default();
|
||||
let data = rows(e, &mut p, &env);
|
||||
p.u[0][0] = pr.e("mapping");
|
||||
p.f[0] = f4([1.0 / b.scale.max(1e-6), pr.f("height") / 100.0 * 20.0, es, 0.0]);
|
||||
p.f[1] = f4([eo[0], eo[1], esz[0], esz[1]]);
|
||||
run(e, "ftr_env", &p, b, Some(&hmap), Some(&data))
|
||||
}
|
||||
|
||||
fn glasses(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let mut l = fitted(e, ctx, &b, "leftView", true, false)?.unwrap_or_else(|| b.img.clone());
|
||||
let mut r = fitted(e, ctx, &b, "rightView", true, false)?.unwrap_or_else(|| b.img.clone());
|
||||
if pr.b("leftRightSwap") {
|
||||
std::mem::swap(&mut l, &mut r);
|
||||
}
|
||||
let unit = if pr.e("units") == 1 { ctx.layer_size[0] / 100.0 } else { 1.0 } * b.scale;
|
||||
let conv = pr.f("sceneConvergence") * unit;
|
||||
let va = pr.f("verticalAlignment") * unit;
|
||||
let mut shift = |img: GpuImage, dx: f64, dy: f64| {
|
||||
if dx == 0.0 && dy == 0.0 {
|
||||
return img;
|
||||
}
|
||||
let mut p = Params::default();
|
||||
p.f[0] = f4([dx, dy, 0.0, 0.0]);
|
||||
kernel(e, "ftr_shift", &p, &img, None, None)
|
||||
};
|
||||
let l = shift(l, conv * 0.5, 0.0);
|
||||
let r = shift(r, -conv * 0.5, va);
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = pr.e("view3d");
|
||||
p.f[0] = [b.img.width as f32, b.img.height as f32, (pr.f("balance") / 20.0).clamp(0.0, 1.0) as f32, 0.0];
|
||||
let out = kernel(e, "ftr_glasses", &p, &l, Some(&r), None);
|
||||
Some(GBuf { img: out, ..b })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Bender / CC Blobbylize (distort3.rs)
|
||||
|
||||
fn bender(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
let amount = pr.f("amount") / 100.0;
|
||||
if amount == 0.0 {
|
||||
return Some(b);
|
||||
}
|
||||
let top = b.to_px(pr.v2("top"));
|
||||
let base = b.to_px(pr.v2("base"));
|
||||
let (ax, ay) = (top.0 - base.0, top.1 - base.1);
|
||||
let len = (ax * ax + ay * ay).sqrt().max(1e-6);
|
||||
let (ux, uy) = (ax / len, ay / len);
|
||||
let reference = if pr.b("adjustToDistance") { len } else { ctx.layer_size[1] * b.scale };
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = pr.e("style");
|
||||
p.f[0] = f4([base.0, base.1, ux, uy]);
|
||||
p.f[1] = f4([len, -uy, ux, amount * reference]);
|
||||
run(e, "ftr_bender", &p, b, None, None)
|
||||
}
|
||||
|
||||
fn blobbylize(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
let pr = ctx.params;
|
||||
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 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[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);
|
||||
run(e, "ftr_blob", &p, b, Some(&h), None)
|
||||
}
|
||||
@@ -55,7 +55,9 @@ mod fx_key;
|
||||
mod fx_light;
|
||||
mod fx_noise;
|
||||
mod fx_stylize;
|
||||
mod fx_text;
|
||||
mod fx_tone;
|
||||
mod fx_transition;
|
||||
mod fx_warp;
|
||||
mod ops;
|
||||
mod particles;
|
||||
@@ -216,8 +218,12 @@ mod tests_fx_noise;
|
||||
#[cfg(test)]
|
||||
mod tests_fx_stylize;
|
||||
#[cfg(test)]
|
||||
mod tests_fx_text;
|
||||
#[cfg(test)]
|
||||
mod tests_fx_tone;
|
||||
#[cfg(test)]
|
||||
mod tests_fx_transition;
|
||||
#[cfg(test)]
|
||||
mod tests_fx_warp;
|
||||
#[cfg(test)]
|
||||
mod tests_particles;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// GPU effects, Numbers and Timecode: see src/fx_text.rs. The glyph coverage is rasterised on
|
||||
// the CPU (aux: fill in x, stroke ring in y, opacity applied); this kernel composites it as
|
||||
// textfx::draw_plan does: the layer or transparency, Timecode's box, then fill and stroke.
|
||||
// u[0] = (display options, keep the layer, box); f[0] = fill colour; f[1] = stroke colour;
|
||||
// f[2] = box (x0, x1, y0, y1); f[3] = (box colour, box opacity).
|
||||
|
||||
fn ftx_over(dst: vec4<f32>, s: vec4<f32>) -> vec4<f32> {
|
||||
return s + dst * (1.0 - s.w);
|
||||
}
|
||||
|
||||
// textfx::tint.
|
||||
fn ftx_tint(c: vec4<f32>, a0: f32) -> vec4<f32> {
|
||||
let a = clamp(a0 * c.w, 0.0, 1.0);
|
||||
return vec4<f32>(c.xyz * a, a);
|
||||
}
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftx_text(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
let dims = out_dims();
|
||||
if (p.x >= dims.x || p.y >= dims.y) {
|
||||
return;
|
||||
}
|
||||
var o = vec4<f32>(0.0);
|
||||
if (P.u[0].y != 0u) {
|
||||
o = textureLoad(src, p, 0);
|
||||
}
|
||||
if (P.u[0].z != 0u) {
|
||||
let r = P.f[2];
|
||||
let fy = f32(p.y) + 0.5;
|
||||
let cy = clamp(fy - r.z + 0.5, 0.0, 1.0) * clamp(r.w - fy + 0.5, 0.0, 1.0);
|
||||
if (cy > 0.0) {
|
||||
let fx = f32(p.x) + 0.5;
|
||||
let cx = clamp(fx - r.x + 0.5, 0.0, 1.0) * clamp(r.y - fx + 0.5, 0.0, 1.0);
|
||||
let k = cx * cy * P.f[3].w;
|
||||
if (k > 0.0) {
|
||||
o = ftx_over(o, vec4<f32>(P.f[3].xyz * k, k));
|
||||
}
|
||||
}
|
||||
}
|
||||
let cov = textureLoad(aux, p, 0);
|
||||
let fill = ftx_tint(P.f[0], cov.x);
|
||||
let stroke = ftx_tint(P.f[1], cov.y);
|
||||
switch P.u[0].x {
|
||||
case 1u: {
|
||||
o = ftx_over(o, stroke);
|
||||
}
|
||||
case 2u: {
|
||||
o = ftx_over(ftx_over(o, stroke), fill);
|
||||
}
|
||||
case 3u: {
|
||||
o = ftx_over(ftx_over(o, fill), stroke);
|
||||
}
|
||||
default: {
|
||||
o = ftx_over(o, fill);
|
||||
}
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
@@ -0,0 +1,883 @@
|
||||
// GPU effects, transitions and perspective: see src/fx_transition.rs. Block Dissolve, the CC
|
||||
// transitions (Glass Wipe, Grid Wipe, Image Wipe, Jaws, Line Sweep, Radial ScaleWipe, Scale
|
||||
// Wipe, Twister, WarpoMatic), Radial Shadow, CC Bender, CC Blobbylize, CC Cylinder, CC Sphere,
|
||||
// CC Spotlight, CC Environment and 3D Glasses, each mirroring the CPU effect operation for
|
||||
// operation. Coordinates are pixel centres relative to the effect's centre where it has one.
|
||||
|
||||
fn ftr_inside(p: vec2<i32>) -> bool {
|
||||
let d = out_dims();
|
||||
return p.x < d.x && p.y < d.y;
|
||||
}
|
||||
|
||||
fn ftr_centre(p: vec2<i32>) -> vec2<f32> {
|
||||
return vec2<f32>(f32(p.x) + 0.5, f32(p.y) + 0.5);
|
||||
}
|
||||
|
||||
// util::smoothstep.
|
||||
fn ftr_smoothstep(e0: f32, e1: f32, x: f32) -> f32 {
|
||||
if (abs(e1 - e0) < 1e-9) {
|
||||
return select(1.0, 0.0, x < e0);
|
||||
}
|
||||
let t = clamp((x - e0) / (e1 - e0), 0.0, 1.0);
|
||||
return t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
|
||||
// util::unpremul's colour.
|
||||
fn ftr_unpremul(p: vec4<f32>) -> vec3<f32> {
|
||||
if (p.w > 1e-6) {
|
||||
return p.xyz / p.w;
|
||||
}
|
||||
return vec3<f32>(0.0);
|
||||
}
|
||||
|
||||
// The third image, copied into `data` as RGBA f32 rows: P.u[3] = (row stride, width, height).
|
||||
fn ftr_row(x: i32, y: i32) -> vec4<f32> {
|
||||
if (x < 0 || y < 0 || x >= i32(P.u[3].y) || y >= i32(P.u[3].z)) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
let i = (u32(y) * P.u[3].x + u32(x)) * 4u;
|
||||
return vec4<f32>(data[i], data[i + 1u], data[i + 2u], data[i + 3u]);
|
||||
}
|
||||
|
||||
fn ftr_row_clamped(x: i32, y: i32) -> vec4<f32> {
|
||||
return ftr_row(clamp(x, 0, i32(P.u[3].y) - 1), clamp(y, 0, i32(P.u[3].z) - 1));
|
||||
}
|
||||
|
||||
// Image::sample_bilinear on the third image.
|
||||
fn ftr_row_bilinear(x: f32, y: f32) -> vec4<f32> {
|
||||
let fx = x - 0.5;
|
||||
let fy = y - 0.5;
|
||||
let x0 = floor(fx);
|
||||
let y0 = floor(fy);
|
||||
let tx = fx - x0;
|
||||
let ty = fy - y0;
|
||||
let xi = i32(x0);
|
||||
let yi = i32(y0);
|
||||
let a = ftr_row(xi, yi);
|
||||
let b = ftr_row(xi + 1, yi);
|
||||
let c = ftr_row(xi, yi + 1);
|
||||
let d = ftr_row(xi + 1, yi + 1);
|
||||
let top = a + (b - a) * tx;
|
||||
let bot = c + (d - c) * tx;
|
||||
return top + (bot - top) * ty;
|
||||
}
|
||||
|
||||
// Image::sample_bilinear_clamped on the third image.
|
||||
fn ftr_row_bilinear_clamped(x: f32, y: f32) -> vec4<f32> {
|
||||
let fx = x - 0.5;
|
||||
let fy = y - 0.5;
|
||||
let x0 = floor(fx);
|
||||
let y0 = floor(fy);
|
||||
let tx = fx - x0;
|
||||
let ty = fy - y0;
|
||||
let xi = i32(x0);
|
||||
let yi = i32(y0);
|
||||
let a = ftr_row_clamped(xi, yi);
|
||||
let b = ftr_row_clamped(xi + 1, yi);
|
||||
let c = ftr_row_clamped(xi, yi + 1);
|
||||
let d = ftr_row_clamped(xi + 1, yi + 1);
|
||||
let top = a + (b - a) * tx;
|
||||
let bot = c + (d - c) * tx;
|
||||
return top + (bot - top) * ty;
|
||||
}
|
||||
|
||||
// Central differences of the aux plane (x), edges clamped (transition2::grad).
|
||||
fn ftr_grad(p: vec2<i32>) -> vec2<f32> {
|
||||
let gx = (tex_get_clamped(aux, p.x + 1, p.y).x - tex_get_clamped(aux, p.x - 1, p.y).x) * 0.5;
|
||||
let gy = (tex_get_clamped(aux, p.x, p.y + 1).x - tex_get_clamped(aux, p.x, p.y - 1).x) * 0.5;
|
||||
return vec2<f32>(gx, gy);
|
||||
}
|
||||
|
||||
// transition2::wipe_t.
|
||||
fn ftr_wipe_t(g: f32, c: f32, w0: f32) -> f32 {
|
||||
let w = max(w0, 1e-3);
|
||||
let edge = c * (1.0 + 2.0 * w) - w;
|
||||
return 1.0 - ftr_smoothstep(edge - w, edge + w, g);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- planes
|
||||
|
||||
// A plane (x) from the source: u[0].x = util::Src index (Red, Green, Blue, Alpha, Luminance,
|
||||
// Hue, Lightness, Saturation, Full, Half, Off) times alpha except for Alpha itself
|
||||
// (transition2::prop_plane, CC Blobbylize); 11 = CC Environment's relief.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_plane(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let px = textureLoad(src, p, 0);
|
||||
let c = ftr_unpremul(px);
|
||||
let a = max(px.w, 0.0);
|
||||
var v: f32;
|
||||
switch P.u[0].x {
|
||||
case 0u: {
|
||||
v = c.x;
|
||||
}
|
||||
case 1u: {
|
||||
v = c.y;
|
||||
}
|
||||
case 2u: {
|
||||
v = c.z;
|
||||
}
|
||||
case 3u: {
|
||||
v = a;
|
||||
}
|
||||
case 4u: {
|
||||
v = luminance(c);
|
||||
}
|
||||
case 5u: {
|
||||
v = rgb_to_hsl(c).x;
|
||||
}
|
||||
case 6u: {
|
||||
v = rgb_to_hsl(c).z;
|
||||
}
|
||||
case 7u: {
|
||||
v = rgb_to_hsl(c).y;
|
||||
}
|
||||
case 8u: {
|
||||
v = 1.0;
|
||||
}
|
||||
case 9u: {
|
||||
v = 0.5;
|
||||
}
|
||||
case 11u: {
|
||||
v = luminance(c) * 0.5 + 0.5;
|
||||
}
|
||||
default: {
|
||||
v = 0.0;
|
||||
}
|
||||
}
|
||||
if (P.u[0].x != 3u) {
|
||||
v *= a;
|
||||
}
|
||||
textureStore(out, p, vec4<f32>(v, 0.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
// src × aux.x (Image::mul_mask).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_mul(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
textureStore(out, p, textureLoad(src, p, 0) * textureLoad(aux, p, 0).x);
|
||||
}
|
||||
|
||||
// aux + src × (1 − aux.a): src composited under aux.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_under(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let o = textureLoad(aux, p, 0);
|
||||
textureStore(out, p, o + textureLoad(src, p, 0) * (1.0 - o.w));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Block Dissolve (transition::block_dissolve)
|
||||
// The kept-block mask (x). u[0] = (sub-samples per axis, seed, column table length);
|
||||
// f[0].x = completion; data = block columns (width × n, u32) then block rows (height × n).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_blocks(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let n = P.u[0].x;
|
||||
var hits = 0u;
|
||||
for (var j = 0u; j < n; j++) {
|
||||
let by = bitcast<u32>(data[P.u[0].z + u32(p.y) * n + j]);
|
||||
for (var i = 0u; i < n; i++) {
|
||||
let bx = bitcast<u32>(data[u32(p.x) * n + i]);
|
||||
if (hash_noise(bx, by, P.u[0].y) >= P.f[0].x) {
|
||||
hits += 1u;
|
||||
}
|
||||
}
|
||||
}
|
||||
textureStore(out, p, vec4<f32>(f32(hits) / f32(n * n), 0.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Grid Wipe (transition::grid_wipe)
|
||||
// u[0] = (shape, reverse); f[0] = (cx, cy, sin, cos); f[1] = (cell size, max distance,
|
||||
// completion, border).
|
||||
|
||||
fn ftr_metric(shape: u32, d: vec2<f32>) -> f32 {
|
||||
switch shape {
|
||||
case 0u: {
|
||||
return abs(d.x);
|
||||
}
|
||||
case 2u: {
|
||||
return max(abs(d.x), abs(d.y));
|
||||
}
|
||||
default: {
|
||||
return length(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_grid(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let px = textureLoad(src, p, 0);
|
||||
let d = ftr_centre(p) - P.f[0].xy;
|
||||
let s = P.f[0].z;
|
||||
let co = P.f[0].w;
|
||||
let q = vec2<f32>(d.x * co + d.y * s, -d.x * s + d.y * co);
|
||||
let cs = P.f[1].x;
|
||||
let cc = (floor(q / cs) + 0.5) * cs;
|
||||
var o = clamp(ftr_metric(P.u[0].x, cc) / P.f[1].y, 0.0, 1.0);
|
||||
if (P.u[0].y != 0u) {
|
||||
o = 1.0 - o;
|
||||
}
|
||||
let spread = 0.25;
|
||||
let pr = clamp((P.f[1].z * (1.0 + spread) - o) / spread, 0.0, 1.0);
|
||||
if (pr <= 0.0) {
|
||||
textureStore(out, p, px);
|
||||
return;
|
||||
}
|
||||
let half = cs * 0.5 * (1.0 - pr);
|
||||
let m = max(abs(q.x - cc.x), abs(q.y - cc.y));
|
||||
var k = select(0.0, 1.0, m < half);
|
||||
if (P.f[1].w > 0.0) {
|
||||
k = clamp((half - m) / P.f[1].w + 0.5, 0.0, 1.0);
|
||||
}
|
||||
textureStore(out, p, px * k);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Radial ScaleWipe / CC Scale Wipe (transition.rs)
|
||||
// Radial: u[0].x = reverse; f[0] = (cx, cy, max distance, completion).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_radial_scale(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let c = P.f[0].xy;
|
||||
let maxd = P.f[0].z;
|
||||
let done = P.f[0].w;
|
||||
let d = ftr_centre(p) - c;
|
||||
let r = length(d);
|
||||
var o = vec4<f32>(0.0);
|
||||
if (P.u[0].x != 0u) {
|
||||
let lim = (1.0 - done) * maxd;
|
||||
if (!(r >= lim || lim <= 1e-9)) {
|
||||
let k = maxd / lim;
|
||||
o = sample_bilinear(src, c.x + d.x * k, c.y + d.y * k);
|
||||
}
|
||||
} else {
|
||||
let hole = done * maxd;
|
||||
if (!(r <= hole || maxd - hole <= 1e-9)) {
|
||||
let rs = (r - hole) * maxd / (maxd - hole);
|
||||
o = sample_bilinear(src, c.x + d.x / r * rs, c.y + d.y / r * rs);
|
||||
}
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
||||
// Scale Wipe: f[0] = (cx, cy, direction x, direction y); f[1].x = stretch.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_scale(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let f = ftr_centre(p);
|
||||
let dir = P.f[0].zw;
|
||||
let u = dot(f - P.f[0].xy, dir);
|
||||
var q = f;
|
||||
if (u > 0.0) {
|
||||
let back = u - u / (1.0 + P.f[1].x);
|
||||
q = f - dir * back;
|
||||
}
|
||||
textureStore(out, p, sample_bilinear_clamped(src, q.x, q.y));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Glass Wipe (transition2::glass_wipe)
|
||||
// aux = the gradient plane (x); data = the layer to reveal (rows). u[0].x = has reveal;
|
||||
// f[0] = (completion, band half-width, displacement).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_glass(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let t = ftr_wipe_t(textureLoad(aux, p, 0).x, P.f[0].x, P.f[0].y);
|
||||
let band = 4.0 * t * (1.0 - t);
|
||||
let g = ftr_grad(p);
|
||||
let f = ftr_centre(p);
|
||||
let o = g * P.f[0].z * band * 10.0;
|
||||
var a = textureLoad(src, p, 0);
|
||||
var r = vec4<f32>(0.0);
|
||||
if (band > 1e-4) {
|
||||
a = sample_bilinear(src, f.x + o.x, f.y + o.y);
|
||||
if (P.u[0].x != 0u) {
|
||||
r = ftr_row_bilinear(f.x - o.x, f.y - o.y);
|
||||
}
|
||||
} else if (P.u[0].x != 0u) {
|
||||
r = ftr_row(p.x, p.y);
|
||||
}
|
||||
textureStore(out, p, a + (r - a) * t);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Image Wipe (transition2::image_wipe)
|
||||
// aux = the gradient plane (x). u[0].x = inverse; f[0] = (completion, band half-width).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_image_wipe(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
var g = textureLoad(aux, p, 0).x;
|
||||
if (P.u[0].x != 0u) {
|
||||
g = 1.0 - g;
|
||||
}
|
||||
let k = 1.0 - ftr_wipe_t(g, P.f[0].x, P.f[0].y);
|
||||
textureStore(out, p, textureLoad(src, p, 0) * k);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Jaws (transition2::jaws)
|
||||
// u[0].x = shape; f[0] = (cx, cy, sin, cos); f[1] = (tooth width, amplitude, travel).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_jaws(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let c = P.f[0].xy;
|
||||
let sn = P.f[0].z;
|
||||
let cs = P.f[0].w;
|
||||
let width = P.f[1].x;
|
||||
let amp = P.f[1].y;
|
||||
let dd = P.f[1].z;
|
||||
let d = ftr_centre(p) - c;
|
||||
let u = d.x * cs + d.y * sn;
|
||||
let v = -d.x * sn + d.y * cs;
|
||||
let ph = fract_euclid(u / width);
|
||||
var f: f32;
|
||||
switch P.u[0].x {
|
||||
case 0u: {
|
||||
f = amp * (1.0 - 4.0 * abs(ph - 0.5));
|
||||
}
|
||||
case 1u: {
|
||||
f = amp * clamp((1.0 - 4.0 * abs(ph - 0.5)) * 2.0, -1.0, 1.0);
|
||||
}
|
||||
case 2u: {
|
||||
f = select(-amp, amp, ph < 0.5);
|
||||
}
|
||||
default: {
|
||||
f = amp * sin(ph * 6.283185307179586);
|
||||
}
|
||||
}
|
||||
var vs = 0.0;
|
||||
var hit = true;
|
||||
if (v + dd < f) {
|
||||
vs = v + dd;
|
||||
} else if (v - dd >= f) {
|
||||
vs = v - dd;
|
||||
} else {
|
||||
hit = false;
|
||||
}
|
||||
var o = vec4<f32>(0.0);
|
||||
if (hit) {
|
||||
o = sample_bilinear(src, u * cs - vs * sn + c.x, u * sn + vs * cs + c.y);
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Line Sweep (transition2::line_sweep)
|
||||
// u[0] = (flip, slant ≥ 0); f[0] = (sin, cos, v0, u0); f[1] = (thickness, |slant|, strips,
|
||||
// total travel); f[2].x = completion.
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_line_sweep(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let f = ftr_centre(p);
|
||||
let sn = P.f[0].x;
|
||||
let cs = P.f[0].y;
|
||||
let u = f.x * cs + f.y * sn;
|
||||
let v = -f.x * sn + f.y * cs;
|
||||
let nstr = P.f[1].z;
|
||||
var k = floor((v - P.f[0].z) / P.f[1].x);
|
||||
if (P.u[0].x != 0u) {
|
||||
k = nstr - 1.0 - k;
|
||||
}
|
||||
let stagger = select(nstr - 1.0 - k, k, P.u[0].y != 0u);
|
||||
let front = P.f[0].w + P.f[2].x * P.f[1].w - P.f[1].y * stagger;
|
||||
textureStore(out, p, select(textureLoad(src, p, 0), vec4<f32>(0.0), u < front));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Twister (transition2::twister)
|
||||
// aux = the backside layer (u[0].x = has one); u[0].y = shading; f[0] = (cx, cy, sin, cos);
|
||||
// f[1] = (half diagonal, completion).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_twister(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let c = P.f[0].xy;
|
||||
let sn = P.f[0].z;
|
||||
let cs = P.f[0].w;
|
||||
let d = ftr_centre(p) - c;
|
||||
let u = d.x * cs + d.y * sn;
|
||||
let v = -d.x * sn + d.y * cs;
|
||||
let s01 = clamp((u / P.f[1].x) * 0.5 + 0.5, 0.0, 1.0);
|
||||
let th = 3.141592653589793 * clamp(P.f[1].y * 2.0 - s01, 0.0, 1.0);
|
||||
let k = cos(th);
|
||||
if (abs(k) < 1e-4) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
let vs = v / k;
|
||||
var o: vec4<f32>;
|
||||
if (k > 0.0 || P.u[0].x == 0u) {
|
||||
o = sample_bilinear(src, u * cs - vs * sn + c.x, u * sn + vs * cs + c.y);
|
||||
} else {
|
||||
o = sample_bilinear(aux, u * cs + vs * sn + c.x, u * sn - vs * cs + c.y);
|
||||
}
|
||||
if (P.u[0].y != 0u) {
|
||||
let l = 0.4 + 0.6 * abs(k);
|
||||
o = vec4<f32>(o.xyz * l, o.w);
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC WarpoMatic (transition2::warpomatic)
|
||||
|
||||
// Reactor "Contrast Differences": src = the plane, aux = its wide mean.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_react_contrast(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let a = textureLoad(src, p, 0).x;
|
||||
let b = textureLoad(aux, p, 0).x;
|
||||
textureStore(out, p, vec4<f32>(min(abs(a - b) * 4.0, 1.0), 0.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
// Reactor "Local Differences": aux = the plane; its gradient magnitude.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_react_local(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let g = ftr_grad(p);
|
||||
textureStore(out, p, vec4<f32>(min(sqrt(g.x * g.x + g.y * g.y) * 8.0, 1.0), 0.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
// aux = the reactor plane (x); data = the layer to reveal (rows). u[0] = (warp direction,
|
||||
// has reveal); f[0] = (completion, blend span, warp amount).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_warpo(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let q = ftr_wipe_t(textureLoad(aux, p, 0).x, P.f[0].x, P.f[0].y);
|
||||
let g = ftr_grad(p);
|
||||
let f = ftr_centre(p);
|
||||
let amt = P.f[0].z;
|
||||
let o = g * amt * q;
|
||||
var i = g * amt * (1.0 - q);
|
||||
if (P.u[0].x == 1u) {
|
||||
i = -i;
|
||||
} else if (P.u[0].x == 2u) {
|
||||
i = vec2<f32>(-i.y, i.x);
|
||||
}
|
||||
var a = vec4<f32>(0.0);
|
||||
if (q < 1.0) {
|
||||
a = sample_bilinear(src, f.x + o.x, f.y + o.y);
|
||||
}
|
||||
var inc = vec4<f32>(0.0);
|
||||
if (P.u[0].y != 0u && q > 0.0) {
|
||||
inc = ftr_row_bilinear(f.x + i.x, f.y + i.y);
|
||||
}
|
||||
textureStore(out, p, a + (inc - a) * q);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Radial Shadow (perspective::radial_shadow)
|
||||
// The shadow before its blur. u[0].x = glass edge; f[0] = (light x, y, projection k,
|
||||
// opacity × colour alpha); f[1] = (colour, colour influence).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_rshadow(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let l = P.f[0].xy;
|
||||
let q = ftr_centre(p);
|
||||
let s = sample_bilinear(src, l.x + (q.x - l.x) / P.f[0].z, l.y + (q.y - l.y) / P.f[0].z);
|
||||
let sc = ftr_unpremul(s);
|
||||
let sa = max(s.w, 0.0);
|
||||
var a = sa;
|
||||
if (P.u[0].x != 0u) {
|
||||
a = clamp(sa * (1.0 - sa) * 4.0, 0.0, 1.0);
|
||||
}
|
||||
a *= P.f[0].w;
|
||||
let col = P.f[1].xyz;
|
||||
let c = col + (sc - col) * P.f[1].w;
|
||||
textureStore(out, p, vec4<f32>(c * a, a));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Bender (distort3::cc_bender)
|
||||
// u[0].x = style; f[0] = (base x, y, axis x, y); f[1] = (axis length, normal x, y, amount ×
|
||||
// reference).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_bender(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let f = ftr_centre(p);
|
||||
let r = f - P.f[0].xy;
|
||||
let t = clamp(dot(r, P.f[0].zw) / P.f[1].x, 0.0, 1.0);
|
||||
let pi = 3.141592653589793;
|
||||
var b: f32;
|
||||
switch P.u[0].x {
|
||||
case 1u: {
|
||||
b = 0.5 * sin(pi * t) * t;
|
||||
}
|
||||
case 2u: {
|
||||
b = 0.5 * t;
|
||||
}
|
||||
case 3u: {
|
||||
b = 0.25 * (1.0 - cos(pi * t)) - 0.1 * sin(2.0 * pi * t);
|
||||
}
|
||||
default: {
|
||||
b = 0.5 * t * t;
|
||||
}
|
||||
}
|
||||
let d = P.f[1].w * b;
|
||||
textureStore(out, p, sample_bilinear(src, f.x - P.f[1].y * d, f.y - P.f[1].z * d));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 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.
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_blob(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let depth = P.f[0].x;
|
||||
let cut = P.f[0].y;
|
||||
let hv = textureLoad(aux, p, 0).x;
|
||||
let g = ftr_grad(p);
|
||||
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;
|
||||
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));
|
||||
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));
|
||||
}
|
||||
let intensity = P.f[0].z;
|
||||
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 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));
|
||||
}
|
||||
|
||||
// 0.5 + atan2(x, z) / 2π, kept on the side of the ±π seam that the sign of `x` puts it on (a
|
||||
// GPU's approximate atan2 can land just across it, which wraps to the opposite edge of the
|
||||
// texture). x = ±0 takes the CPU's −π (its zero is negative there).
|
||||
fn ftr_lon_u(x: f32, z: f32) -> f32 {
|
||||
let u = 0.5 + atan2(x, z) / 6.283185307179586;
|
||||
if (x <= 0.0) {
|
||||
return clamp(u, 0.0, 0.5);
|
||||
}
|
||||
return clamp(u, 0.5, 0.99999994);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Cylinder / CC Sphere (perspective.rs)
|
||||
// Shading (perspective::Shading): f[3] = (light direction, intensity); f[4] = (light colour,
|
||||
// ambient); f[5] = (diffuse, specular, shininess); f[6] = half vector. The layer rectangle in
|
||||
// f[1] = (x0, y0, width, height).
|
||||
|
||||
fn ftr_shade(px: vec4<f32>, n: vec3<f32>) -> vec4<f32> {
|
||||
let c = ftr_unpremul(px);
|
||||
let a = max(px.w, 0.0);
|
||||
if (a <= 0.0) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
let nl = max(dot(n, P.f[3].xyz), 0.0);
|
||||
var spec = 0.0;
|
||||
if (nl > 0.0) {
|
||||
spec = powz(max(dot(n, P.f[6].xyz), 0.0), P.f[5].z) * P.f[5].y;
|
||||
}
|
||||
let lc = P.f[4].xyz * P.f[3].w;
|
||||
let o = c * (P.f[4].w + P.f[5].x * nl * lc) + spec * lc;
|
||||
return vec4<f32>(o * a, a);
|
||||
}
|
||||
|
||||
// perspective::texel: the layer as a texture at (u, v), wrapping horizontally.
|
||||
fn ftr_texel(u0: f32, v: f32) -> vec4<f32> {
|
||||
let u = fract_euclid(u0);
|
||||
if (v < 0.0 || v > 1.0) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
let r = P.f[1];
|
||||
return sample_bilinear_clamped(src, r.x + u * r.z, r.y + v * r.w);
|
||||
}
|
||||
|
||||
fn ftr_over(top: vec4<f32>, bottom: vec4<f32>) -> vec4<f32> {
|
||||
return top + bottom * (1.0 - top.w);
|
||||
}
|
||||
|
||||
fn ftr_faces(mode: u32, front: vec4<f32>, back: vec4<f32>) -> vec4<f32> {
|
||||
if (mode == 1u) {
|
||||
return front;
|
||||
}
|
||||
if (mode == 2u) {
|
||||
return back;
|
||||
}
|
||||
return ftr_over(front, back);
|
||||
}
|
||||
|
||||
// Cylinder: u[0].x = render; f[0] = (cx, cy, radius, layer height); f[2].x = rotation.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_cylinder(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let d = ftr_centre(p) - P.f[0].xy;
|
||||
let v = d.y / P.f[0].w + 0.5;
|
||||
let s = d.x / P.f[0].z;
|
||||
if (abs(s) >= 1.0) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
let tau = 6.283185307179586;
|
||||
let th = asin(s);
|
||||
let cz = cos(th);
|
||||
let spin = P.f[2].x;
|
||||
let front = ftr_shade(ftr_texel((th + spin) / tau + 0.5, v), vec3<f32>(s, 0.0, cz));
|
||||
let back = ftr_shade(ftr_texel((3.141592653589793 - th + spin) / tau + 0.5, v), vec3<f32>(-s, 0.0, cz));
|
||||
textureStore(out, p, ftr_faces(P.u[0].x, front, back));
|
||||
}
|
||||
|
||||
// Sphere: u[0].x = render; f[0] = (cx, cy, radius); f[7..10] = the rotation matrix rows.
|
||||
fn ftr_sphere_tex(q: vec3<f32>) -> vec4<f32> {
|
||||
let m0 = P.f[7].xyz;
|
||||
let m1 = P.f[8].xyz;
|
||||
let m2 = P.f[9].xyz;
|
||||
let t = m0 * q.x + m1 * q.y + m2 * q.z;
|
||||
let lat = asin(clamp(t.y, -1.0, 1.0));
|
||||
return ftr_texel(ftr_lon_u(t.x, t.z), lat / 3.141592653589793 + 0.5);
|
||||
}
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_sphere(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let sxy = (ftr_centre(p) - P.f[0].xy) / P.f[0].z;
|
||||
let d2 = sxy.x * sxy.x + sxy.y * sxy.y;
|
||||
if (d2 >= 1.0) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
let z = sqrt(1.0 - d2);
|
||||
let front = ftr_shade(ftr_sphere_tex(vec3<f32>(sxy, z)), vec3<f32>(sxy, z));
|
||||
let back = ftr_shade(ftr_sphere_tex(vec3<f32>(sxy, -z)), vec3<f32>(-sxy, z));
|
||||
textureStore(out, p, ftr_faces(P.u[0].x, front, back));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Spotlight (perspective::spotlight)
|
||||
// u[0].x = light only; f[0] = (from x, y, height, intensity); f[1] = (axis, inner angle);
|
||||
// f[2] = (colour, cone angle).
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_spot(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let d = ftr_centre(p) - P.f[0].xy;
|
||||
let v = normalize(vec3<f32>(d, -P.f[0].z));
|
||||
let axis = P.f[1].xyz;
|
||||
// acos(v · axis), well conditioned near the axis.
|
||||
let ang = atan2(length(cross(v, axis)), dot(v, axis));
|
||||
let m = (1.0 - ftr_smoothstep(P.f[1].w, P.f[2].w, ang)) * P.f[0].w;
|
||||
let px = textureLoad(src, p, 0);
|
||||
if (P.u[0].x != 0u) {
|
||||
let a = clamp(m, 0.0, 1.0);
|
||||
textureStore(out, p, vec4<f32>(P.f[2].xyz * a, a));
|
||||
} else {
|
||||
textureStore(out, p, vec4<f32>(px.xyz * (m * P.f[2].xyz), px.w));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CC Environment (perspective2::cc_environment)
|
||||
// aux = the relief height map (x); data = the environment image (rows). u[0].x = mapping;
|
||||
// f[0] = (1 / buffer scale, height, environment scale); f[1] = (environment offset, size).
|
||||
|
||||
fn ftr_env_uv(mapping: u32, r: vec3<f32>) -> vec2<f32> {
|
||||
if (mapping == 1u) {
|
||||
let m = 2.0 * max(sqrt(r.x * r.x + r.y * r.y + (r.z + 1.0) * (r.z + 1.0)), 1e-9);
|
||||
return vec2<f32>(0.5 + r.x / m, 0.5 + r.y / m);
|
||||
}
|
||||
if (mapping == 2u) {
|
||||
let ax = abs(r.x);
|
||||
let ay = abs(r.y);
|
||||
let az = abs(r.z);
|
||||
var cr: vec4<f32>;
|
||||
if (ax >= ay && ax >= az) {
|
||||
if (r.x > 0.0) {
|
||||
cr = vec4<f32>(2.0, 1.0, -r.z / ax, r.y / ax);
|
||||
} else {
|
||||
cr = vec4<f32>(0.0, 1.0, r.z / ax, r.y / ax);
|
||||
}
|
||||
} else if (ay >= az) {
|
||||
if (r.y < 0.0) {
|
||||
cr = vec4<f32>(1.0, 0.0, r.x / ay, -r.z / ay);
|
||||
} else {
|
||||
cr = vec4<f32>(1.0, 2.0, r.x / ay, r.z / ay);
|
||||
}
|
||||
} else if (r.z > 0.0) {
|
||||
cr = vec4<f32>(1.0, 1.0, r.x / az, r.y / az);
|
||||
} else {
|
||||
cr = vec4<f32>(1.0, 3.0, r.x / az, -r.y / az);
|
||||
}
|
||||
return vec2<f32>((cr.x + (cr.z + 1.0) * 0.5) / 3.0, (cr.y + (cr.w + 1.0) * 0.5) / 4.0);
|
||||
}
|
||||
let lat = asin(clamp(r.y, -1.0, 1.0));
|
||||
return vec2<f32>(ftr_lon_u(r.x, r.z), 0.5 + lat / 3.141592653589793);
|
||||
}
|
||||
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_env(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let a = textureLoad(src, p, 0).w;
|
||||
if (a <= 0.0) {
|
||||
textureStore(out, p, vec4<f32>(0.0));
|
||||
return;
|
||||
}
|
||||
let g = ftr_grad(p) * P.f[0].x;
|
||||
let height = P.f[0].y;
|
||||
let n = normalize(vec3<f32>(-g.x * height, -g.y * height, 1.0));
|
||||
let d = -n.z;
|
||||
let r = vec3<f32>(-2.0 * d * n.x, -2.0 * d * n.y, -(-1.0 - 2.0 * d * n.z));
|
||||
let uv = ftr_env_uv(P.u[0].x, r);
|
||||
let es = P.f[0].z;
|
||||
let ex = fract_euclid(uv.x) * P.f[1].z * es + P.f[1].x;
|
||||
let ey = clamp(uv.y, 0.0, 1.0) * P.f[1].w * es + P.f[1].y;
|
||||
let env = ftr_row_bilinear_clamped(ex, ey);
|
||||
let ec = env.xyz * (1.0 / max(env.w, 1e-3));
|
||||
textureStore(out, p, vec4<f32>(ec * a, a));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 3D Glasses (perspective2::glasses_3d)
|
||||
|
||||
// Translate by f[0].xy (perspective2::shift).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_shift(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let f = ftr_centre(p) - P.f[0].xy;
|
||||
textureStore(out, p, sample_bilinear(src, f.x, f.y));
|
||||
}
|
||||
|
||||
// src = left view, aux = right view. u[0].x = 3D view; f[0] = (width, height, balance).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn ftr_glasses(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (!ftr_inside(p)) {
|
||||
return;
|
||||
}
|
||||
let f = ftr_centre(p);
|
||||
let w = P.f[0].x;
|
||||
let h = P.f[0].y;
|
||||
let k = P.f[0].z;
|
||||
let view = P.u[0].x;
|
||||
var o: vec4<f32>;
|
||||
if (view == 0u) {
|
||||
if (f.x < w * 0.5) {
|
||||
o = sample_bilinear(src, f.x * 2.0, f.y);
|
||||
} else {
|
||||
o = sample_bilinear(aux, (f.x - w * 0.5) * 2.0, f.y);
|
||||
}
|
||||
} else if (view == 1u) {
|
||||
if (f.y < h * 0.5) {
|
||||
o = sample_bilinear(src, f.x, f.y * 2.0);
|
||||
} else {
|
||||
o = sample_bilinear(aux, f.x, (f.y - h * 0.5) * 2.0);
|
||||
}
|
||||
} else if (view == 2u) {
|
||||
if (p.y % 2 == 0) {
|
||||
o = textureLoad(src, p, 0);
|
||||
} else {
|
||||
o = textureLoad(aux, p, 0);
|
||||
}
|
||||
} else {
|
||||
let lp = textureLoad(src, p, 0);
|
||||
let rp = textureLoad(aux, p, 0);
|
||||
let lc = ftr_unpremul(lp);
|
||||
let rc = ftr_unpremul(rp);
|
||||
let a = max(max(lp.w, 0.0), max(rp.w, 0.0));
|
||||
let ll = luminance(lc);
|
||||
let rl = luminance(rc);
|
||||
var c: vec3<f32>;
|
||||
switch view {
|
||||
case 3u: {
|
||||
c = vec3<f32>(ll, rl, 0.0);
|
||||
}
|
||||
case 4u: {
|
||||
c = vec3<f32>(ll, 0.0, rl);
|
||||
}
|
||||
case 5u: {
|
||||
c = vec3<f32>(lc.x + (ll - lc.x) * k, rc.y, rc.z);
|
||||
}
|
||||
case 6u: {
|
||||
c = vec3<f32>(ll * (1.0 - 0.5 * k) + lc.x * 0.5 * k, rl, 0.0);
|
||||
}
|
||||
case 7u: {
|
||||
c = vec3<f32>(ll * (1.0 - 0.5 * k) + lc.x * 0.5 * k, 0.0, rl);
|
||||
}
|
||||
default: {
|
||||
c = abs(lc - rc);
|
||||
}
|
||||
}
|
||||
o = vec4<f32>(c * a, a);
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//! GPU Numbers and Timecode vs the CPU effects (the oracle): direct on a buffer at full and half
|
||||
//! resolution, as adjustment, and composited at 8 and 32 bpc.
|
||||
|
||||
use effectcraft_keyframe::Value;
|
||||
|
||||
use crate::tests::{c, effect_case, n};
|
||||
|
||||
fn e(v: u32) -> Value {
|
||||
Value::Enum(v)
|
||||
}
|
||||
|
||||
fn pt(x: f64, y: f64) -> Value {
|
||||
Value::Vec2([x, y])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numbers() {
|
||||
effect_case("ec.text.numbers", &[("format/value", n(42.5)), ("size", n(14.0))]);
|
||||
effect_case(
|
||||
"ec.text.numbers",
|
||||
&[("format/value", n(-7.0)), ("size", n(12.0)), ("fillAndStroke/displayOptions", e(2)), ("fillAndStroke/strokeWidth", n(1.5)), ("tracking", n(2.0))],
|
||||
);
|
||||
effect_case(
|
||||
"ec.text.numbers",
|
||||
&[
|
||||
("format/value", n(3.25)),
|
||||
("size", n(16.0)),
|
||||
("fillAndStroke/displayOptions", e(3)),
|
||||
("fillAndStroke/fillColor", c(0.2, 0.6, 1.0)),
|
||||
("compositeOnOriginal", Value::Bool(false)),
|
||||
],
|
||||
);
|
||||
effect_case("ec.text.numbers", &[("size", n(10.0)), ("fillAndStroke/displayOptions", e(1)), ("fillAndStroke/position", pt(20.0, 15.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timecode() {
|
||||
effect_case("ec.text.timecode", &[("textSize", n(10.0)), ("textPosition", pt(35.0, 22.0))]);
|
||||
effect_case(
|
||||
"ec.text.timecode",
|
||||
&[("textSize", n(9.0)), ("displayFormat", e(1)), ("renderOnOriginal", Value::Bool(false)), ("textColor", c(1.0, 0.8, 0.1))],
|
||||
);
|
||||
effect_case("ec.text.timecode", &[("textSize", n(8.0)), ("showBox", Value::Bool(true)), ("boxColor", c(0.1, 0.1, 0.4)), ("opacity", n(70.0))]);
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
//! GPU transition and perspective effects (Block Dissolve, the CC transitions, Radial Shadow,
|
||||
//! CC Bender, CC Blobbylize, CC Cylinder, CC Sphere, CC Spotlight, CC Environment, 3D Glasses)
|
||||
//! vs the CPU effects (the oracle): direct on a buffer at full and half resolution, as
|
||||
//! adjustment, and composited at 8 and 32 bpc; effects reading other layers also with a layer
|
||||
//! chosen.
|
||||
|
||||
use effectcraft_keyframe::Value;
|
||||
use effectcraft_project::BitDepth;
|
||||
use effectcraft_render::RenderOpts;
|
||||
use effectcraft_time::Tick;
|
||||
|
||||
use crate::tests::{Scene, c, check, compare_at, effect_case, n, opts, set};
|
||||
|
||||
fn e(v: u32) -> Value {
|
||||
Value::Enum(v)
|
||||
}
|
||||
|
||||
fn on() -> Value {
|
||||
Value::Bool(true)
|
||||
}
|
||||
|
||||
fn pt(x: f64, y: f64) -> Value {
|
||||
Value::Vec2([x, y])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_dissolve() {
|
||||
effect_case("ec.transition.blockdissolve", &[("completion", n(40.0))]);
|
||||
effect_case("ec.transition.blockdissolve", &[("completion", n(55.0)), ("blockWidth", n(7.0)), ("blockHeight", n(5.5)), ("feather", n(3.0))]);
|
||||
effect_case("ec.transition.blockdissolve", &[("completion", n(30.0)), ("blockWidth", n(4.3)), ("blockHeight", n(9.0)), ("softEdges", Value::Bool(false))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_and_scale_wipes() {
|
||||
effect_case("ec.transition.ccgridwipe", &[("completion", n(40.0)), ("center", pt(33.0, 21.0))]);
|
||||
effect_case("ec.transition.ccgridwipe", &[("completion", n(60.0)), ("rotation", n(20.0)), ("border", n(3.0)), ("tiles", n(7.0)), ("shape", e(0))]);
|
||||
effect_case("ec.transition.ccgridwipe", &[("completion", n(35.0)), ("shape", e(2)), ("reverse", on()), ("tiles", n(5.0))]);
|
||||
effect_case("ec.transition.ccradialscalewipe", &[("completion", n(40.0)), ("center", pt(30.0, 20.0))]);
|
||||
effect_case("ec.transition.ccradialscalewipe", &[("completion", n(30.0)), ("center", pt(40.0, 25.0)), ("reverse", on())]);
|
||||
effect_case("ec.transition.ccscalewipe", &[("stretch", n(2.0)), ("center", pt(30.0, 20.0))]);
|
||||
effect_case("ec.transition.ccscalewipe", &[("stretch", n(0.7)), ("direction", n(200.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradient_driven_wipes() {
|
||||
effect_case("ec.transition.ccglasswipe", &[("completion", n(45.0))]);
|
||||
effect_case("ec.transition.ccglasswipe", &[("completion", n(30.0)), ("softness", n(40.0)), ("displacementAmount", n(25.0))]);
|
||||
effect_case("ec.transition.ccimagewipe", &[("completion", n(50.0))]);
|
||||
effect_case(
|
||||
"ec.transition.ccimagewipe",
|
||||
&[("completion", n(40.0)), ("property", e(5)), ("blur", n(4.0)), ("inverseGradient", on()), ("borderSoftness", n(20.0))],
|
||||
);
|
||||
effect_case("ec.transition.ccimagewipe", &[("completion", n(60.0)), ("property", e(6)), ("autoSoftness", Value::Bool(false))]);
|
||||
effect_case("ec.transition.ccwarpomatic", &[("completion", n(40.0))]);
|
||||
effect_case("ec.transition.ccwarpomatic", &[("completion", n(55.0)), ("reactor", e(1)), ("warpDirection", e(1))]);
|
||||
effect_case("ec.transition.ccwarpomatic", &[("completion", n(35.0)), ("reactor", e(2)), ("warpDirection", e(2)), ("blendSpan", n(30.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn geometric_transitions() {
|
||||
effect_case("ec.transition.ccjaws", &[("completion", n(30.0)), ("center", pt(33.0, 21.0))]);
|
||||
for shape in 1..4 {
|
||||
effect_case("ec.transition.ccjaws", &[("completion", n(20.0)), ("shape", e(shape)), ("direction", n(25.0)), ("width", n(13.0))]);
|
||||
}
|
||||
effect_case("ec.transition.cclinesweep", &[("completion", n(40.0))]);
|
||||
effect_case(
|
||||
"ec.transition.cclinesweep",
|
||||
&[("completion", n(50.0)), ("direction", n(35.0)), ("slant", n(-12.0)), ("thickness", n(7.0)), ("flipDirection", on())],
|
||||
);
|
||||
effect_case("ec.transition.cctwister", &[("completion", n(30.0)), ("center", pt(33.0, 21.0))]);
|
||||
effect_case("ec.transition.cctwister", &[("completion", n(60.0)), ("axis", n(10.0)), ("shading", Value::Bool(false))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn perspective() {
|
||||
effect_case("ec.perspective.radialshadow", &[("lightSource", pt(20.0, 10.0)), ("projectionDistance", n(15.0))]);
|
||||
effect_case(
|
||||
"ec.perspective.radialshadow",
|
||||
&[
|
||||
("lightSource", pt(50.0, 15.0)),
|
||||
("softness", n(6.0)),
|
||||
("render", e(1)),
|
||||
("colorInfluence", n(60.0)),
|
||||
("resizeLayer", on()),
|
||||
("color", c(0.3, 0.0, 0.5)),
|
||||
],
|
||||
);
|
||||
effect_case("ec.perspective.radialshadow", &[("shadowOnly", on()), ("opacity", n(80.0)), ("resizeLayer", on())]);
|
||||
effect_case("ec.perspective.cccylinder", &[("position", pt(35.0, 22.0)), ("rotation", n(30.0))]);
|
||||
effect_case("ec.perspective.cccylinder", &[("radius", n(60.0)), ("render", e(1)), ("lightDirection", n(70.0)), ("roughness", n(0.2))]);
|
||||
effect_case("ec.perspective.cccylinder", &[("render", e(2)), ("lightHeight", n(20.0)), ("specular", n(60.0))]);
|
||||
effect_case("ec.perspective.ccsphere", &[("offset", pt(35.0, 22.0)), ("radius", n(18.0)), ("rotationX", n(20.0)), ("rotationY", n(35.0))]);
|
||||
effect_case("ec.perspective.ccsphere", &[("radius", n(20.0)), ("rotationZ", n(50.0)), ("render", e(2))]);
|
||||
effect_case("ec.perspective.ccspotlight", &[("from", pt(15.0, 8.0)), ("to", pt(35.0, 22.0))]);
|
||||
effect_case(
|
||||
"ec.perspective.ccspotlight",
|
||||
&[("from", pt(50.0, 5.0)), ("to", pt(30.0, 30.0)), ("coneAngle", n(35.0)), ("edgeSoftness", n(60.0)), ("render", e(1))],
|
||||
);
|
||||
effect_case("ec.perspective.ccspotlight", &[("to", pt(35.0, 22.0)), ("edgeSoftness", n(0.0)), ("color", c(1.0, 0.7, 0.3)), ("intensity", n(150.0))]);
|
||||
for mapping in 0..3 {
|
||||
effect_case("ec.perspective.ccenvironment", &[("mapping", e(mapping)), ("height", n(40.0))]);
|
||||
}
|
||||
effect_case("ec.perspective.ccenvironment", &[("filterEnvironment", Value::Bool(false))]);
|
||||
for view in 0..9 {
|
||||
effect_case("ec.perspective.3dglasses", &[("view3d", e(view)), ("sceneConvergence", n(4.0)), ("verticalAlignment", n(1.5))]);
|
||||
}
|
||||
effect_case("ec.perspective.3dglasses", &[("units", e(1)), ("sceneConvergence", n(3.0)), ("leftRightSwap", on()), ("balance", n(12.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distort() {
|
||||
effect_case("ec.distort.ccbender", &[("amount", n(40.0)), ("top", pt(35.0, 0.0)), ("base", pt(35.0, 44.0))]);
|
||||
for style in 1..4 {
|
||||
effect_case(
|
||||
"ec.distort.ccbender",
|
||||
&[("amount", n(-30.0)), ("style", e(style)), ("top", pt(20.0, 5.0)), ("base", pt(40.0, 40.0)), ("adjustToDistance", on())],
|
||||
);
|
||||
}
|
||||
effect_case("ec.distort.ccblobbylize", &[]);
|
||||
effect_case(
|
||||
"ec.distort.ccblobbylize",
|
||||
&[("property", e(4)), ("softness", n(3.0)), ("cutAway", n(30.0)), ("lightType", e(1)), ("lightPosition", pt(20.0, 10.0))],
|
||||
);
|
||||
effect_case("ec.distort.ccblobbylize", &[("property", e(5)), ("metal", n(30.0)), ("roughness", n(0.2))]);
|
||||
}
|
||||
|
||||
/// Effects reading another layer (gradient, reveal, backside, environment, stereo views).
|
||||
#[test]
|
||||
fn with_other_layers() {
|
||||
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
||||
let cases: Vec<(&str, Vec<(&str, Value)>)> = vec![
|
||||
("ec.transition.ccglasswipe", vec![("completion", n(40.0)), ("gradientLayer", Value::Layer(None)), ("layerToReveal", Value::Layer(None))]),
|
||||
("ec.transition.ccimagewipe", vec![("completion", n(45.0)), ("layer", Value::Layer(None))]),
|
||||
("ec.transition.ccwarpomatic", vec![("completion", n(40.0)), ("reactorLayer", Value::Layer(None)), ("layerToReveal", Value::Layer(None))]),
|
||||
("ec.transition.cctwister", vec![("completion", n(70.0)), ("backside", Value::Layer(None))]),
|
||||
("ec.perspective.ccenvironment", vec![("environment", Value::Layer(None))]),
|
||||
("ec.perspective.3dglasses", vec![("view3d", e(5)), ("leftView", Value::Layer(None)), ("sceneConvergence", n(3.0))]),
|
||||
("ec.distort.ccblobbylize", vec![("blobLayer", Value::Layer(None))]),
|
||||
];
|
||||
for (id, vals) in cases {
|
||||
let mut s = Scene::new(depth);
|
||||
let bg = s.solid([0.15, 0.1, 0.2], 97, 61);
|
||||
s.push(bg);
|
||||
let mut other = s.footage(50, 36);
|
||||
other.switches.video = false;
|
||||
let oid = s.push(other);
|
||||
let vals: Vec<(&str, Value)> = vals.into_iter().map(|(k, v)| (k, if v == Value::Layer(None) { Value::Layer(Some(oid.0)) } else { v })).collect();
|
||||
let mut l = s.footage(70, 44);
|
||||
s.effect(&mut l, id, &vals);
|
||||
set(&mut l, "transform/rotation", Value::Scalar(8.0));
|
||||
s.push(l);
|
||||
let label = format!("{id} with a layer {depth:?}");
|
||||
check(&label, compare_at(&s, opts(), Tick::ZERO), 0.0);
|
||||
check(&format!("{label} half"), compare_at(&s, RenderOpts { scale: 0.5, ..opts() }, Tick::ZERO), 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-21
@@ -5,7 +5,7 @@
|
||||
|
||||
EffectCraft ships 306 effects with 3009 parameters in total, grouped into the same categories as the Effects & Presets panel. Every effect is our own implementation, written from public behaviour descriptions and standard image-processing literature. Parameter names, order, popup options, units, defaults and ranges follow the reference application so that projects, expressions and muscle memory carry over.
|
||||
|
||||
- **GPU**: 170 effects also run on the GPU compositor with identical results.
|
||||
- **GPU**: 190 effects also run on the GPU compositor with identical results.
|
||||
- **32**: 306 effects process 32-bit float (HDR, overbright) pixels without clamping.
|
||||
- **Status**: 306 implemented in full, 0 partial (what is missing is listed).
|
||||
|
||||
@@ -133,8 +133,8 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Bezier Warp | `ec.distort.bezierwarp` | 13 | GPU | 32 | Implemented |
|
||||
| Bulge | `ec.distort.bulge` | 6 | GPU | 32 | Implemented |
|
||||
| CC Bend It | `ec.distort.ccbendit` | 4 | GPU | 32 | Implemented |
|
||||
| CC Bender | `ec.distort.ccbender` | 5 | | 32 | Implemented |
|
||||
| CC Blobbylize | `ec.distort.ccblobbylize` | 15 | | 32 | Implemented |
|
||||
| CC Bender | `ec.distort.ccbender` | 5 | GPU | 32 | Implemented |
|
||||
| CC Blobbylize | `ec.distort.ccblobbylize` | 15 | GPU | 32 | Implemented |
|
||||
| CC Flo Motion | `ec.distort.ccflomotion` | 7 | GPU | 32 | Implemented |
|
||||
| CC Griddler | `ec.distort.ccgriddler` | 5 | GPU | 32 | Implemented |
|
||||
| CC Lens | `ec.distort.cclens` | 3 | GPU | 32 | Implemented |
|
||||
@@ -303,15 +303,15 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| 3D Camera Tracker | `ec.perspective.cameratracker` | 12 | | 32 | Implemented |
|
||||
| 3D Glasses | `ec.perspective.3dglasses` | 8 | | 32 | Implemented |
|
||||
| 3D Glasses | `ec.perspective.3dglasses` | 8 | GPU | 32 | Implemented |
|
||||
| Bevel Alpha | `ec.perspective.bevelalpha` | 4 | GPU | 32 | Implemented |
|
||||
| Bevel Edges | `ec.perspective.beveledges` | 4 | GPU | 32 | Implemented |
|
||||
| CC Cylinder | `ec.perspective.cccylinder` | 12 | | 32 | Implemented |
|
||||
| CC Environment | `ec.perspective.ccenvironment` | 4 | | 32 | Implemented |
|
||||
| CC Sphere | `ec.perspective.ccsphere` | 14 | | 32 | Implemented |
|
||||
| CC Spotlight | `ec.perspective.ccspotlight` | 8 | | 32 | Implemented |
|
||||
| CC Cylinder | `ec.perspective.cccylinder` | 12 | GPU | 32 | Implemented |
|
||||
| CC Environment | `ec.perspective.ccenvironment` | 4 | GPU | 32 | Implemented |
|
||||
| CC Sphere | `ec.perspective.ccsphere` | 14 | GPU | 32 | Implemented |
|
||||
| CC Spotlight | `ec.perspective.ccspotlight` | 8 | GPU | 32 | Implemented |
|
||||
| Drop Shadow | `ec.perspective.dropshadow` | 6 | GPU | 32 | Implemented |
|
||||
| Radial Shadow | `ec.perspective.radialshadow` | 9 | | 32 | Implemented |
|
||||
| Radial Shadow | `ec.perspective.radialshadow` | 9 | GPU | 32 | Implemented |
|
||||
|
||||
## Simulation
|
||||
|
||||
@@ -370,8 +370,8 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Numbers | `ec.text.numbers` | 14 | | 32 | Implemented |
|
||||
| Timecode | `ec.text.timecode` | 12 | | 32 | Implemented |
|
||||
| Numbers | `ec.text.numbers` | 14 | GPU | 32 | Implemented |
|
||||
| Timecode | `ec.text.timecode` | 12 | GPU | 32 | Implemented |
|
||||
|
||||
## Time
|
||||
|
||||
@@ -390,17 +390,17 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Block Dissolve | `ec.transition.blockdissolve` | 5 | | 32 | Implemented |
|
||||
| CC Glass Wipe | `ec.transition.ccglasswipe` | 5 | | 32 | Implemented |
|
||||
| CC Grid Wipe | `ec.transition.ccgridwipe` | 7 | | 32 | Implemented |
|
||||
| CC Image Wipe | `ec.transition.ccimagewipe` | 7 | | 32 | Implemented |
|
||||
| CC Jaws | `ec.transition.ccjaws` | 6 | | 32 | Implemented |
|
||||
| Block Dissolve | `ec.transition.blockdissolve` | 5 | GPU | 32 | Implemented |
|
||||
| CC Glass Wipe | `ec.transition.ccglasswipe` | 5 | GPU | 32 | Implemented |
|
||||
| CC Grid Wipe | `ec.transition.ccgridwipe` | 7 | GPU | 32 | Implemented |
|
||||
| CC Image Wipe | `ec.transition.ccimagewipe` | 7 | GPU | 32 | Implemented |
|
||||
| CC Jaws | `ec.transition.ccjaws` | 6 | GPU | 32 | Implemented |
|
||||
| CC Light Wipe | `ec.transition.cclightwipe` | 8 | GPU | 32 | Implemented |
|
||||
| CC Line Sweep | `ec.transition.cclinesweep` | 5 | | 32 | Implemented |
|
||||
| CC Radial ScaleWipe | `ec.transition.ccradialscalewipe` | 3 | | 32 | Implemented |
|
||||
| CC Scale Wipe | `ec.transition.ccscalewipe` | 3 | | 32 | Implemented |
|
||||
| CC Twister | `ec.transition.cctwister` | 5 | | 32 | Implemented |
|
||||
| CC WarpoMatic | `ec.transition.ccwarpomatic` | 8 | | 32 | Implemented |
|
||||
| CC Line Sweep | `ec.transition.cclinesweep` | 5 | GPU | 32 | Implemented |
|
||||
| CC Radial ScaleWipe | `ec.transition.ccradialscalewipe` | 3 | GPU | 32 | Implemented |
|
||||
| CC Scale Wipe | `ec.transition.ccscalewipe` | 3 | GPU | 32 | Implemented |
|
||||
| CC Twister | `ec.transition.cctwister` | 5 | GPU | 32 | Implemented |
|
||||
| CC WarpoMatic | `ec.transition.ccwarpomatic` | 8 | GPU | 32 | Implemented |
|
||||
| Card Wipe | `ec.transition.cardwipe` | 44 | | 32 | Implemented |
|
||||
| Gradient Wipe | `ec.transition.gradientwipe` | 5 | GPU | 32 | Implemented |
|
||||
| Iris Wipe | `ec.transition.iriswipe` | 7 | GPU | 32 | Implemented |
|
||||
|
||||
Reference in New Issue
Block a user