M13.23: GPU Immersive Video effects
VR Blur, Chromatic Aberrations, Color Gradients, Converter (all projections and cube layouts), De-Noise, Digital Glitch, Fractal Noise, Glow, Plane to Sphere, Rotate Sphere, Sharpen and Sphere to Plane run on the GPU (fx_vr.rs / fx_vr.wgsl) with the CPU's equirectangular maths: seam-wrapping, pole-continuing sampling and latitude-widened box blurs, per eye for stereo layouts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e3e35a7ec2
commit
2e5ea051a8
|
|
@ -101,6 +101,7 @@ pub use ocio::color_stabilizer_maps;
|
|||
pub use channel3d::{CRYPTO_LAYERS, id_color, selection_hashes};
|
||||
pub use ocio::{ColorOp, Straight, Tf, color_program};
|
||||
pub use utility::{Lut, load_lut};
|
||||
pub use vr::{equi_dir as vr_equi_dir, rotation as vr_rotation};
|
||||
|
||||
/// Effect categories in Effects & Presets order.
|
||||
pub const CATEGORIES: &[&str] = &[
|
||||
|
|
@ -952,6 +953,19 @@ pub const GPU_EFFECTS: &[&str] = &[
|
|||
"ec.color.ociofile",
|
||||
"ec.color.ociolook",
|
||||
"ec.utility.colorprofileconverter",
|
||||
// Immersive Video.
|
||||
"ec.vr.blur",
|
||||
"ec.vr.chromaticaberrations",
|
||||
"ec.vr.colorgradients",
|
||||
"ec.vr.converter",
|
||||
"ec.vr.denoise",
|
||||
"ec.vr.digitalglitch",
|
||||
"ec.vr.fractalnoise",
|
||||
"ec.vr.glow",
|
||||
"ec.vr.planetosphere",
|
||||
"ec.vr.rotatesphere",
|
||||
"ec.vr.sharpen",
|
||||
"ec.vr.spheretoplane",
|
||||
];
|
||||
|
||||
/// Effects whose output depends on [`EffectCtx::time`] directly (not only through animated
|
||||
|
|
|
|||
|
|
@ -316,6 +316,7 @@ impl GpuContext {
|
|||
include_str!("shaders/fx_extra.wgsl"),
|
||||
include_str!("shaders/fx_depth.wgsl"),
|
||||
include_str!("shaders/fx_lut.wgsl"),
|
||||
include_str!("shaders/fx_vr.wgsl"),
|
||||
]
|
||||
.concat();
|
||||
let module =
|
||||
|
|
@ -377,6 +378,7 @@ impl GpuContext {
|
|||
.chain(crate::fx_extra::KERNELS)
|
||||
.chain(crate::fx_depth::KERNELS)
|
||||
.chain(crate::fx_lut::KERNELS)
|
||||
.chain(crate::fx_vr::KERNELS)
|
||||
.map(|e| (e, &layout))
|
||||
.chain(EXT_ENTRIES.iter().map(|e| (e, &layout_ext)))
|
||||
.map(|(e, layout)| {
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
|||
_ if crate::fx_extra::IDS.contains(&id) => crate::fx_extra::apply(e, id, ctx, b),
|
||||
_ if crate::fx_depth::IDS.contains(&id) => crate::fx_depth::apply(e, id, ctx, b),
|
||||
_ if crate::fx_lut::IDS.contains(&id) => crate::fx_lut::apply(e, id, ctx, b),
|
||||
_ if crate::fx_vr::IDS.contains(&id) => crate::fx_vr::apply(e, id, ctx, b),
|
||||
_ => pointwise(e, id, ctx, b),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ pub(crate) fn fractal(e: &mut Enc, ctx: &EffectCtx, b: GBuf, turbulent: bool) ->
|
|||
// ---------------------------------------------------------------- Median family (noise.rs, noise2.rs)
|
||||
|
||||
/// noise::median_image (of the straight colour with alpha 1 when `straight`).
|
||||
fn median_image(e: &mut Enc, img: &GpuImage, r: usize, straight: bool) -> GpuImage {
|
||||
pub(crate) fn median_image(e: &mut Enc, img: &GpuImage, r: usize, straight: bool) -> GpuImage {
|
||||
let q = run(e, "fxn_quant", &with_u0([straight as u32, 0, 0, 0]), img, None, None);
|
||||
if r <= MEDIAN_BISECT_R {
|
||||
return run(e, "fxn_median", &with_u0([r as u32, 0, 0, 0]), &q, None, None);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,431 @@
|
|||
//! GPU effects, Immersive Video family (kernels in `shaders/fx_vr.wgsl`, every entry point
|
||||
//! prefixed `fxv_`): VR Blur, VR Chromatic Aberrations, VR Color Gradients, VR Converter, VR
|
||||
//! De-Noise, VR Digital Glitch, VR Fractal Noise, VR Glow, VR Plane to Sphere, VR Rotate Sphere,
|
||||
//! VR Sharpen and VR Sphere to Plane.
|
||||
//!
|
||||
//! The equirectangular maths of `effects::vr` (view directions, the projections and cube-map
|
||||
//! layouts of VR Converter, seam-wrapping and pole-continuing bilinear sampling, the
|
||||
//! latitude-widened seam-aware box blurs) is ported to WGSL operation for operation; rotation
|
||||
//! matrices, per-row blur radii and VR Digital Glitch's per-band offsets are computed on the CPU
|
||||
//! exactly as the CPU effect does. Stereo layouts run each eye on its own cropped image. Angles
|
||||
//! between directions use atan2(|a × b|, a · b), the f32-stable form of the CPU's f64 acos;
|
||||
//! VR Color Gradients' inverse-distance weights are normalised by the nearest point (the same
|
||||
//! ratios without f32 overflow). VR De-Noise reuses the noise family's guided filter and median.
|
||||
|
||||
use effectcraft_effects::EffectCtx;
|
||||
use effectcraft_raster::hash_noise;
|
||||
|
||||
use crate::context::{Enc, GpuImage, Params};
|
||||
use crate::effects::GBuf;
|
||||
|
||||
/// Compute entry points in `fx_vr.wgsl`.
|
||||
pub(crate) const KERNELS: &[&str] = &["fxv_convert", "fxv_box_h", "fxv_box_v", "fxv_point", "fxv_gen", "fxv_wrap_pad"];
|
||||
|
||||
/// Effect ids implemented here.
|
||||
pub(crate) const IDS: &[&str] = &[
|
||||
"ec.vr.blur",
|
||||
"ec.vr.chromaticaberrations",
|
||||
"ec.vr.colorgradients",
|
||||
"ec.vr.converter",
|
||||
"ec.vr.denoise",
|
||||
"ec.vr.digitalglitch",
|
||||
"ec.vr.fractalnoise",
|
||||
"ec.vr.glow",
|
||||
"ec.vr.planetosphere",
|
||||
"ec.vr.rotatesphere",
|
||||
"ec.vr.sharpen",
|
||||
"ec.vr.spheretoplane",
|
||||
];
|
||||
|
||||
/// 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> {
|
||||
if b.img.width == 0 || b.img.height == 0 {
|
||||
return Some(b);
|
||||
}
|
||||
let img = match id {
|
||||
"ec.vr.blur" => blur(e, ctx, &b.img, b.scale)?,
|
||||
"ec.vr.sharpen" => sharpen(e, ctx, &b.img, b.scale)?,
|
||||
"ec.vr.glow" => glow(e, ctx, &b.img, b.scale)?,
|
||||
"ec.vr.denoise" => denoise(e, ctx, &b)?,
|
||||
"ec.vr.rotatesphere" => rotate(e, ctx, &b.img)?,
|
||||
"ec.vr.converter" => converter(e, ctx, &b.img)?,
|
||||
"ec.vr.spheretoplane" => {
|
||||
let m = rot(ctx, "tilt", "pan", "roll");
|
||||
let fov = ctx.params.f("horizontalFov") as f32;
|
||||
per_eye(e, &b.img, layout(ctx), |e, img, _| Some(convert(e, img, (0, 360.0), (5, fov), &m, img.width, img.height, None)))?
|
||||
}
|
||||
"ec.vr.planetosphere" => {
|
||||
let fov = ctx.params.f("scale").clamp(1.0, 179.0);
|
||||
let m = rot(ctx, "tilt", "pan", "roll");
|
||||
let feather = (ctx.params.f("feather") / 100.0).clamp(0.0, 1.0);
|
||||
let t = (fov / 2.0).to_radians().tan();
|
||||
let (w, h) = (b.img.width, b.img.height);
|
||||
convert(e, &b.img, (5, fov as f32), (0, 360.0), &m, w, h, (feather > 0.0).then_some([feather as f32, t as f32]))
|
||||
}
|
||||
"ec.vr.chromaticaberrations" => chromatic(e, ctx, &b.img)?,
|
||||
"ec.vr.colorgradients" => gradients(e, ctx, &b.img)?,
|
||||
"ec.vr.fractalnoise" => fractal(e, ctx, &b.img)?,
|
||||
"ec.vr.digitalglitch" => glitch(e, ctx, &b.img)?,
|
||||
_ => return None,
|
||||
};
|
||||
Some(GBuf { img, ..b })
|
||||
}
|
||||
|
||||
fn layout(ctx: &EffectCtx) -> u32 {
|
||||
ctx.params.e("frameLayout")
|
||||
}
|
||||
|
||||
/// vr::eyes.
|
||||
fn eyes(w: u32, h: u32, layout: u32) -> Vec<(u32, u32, u32, u32)> {
|
||||
match layout {
|
||||
1 if h >= 2 => vec![(0, 0, w, h / 2), (0, h / 2, w, h - h / 2)],
|
||||
2 if w >= 2 => vec![(0, 0, w / 2, h), (w / 2, 0, w - w / 2, h)],
|
||||
_ => vec![(0, 0, w, h)],
|
||||
}
|
||||
}
|
||||
|
||||
/// vr::per_eye: run `f` on each eye's own image and reassemble.
|
||||
fn per_eye(e: &mut Enc, img: &GpuImage, layout: u32, mut f: impl FnMut(&mut Enc, &GpuImage, usize) -> Option<GpuImage>) -> Option<GpuImage> {
|
||||
let rects = eyes(img.width, img.height, layout);
|
||||
if rects.len() == 1 {
|
||||
return f(e, img, 0);
|
||||
}
|
||||
let out = e.scratch(img.width, img.height);
|
||||
for (i, &(x, y, w, h)) in rects.iter().enumerate() {
|
||||
let crop = e.scratch(w, h);
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [x, y, 0, 0];
|
||||
e.pixels("fxn_crop", &p, img, None, &crop, None);
|
||||
let r = f(e, &crop, i)?;
|
||||
e.copy_into(&r, &out, x, y);
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
fn rot(ctx: &EffectCtx, t: &str, p: &str, r: &str) -> [[f32; 3]; 3] {
|
||||
effectcraft_effects::vr_rotation(ctx.params.f(t), ctx.params.f(p), ctx.params.f(r)).map(|r| r.map(|v| v as f32))
|
||||
}
|
||||
|
||||
fn put_matrix(p: &mut Params, at: usize, m: &[[f32; 3]; 3]) {
|
||||
for (i, r) in m.iter().enumerate() {
|
||||
p.f[at + i] = [r[0], r[1], r[2], 0.0];
|
||||
}
|
||||
}
|
||||
|
||||
/// vr::convert: re-project `src` (projection `from` = (kind, fov)) into a `w`×`h` frame in `to`,
|
||||
/// output direction → source direction = mᵀ · d. `feather` = (feather, tan(fov / 2)) for VR
|
||||
/// Plane to Sphere's edge.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn convert(e: &mut Enc, src: &GpuImage, from: (u32, f32), to: (u32, f32), m: &[[f32; 3]; 3], w: u32, h: u32, feather: Option<[f32; 2]>) -> GpuImage {
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [from.0, to.0, feather.is_some() as u32, 0];
|
||||
p.f[0] = [from.1, to.1, 0.0, 0.0];
|
||||
put_matrix(&mut p, 1, m);
|
||||
if let Some(f) = feather {
|
||||
p.f[4] = [f[0], f[1], 0.0, 0.0];
|
||||
}
|
||||
let out = e.scratch(w, h);
|
||||
e.pixels("fxv_convert", &p, src, None, &out, None);
|
||||
out
|
||||
}
|
||||
|
||||
fn rotate(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage) -> Option<GpuImage> {
|
||||
let pr = ctx.params;
|
||||
if pr.f("tilt") == 0.0 && pr.f("pan") == 0.0 && pr.f("roll") == 0.0 {
|
||||
return Some(img.clone());
|
||||
}
|
||||
let mut m = rot(ctx, "tilt", "pan", "roll");
|
||||
if pr.b("invertRotation") {
|
||||
m = [[m[0][0], m[1][0], m[2][0]], [m[0][1], m[1][1], m[2][1]], [m[0][2], m[1][2], m[2][2]]];
|
||||
}
|
||||
per_eye(e, img, layout(ctx), |e, img, _| Some(convert(e, img, (0, 360.0), (0, 360.0), &m, img.width, img.height, None)))
|
||||
}
|
||||
|
||||
fn converter(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage) -> Option<GpuImage> {
|
||||
let pr = ctx.params;
|
||||
let from = (pr.e("sourceProjection"), pr.f("sourceHorizontalFov"));
|
||||
let to = (pr.e("targetProjection"), pr.f("targetHorizontalFov"));
|
||||
if from.0 == to.0 && (!matches!(from.0, 4 | 5) || from.1 == to.1) && pr.f("tilt") == 0.0 && pr.f("pan") == 0.0 && pr.f("roll") == 0.0 {
|
||||
return Some(img.clone());
|
||||
}
|
||||
let m = rot(ctx, "tilt", "pan", "roll");
|
||||
let (from, to) = ((from.0, from.1 as f32), (to.0, to.1 as f32));
|
||||
per_eye(e, img, layout(ctx), |e, img, _| Some(convert(e, img, from, to, &m, img.width, img.height, None)))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- seam-aware blur
|
||||
|
||||
/// vr::sphere_blur: 3 latitude-widened, seam-wrapping horizontal box passes, then 3
|
||||
/// pole-continuing vertical ones.
|
||||
fn sphere_blur(e: &mut Enc, img: &GpuImage, sigma: f64) -> GpuImage {
|
||||
if sigma < 0.1 {
|
||||
return img.clone();
|
||||
}
|
||||
let (w, h) = (img.width as usize, img.height as usize);
|
||||
let radii = effectcraft_effects::util::box_radii(sigma, 3);
|
||||
let mut cur = img.clone();
|
||||
for &r in &radii {
|
||||
let rows: Vec<f32> = (0..h)
|
||||
.map(|y| {
|
||||
let lat = (0.5 - (y as f64 + 0.5) / h as f64) * std::f64::consts::PI;
|
||||
let rr = ((r as f64) / lat.cos().max(1e-3)).round().min((w / 2) as f64) as usize;
|
||||
// box_row_wrap's own limit.
|
||||
let rr = if w == 0 { 0 } else { rr.min((w - 1) / 2) };
|
||||
rr as f32
|
||||
})
|
||||
.collect();
|
||||
if rows.iter().all(|r| *r == 0.0) {
|
||||
continue;
|
||||
}
|
||||
let buf = e.data(&rows);
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = 256;
|
||||
let out = e.scratch(cur.width, cur.height);
|
||||
e.dispatch("fxv_box_h", &p, &cur, None, &out, Some(&buf), (cur.height.div_ceil(64), cur.width.div_ceil(256)));
|
||||
cur = out;
|
||||
}
|
||||
for &r in &radii {
|
||||
if r == 0 {
|
||||
continue;
|
||||
}
|
||||
let block = (4 * r).clamp(32, 256) as u32;
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [block, r as u32, 0, 0];
|
||||
let out = e.scratch(cur.width, cur.height);
|
||||
e.dispatch("fxv_box_v", &p, &cur, None, &out, None, (cur.width.div_ceil(64), cur.height.div_ceil(block)));
|
||||
cur = out;
|
||||
}
|
||||
cur
|
||||
}
|
||||
|
||||
fn op(code: u32) -> Params {
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = code;
|
||||
p
|
||||
}
|
||||
|
||||
fn point(e: &mut Enc, p: &Params, src: &GpuImage, aux: Option<&GpuImage>) -> GpuImage {
|
||||
let out = e.scratch(src.width, src.height);
|
||||
e.pixels("fxv_point", p, src, aux, &out, None);
|
||||
out
|
||||
}
|
||||
|
||||
fn blur(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage, scale: f64) -> Option<GpuImage> {
|
||||
let s = ctx.params.f("blurriness").max(0.0) * scale * 0.5;
|
||||
if s < 0.1 {
|
||||
return Some(img.clone());
|
||||
}
|
||||
per_eye(e, img, layout(ctx), |e, img, _| Some(sphere_blur(e, img, s)))
|
||||
}
|
||||
|
||||
fn sharpen(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage, scale: f64) -> Option<GpuImage> {
|
||||
let amt = ctx.params.f("sharpenAmount") as f32 / 100.0;
|
||||
if amt == 0.0 {
|
||||
return Some(img.clone());
|
||||
}
|
||||
let s = (1.5 * scale).max(0.5);
|
||||
per_eye(e, img, layout(ctx), |e, img, _| {
|
||||
let bl = sphere_blur(e, img, s);
|
||||
let mut p = op(2);
|
||||
p.f[0][0] = amt;
|
||||
Some(point(e, &p, img, Some(&bl)))
|
||||
})
|
||||
}
|
||||
|
||||
fn glow(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage, scale: f64) -> Option<GpuImage> {
|
||||
let pr = ctx.params;
|
||||
let bright = pr.f("glowBrightness") as f32 / 100.0;
|
||||
if bright <= 0.0 {
|
||||
return Some(img.clone());
|
||||
}
|
||||
let radius = pr.f("glowRadius").max(0.0) * scale;
|
||||
let tc = pr.color("tintColor");
|
||||
let mut hp = op(0);
|
||||
hp.u[0][1] = pr.b("useTintColor") as u32;
|
||||
hp.f[0] = [pr.f("luminanceThreshold") as f32 / 100.0, pr.f("glowSaturation") as f32 / 100.0, 0.0, 0.0];
|
||||
hp.f[1] = [tc[0], tc[1], tc[2], 0.0];
|
||||
let mut add = op(1);
|
||||
add.f[0][0] = bright;
|
||||
per_eye(e, img, layout(ctx), |e, img, _| {
|
||||
let hi = point(e, &hp, img, None);
|
||||
let g = sphere_blur(e, &hi, (radius * 0.5).max(0.5));
|
||||
Some(point(e, &add, img, Some(&g)))
|
||||
})
|
||||
}
|
||||
|
||||
fn denoise(e: &mut Enc, ctx: &EffectCtx, b: &GBuf) -> Option<GpuImage> {
|
||||
let pr = ctx.params;
|
||||
let level = pr.f("noiseLevel").max(0.0) / 100.0;
|
||||
if level <= 0.0 {
|
||||
return Some(b.img.clone());
|
||||
}
|
||||
let r = ((1.0 + level * 4.0) * b.scale).max(1.0) as usize;
|
||||
if pr.e("noiseType") == 1 {
|
||||
let mr = ((level * 3.0 * b.scale).round() as usize).max(1);
|
||||
return per_eye(e, &b.img, layout(ctx), |e, img, _| Some(crate::fx_noise::median_image(e, img, mr, false)));
|
||||
}
|
||||
let eps = (level * level * 0.02) as f32;
|
||||
let detail = (pr.f("detail") / 100.0).clamp(0.0, 1.0) as f32;
|
||||
per_eye(e, &b.img, layout(ctx), |e, img, _| {
|
||||
let pad = (r * 2 + 1) as u32;
|
||||
let (pw, ph) = (img.width + 2 * pad, img.height + 2 * pad);
|
||||
if !e.g.fits(pw, ph) {
|
||||
return None;
|
||||
}
|
||||
let padded = e.scratch(pw, ph);
|
||||
let mut p = Params::default();
|
||||
p.u[0][0] = pad;
|
||||
e.pixels("fxv_wrap_pad", &p, img, None, &padded, None);
|
||||
let guide = point(e, &op(4), &padded, None);
|
||||
let f = crate::fx_noise::guided_filter(e, &guide, &padded, r, eps.max(1e-6));
|
||||
let crop = e.scratch(img.width, img.height);
|
||||
let mut p = Params::default();
|
||||
p.u[0] = [pad, pad, 0, 0];
|
||||
e.pixels("fxn_crop", &p, &f, None, &crop, None);
|
||||
let mut p = op(3);
|
||||
p.f[0][0] = detail;
|
||||
Some(point(e, &p, &crop, Some(img)))
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- generators
|
||||
|
||||
/// BLEND_OPTS → (none, blend mode id).
|
||||
fn blend(i: u32) -> (u32, u32) {
|
||||
use effectcraft_color::BlendMode::*;
|
||||
let m = match i {
|
||||
0 => return (1, 0),
|
||||
2 => Add,
|
||||
3 => Multiply,
|
||||
4 => Screen,
|
||||
5 => Overlay,
|
||||
_ => Normal,
|
||||
};
|
||||
(0, crate::ops::mode_id(m))
|
||||
}
|
||||
|
||||
fn chromatic(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage) -> Option<GpuImage> {
|
||||
let pr = ctx.params;
|
||||
let k = [pr.f("aberrationRed"), pr.f("aberrationGreen"), pr.f("aberrationBlue")].map(|v| (v / 100.0 * 0.1) as f32);
|
||||
if k.iter().all(|v| *v == 0.0) {
|
||||
return Some(img.clone());
|
||||
}
|
||||
let r = effectcraft_effects::vr_rotation(pr.f("centerTilt"), pr.f("centerPan"), 0.0);
|
||||
let c = [r[0][2], r[1][2], r[2][2]].map(|v| v as f32);
|
||||
let mut p = op(0);
|
||||
p.u[0][1] = pr.b("falloffInvert") as u32;
|
||||
p.f[0] = [k[0], k[1], k[2], (pr.f("falloff") / 100.0).clamp(0.0, 1.0) as f32];
|
||||
p.f[1] = [c[0], c[1], c[2], 0.0];
|
||||
per_eye(e, img, layout(ctx), |e, img, _| Some(gen_pass(e, &p, img, None)))
|
||||
}
|
||||
|
||||
fn gen_pass(e: &mut Enc, p: &Params, img: &GpuImage, data: Option<&wgpu::Buffer>) -> GpuImage {
|
||||
let out = e.scratch(img.width, img.height);
|
||||
e.pixels("fxv_gen", p, img, None, &out, data);
|
||||
out
|
||||
}
|
||||
|
||||
fn gradients(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage) -> Option<GpuImage> {
|
||||
let pr = ctx.params;
|
||||
let (lw, lh) = (ctx.layer_size[0].max(1.0), ctx.layer_size[1].max(1.0));
|
||||
let mut d = vec![];
|
||||
for i in 1..=5 {
|
||||
if !pr.b(&format!("enablePoint{i}")) {
|
||||
continue;
|
||||
}
|
||||
let pt = pr.v2(&format!("point{i}"));
|
||||
let dir = effectcraft_effects::vr_equi_dir(pt[0], pt[1], lw, lh);
|
||||
let c = pr.color(&format!("color{i}"));
|
||||
d.extend([dir[0] as f32, dir[1] as f32, dir[2] as f32, c[0], c[1], c[2]]);
|
||||
}
|
||||
if d.is_empty() {
|
||||
return Some(img.clone());
|
||||
}
|
||||
let (none, mode) = blend(pr.e("blendingMode"));
|
||||
let mut p = op(1);
|
||||
p.u[0] = [1, (d.len() / 6) as u32, none, mode];
|
||||
p.f[0] = [pr.f("blend").max(0.1) as f32, (pr.f("opacity") / 100.0) as f32, 0.0, 0.0];
|
||||
let buf = e.data(&d);
|
||||
per_eye(e, img, layout(ctx), |e, img, _| Some(gen_pass(e, &p, img, Some(&buf))))
|
||||
}
|
||||
|
||||
fn fractal(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage) -> Option<GpuImage> {
|
||||
let pr = ctx.params;
|
||||
let g = |id: &str, d: f64| pr.get(id).map(|v| v.as_f64()).unwrap_or(d);
|
||||
let octaves = pr.f("complexity").clamp(1.0, 20.0);
|
||||
let scale = (pr.f("transform/scale") / 100.0).max(0.01) as f32;
|
||||
let (none, mode) = blend(pr.e("blendingMode"));
|
||||
let mut p = op(2);
|
||||
p.u[0] = [2, pr.e("fractalType"), none, mode];
|
||||
p.u[1] = [octaves.ceil() as u32, pr.f("randomSeed") as u32, pr.b("invert") as u32, 0];
|
||||
p.f[0] = [
|
||||
pr.f("contrast") as f32 / 100.0,
|
||||
pr.f("brightness") as f32 / 100.0,
|
||||
(g("subSettings/subInfluence", 50.0) / 100.0).clamp(0.0, 1.0) as f32,
|
||||
(g("subSettings/subScaling", 50.0) / 100.0).clamp(0.1, 1.0) as f32,
|
||||
];
|
||||
p.f[1] = [4.0 / scale, (pr.f("evolution") / 360.0) as f32, (pr.f("opacity") / 100.0) as f32, octaves.fract() as f32];
|
||||
put_matrix(&mut p, 2, &rot(ctx, "transform/tilt", "transform/pan", "transform/roll"));
|
||||
per_eye(e, img, layout(ctx), |e, img, _| Some(gen_pass(e, &p, img, None)))
|
||||
}
|
||||
|
||||
fn glitch(e: &mut Enc, ctx: &EffectCtx, img: &GpuImage) -> Option<GpuImage> {
|
||||
let pr = ctx.params;
|
||||
let amp = (pr.f("masterAmplitude") / 100.0).max(0.0);
|
||||
if amp <= 0.0 {
|
||||
return Some(img.clone());
|
||||
}
|
||||
let g = |id: &str, d: f64| pr.get(id).map(|v| v.as_f64()).unwrap_or(d);
|
||||
let rate = pr.f("distortionRate").max(0.0);
|
||||
let geo = pr.f("geometricDistortion") / 100.0 * amp;
|
||||
let (hdisp, vdisp) = (g("geometric/horizontalDisplacement", 100.0) / 100.0, g("geometric/verticalDisplacement", 0.0) / 100.0);
|
||||
let complexity = pr.f("distortionComplexity").clamp(1.0, 100.0);
|
||||
let rgb = pr.f("colorDistortion") / 100.0 * amp;
|
||||
let offs = [g("color/redOffset", -1.0), g("color/greenOffset", 0.0), g("color/blueOffset", 1.0)];
|
||||
let scan = (pr.f("scanlines") / 100.0 * amp) as f32;
|
||||
let spacing = g("scanlineSpacing", 2.0).round().max(2.0) as usize;
|
||||
let seed = (pr.f("randomSeed") as u32) ^ ctx.seed;
|
||||
let mut evo = g("distortionEvolution", 0.0) / 360.0;
|
||||
if pr.b("evolutionOptions/cycleEvolution") {
|
||||
evo = evo.rem_euclid(g("evolutionOptions/cycle", 1.0).round().max(1.0));
|
||||
}
|
||||
let base = if rate > 0.0 { ctx.time * rate } else { 0.0 } + evo;
|
||||
let slot = base.floor() as i64 as u32;
|
||||
let frac = base - base.floor();
|
||||
let tr = effectcraft_effects::vr_rotation(g("target/tilt", 0.0), g("target/pan", 0.0), 0.0);
|
||||
let target = [tr[0][2], tr[1][2], tr[2][2]].map(|v| v as f32);
|
||||
let radius = g("target/radius", 0.0).to_radians() as f32;
|
||||
let feather = g("target/feather", 0.0).to_radians() as f32;
|
||||
per_eye(e, img, layout(ctx), |e, img, eye| {
|
||||
let (w, h) = (img.width as f64, img.height as f64);
|
||||
let bands = complexity.round() as u32;
|
||||
// Per row: horizontal / vertical shift, channel split and scan-line darkening.
|
||||
let mut d = Vec::with_capacity(img.height as usize * 4);
|
||||
for y in 0..img.height as usize {
|
||||
let band = ((y as f64 / h) * bands as f64) as u32;
|
||||
let at = |s: u32| {
|
||||
let r0 = hash_noise(band, s, seed ^ (eye as u32 * 977));
|
||||
let on = r0 < 0.35;
|
||||
let shift = if on { (hash_noise(band, s + 1, seed) as f64 * 2.0 - 1.0) * geo } else { 0.0 };
|
||||
let split = if on { rgb * w * 0.01 * (1.0 + hash_noise(band, s + 2, seed) as f64) } else { 0.0 };
|
||||
(shift, split)
|
||||
};
|
||||
let (s0, p0) = at(slot);
|
||||
let (shift, split) = if frac > 0.0 {
|
||||
let (s1, p1) = at(slot + 1);
|
||||
(s0 + (s1 - s0) * frac, p0 + (p1 - p0) * frac)
|
||||
} else {
|
||||
(s0, p0)
|
||||
};
|
||||
let dark = if y % spacing == 0 { 1.0 - scan * 0.5 } else { 1.0 };
|
||||
d.extend([(shift * w * 0.15 * hdisp) as f32, (shift * h * 0.15 * vdisp) as f32, split as f32, dark]);
|
||||
}
|
||||
let buf = e.data(&d);
|
||||
let mut p = op(3);
|
||||
p.u[0][0] = 3;
|
||||
p.f[0] = [offs[0] as f32, offs[1] as f32, offs[2] as f32, 0.0];
|
||||
p.f[1] = [target[0], target[1], target[2], 0.0];
|
||||
p.f[2] = [radius, feather, 0.0, 0.0];
|
||||
Some(gen_pass(e, &p, img, Some(&buf)))
|
||||
})
|
||||
}
|
||||
|
|
@ -57,6 +57,7 @@ mod fx_lut;
|
|||
mod fx_noise;
|
||||
mod fx_stylize;
|
||||
mod fx_tone;
|
||||
mod fx_vr;
|
||||
mod fx_warp;
|
||||
mod ops;
|
||||
mod particles;
|
||||
|
|
@ -221,6 +222,8 @@ mod tests_fx_stylize;
|
|||
#[cfg(test)]
|
||||
mod tests_fx_tone;
|
||||
#[cfg(test)]
|
||||
mod tests_fx_vr;
|
||||
#[cfg(test)]
|
||||
mod tests_fx_warp;
|
||||
#[cfg(test)]
|
||||
mod tests_particles;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,640 @@
|
|||
// ---------------------------------------------------------------- Immersive Video (fx_vr.rs)
|
||||
//
|
||||
// Equirectangular frames: longitude across, latitude down, the front (+Z) at the centre, y up
|
||||
// (effects::vr). Every function mirrors its CPU namesake.
|
||||
|
||||
const FXV_PI: f32 = 3.14159265358979;
|
||||
|
||||
fn fxv_unpremul(p: vec4<f32>) -> vec4<f32> {
|
||||
if (p.w > 1e-6) {
|
||||
return vec4<f32>(p.xyz / p.w, p.w);
|
||||
}
|
||||
return vec4<f32>(0.0, 0.0, 0.0, max(p.w, 0.0));
|
||||
}
|
||||
|
||||
// vr::equi_dir.
|
||||
fn fxv_equi_dir(x: f32, y: f32, w: f32, h: f32) -> vec3<f32> {
|
||||
let lon = (x / w - 0.5) * 2.0 * FXV_PI;
|
||||
let lat = (0.5 - y / h) * FXV_PI;
|
||||
return vec3<f32>(cos(lat) * sin(lon), sin(lat), cos(lat) * cos(lon));
|
||||
}
|
||||
|
||||
// vr::equi_px.
|
||||
fn fxv_equi_px(d: vec3<f32>, w: f32, h: f32) -> vec2<f32> {
|
||||
let lon = atan2(d.x, d.z);
|
||||
let lat = asin(clamp(d.y, -1.0, 1.0));
|
||||
return vec2<f32>((lon / (2.0 * FXV_PI) + 0.5) * w, (0.5 - lat / FXV_PI) * h);
|
||||
}
|
||||
|
||||
// Angle between two unit directions (the CPU's acos of the dot, in its f32-stable form).
|
||||
fn fxv_angle(a: vec3<f32>, b: vec3<f32>) -> f32 {
|
||||
return atan2(length(cross(a, b)), dot(a, b));
|
||||
}
|
||||
|
||||
// vr::sample_equi's texel fetch: wraps horizontally, continues over the poles.
|
||||
fn fxv_equi_at(xi0: i32, yi0: i32, w: i32, h: i32) -> vec4<f32> {
|
||||
var xi = xi0;
|
||||
var yi = yi0;
|
||||
if (yi < 0) {
|
||||
yi = -1 - yi;
|
||||
xi += w / 2;
|
||||
} else if (yi >= h) {
|
||||
yi = 2 * h - 1 - yi;
|
||||
xi += w / 2;
|
||||
}
|
||||
let xm = ((xi % w) + w) % w;
|
||||
return textureLoad(src, vec2<i32>(xm, clamp(yi, 0, h - 1)), 0);
|
||||
}
|
||||
|
||||
// vr::sample_equi (bilinear).
|
||||
fn fxv_sample_equi(x: f32, y: f32) -> vec4<f32> {
|
||||
let d = vec2<i32>(textureDimensions(src));
|
||||
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 = fxv_equi_at(xi, yi, d.x, d.y);
|
||||
let b = fxv_equi_at(xi + 1, yi, d.x, d.y);
|
||||
let c = fxv_equi_at(xi, yi + 1, d.x, d.y);
|
||||
let e = fxv_equi_at(xi + 1, yi + 1, d.x, d.y);
|
||||
let top = a + (b - a) * tx;
|
||||
let bot = c + (e - c) * tx;
|
||||
return top + (bot - top) * ty;
|
||||
}
|
||||
|
||||
// ---- VR Converter's projections (vr::Proj)
|
||||
|
||||
fn fxv_face_fw(f: u32) -> vec3<f32> {
|
||||
var v = array<vec3<f32>, 6>(vec3<f32>(1.0, 0.0, 0.0), vec3<f32>(-1.0, 0.0, 0.0), vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(0.0, -1.0, 0.0), vec3<f32>(0.0, 0.0, 1.0), vec3<f32>(0.0, 0.0, -1.0));
|
||||
return v[f];
|
||||
}
|
||||
|
||||
fn fxv_face_r(f: u32) -> vec3<f32> {
|
||||
var v = array<vec3<f32>, 6>(vec3<f32>(0.0, 0.0, -1.0), vec3<f32>(0.0, 0.0, 1.0), vec3<f32>(1.0, 0.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), vec3<f32>(-1.0, 0.0, 0.0));
|
||||
return v[f];
|
||||
}
|
||||
|
||||
fn fxv_face_u(f: u32) -> vec3<f32> {
|
||||
var v = array<vec3<f32>, 6>(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(0.0, 0.0, -1.0), vec3<f32>(0.0, 0.0, 1.0), vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(0.0, 1.0, 0.0));
|
||||
return v[f];
|
||||
}
|
||||
|
||||
// vr::cube_grid: the grid size.
|
||||
fn fxv_grid(kind: u32) -> vec2<f32> {
|
||||
switch (kind) {
|
||||
case 1u: {
|
||||
return vec2<f32>(4.0, 3.0);
|
||||
}
|
||||
case 2u, 6u, 7u: {
|
||||
return vec2<f32>(3.0, 2.0);
|
||||
}
|
||||
default: {
|
||||
return vec2<f32>(6.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vr::cube_grid: (column, row) of face f.
|
||||
fn fxv_cell(kind: u32, f: u32) -> vec2<f32> {
|
||||
switch (kind) {
|
||||
case 1u: {
|
||||
var c = array<vec2<f32>, 6>(vec2<f32>(2.0, 1.0), vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 0.0), vec2<f32>(1.0, 2.0), vec2<f32>(1.0, 1.0), vec2<f32>(3.0, 1.0));
|
||||
return c[f];
|
||||
}
|
||||
case 2u: {
|
||||
var c = array<vec2<f32>, 6>(vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 0.0), vec2<f32>(2.0, 0.0), vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 1.0), vec2<f32>(2.0, 1.0));
|
||||
return c[f];
|
||||
}
|
||||
case 6u, 7u: {
|
||||
var c = array<vec2<f32>, 6>(vec2<f32>(2.0, 0.0), vec2<f32>(0.0, 0.0), vec2<f32>(2.0, 1.0), vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 0.0), vec2<f32>(1.0, 1.0));
|
||||
return c[f];
|
||||
}
|
||||
default: {
|
||||
return vec2<f32>(f32(f), 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fxv_is_cube(kind: u32) -> bool {
|
||||
return (kind >= 1u && kind <= 3u) || kind == 6u || kind == 7u;
|
||||
}
|
||||
|
||||
// Proj::dir: the direction seen at frame position (x, y); w = 2 when outside the image.
|
||||
fn fxv_dir(kind: u32, fov: f32, x: f32, y: f32, w: f32, h: f32) -> vec4<f32> {
|
||||
let none = vec4<f32>(0.0, 0.0, 0.0, 2.0);
|
||||
if (kind == 0u) {
|
||||
return vec4<f32>(fxv_equi_dir(x, y, w, h), 1.0);
|
||||
}
|
||||
if (fxv_is_cube(kind)) {
|
||||
let g = fxv_grid(kind);
|
||||
let cw = w / g.x;
|
||||
let ch = h / g.y;
|
||||
let cx = floor(x / cw);
|
||||
let cy = floor(y / ch);
|
||||
var f = 6u;
|
||||
for (var i = 0u; i < 6u; i++) {
|
||||
let c = fxv_cell(kind, i);
|
||||
if (f == 6u && c.x == cx && c.y == cy) {
|
||||
f = i;
|
||||
}
|
||||
}
|
||||
if (f == 6u) {
|
||||
return none;
|
||||
}
|
||||
var a = (x - cx * cw) / cw * 2.0 - 1.0;
|
||||
var b = (y - cy * ch) / ch * 2.0 - 1.0;
|
||||
if (kind == 7u) {
|
||||
a = tan(a * FXV_PI / 4.0);
|
||||
b = tan(b * FXV_PI / 4.0);
|
||||
}
|
||||
return vec4<f32>(normalize(fxv_face_fw(f) + a * fxv_face_r(f) - b * fxv_face_u(f)), 1.0);
|
||||
}
|
||||
if (kind == 8u) {
|
||||
let s = min(w, h) / 2.0;
|
||||
let u = (x - w / 2.0) / s;
|
||||
let v = (y - h / 2.0) / s;
|
||||
let r = sqrt(u * u + v * v);
|
||||
if (r > 1.0) {
|
||||
return none;
|
||||
}
|
||||
let th = r * FXV_PI / 2.0;
|
||||
var cu = 0.0;
|
||||
var cv = 0.0;
|
||||
if (r > 1e-12) {
|
||||
cu = u / r;
|
||||
cv = v / r;
|
||||
}
|
||||
return vec4<f32>(sin(th) * cu, cos(th), sin(th) * cv, 1.0);
|
||||
}
|
||||
if (kind == 4u) {
|
||||
let half = radians(clamp(fov, 1.0, 360.0) / 2.0);
|
||||
let s = min(w, h) / 2.0;
|
||||
let u = (x - w / 2.0) / s;
|
||||
let v = (h / 2.0 - y) / s;
|
||||
let r = sqrt(u * u + v * v);
|
||||
if (r > 1.0) {
|
||||
return none;
|
||||
}
|
||||
let th = r * half;
|
||||
var cu = 0.0;
|
||||
var cv = 0.0;
|
||||
if (r > 1e-12) {
|
||||
cu = u / r;
|
||||
cv = v / r;
|
||||
}
|
||||
return vec4<f32>(sin(th) * cu, sin(th) * cv, cos(th), 1.0);
|
||||
}
|
||||
let t = tan(radians(clamp(fov, 1.0, 179.0) / 2.0));
|
||||
let u = (x / w * 2.0 - 1.0) * t;
|
||||
let v = (1.0 - y / h * 2.0) * t * h / w;
|
||||
return vec4<f32>(normalize(vec3<f32>(u, v, 1.0)), 1.0);
|
||||
}
|
||||
|
||||
// Proj::sample of `src` (in projection `kind`) in direction d.
|
||||
fn fxv_proj_sample(kind: u32, fov: f32, d: vec3<f32>) -> vec4<f32> {
|
||||
let dims = vec2<f32>(textureDimensions(src));
|
||||
let w = dims.x;
|
||||
let h = dims.y;
|
||||
if (kind == 0u) {
|
||||
let q = fxv_equi_px(d, w, h);
|
||||
return fxv_sample_equi(q.x, q.y);
|
||||
}
|
||||
if (fxv_is_cube(kind)) {
|
||||
// The face facing d most (the last one on ties, as max_by).
|
||||
var f = 0u;
|
||||
var best = dot(d, fxv_face_fw(0u));
|
||||
for (var i = 1u; i < 6u; i++) {
|
||||
let k = dot(d, fxv_face_fw(i));
|
||||
if (k >= best) {
|
||||
best = k;
|
||||
f = i;
|
||||
}
|
||||
}
|
||||
let fw = fxv_face_fw(f);
|
||||
let k = dot(d, fw);
|
||||
var a = dot(d, fxv_face_r(f)) / k;
|
||||
var b = -dot(d, fxv_face_u(f)) / k;
|
||||
if (kind == 7u) {
|
||||
a = atan(a) / (FXV_PI / 4.0);
|
||||
b = atan(b) / (FXV_PI / 4.0);
|
||||
}
|
||||
let g = fxv_grid(kind);
|
||||
let cw = w / g.x;
|
||||
let ch = h / g.y;
|
||||
let c = fxv_cell(kind, f);
|
||||
let x0 = c.x * cw;
|
||||
let y0 = c.y * ch;
|
||||
let x = clamp(x0 + (a + 1.0) * 0.5 * cw, x0 + 0.5, x0 + cw - 0.5);
|
||||
let y = clamp(y0 + (b + 1.0) * 0.5 * ch, y0 + 0.5, y0 + ch - 0.5);
|
||||
return sample_bilinear_clamped(src, x, y);
|
||||
}
|
||||
if (kind == 8u) {
|
||||
let th = acos(clamp(d.y, -1.0, 1.0));
|
||||
if (th > FXV_PI / 2.0 + 1e-6) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
let s = min(w, h) / 2.0;
|
||||
let r = th / (FXV_PI / 2.0);
|
||||
let l = sqrt(d.x * d.x + d.z * d.z);
|
||||
var cu = 0.0;
|
||||
var cv = 0.0;
|
||||
if (l > 1e-12) {
|
||||
cu = d.x / l;
|
||||
cv = d.z / l;
|
||||
}
|
||||
return sample_bilinear_clamped(src, w / 2.0 + cu * r * s, h / 2.0 + cv * r * s);
|
||||
}
|
||||
if (kind == 4u) {
|
||||
let half = radians(clamp(fov, 1.0, 360.0) / 2.0);
|
||||
let th = acos(clamp(d.z, -1.0, 1.0));
|
||||
if (th > half + 1e-6) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
let s = min(w, h) / 2.0;
|
||||
let r = th / half;
|
||||
let l = sqrt(d.x * d.x + d.y * d.y);
|
||||
var cu = 0.0;
|
||||
var cv = 0.0;
|
||||
if (l > 1e-12) {
|
||||
cu = d.x / l;
|
||||
cv = d.y / l;
|
||||
}
|
||||
return sample_bilinear_clamped(src, w / 2.0 + cu * r * s, h / 2.0 - cv * r * s);
|
||||
}
|
||||
if (d.z <= 1e-9) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
let t = tan(radians(clamp(fov, 1.0, 179.0) / 2.0));
|
||||
let u = d.x / d.z / t;
|
||||
let v = d.y / d.z / (t * h / w);
|
||||
if (abs(u) > 1.0 || abs(v) > 1.0) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
return sample_bilinear_clamped(src, (u + 1.0) * 0.5 * w, (1.0 - v) * 0.5 * h);
|
||||
}
|
||||
|
||||
// mᵀ · d with m's rows in f[at..at + 3].
|
||||
fn fxv_mtv(at: u32, d: vec3<f32>) -> vec3<f32> {
|
||||
let r0 = P.f[at].xyz;
|
||||
let r1 = P.f[at + 1u].xyz;
|
||||
let r2 = P.f[at + 2u].xyz;
|
||||
return r0 * d.x + r1 * d.y + r2 * d.z;
|
||||
}
|
||||
|
||||
// m · d.
|
||||
fn fxv_mv(at: u32, d: vec3<f32>) -> vec3<f32> {
|
||||
return vec3<f32>(dot(P.f[at].xyz, d), dot(P.f[at + 1u].xyz, d), dot(P.f[at + 2u].xyz, d));
|
||||
}
|
||||
|
||||
// vr::convert. u[0] = (from kind, to kind, feather); f[0] = (from fov, to fov);
|
||||
// f[1..4] = rotation rows; f[4] = (feather, tan(fov / 2)) (VR Plane to Sphere).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxv_convert(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let dims = out_dims();
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (p.x >= dims.x || p.y >= dims.y) {
|
||||
return;
|
||||
}
|
||||
let w = f32(dims.x);
|
||||
let h = f32(dims.y);
|
||||
let x = f32(p.x) + 0.5;
|
||||
let y = f32(p.y) + 0.5;
|
||||
let dd = fxv_dir(P.u[0].y, P.f[0].y, x, y, w, h);
|
||||
var o = vec4<f32>(0.0);
|
||||
if (dd.w < 1.5) {
|
||||
o = fxv_proj_sample(P.u[0].x, P.f[0].x, fxv_mtv(1u, dd.xyz));
|
||||
}
|
||||
if (P.u[0].z != 0u) {
|
||||
let d = fxv_mtv(1u, fxv_equi_dir(x, y, w, h));
|
||||
if (d.z > 0.0) {
|
||||
let t = P.f[4].y;
|
||||
let u = abs(d.x / d.z / t);
|
||||
let v = abs(d.y / d.z / (t * h / w));
|
||||
let edge = 1.0 - max(u, v);
|
||||
o = o * clamp(edge / P.f[4].x, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
||||
// ---- seam-aware box blurs
|
||||
|
||||
// Horizontal wrap-around box mean (vr::box_row_wrap); each row's radius in data[y].
|
||||
// u[0].x = block; dispatched over (row, block).
|
||||
@compute @workgroup_size(64, 1)
|
||||
fn fxv_box_h(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let dims = out_dims();
|
||||
let y = i32(gid.x);
|
||||
let block = i32(P.u[0].x);
|
||||
let b0 = i32(gid.y) * block;
|
||||
let w = dims.x;
|
||||
if (y >= dims.y || b0 >= w) {
|
||||
return;
|
||||
}
|
||||
let r = i32(data[y]);
|
||||
let b1 = min(b0 + block, w);
|
||||
if (r == 0) {
|
||||
for (var x = b0; x < b1; x++) {
|
||||
textureStore(out, vec2<i32>(x, y), textureLoad(src, vec2<i32>(x, y), 0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
let norm = 1.0 / f32(2 * r + 1);
|
||||
var acc = vec4<f32>(0.0);
|
||||
for (var i = b0 - r; i <= b0 + r; i++) {
|
||||
acc += textureLoad(src, vec2<i32>(((i % w) + w) % w, y), 0);
|
||||
}
|
||||
for (var x = b0; x < b1; x++) {
|
||||
textureStore(out, vec2<i32>(x, y), acc * norm);
|
||||
let add = textureLoad(src, vec2<i32>((x + r + 1) % w, y), 0);
|
||||
let sub = textureLoad(src, vec2<i32>(((x - r) % w + w) % w, y), 0);
|
||||
acc += add - sub;
|
||||
}
|
||||
}
|
||||
|
||||
// vr::box_cols_pole's fetch.
|
||||
fn fxv_pole_at(x0: i32, y0: i32, w: i32, h: i32) -> vec4<f32> {
|
||||
var x = x0;
|
||||
var y = y0;
|
||||
if (y < 0) {
|
||||
y = -1 - y;
|
||||
x = (x + w / 2) % w;
|
||||
} else if (y >= h) {
|
||||
y = 2 * h - 1 - y;
|
||||
x = (x + w / 2) % w;
|
||||
}
|
||||
return textureLoad(src, vec2<i32>(x, clamp(y, 0, h - 1)), 0);
|
||||
}
|
||||
|
||||
// Vertical box mean continuing over the poles. u[0] = (block, radius); dispatched over
|
||||
// (column, block).
|
||||
@compute @workgroup_size(64, 1)
|
||||
fn fxv_box_v(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let dims = out_dims();
|
||||
let x = i32(gid.x);
|
||||
let block = i32(P.u[0].x);
|
||||
let r = i32(P.u[0].y);
|
||||
let b0 = i32(gid.y) * block;
|
||||
if (x >= dims.x || b0 >= dims.y) {
|
||||
return;
|
||||
}
|
||||
let norm = 1.0 / f32(2 * r + 1);
|
||||
var acc = vec4<f32>(0.0);
|
||||
for (var j = b0 - r; j <= b0 + r; j++) {
|
||||
acc += fxv_pole_at(x, j, dims.x, dims.y);
|
||||
}
|
||||
let b1 = min(b0 + block, dims.y);
|
||||
for (var y = b0; y < b1; y++) {
|
||||
textureStore(out, vec2<i32>(x, y), acc * norm);
|
||||
acc += fxv_pole_at(x, y + r + 1, dims.x, dims.y) - fxv_pole_at(x, y - r, dims.x, dims.y);
|
||||
}
|
||||
}
|
||||
|
||||
// vr::wrap_pad: u[0].x = pad.
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxv_wrap_pad(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let dims = out_dims();
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (p.x >= dims.x || p.y >= dims.y) {
|
||||
return;
|
||||
}
|
||||
let s = vec2<i32>(textureDimensions(src));
|
||||
let pad = i32(P.u[0].x);
|
||||
var sx = p.x - pad;
|
||||
var sy = p.y - pad;
|
||||
if (sy < 0) {
|
||||
sy = -1 - sy;
|
||||
sx += s.x / 2;
|
||||
} else if (sy >= s.y) {
|
||||
sy = 2 * s.y - 1 - sy;
|
||||
sx += s.x / 2;
|
||||
}
|
||||
textureStore(out, p, textureLoad(src, vec2<i32>(((sx % s.x) + s.x) % s.x, clamp(sy, 0, s.y - 1)), 0));
|
||||
}
|
||||
|
||||
// Per-pixel steps. u[0].x = 0: VR Glow's highlights (u[0].y tint; f[0] = (threshold,
|
||||
// saturation); f[1] = tint); 1: add the glow (aux) × f[0].x; 2: VR Sharpen (aux = blurred,
|
||||
// f[0].x = amount); 3: VR De-Noise's detail mix (src = filtered, aux = original, f[0].x);
|
||||
// 4: the guided filter's guide (straight luminance).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxv_point(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let dims = out_dims();
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (p.x >= dims.x || p.y >= dims.y) {
|
||||
return;
|
||||
}
|
||||
let px = textureLoad(src, p, 0);
|
||||
let q = tex_get(aux, p.x, p.y);
|
||||
var o = px;
|
||||
switch (P.u[0].x) {
|
||||
case 0u: {
|
||||
let s = fxv_unpremul(px);
|
||||
let th = P.f[0].x;
|
||||
let l = luminance(s.xyz);
|
||||
var k = 0.0;
|
||||
if (l > th) {
|
||||
k = min((l - th) / max(1.0 - th, 1e-3), 4.0);
|
||||
}
|
||||
var c = s.xyz * k;
|
||||
let lc = luminance(c);
|
||||
c = lc + (c - lc) * P.f[0].y;
|
||||
if (P.u[0].y != 0u) {
|
||||
c = lc * P.f[1].xyz;
|
||||
}
|
||||
o = vec4<f32>(c * s.w, s.w * min(k, 1.0));
|
||||
}
|
||||
case 1u: {
|
||||
let b = P.f[0].x;
|
||||
o = vec4<f32>(px.xyz + q.xyz * b, min(px.w + q.w * b, 1.0));
|
||||
}
|
||||
case 2u: {
|
||||
let hi = max(px.w, 0.0) * 4.0;
|
||||
let c = clamp(px.xyz + (px.xyz - q.xyz) * P.f[0].x, vec3<f32>(0.0), vec3<f32>(hi));
|
||||
o = vec4<f32>(c, px.w);
|
||||
}
|
||||
case 3u: {
|
||||
var m = px + (q - px) * P.f[0].x;
|
||||
m.w = clamp(m.w, 0.0, 1.0);
|
||||
o = vec4<f32>(max(m.xyz, vec3<f32>(0.0)), m.w);
|
||||
}
|
||||
case 4u: {
|
||||
o = vec4<f32>(luminance(fxv_unpremul(px).xyz));
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
||||
// vr::composite of a generated straight colour (alpha 1) over the pixel. u[0].z = 1: mode None
|
||||
// (replace); u[0].w = blend mode id; f[0].y = opacity.
|
||||
fn fxv_composite(d: vec4<f32>, c: vec3<f32>, opacity: f32) -> vec4<f32> {
|
||||
let s = vec4<f32>(c * opacity, opacity);
|
||||
if (P.u[0].z != 0u) {
|
||||
return s;
|
||||
}
|
||||
var r = blend_pixel(P.u[0].w, d, s, 0.5);
|
||||
let a = d.w;
|
||||
if (a < 1.0) {
|
||||
let k = a / max(r.w, 1e-6);
|
||||
if (k < 1.0) {
|
||||
r = r * k;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Generated looks. u[0].x = 0: VR Chromatic Aberrations; 1: VR Color Gradients; 2: VR Fractal
|
||||
// Noise; 3: VR Digital Glitch (parameter layouts in fx_vr.rs).
|
||||
@compute @workgroup_size(16, 16)
|
||||
fn fxv_gen(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let dims = out_dims();
|
||||
let p = vec2<i32>(gid.xy);
|
||||
if (p.x >= dims.x || p.y >= dims.y) {
|
||||
return;
|
||||
}
|
||||
let w = f32(dims.x);
|
||||
let h = f32(dims.y);
|
||||
let fx = f32(p.x) + 0.5;
|
||||
let fy = f32(p.y) + 0.5;
|
||||
let px = textureLoad(src, p, 0);
|
||||
var o = px;
|
||||
switch (P.u[0].x) {
|
||||
// f[0] = (aberration R, G, B, falloff); f[1] = centre; u[0].y = invert.
|
||||
case 0u: {
|
||||
let d = fxv_equi_dir(fx, fy, w, h);
|
||||
let center = P.f[1].xyz;
|
||||
let ax = cross(center, d);
|
||||
let al = length(ax);
|
||||
let th = atan2(al, dot(center, d));
|
||||
var weight = powz(th / FXV_PI, 1.0 + P.f[0].w * 3.0);
|
||||
if (P.u[0].y != 0u) {
|
||||
weight = 1.0 - weight;
|
||||
}
|
||||
var res = vec4<f32>(0.0);
|
||||
for (var c = 0; c < 3; c++) {
|
||||
var s = px;
|
||||
if (al >= 1e-9) {
|
||||
let new_th = th * (1.0 - P.f[0][c] * weight);
|
||||
let perp = normalize(d - center * cos(th));
|
||||
let nd = center * cos(new_th) + perp * sin(new_th);
|
||||
let q = fxv_equi_px(nd, w, h);
|
||||
s = fxv_sample_equi(q.x, q.y);
|
||||
}
|
||||
res[c] = s[c];
|
||||
res.w = max(res.w, s.w);
|
||||
}
|
||||
o = res;
|
||||
}
|
||||
// data: points (direction, colour); u[0].y = count; f[0] = (power, opacity).
|
||||
case 1u: {
|
||||
let d = fxv_equi_dir(fx, fy, w, h);
|
||||
let n = P.u[0].y;
|
||||
var amin = 10.0;
|
||||
var exact = -1;
|
||||
for (var i = 0u; i < n; i++) {
|
||||
let a = fxv_angle(d, vec3<f32>(data[i * 6u], data[i * 6u + 1u], data[i * 6u + 2u]));
|
||||
if (a < 1e-6 && exact < 0) {
|
||||
exact = i32(i);
|
||||
}
|
||||
amin = min(amin, a);
|
||||
}
|
||||
var c: vec3<f32>;
|
||||
if (exact >= 0) {
|
||||
let e = u32(exact);
|
||||
c = vec3<f32>(data[e * 6u + 3u], data[e * 6u + 4u], data[e * 6u + 5u]);
|
||||
} else {
|
||||
var acc = vec3<f32>(0.0);
|
||||
var ws = 0.0;
|
||||
for (var i = 0u; i < n; i++) {
|
||||
let a = fxv_angle(d, vec3<f32>(data[i * 6u], data[i * 6u + 1u], data[i * 6u + 2u]));
|
||||
let wgt = pow(amin / a, P.f[0].x);
|
||||
acc += vec3<f32>(data[i * 6u + 3u], data[i * 6u + 4u], data[i * 6u + 5u]) * wgt;
|
||||
ws += wgt;
|
||||
}
|
||||
c = acc / ws;
|
||||
}
|
||||
o = fxv_composite(px, c, P.f[0].y);
|
||||
}
|
||||
// u[0].y = type; u[1] = (octaves, seed, invert); f[0] = (contrast, brightness, sub
|
||||
// influence, sub scaling); f[1] = (base frequency, evolution, opacity, octave fraction);
|
||||
// f[2..5] = rotation rows.
|
||||
case 2u: {
|
||||
let d = fxv_mv(2u, fxv_equi_dir(fx, fy, w, h));
|
||||
let kind = P.u[0].y;
|
||||
let n_oct = P.u[1].x;
|
||||
let evo = P.f[1].y;
|
||||
var v = 0.0;
|
||||
var best = 0.0;
|
||||
var amp = 1.0;
|
||||
var freq = P.f[1].x;
|
||||
var norm = 0.0;
|
||||
for (var oc = 0u; oc < n_oct; oc++) {
|
||||
var wgt = 1.0;
|
||||
if (oc + 1u == n_oct && P.f[1].w > 0.0) {
|
||||
wgt = P.f[1].w;
|
||||
}
|
||||
let q = vec3<f32>(d.x * freq + evo * 0.37, d.y * freq + evo * 0.71, d.z * freq + evo * 0.59);
|
||||
var nv = value_noise(q.x + f32(oc) * 17.3, q.y, q.z, P.u[1].y + oc);
|
||||
if (kind == 1u) {
|
||||
nv = 1.0 - abs(nv * 2.0 - 1.0);
|
||||
} else if (kind == 3u) {
|
||||
let t = clamp(abs(nv * 2.0 - 1.0) / 0.12, 0.0, 1.0);
|
||||
nv = 1.0 - t * t;
|
||||
}
|
||||
best = max(best, nv * amp);
|
||||
v += nv * amp * wgt;
|
||||
norm += amp * wgt;
|
||||
amp *= P.f[0].z;
|
||||
freq /= P.f[0].w;
|
||||
}
|
||||
if (kind == 2u) {
|
||||
v = best;
|
||||
} else {
|
||||
v = v / max(norm, 1e-6);
|
||||
}
|
||||
v = (v - 0.5) * P.f[0].x + 0.5 + P.f[0].y;
|
||||
if (P.u[1].z != 0u) {
|
||||
v = 1.0 - v;
|
||||
}
|
||||
v = clamp(v, 0.0, 1.0);
|
||||
o = fxv_composite(px, vec3<f32>(v), P.f[1].z);
|
||||
}
|
||||
// data: per row (dx, dy, split, dark); f[0] = channel offsets; f[1] = target direction;
|
||||
// f[2] = (radius, feather) in radians.
|
||||
case 3u: {
|
||||
let y = u32(p.y);
|
||||
let dx = data[y * 4u];
|
||||
let dy = data[y * 4u + 1u];
|
||||
let split = data[y * 4u + 2u];
|
||||
let dark = data[y * 4u + 3u];
|
||||
let radius = P.f[2].x;
|
||||
let feather = P.f[2].y;
|
||||
var k = 1.0;
|
||||
if (radius > 0.0) {
|
||||
let a = fxv_angle(fxv_equi_dir(fx, fy, w, h), P.f[1].xyz);
|
||||
if (a <= radius) {
|
||||
k = 1.0;
|
||||
} else if (feather > 0.0) {
|
||||
k = clamp(1.0 - (a - radius) / feather, 0.0, 1.0);
|
||||
} else {
|
||||
k = 0.0;
|
||||
}
|
||||
}
|
||||
if (k > 0.0) {
|
||||
let sx = fx - dx * k;
|
||||
let sy = clamp(fy - dy * k, 0.5, h - 0.5);
|
||||
let c0 = fxv_sample_equi(sx + P.f[0].x * split * k, sy);
|
||||
let c1 = fxv_sample_equi(sx + P.f[0].y * split * k, sy);
|
||||
let c2 = fxv_sample_equi(sx + P.f[0].z * split * k, sy);
|
||||
let dk = 1.0 - (1.0 - dark) * k;
|
||||
o = vec4<f32>(c0.x * dk, c1.y * dk, c2.z * dk, max(max(c0.w, c1.w), c2.w));
|
||||
}
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
textureStore(out, p, o);
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
//! GPU Immersive Video family vs the CPU effects (the oracle): direct on a buffer (full and half
|
||||
//! resolution, as adjustment) and composited at 8 and 32 bpc, in mono and both stereo layouts.
|
||||
//!
|
||||
//! Exception: re-projections that cross a cube-map face edge or a fisheye rim may pick the
|
||||
//! neighbouring face / side for directions within f32 rounding of the edge (the CPU decides in
|
||||
//! f64), so up to 0.5 % of the pixels of those cases may differ.
|
||||
|
||||
use effectcraft_keyframe::Value;
|
||||
use effectcraft_project::BitDepth;
|
||||
use effectcraft_render::RenderOpts;
|
||||
use effectcraft_time::Tick;
|
||||
|
||||
use crate::tests::{Scene, c, check, compare_at, diff, gpu, n, opts, pattern, set};
|
||||
|
||||
fn e(v: u32) -> Value {
|
||||
Value::Enum(v)
|
||||
}
|
||||
|
||||
fn on() -> Value {
|
||||
Value::Bool(true)
|
||||
}
|
||||
|
||||
/// The effect alone on a buffer (`allow` = fraction of pixels allowed over 1e-3).
|
||||
fn direct(id: &str, vals: &[(&str, Value)], allow: f64) {
|
||||
let Some(g) = gpu() else { return };
|
||||
let spec = effectcraft_effects::find(id).unwrap();
|
||||
let size = [96.0, 48.0];
|
||||
let mut params =
|
||||
effectcraft_effects::Params { values: spec.params.iter().map(|ps| (ps.id.to_string(), effectcraft_effects::default_value(ps, size))).collect() };
|
||||
for (k, v) in vals {
|
||||
params.values.insert(k.to_string(), v.clone());
|
||||
}
|
||||
for adjustment in [false, true] {
|
||||
for scale in [1.0, 0.5] {
|
||||
let img = pattern(7, (size[0] * scale) as u32, (size[1] * scale) as u32);
|
||||
let buf = effectcraft_effects::Buf { img, offset: [0.0, 0.0], scale };
|
||||
let ctx = || effectcraft_effects::EffectCtx { params: ¶ms, time: 0.4, layer_size: size, seed: 11, adjustment, env: Default::default() };
|
||||
let cpu = (spec.render)(&ctx(), buf.clone());
|
||||
let out =
|
||||
effectcraft_render::Accelerator::effects(g, &[effectcraft_render::FxStep { spec, ctx: ctx() }], &buf, None).expect("the GPU runs the effect");
|
||||
assert_eq!((out.offset, out.scale), (cpu.offset, cpu.scale), "{id}: geometry");
|
||||
let d = diff(&cpu.img, &out.img, 1e-3);
|
||||
assert!(
|
||||
d.over as f64 <= allow * d.total as f64,
|
||||
"{id} {vals:?} adj {adjustment} scale {scale}: {} of {} pixels over 1e-3 (max {}); worst {:?}",
|
||||
d.over,
|
||||
d.total,
|
||||
d.max,
|
||||
d.worst
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn case_allow(id: &str, vals: &[(&str, Value)], allow: f64) {
|
||||
direct(id, vals, allow);
|
||||
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
||||
let mut s = Scene::new(depth);
|
||||
let bg = s.solid([0.15, 0.1, 0.2], 97, 61);
|
||||
s.push(bg);
|
||||
let mut l = s.footage(96, 48);
|
||||
s.effect(&mut l, id, vals);
|
||||
set(&mut l, "transform/rotation", Value::Scalar(8.0));
|
||||
s.push(l);
|
||||
check(&format!("{id} {vals:?} {depth:?}"), compare_at(&s, opts(), Tick::ZERO), allow);
|
||||
check(&format!("{id} {vals:?} {depth:?} half"), compare_at(&s, RenderOpts { scale: 0.5, ..opts() }, Tick::ZERO), allow);
|
||||
}
|
||||
}
|
||||
|
||||
fn case(id: &str, vals: &[(&str, Value)]) {
|
||||
case_allow(id, vals, 0.0);
|
||||
}
|
||||
|
||||
/// `vals` in mono and in both stereo layouts.
|
||||
fn layouts(id: &str, vals: &[(&str, Value)]) {
|
||||
for l in 0..3 {
|
||||
case(id, &[vals, &[("frameLayout", e(l))]].concat());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blur_sharpen_glow() {
|
||||
layouts("ec.vr.blur", &[("blurriness", n(12.0))]);
|
||||
case("ec.vr.blur", &[("blurriness", n(60.0))]);
|
||||
layouts("ec.vr.sharpen", &[("sharpenAmount", n(60.0))]);
|
||||
layouts("ec.vr.glow", &[("luminanceThreshold", n(40.0)), ("glowRadius", n(10.0))]);
|
||||
case("ec.vr.glow", &[("useTintColor", on()), ("glowSaturation", n(150.0)), ("glowBrightness", n(250.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denoise() {
|
||||
layouts("ec.vr.denoise", &[("noiseLevel", n(40.0))]);
|
||||
case("ec.vr.denoise", &[("noiseLevel", n(60.0)), ("detail", n(30.0))]);
|
||||
case("ec.vr.denoise", &[("noiseType", e(1)), ("noiseLevel", n(50.0))]);
|
||||
case("ec.vr.denoise", &[("noiseType", e(1)), ("noiseLevel", n(80.0)), ("frameLayout", e(1))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_and_planes() {
|
||||
layouts("ec.vr.rotatesphere", &[("tilt", n(20.0)), ("pan", n(35.0)), ("roll", n(-10.0))]);
|
||||
case("ec.vr.rotatesphere", &[("tilt", n(-40.0)), ("pan", n(170.0)), ("invertRotation", on())]);
|
||||
layouts("ec.vr.spheretoplane", &[("horizontalFov", n(100.0)), ("tilt", n(10.0)), ("pan", n(-30.0))]);
|
||||
case("ec.vr.planetosphere", &[("scale", n(80.0)), ("pan", n(20.0)), ("tilt", n(-15.0))]);
|
||||
case("ec.vr.planetosphere", &[("scale", n(120.0)), ("feather", n(30.0)), ("roll", n(25.0))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converter() {
|
||||
for from in 0..9 {
|
||||
for to in [0u32, 1, 7] {
|
||||
if from == to {
|
||||
continue;
|
||||
}
|
||||
case_allow(
|
||||
"ec.vr.converter",
|
||||
&[("sourceProjection", e(from)), ("targetProjection", e(to)), ("sourceHorizontalFov", n(200.0)), ("pan", n(15.0))],
|
||||
0.005,
|
||||
);
|
||||
}
|
||||
}
|
||||
for to in [2u32, 3, 4, 5, 6, 8] {
|
||||
case_allow("ec.vr.converter", &[("targetProjection", e(to)), ("targetHorizontalFov", n(120.0)), ("tilt", n(12.0))], 0.005);
|
||||
}
|
||||
case_allow("ec.vr.converter", &[("targetProjection", e(1)), ("frameLayout", e(1))], 0.005);
|
||||
case_allow("ec.vr.converter", &[("targetProjection", e(6)), ("frameLayout", e(2)), ("roll", n(30.0))], 0.005);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated() {
|
||||
layouts("ec.vr.chromaticaberrations", &[("aberrationRed", n(40.0)), ("aberrationBlue", n(-30.0))]);
|
||||
case("ec.vr.chromaticaberrations", &[("centerTilt", n(30.0)), ("centerPan", n(-60.0)), ("falloff", n(10.0)), ("falloffInvert", on())]);
|
||||
layouts("ec.vr.colorgradients", &[]);
|
||||
case("ec.vr.colorgradients", &[("blend", n(5.0)), ("opacity", n(60.0)), ("blendingMode", e(5)), ("enablePoint5", on())]);
|
||||
for mode in 1..5 {
|
||||
case("ec.vr.colorgradients", &[("blendingMode", e(mode)), ("color2", c(0.2, 0.9, 0.4))]);
|
||||
}
|
||||
layouts("ec.vr.fractalnoise", &[]);
|
||||
for kind in 1..4 {
|
||||
case("ec.vr.fractalnoise", &[("fractalType", e(kind)), ("complexity", n(4.5)), ("evolution", n(70.0)), ("transform/pan", n(30.0))]);
|
||||
}
|
||||
case("ec.vr.fractalnoise", &[("blendingMode", e(4)), ("opacity", n(70.0)), ("invert", on()), ("transform/scale", n(250.0))]);
|
||||
layouts("ec.vr.digitalglitch", &[]);
|
||||
case(
|
||||
"ec.vr.digitalglitch",
|
||||
&[
|
||||
("distortionEvolution", n(100.0)),
|
||||
("geometric/verticalDisplacement", n(80.0)),
|
||||
("target/radius", n(60.0)),
|
||||
("target/feather", n(30.0)),
|
||||
("target/pan", n(40.0)),
|
||||
("scanlineSpacing", n(3.0)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -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**: 181 effects also run on the GPU compositor with identical results.
|
||||
- **GPU**: 193 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).
|
||||
|
||||
|
|
@ -219,18 +219,18 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
|||
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| VR Blur | `ec.vr.blur` | 2 | | 32 | Implemented |
|
||||
| VR Chromatic Aberrations | `ec.vr.chromaticaberrations` | 8 | | 32 | Implemented |
|
||||
| VR Color Gradients | `ec.vr.colorgradients` | 19 | | 32 | Implemented |
|
||||
| VR Converter | `ec.vr.converter` | 8 | | 32 | Implemented |
|
||||
| VR De-Noise | `ec.vr.denoise` | 4 | | 32 | Implemented |
|
||||
| VR Digital Glitch | `ec.vr.digitalglitch` | 21 | | 32 | Implemented |
|
||||
| VR Fractal Noise | `ec.vr.fractalnoise` | 16 | | 32 | Implemented |
|
||||
| VR Glow | `ec.vr.glow` | 7 | | 32 | Implemented |
|
||||
| VR Plane to Sphere | `ec.vr.planetosphere` | 6 | | 32 | Implemented |
|
||||
| VR Rotate Sphere | `ec.vr.rotatesphere` | 5 | | 32 | Implemented |
|
||||
| VR Sharpen | `ec.vr.sharpen` | 2 | | 32 | Implemented |
|
||||
| VR Sphere to Plane | `ec.vr.spheretoplane` | 5 | | 32 | Implemented |
|
||||
| VR Blur | `ec.vr.blur` | 2 | GPU | 32 | Implemented |
|
||||
| VR Chromatic Aberrations | `ec.vr.chromaticaberrations` | 8 | GPU | 32 | Implemented |
|
||||
| VR Color Gradients | `ec.vr.colorgradients` | 19 | GPU | 32 | Implemented |
|
||||
| VR Converter | `ec.vr.converter` | 8 | GPU | 32 | Implemented |
|
||||
| VR De-Noise | `ec.vr.denoise` | 4 | GPU | 32 | Implemented |
|
||||
| VR Digital Glitch | `ec.vr.digitalglitch` | 21 | GPU | 32 | Implemented |
|
||||
| VR Fractal Noise | `ec.vr.fractalnoise` | 16 | GPU | 32 | Implemented |
|
||||
| VR Glow | `ec.vr.glow` | 7 | GPU | 32 | Implemented |
|
||||
| VR Plane to Sphere | `ec.vr.planetosphere` | 6 | GPU | 32 | Implemented |
|
||||
| VR Rotate Sphere | `ec.vr.rotatesphere` | 5 | GPU | 32 | Implemented |
|
||||
| VR Sharpen | `ec.vr.sharpen` | 2 | GPU | 32 | Implemented |
|
||||
| VR Sphere to Plane | `ec.vr.spheretoplane` | 5 | GPU | 32 | Implemented |
|
||||
|
||||
## Keying
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue