feat: add 23 new video effects across 5 categories
Implement color (hue rotate, temperature, tint, posterize, duotone, cross process), distortion (pixelate, chromatic aberration, glitch, wave, mirror, kaleidoscope, fisheye), light (sharpen, glow, exposure, shadows/highlights), edge (detection, emboss), and style (film grain, halftone, scanlines, color key) effects as WGSL shaders with Rust pack functions and TypeScript definitions.
This commit is contained in:
parent
96eeb2901f
commit
1daba0e9ea
|
|
@ -0,0 +1,41 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const CHROMATIC_ABERRATION_SHADER = "chromatic-aberration";
|
||||
|
||||
export const chromaticAberrationEffectDefinition: EffectDefinition = {
|
||||
type: "chromatic-aberration",
|
||||
name: "Chromatic Aberration",
|
||||
keywords: [
|
||||
"chromatic aberration",
|
||||
"chromatic",
|
||||
"aberration",
|
||||
"rgb split",
|
||||
"fringe",
|
||||
"prism",
|
||||
"color fringe",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "offset",
|
||||
label: "Offset",
|
||||
type: "number",
|
||||
default: 15,
|
||||
min: 0,
|
||||
max: 50,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: CHROMATIC_ABERRATION_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_offset:
|
||||
typeof effectParams.offset === "number"
|
||||
? effectParams.offset
|
||||
: 15,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const COLOR_KEY_SHADER = "color-key";
|
||||
|
||||
function hexToRgb(hex: string): [number, number, number] {
|
||||
const clean = hex.replace("#", "");
|
||||
return [
|
||||
Number.parseInt(clean.substring(0, 2), 16) / 255,
|
||||
Number.parseInt(clean.substring(2, 4), 16) / 255,
|
||||
Number.parseInt(clean.substring(4, 6), 16) / 255,
|
||||
];
|
||||
}
|
||||
|
||||
export const colorKeyEffectDefinition: EffectDefinition = {
|
||||
type: "color-key",
|
||||
name: "Color Key",
|
||||
keywords: [
|
||||
"color key",
|
||||
"colorkey",
|
||||
"green screen",
|
||||
"chroma key",
|
||||
"chromakey",
|
||||
"remove color",
|
||||
"transparent",
|
||||
"background",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "keyColor",
|
||||
label: "Key Color",
|
||||
type: "color",
|
||||
default: "#00ff00",
|
||||
},
|
||||
{
|
||||
key: "tolerance",
|
||||
label: "Tolerance",
|
||||
type: "number",
|
||||
default: 40,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "softness",
|
||||
label: "Softness",
|
||||
type: "number",
|
||||
default: 10,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: COLOR_KEY_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_tolerance:
|
||||
typeof effectParams.tolerance === "number"
|
||||
? effectParams.tolerance
|
||||
: 40,
|
||||
u_softness:
|
||||
typeof effectParams.softness === "number"
|
||||
? effectParams.softness
|
||||
: 10,
|
||||
u_key_color:
|
||||
typeof effectParams.keyColor === "string"
|
||||
? hexToRgb(effectParams.keyColor)
|
||||
: hexToRgb("#00ff00"),
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const COLOR_TEMPERATURE_SHADER = "color-temperature";
|
||||
|
||||
export const colorTemperatureEffectDefinition: EffectDefinition = {
|
||||
type: "color-temperature",
|
||||
name: "Color Temperature",
|
||||
keywords: ["temperature", "warm", "cool", "white balance", "kelvin"],
|
||||
params: [
|
||||
{
|
||||
key: "temperature",
|
||||
label: "Temperature",
|
||||
type: "number",
|
||||
default: 0,
|
||||
min: -100,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: COLOR_TEMPERATURE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_temperature:
|
||||
typeof effectParams.temperature === "number"
|
||||
? effectParams.temperature / 100
|
||||
: 0,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const CROSS_PROCESS_SHADER = "cross-process";
|
||||
|
||||
export const crossProcessEffectDefinition: EffectDefinition = {
|
||||
type: "cross-process",
|
||||
name: "Cross Process",
|
||||
keywords: [
|
||||
"cross process",
|
||||
"crossprocess",
|
||||
"film",
|
||||
"analog",
|
||||
"vintage",
|
||||
"lo-fi",
|
||||
"lofi",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 100,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: CROSS_PROCESS_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 100,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const DUOTONE_SHADER = "duotone";
|
||||
|
||||
function hexToRgb(hex: string): [number, number, number] {
|
||||
const cleaned = hex.replace("#", "");
|
||||
if (cleaned.length === 3) {
|
||||
const r = parseInt(cleaned[0] + cleaned[0], 16) / 255;
|
||||
const g = parseInt(cleaned[1] + cleaned[1], 16) / 255;
|
||||
const b = parseInt(cleaned[2] + cleaned[2], 16) / 255;
|
||||
return [r, g, b];
|
||||
}
|
||||
const r = parseInt(cleaned.substring(0, 2), 16) / 255;
|
||||
const g = parseInt(cleaned.substring(2, 4), 16) / 255;
|
||||
const b = parseInt(cleaned.substring(4, 6), 16) / 255;
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
export const duotoneEffectDefinition: EffectDefinition = {
|
||||
type: "duotone",
|
||||
name: "Duotone",
|
||||
keywords: ["duotone", "two tone", "two color", "dual tone"],
|
||||
params: [
|
||||
{
|
||||
key: "shadowColor",
|
||||
label: "Shadow Color",
|
||||
type: "color",
|
||||
default: "#001428",
|
||||
},
|
||||
{
|
||||
key: "highlightColor",
|
||||
label: "Highlight Color",
|
||||
type: "color",
|
||||
default: "#ff6b35",
|
||||
},
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 100,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: DUOTONE_SHADER,
|
||||
uniforms: ({ effectParams }) => {
|
||||
const shadowHex =
|
||||
typeof effectParams.shadowColor === "string"
|
||||
? effectParams.shadowColor
|
||||
: "#001428";
|
||||
const highlightHex =
|
||||
typeof effectParams.highlightColor === "string"
|
||||
? effectParams.highlightColor
|
||||
: "#ff6b35";
|
||||
const [sr, sg, sb] = hexToRgb(shadowHex);
|
||||
const [hr, hg, hb] = hexToRgb(highlightHex);
|
||||
return {
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 100,
|
||||
u_shadow_color: [sr, sg, sb],
|
||||
u_highlight_color: [hr, hg, hb],
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const EDGE_DETECTION_SHADER = "edge-detection";
|
||||
|
||||
export const edgeDetectionEffectDefinition: EffectDefinition = {
|
||||
type: "edge-detection",
|
||||
name: "Edge Detection",
|
||||
keywords: [
|
||||
"edge",
|
||||
"edges",
|
||||
"detection",
|
||||
"sobel",
|
||||
"outline",
|
||||
"contour",
|
||||
"border",
|
||||
"trace",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 100,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "threshold",
|
||||
label: "Threshold",
|
||||
type: "number",
|
||||
default: 10,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: EDGE_DETECTION_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 100,
|
||||
u_threshold:
|
||||
typeof effectParams.threshold === "number"
|
||||
? effectParams.threshold
|
||||
: 10,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const EMBOSS_SHADER = "emboss";
|
||||
|
||||
export const embossEffectDefinition: EffectDefinition = {
|
||||
type: "emboss",
|
||||
name: "Emboss",
|
||||
keywords: [
|
||||
"emboss",
|
||||
"relief",
|
||||
"3d",
|
||||
"raised",
|
||||
"bump",
|
||||
"sculpture",
|
||||
"engrave",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "angle",
|
||||
label: "Angle",
|
||||
type: "number",
|
||||
default: 135,
|
||||
min: 0,
|
||||
max: 360,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: EMBOSS_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 50,
|
||||
u_angle:
|
||||
typeof effectParams.angle === "number"
|
||||
? effectParams.angle
|
||||
: 135,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const EXPOSURE_SHADER = "exposure";
|
||||
|
||||
export const exposureEffectDefinition: EffectDefinition = {
|
||||
type: "exposure",
|
||||
name: "Exposure",
|
||||
keywords: [
|
||||
"exposure",
|
||||
"brightness",
|
||||
"light",
|
||||
"photo",
|
||||
"ev",
|
||||
"camera",
|
||||
"overexpose",
|
||||
"underexpose",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "exposure",
|
||||
label: "Exposure",
|
||||
type: "number",
|
||||
default: 0,
|
||||
min: -3,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: EXPOSURE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_exposure:
|
||||
typeof effectParams.exposure === "number"
|
||||
? effectParams.exposure
|
||||
: 0,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const FILM_GRAIN_SHADER = "film-grain";
|
||||
|
||||
export const filmGrainEffectDefinition: EffectDefinition = {
|
||||
type: "film-grain",
|
||||
name: "Film Grain",
|
||||
keywords: [
|
||||
"film grain",
|
||||
"grain",
|
||||
"noise",
|
||||
"film",
|
||||
"analog",
|
||||
"dust",
|
||||
"scratch",
|
||||
"vintage",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 30,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "size",
|
||||
label: "Grain Size",
|
||||
type: "number",
|
||||
default: 1,
|
||||
min: 1,
|
||||
max: 10,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: FILM_GRAIN_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 30,
|
||||
u_size:
|
||||
typeof effectParams.size === "number"
|
||||
? effectParams.size
|
||||
: 1,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const FISHEYE_SHADER = "fisheye";
|
||||
|
||||
export const fisheyeEffectDefinition: EffectDefinition = {
|
||||
type: "fisheye",
|
||||
name: "Fisheye",
|
||||
keywords: [
|
||||
"fisheye",
|
||||
"fish eye",
|
||||
"barrel",
|
||||
"pincushion",
|
||||
"lens",
|
||||
"wide angle",
|
||||
"distortion",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "strength",
|
||||
label: "Strength",
|
||||
type: "number",
|
||||
default: 50,
|
||||
min: -100,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: FISHEYE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_strength:
|
||||
typeof effectParams.strength === "number"
|
||||
? effectParams.strength
|
||||
: 50,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const GLITCH_SHADER = "glitch";
|
||||
|
||||
export const glitchEffectDefinition: EffectDefinition = {
|
||||
type: "glitch",
|
||||
name: "Glitch",
|
||||
keywords: [
|
||||
"glitch",
|
||||
"corruption",
|
||||
"digital",
|
||||
"error",
|
||||
"bug",
|
||||
"distort",
|
||||
"vhs",
|
||||
"corrupt",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "blockSize",
|
||||
label: "Block Size",
|
||||
type: "number",
|
||||
default: 20,
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: GLITCH_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 50,
|
||||
u_block_size:
|
||||
typeof effectParams.blockSize === "number"
|
||||
? effectParams.blockSize
|
||||
: 20,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const GLOW_SHADER = "glow";
|
||||
|
||||
export const glowEffectDefinition: EffectDefinition = {
|
||||
type: "glow",
|
||||
name: "Glow",
|
||||
keywords: [
|
||||
"glow",
|
||||
"bloom",
|
||||
"light",
|
||||
"radiance",
|
||||
"luminous",
|
||||
"shine",
|
||||
"bright glow",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "threshold",
|
||||
label: "Threshold",
|
||||
type: "number",
|
||||
default: 70,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "radius",
|
||||
label: "Radius",
|
||||
type: "number",
|
||||
default: 4,
|
||||
min: 1,
|
||||
max: 20,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: GLOW_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 50,
|
||||
u_threshold:
|
||||
typeof effectParams.threshold === "number"
|
||||
? effectParams.threshold
|
||||
: 70,
|
||||
u_radius:
|
||||
typeof effectParams.radius === "number"
|
||||
? effectParams.radius
|
||||
: 4,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const HALFTONE_SHADER = "halftone";
|
||||
|
||||
export const halftoneEffectDefinition: EffectDefinition = {
|
||||
type: "halftone",
|
||||
name: "Halftone",
|
||||
keywords: [
|
||||
"halftone",
|
||||
"half tone",
|
||||
"dots",
|
||||
"print",
|
||||
"newspaper",
|
||||
"comic",
|
||||
"pop art",
|
||||
"screen",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "dotSize",
|
||||
label: "Dot Size",
|
||||
type: "number",
|
||||
default: 4,
|
||||
min: 1,
|
||||
max: 20,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "angle",
|
||||
label: "Angle",
|
||||
type: "number",
|
||||
default: 45,
|
||||
min: 0,
|
||||
max: 360,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: HALFTONE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_dot_size:
|
||||
typeof effectParams.dotSize === "number"
|
||||
? effectParams.dotSize
|
||||
: 4,
|
||||
u_angle:
|
||||
typeof effectParams.angle === "number"
|
||||
? effectParams.angle
|
||||
: 45,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const HUE_ROTATE_SHADER = "hue-rotate";
|
||||
|
||||
export const hueRotateEffectDefinition: EffectDefinition = {
|
||||
type: "hue-rotate",
|
||||
name: "Hue Rotate",
|
||||
keywords: ["hue", "rotate", "color shift", "color wheel", "phase"],
|
||||
params: [
|
||||
{
|
||||
key: "angle",
|
||||
label: "Angle",
|
||||
type: "number",
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 360,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: HUE_ROTATE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_angle:
|
||||
(typeof effectParams.angle === "number"
|
||||
? effectParams.angle
|
||||
: 0) / 360,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -6,6 +6,29 @@ import { invertEffectDefinition } from "./invert";
|
|||
import { saturationEffectDefinition } from "./saturation";
|
||||
import { sepiaEffectDefinition } from "./sepia";
|
||||
import { vignetteEffectDefinition } from "./vignette";
|
||||
import { hueRotateEffectDefinition } from "./hue-rotate";
|
||||
import { colorTemperatureEffectDefinition } from "./color-temperature";
|
||||
import { tintEffectDefinition } from "./tint";
|
||||
import { posterizeEffectDefinition } from "./posterize";
|
||||
import { duotoneEffectDefinition } from "./duotone";
|
||||
import { crossProcessEffectDefinition } from "./cross-process";
|
||||
import { pixelateEffectDefinition } from "./pixelate";
|
||||
import { chromaticAberrationEffectDefinition } from "./chromatic-aberration";
|
||||
import { glitchEffectDefinition } from "./glitch";
|
||||
import { waveEffectDefinition } from "./wave";
|
||||
import { mirrorEffectDefinition } from "./mirror";
|
||||
import { kaleidoscopeEffectDefinition } from "./kaleidoscope";
|
||||
import { fisheyeEffectDefinition } from "./fisheye";
|
||||
import { sharpenEffectDefinition } from "./sharpen";
|
||||
import { glowEffectDefinition } from "./glow";
|
||||
import { exposureEffectDefinition } from "./exposure";
|
||||
import { shadowsHighlightsEffectDefinition } from "./shadows-highlights";
|
||||
import { edgeDetectionEffectDefinition } from "./edge-detection";
|
||||
import { embossEffectDefinition } from "./emboss";
|
||||
import { filmGrainEffectDefinition } from "./film-grain";
|
||||
import { halftoneEffectDefinition } from "./halftone";
|
||||
import { scanlinesEffectDefinition } from "./scanlines";
|
||||
import { colorKeyEffectDefinition } from "./color-key";
|
||||
|
||||
const defaultEffects = [
|
||||
blurEffectDefinition,
|
||||
|
|
@ -15,6 +38,29 @@ const defaultEffects = [
|
|||
sepiaEffectDefinition,
|
||||
invertEffectDefinition,
|
||||
vignetteEffectDefinition,
|
||||
hueRotateEffectDefinition,
|
||||
colorTemperatureEffectDefinition,
|
||||
tintEffectDefinition,
|
||||
posterizeEffectDefinition,
|
||||
duotoneEffectDefinition,
|
||||
crossProcessEffectDefinition,
|
||||
pixelateEffectDefinition,
|
||||
chromaticAberrationEffectDefinition,
|
||||
glitchEffectDefinition,
|
||||
waveEffectDefinition,
|
||||
mirrorEffectDefinition,
|
||||
kaleidoscopeEffectDefinition,
|
||||
fisheyeEffectDefinition,
|
||||
sharpenEffectDefinition,
|
||||
glowEffectDefinition,
|
||||
exposureEffectDefinition,
|
||||
shadowsHighlightsEffectDefinition,
|
||||
edgeDetectionEffectDefinition,
|
||||
embossEffectDefinition,
|
||||
filmGrainEffectDefinition,
|
||||
halftoneEffectDefinition,
|
||||
scanlinesEffectDefinition,
|
||||
colorKeyEffectDefinition,
|
||||
];
|
||||
|
||||
export function registerDefaultEffects(): void {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const KALEIDOSCOPE_SHADER = "kaleidoscope";
|
||||
|
||||
export const kaleidoscopeEffectDefinition: EffectDefinition = {
|
||||
type: "kaleidoscope",
|
||||
name: "Kaleidoscope",
|
||||
keywords: [
|
||||
"kaleidoscope",
|
||||
"kaleidoscopic",
|
||||
"radial",
|
||||
"symmetry",
|
||||
"pattern",
|
||||
"mandala",
|
||||
"segments",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "segments",
|
||||
label: "Segments",
|
||||
type: "number",
|
||||
default: 6,
|
||||
min: 2,
|
||||
max: 24,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "angle",
|
||||
label: "Angle",
|
||||
type: "number",
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 360,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: KALEIDOSCOPE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_segments:
|
||||
typeof effectParams.segments === "number"
|
||||
? effectParams.segments
|
||||
: 6,
|
||||
u_angle:
|
||||
typeof effectParams.angle === "number"
|
||||
? effectParams.angle
|
||||
: 0,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const MIRROR_SHADER = "mirror";
|
||||
|
||||
export const mirrorEffectDefinition: EffectDefinition = {
|
||||
type: "mirror",
|
||||
name: "Mirror",
|
||||
keywords: [
|
||||
"mirror",
|
||||
"reflect",
|
||||
"flip",
|
||||
"symmetry",
|
||||
"reflection",
|
||||
"symmetric",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "axis",
|
||||
label: "Axis",
|
||||
type: "number",
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "position",
|
||||
label: "Position",
|
||||
type: "number",
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: MIRROR_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_axis:
|
||||
typeof effectParams.axis === "number"
|
||||
? effectParams.axis
|
||||
: 0,
|
||||
u_position:
|
||||
typeof effectParams.position === "number"
|
||||
? effectParams.position
|
||||
: 50,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const PIXELATE_SHADER = "pixelate";
|
||||
|
||||
export const pixelateEffectDefinition: EffectDefinition = {
|
||||
type: "pixelate",
|
||||
name: "Pixelate",
|
||||
keywords: [
|
||||
"pixelate",
|
||||
"pixel",
|
||||
"mosaic",
|
||||
"mosaic",
|
||||
"censor",
|
||||
"blocky",
|
||||
"pixel art",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "size",
|
||||
label: "Pixel Size",
|
||||
type: "number",
|
||||
default: 10,
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: PIXELATE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_size:
|
||||
typeof effectParams.size === "number"
|
||||
? effectParams.size
|
||||
: 10,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const POSTERIZE_SHADER = "posterize";
|
||||
|
||||
export const posterizeEffectDefinition: EffectDefinition = {
|
||||
type: "posterize",
|
||||
name: "Posterize",
|
||||
keywords: ["posterize", "poster", "levels", "reduce colors", "flat"],
|
||||
params: [
|
||||
{
|
||||
key: "levels",
|
||||
label: "Levels",
|
||||
type: "number",
|
||||
default: 8,
|
||||
min: 2,
|
||||
max: 32,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: POSTERIZE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_levels:
|
||||
typeof effectParams.levels === "number"
|
||||
? effectParams.levels
|
||||
: 8,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const SCANLINES_SHADER = "scanlines";
|
||||
|
||||
export const scanlinesEffectDefinition: EffectDefinition = {
|
||||
type: "scanlines",
|
||||
name: "Scanlines",
|
||||
keywords: [
|
||||
"scanlines",
|
||||
"scan lines",
|
||||
"crt",
|
||||
"tv",
|
||||
"television",
|
||||
"retro",
|
||||
"vhs",
|
||||
"display",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 30,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "count",
|
||||
label: "Line Count",
|
||||
type: "number",
|
||||
default: 240,
|
||||
min: 10,
|
||||
max: 1080,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: SCANLINES_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 30,
|
||||
u_count:
|
||||
typeof effectParams.count === "number"
|
||||
? effectParams.count
|
||||
: 240,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const SHADOWS_HIGHLIGHTS_SHADER = "shadows-highlights";
|
||||
|
||||
export const shadowsHighlightsEffectDefinition: EffectDefinition = {
|
||||
type: "shadows-highlights",
|
||||
name: "Shadows / Highlights",
|
||||
keywords: [
|
||||
"shadows",
|
||||
"highlights",
|
||||
"shadow",
|
||||
"highlight",
|
||||
"tone",
|
||||
"recover",
|
||||
"fill light",
|
||||
"contrast",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "shadows",
|
||||
label: "Shadows",
|
||||
type: "number",
|
||||
default: 0,
|
||||
min: -100,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "highlights",
|
||||
label: "Highlights",
|
||||
type: "number",
|
||||
default: 0,
|
||||
min: -100,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: SHADOWS_HIGHLIGHTS_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_shadows:
|
||||
typeof effectParams.shadows === "number"
|
||||
? effectParams.shadows
|
||||
: 0,
|
||||
u_highlights:
|
||||
typeof effectParams.highlights === "number"
|
||||
? effectParams.highlights
|
||||
: 0,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const SHARPEN_SHADER = "sharpen";
|
||||
|
||||
export const sharpenEffectDefinition: EffectDefinition = {
|
||||
type: "sharpen",
|
||||
name: "Sharpen",
|
||||
keywords: [
|
||||
"sharpen",
|
||||
"sharp",
|
||||
"focus",
|
||||
"detail",
|
||||
"crisp",
|
||||
"enhance",
|
||||
"unsharp mask",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: SHARPEN_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 50,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const TINT_SHADER = "tint";
|
||||
|
||||
function hexToRgb(hex: string): [number, number, number] {
|
||||
const cleaned = hex.replace("#", "");
|
||||
if (cleaned.length === 3) {
|
||||
const r = parseInt(cleaned[0] + cleaned[0], 16) / 255;
|
||||
const g = parseInt(cleaned[1] + cleaned[1], 16) / 255;
|
||||
const b = parseInt(cleaned[2] + cleaned[2], 16) / 255;
|
||||
return [r, g, b];
|
||||
}
|
||||
const r = parseInt(cleaned.substring(0, 2), 16) / 255;
|
||||
const g = parseInt(cleaned.substring(2, 4), 16) / 255;
|
||||
const b = parseInt(cleaned.substring(4, 6), 16) / 255;
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
export const tintEffectDefinition: EffectDefinition = {
|
||||
type: "tint",
|
||||
name: "Tint",
|
||||
keywords: ["tint", "color overlay", "colorize", "wash"],
|
||||
params: [
|
||||
{
|
||||
key: "color",
|
||||
label: "Color",
|
||||
type: "color",
|
||||
default: "#ff0000",
|
||||
},
|
||||
{
|
||||
key: "intensity",
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: TINT_SHADER,
|
||||
uniforms: ({ effectParams }) => {
|
||||
const hex =
|
||||
typeof effectParams.color === "string"
|
||||
? effectParams.color
|
||||
: "#ff0000";
|
||||
const [r, g, b] = hexToRgb(hex);
|
||||
return {
|
||||
u_intensity:
|
||||
typeof effectParams.intensity === "number"
|
||||
? effectParams.intensity
|
||||
: 50,
|
||||
u_tint_color: [r, g, b],
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import type { EffectDefinition } from "@/lib/effects/types";
|
||||
|
||||
export const WAVE_SHADER = "wave";
|
||||
|
||||
export const waveEffectDefinition: EffectDefinition = {
|
||||
type: "wave",
|
||||
name: "Wave",
|
||||
keywords: [
|
||||
"wave",
|
||||
"warp",
|
||||
"undulate",
|
||||
"sinusoidal",
|
||||
"ripple",
|
||||
"sine",
|
||||
"wavy",
|
||||
"wiggle",
|
||||
],
|
||||
params: [
|
||||
{
|
||||
key: "amplitude",
|
||||
label: "Amplitude",
|
||||
type: "number",
|
||||
default: 30,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
{
|
||||
key: "frequency",
|
||||
label: "Frequency",
|
||||
type: "number",
|
||||
default: 5,
|
||||
min: 1,
|
||||
max: 50,
|
||||
step: 1,
|
||||
},
|
||||
],
|
||||
renderer: {
|
||||
passes: [
|
||||
{
|
||||
shader: WAVE_SHADER,
|
||||
uniforms: ({ effectParams }) => ({
|
||||
u_amplitude:
|
||||
typeof effectParams.amplitude === "number"
|
||||
? effectParams.amplitude
|
||||
: 30,
|
||||
u_frequency:
|
||||
typeof effectParams.frequency === "number"
|
||||
? effectParams.frequency
|
||||
: 5,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# NeuralCut — Propuesta de Nuevos Efectos
|
||||
|
||||
## Arquitectura del sistema de efectos
|
||||
|
||||
Los efectos usan **WGSL fragment shaders** via wgpu con un buffer de uniforms genérico:
|
||||
|
||||
```
|
||||
GenericUniformBuffer {
|
||||
resolution: vec2f,
|
||||
scalars: array<vec4f, 2>, // hasta 8 floats
|
||||
vectors: array<vec4f, 2>, // hasta 8 componentes vectoriales
|
||||
}
|
||||
```
|
||||
|
||||
Pipeline: TypeScript definition → `resolveEffectPasses()` → JSON → Rust/WASM → wgpu shader → fullscreen quad → ping-pong entre passes.
|
||||
|
||||
Para agregar un efecto se tocan exactamente 4 archivos:
|
||||
1. `rust/crates/effects/src/shaders/<name>.wgsl`
|
||||
2. `rust/crates/effects/src/pipeline.rs` (SHADER_REGISTRY + pack function)
|
||||
3. `apps/web/src/lib/effects/definitions/<name>.ts`
|
||||
4. `apps/web/src/lib/effects/definitions/index.ts`
|
||||
|
||||
---
|
||||
|
||||
## Efectos actuales (7)
|
||||
|
||||
| Tipo | Nombre | Params |
|
||||
|------|--------|--------|
|
||||
| `blur` | Blur | `intensity` (0-100) |
|
||||
| `brightness-contrast` | Brightness / Contrast | `brightness` (-1 a 1), `contrast` (0-3) |
|
||||
| `grayscale` | Grayscale | `intensity` (0-100) |
|
||||
| `saturation` | Saturation | `saturation` (0-300) |
|
||||
| `sepia` | Sepia | `intensity` (0-100) |
|
||||
| `invert` | Invert | `intensity` (0-100) |
|
||||
| `vignette` | Vignette | `radius` (10-100), `softness` (5-80) |
|
||||
|
||||
---
|
||||
|
||||
## Efectos propuestos
|
||||
|
||||
### Color (single-pass)
|
||||
|
||||
| Efecto | Tipo | Params | Descripción |
|
||||
|--------|------|--------|-------------|
|
||||
| **Hue Rotate** | `hue-rotate` | `angle` (0-360) | Rota el hue en espacio HSL |
|
||||
| **Color Temperature** | `color-temperature` | `temperature` (-100 a 100) | Cálido (naranja) ↔ frío (azul) |
|
||||
| **Tint** | `tint` | `color` (hex), `intensity` (0-100) | Superpone un tinte de color |
|
||||
| **Posterize** | `posterize` | `levels` (2-32) | Reduce niveles de color (efecto póster) |
|
||||
| **Duotone** | `duotone` | `shadowColor`, `highlightColor`, `intensity` | Mapea tonos a dos colores |
|
||||
| **Cross Process** | `cross-process` | `intensity` (0-100) | Simula revelado cruzado analógico |
|
||||
|
||||
### Distorsión (single o multi-pass)
|
||||
|
||||
| Efecto | Tipo | Params | Descripción |
|
||||
|--------|------|--------|-------------|
|
||||
| **Pixelate** | `pixelate` | `size` (1-100) | Agrupa píxeles en bloques |
|
||||
| **Chromatic Aberration** | `chromatic-aberration` | `offset` (0-50) | Separa canales RGB |
|
||||
| **Glitch** | `glitch` | `intensity`, `blockSize` | Corrupción digital + RGB split |
|
||||
| **Wave / Warp** | `wave` | `amplitude`, `frequency` | Distorsión sinusoidal |
|
||||
| **Mirror** | `mirror` | `axis`, `position` | Refleja la mitad de la imagen |
|
||||
| **Kaleidoscope** | `kaleidoscope` | `segments`, `angle` | Simetría radial |
|
||||
| **Fisheye** | `fisheye` | `strength` (-100 a 100) | Distorsión barrel/pincushion |
|
||||
|
||||
### Luz (Glow es multi-pass, el resto single)
|
||||
|
||||
| Efecto | Tipo | Params | Descripción |
|
||||
|--------|------|--------|-------------|
|
||||
| **Sharpen** | `sharpen` | `intensity` (0-100) | Convolución de enfoque 3x3 |
|
||||
| **Glow / Bloom** | `glow` | `intensity`, `threshold`, `radius` | Brillo expandido (2-3 passes) |
|
||||
| **Exposure** | `exposure` | `exposure` (-3 a 3) | Ajuste fotográfico multiplicativo |
|
||||
| **Shadows/Highlights** | `shadows-highlights` | `shadows`, `highlights` | Ajuste independiente |
|
||||
|
||||
### Bordes (single-pass, convolución)
|
||||
|
||||
| Efecto | Tipo | Params | Descripción |
|
||||
|--------|------|--------|-------------|
|
||||
| **Edge Detection** | `edge-detection` | `intensity`, `threshold` | Detección Sobel |
|
||||
| **Emboss** | `emboss` | `intensity`, `angle` | Efecto relieve |
|
||||
|
||||
### Estilo (single-pass)
|
||||
|
||||
| Efecto | Tipo | Params | Descripción |
|
||||
|--------|------|--------|-------------|
|
||||
| **Film Grain** | `film-grain` | `intensity`, `size` | Ruido fotográfico analógico |
|
||||
| **Halftone** | `halftone` | `dotSize`, `angle` | Medio tono |
|
||||
| **Scanlines** | `scanlines` | `intensity`, `count`, `speed` | Líneas CRT |
|
||||
| **Color Key** | `color-key` | `keyColor`, `tolerance`, `softness` | Elimina color de fondo |
|
||||
|
||||
---
|
||||
|
||||
## Prioridad recomendada
|
||||
|
||||
1. **Chromatic Aberration** — muy popular, fácil
|
||||
2. **Hue Rotate** — básico esperado en cualquier editor
|
||||
3. **Color Temperature** — ajuste fundamental
|
||||
4. **Pixelate** — simple, útil para censura
|
||||
5. **Film Grain** — muy pedido
|
||||
6. **Glow/Bloom** — complejo pero visualmente impactante (3 passes)
|
||||
7. **Sharpen** — utilidad básica
|
||||
8. **Posterize** — artístico simple
|
||||
|
|
@ -45,6 +45,121 @@ const SHADER_REGISTRY: &[(&str, &str, UniformPacker)] = &[
|
|||
include_str!("shaders/vignette.wgsl"),
|
||||
pack_vignette as UniformPacker,
|
||||
),
|
||||
(
|
||||
"hue-rotate",
|
||||
include_str!("shaders/hue_rotate.wgsl"),
|
||||
pack_hue_rotate as UniformPacker,
|
||||
),
|
||||
(
|
||||
"color-temperature",
|
||||
include_str!("shaders/color_temperature.wgsl"),
|
||||
pack_color_temperature as UniformPacker,
|
||||
),
|
||||
(
|
||||
"tint",
|
||||
include_str!("shaders/tint.wgsl"),
|
||||
pack_tint as UniformPacker,
|
||||
),
|
||||
(
|
||||
"posterize",
|
||||
include_str!("shaders/posterize.wgsl"),
|
||||
pack_posterize as UniformPacker,
|
||||
),
|
||||
(
|
||||
"duotone",
|
||||
include_str!("shaders/duotone.wgsl"),
|
||||
pack_duotone as UniformPacker,
|
||||
),
|
||||
(
|
||||
"cross-process",
|
||||
include_str!("shaders/cross_process.wgsl"),
|
||||
pack_cross_process as UniformPacker,
|
||||
),
|
||||
(
|
||||
"pixelate",
|
||||
include_str!("shaders/pixelate.wgsl"),
|
||||
pack_pixelate as UniformPacker,
|
||||
),
|
||||
(
|
||||
"chromatic-aberration",
|
||||
include_str!("shaders/chromatic_aberration.wgsl"),
|
||||
pack_chromatic_aberration as UniformPacker,
|
||||
),
|
||||
(
|
||||
"glitch",
|
||||
include_str!("shaders/glitch.wgsl"),
|
||||
pack_glitch as UniformPacker,
|
||||
),
|
||||
(
|
||||
"wave",
|
||||
include_str!("shaders/wave.wgsl"),
|
||||
pack_wave as UniformPacker,
|
||||
),
|
||||
(
|
||||
"mirror",
|
||||
include_str!("shaders/mirror.wgsl"),
|
||||
pack_mirror as UniformPacker,
|
||||
),
|
||||
(
|
||||
"kaleidoscope",
|
||||
include_str!("shaders/kaleidoscope.wgsl"),
|
||||
pack_kaleidoscope as UniformPacker,
|
||||
),
|
||||
(
|
||||
"fisheye",
|
||||
include_str!("shaders/fisheye.wgsl"),
|
||||
pack_fisheye as UniformPacker,
|
||||
),
|
||||
(
|
||||
"sharpen",
|
||||
include_str!("shaders/sharpen.wgsl"),
|
||||
pack_sharpen as UniformPacker,
|
||||
),
|
||||
(
|
||||
"glow",
|
||||
include_str!("shaders/glow.wgsl"),
|
||||
pack_glow as UniformPacker,
|
||||
),
|
||||
(
|
||||
"exposure",
|
||||
include_str!("shaders/exposure.wgsl"),
|
||||
pack_exposure as UniformPacker,
|
||||
),
|
||||
(
|
||||
"shadows-highlights",
|
||||
include_str!("shaders/shadows_highlights.wgsl"),
|
||||
pack_shadows_highlights as UniformPacker,
|
||||
),
|
||||
(
|
||||
"edge-detection",
|
||||
include_str!("shaders/edge_detection.wgsl"),
|
||||
pack_edge_detection as UniformPacker,
|
||||
),
|
||||
(
|
||||
"emboss",
|
||||
include_str!("shaders/emboss.wgsl"),
|
||||
pack_emboss as UniformPacker,
|
||||
),
|
||||
(
|
||||
"film-grain",
|
||||
include_str!("shaders/film_grain.wgsl"),
|
||||
pack_film_grain as UniformPacker,
|
||||
),
|
||||
(
|
||||
"halftone",
|
||||
include_str!("shaders/halftone.wgsl"),
|
||||
pack_halftone as UniformPacker,
|
||||
),
|
||||
(
|
||||
"scanlines",
|
||||
include_str!("shaders/scanlines.wgsl"),
|
||||
pack_scanlines as UniformPacker,
|
||||
),
|
||||
(
|
||||
"color-key",
|
||||
include_str!("shaders/color_key.wgsl"),
|
||||
pack_color_key as UniformPacker,
|
||||
),
|
||||
];
|
||||
|
||||
pub struct ApplyEffectsOptions<'a> {
|
||||
|
|
@ -381,6 +496,30 @@ fn read_vec2_uniform(pass: &EffectPass, uniform: &str) -> Result<[f32; 2], Effec
|
|||
Ok([values[0], values[1]])
|
||||
}
|
||||
|
||||
fn read_vec3_uniform(pass: &EffectPass, uniform: &str) -> Result<[f32; 3], EffectsError> {
|
||||
let Some(value) = pass.uniforms.get(uniform) else {
|
||||
return Err(EffectsError::MissingUniform {
|
||||
shader: pass.shader.clone(),
|
||||
uniform: uniform.to_string(),
|
||||
});
|
||||
};
|
||||
let UniformValue::Vector(values) = value else {
|
||||
return Err(EffectsError::InvalidVectorUniform {
|
||||
shader: pass.shader.clone(),
|
||||
uniform: uniform.to_string(),
|
||||
expected_length: 3,
|
||||
});
|
||||
};
|
||||
if values.len() != 3 {
|
||||
return Err(EffectsError::InvalidVectorUniform {
|
||||
shader: pass.shader.clone(),
|
||||
uniform: uniform.to_string(),
|
||||
expected_length: 3,
|
||||
});
|
||||
}
|
||||
Ok([values[0], values[1], values[2]])
|
||||
}
|
||||
|
||||
fn pack_gaussian_blur(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
|
|
@ -480,3 +619,344 @@ fn pack_vignette(
|
|||
buf.scalars[0][1] = softness;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_hue_rotate(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let angle = read_number_uniform_or(pass, "u_angle", 0.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = angle;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_color_temperature(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let temperature = read_number_uniform_or(pass, "u_temperature", 0.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = temperature;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_tint(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 50.0);
|
||||
let tint_color = read_vec3_uniform(pass, "u_tint_color")?;
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
buf.vectors[0][0] = tint_color[0];
|
||||
buf.vectors[0][1] = tint_color[1];
|
||||
buf.vectors[0][2] = tint_color[2];
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_posterize(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let levels = read_number_uniform_or(pass, "u_levels", 8.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = levels;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_duotone(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 100.0);
|
||||
let shadow_color = read_vec3_uniform(pass, "u_shadow_color")?;
|
||||
let highlight_color = read_vec3_uniform(pass, "u_highlight_color")?;
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
buf.vectors[0][0] = shadow_color[0];
|
||||
buf.vectors[0][1] = shadow_color[1];
|
||||
buf.vectors[0][2] = shadow_color[2];
|
||||
buf.vectors[1][0] = highlight_color[0];
|
||||
buf.vectors[1][1] = highlight_color[1];
|
||||
buf.vectors[1][2] = highlight_color[2];
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_cross_process(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 100.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_pixelate(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let size = read_number_uniform_or(pass, "u_size", 10.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = size;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_chromatic_aberration(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let offset = read_number_uniform_or(pass, "u_offset", 15.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = offset;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_glitch(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 50.0);
|
||||
let block_size = read_number_uniform_or(pass, "u_block_size", 20.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
buf.scalars[0][1] = block_size;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_wave(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let amplitude = read_number_uniform_or(pass, "u_amplitude", 30.0);
|
||||
let frequency = read_number_uniform_or(pass, "u_frequency", 5.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = amplitude;
|
||||
buf.scalars[0][1] = frequency;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_mirror(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let axis = read_number_uniform_or(pass, "u_axis", 0.0);
|
||||
let position = read_number_uniform_or(pass, "u_position", 50.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = axis;
|
||||
buf.scalars[0][1] = position;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_kaleidoscope(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let segments = read_number_uniform_or(pass, "u_segments", 6.0);
|
||||
let angle = read_number_uniform_or(pass, "u_angle", 0.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = segments;
|
||||
buf.scalars[0][1] = angle;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_fisheye(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let strength = read_number_uniform_or(pass, "u_strength", 50.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = strength;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_sharpen(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 50.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_glow(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 50.0);
|
||||
let threshold = read_number_uniform_or(pass, "u_threshold", 70.0);
|
||||
let radius = read_number_uniform_or(pass, "u_radius", 4.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
buf.scalars[0][1] = threshold;
|
||||
buf.scalars[1][0] = radius;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_exposure(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let exposure = read_number_uniform_or(pass, "u_exposure", 0.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = exposure;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_shadows_highlights(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let shadows = read_number_uniform_or(pass, "u_shadows", 0.0);
|
||||
let highlights = read_number_uniform_or(pass, "u_highlights", 0.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = shadows;
|
||||
buf.scalars[0][1] = highlights;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_edge_detection(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 100.0);
|
||||
let threshold = read_number_uniform_or(pass, "u_threshold", 10.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
buf.scalars[0][1] = threshold;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_emboss(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 50.0);
|
||||
let angle = read_number_uniform_or(pass, "u_angle", 135.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
buf.scalars[0][1] = angle;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_film_grain(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 30.0);
|
||||
let size = read_number_uniform_or(pass, "u_size", 1.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
buf.scalars[0][1] = size;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_halftone(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let dot_size = read_number_uniform_or(pass, "u_dot_size", 4.0);
|
||||
let angle = read_number_uniform_or(pass, "u_angle", 45.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = dot_size;
|
||||
buf.scalars[0][1] = angle;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_scanlines(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let intensity = read_number_uniform_or(pass, "u_intensity", 30.0);
|
||||
let count = read_number_uniform_or(pass, "u_count", 240.0);
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = intensity;
|
||||
buf.scalars[0][1] = count;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn pack_color_key(
|
||||
pass: &EffectPass,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<GenericUniformBuffer, EffectsError> {
|
||||
let tolerance = read_number_uniform_or(pass, "u_tolerance", 40.0);
|
||||
let softness = read_number_uniform_or(pass, "u_softness", 10.0);
|
||||
let key_color = read_vec3_uniform(pass, "u_key_color")?;
|
||||
|
||||
let mut buf = GenericUniformBuffer::default();
|
||||
buf.resolution = [width as f32, height as f32];
|
||||
buf.scalars[0][0] = tolerance;
|
||||
buf.scalars[0][1] = softness;
|
||||
buf.vectors[0][0] = key_color[0];
|
||||
buf.vectors[0][1] = key_color[1];
|
||||
buf.vectors[0][2] = key_color[2];
|
||||
Ok(buf)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let offset = uniforms.scalars[0].x / 100.0;
|
||||
let direction = normalize(input.tex_coord - vec2f(0.5));
|
||||
let displacement = direction * offset * 0.02;
|
||||
|
||||
let r = textureSample(input_texture, input_sampler, input.tex_coord + displacement).r;
|
||||
let g = textureSample(input_texture, input_sampler, input.tex_coord).g;
|
||||
let b = textureSample(input_texture, input_sampler, input.tex_coord - displacement).b;
|
||||
let a = textureSample(input_texture, input_sampler, input.tex_coord).a;
|
||||
|
||||
return vec4f(r, g, b, a);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let tolerance = uniforms.scalars[0].x / 100.0;
|
||||
let softness = uniforms.scalars[0].y / 100.0;
|
||||
let key_color = uniforms.vectors[0].xyz;
|
||||
|
||||
let diff = distance(color.rgb, key_color);
|
||||
|
||||
let edge = softness + 0.001;
|
||||
let alpha = 1.0 - smoothstep(tolerance - edge, tolerance + edge, diff);
|
||||
|
||||
return vec4f(color.rgb, color.a * alpha);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let temperature = uniforms.scalars[0].x;
|
||||
|
||||
let warm = vec3f(1.0, 0.85, 0.65);
|
||||
let cool = vec3f(0.65, 0.85, 1.0);
|
||||
let tint = select(
|
||||
mix(vec3f(0.0), cool, -temperature),
|
||||
mix(vec3f(0.0), warm, temperature),
|
||||
temperature >= 0.0
|
||||
);
|
||||
|
||||
let result = color.rgb + tint * 0.3;
|
||||
|
||||
return vec4f(clamp(result, vec3f(0.0), vec3f(1.0)), color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
|
||||
let r = min(color.r * 1.2 + 0.05, 1.0);
|
||||
let g = max(color.g * 0.8 - 0.02, 0.0);
|
||||
let b = min(color.b * 1.4 + 0.1, 1.0);
|
||||
|
||||
let contrast_r = (r - 0.5) * 1.2 + 0.5;
|
||||
let contrast_g = (g - 0.5) * 1.1 + 0.5;
|
||||
let contrast_b = (b - 0.5) * 1.3 + 0.5;
|
||||
|
||||
let cross = vec3f(clamp(contrast_r, 0.0, 1.0), clamp(contrast_g, 0.0, 1.0), clamp(contrast_b, 0.0, 1.0));
|
||||
let result = mix(color.rgb, cross, intensity);
|
||||
|
||||
return vec4f(result, color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
|
||||
let shadow = vec3f(uniforms.vectors[0].x, uniforms.vectors[0].y, uniforms.vectors[0].z);
|
||||
let highlight = vec3f(uniforms.vectors[1].x, uniforms.vectors[1].y, uniforms.vectors[1].z);
|
||||
|
||||
let luminance = dot(color.rgb, vec3f(0.2126, 0.7152, 0.0722));
|
||||
let duotone = mix(shadow, highlight, luminance);
|
||||
let result = mix(color.rgb, duotone, intensity);
|
||||
|
||||
return vec4f(clamp(result, vec3f(0.0), vec3f(1.0)), color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
fn luminance(color: vec3f) -> f32 {
|
||||
return dot(color, vec3f(0.2126, 0.7152, 0.0722));
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
let threshold = uniforms.scalars[0].y / 100.0;
|
||||
let texel_size = vec2f(1.0, 1.0) / uniforms.resolution;
|
||||
|
||||
let tl = luminance(textureSample(input_texture, input_sampler, input.tex_coord + vec2f(-texel_size.x, -texel_size.y)).rgb);
|
||||
let t = luminance(textureSample(input_texture, input_sampler, input.tex_coord + vec2f( 0.0, -texel_size.y)).rgb);
|
||||
let tr = luminance(textureSample(input_texture, input_sampler, input.tex_coord + vec2f( texel_size.x, -texel_size.y)).rgb);
|
||||
let l = luminance(textureSample(input_texture, input_sampler, input.tex_coord + vec2f(-texel_size.x, 0.0)).rgb);
|
||||
let r = luminance(textureSample(input_texture, input_sampler, input.tex_coord + vec2f( texel_size.x, 0.0)).rgb);
|
||||
let bl = luminance(textureSample(input_texture, input_sampler, input.tex_coord + vec2f(-texel_size.x, texel_size.y)).rgb);
|
||||
let b = luminance(textureSample(input_texture, input_sampler, input.tex_coord + vec2f( 0.0, texel_size.y)).rgb);
|
||||
let br = luminance(textureSample(input_texture, input_sampler, input.tex_coord + vec2f( texel_size.x, texel_size.y)).rgb);
|
||||
|
||||
let gx = -tl - 2.0 * l - bl + tr + 2.0 * r + br;
|
||||
let gy = -tl - 2.0 * t - tr + bl + 2.0 * b + br;
|
||||
|
||||
let edge = sqrt(gx * gx + gy * gy);
|
||||
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let edge_mask = smoothstep(threshold, threshold + 0.1, edge);
|
||||
|
||||
let result = mix(color.rgb, vec3f(edge_mask), intensity);
|
||||
|
||||
return vec4f(result, color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
let angle = uniforms.scalars[0].y * 3.141592653589793 / 180.0;
|
||||
let texel_size = vec2f(1.0, 1.0) / uniforms.resolution;
|
||||
|
||||
let dx = cos(angle) * texel_size.x;
|
||||
let dy = sin(angle) * texel_size.y;
|
||||
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let back = textureSample(input_texture, input_sampler, input.tex_coord + vec2f(-dx, -dy));
|
||||
let front = textureSample(input_texture, input_sampler, input.tex_coord + vec2f( dx, dy));
|
||||
|
||||
let emboss = (front.rgb - back.rgb + 0.5) * intensity + color.rgb * (1.0 - intensity);
|
||||
|
||||
return vec4f(clamp(emboss, vec3f(0.0), vec3f(1.0)), color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let exposure = uniforms.scalars[0].x;
|
||||
|
||||
let result = color.rgb * pow(2.0, exposure);
|
||||
|
||||
return vec4f(clamp(result, vec3f(0.0), vec3f(1.0)), color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
fn hash21(p: vec2f) -> f32 {
|
||||
var p3 = fract(vec3f(p.x, p.y, p.x) * vec3f(0.1031, 0.1030, 0.0973));
|
||||
p3 = p3 + dot(p3, vec3f(p3.y, p3.z, p3.x) + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
let size = max(uniforms.scalars[0].y, 1.0);
|
||||
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
|
||||
let pixel_pos = input.tex_coord * uniforms.resolution;
|
||||
let grain_uv = floor(pixel_pos / size);
|
||||
let noise = hash21(grain_uv) * 2.0 - 1.0;
|
||||
|
||||
let grain_amount = intensity * 0.3;
|
||||
let result = color.rgb + vec3f(noise * grain_amount);
|
||||
|
||||
return vec4f(clamp(result, vec3f(0.0), vec3f(1.0)), color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let strength = uniforms.scalars[0].x / 100.0;
|
||||
|
||||
let centered = input.tex_coord - vec2f(0.5);
|
||||
let r = length(centered);
|
||||
|
||||
if (r < 0.001) {
|
||||
return textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
}
|
||||
|
||||
let normalized_r = r * 2.0;
|
||||
|
||||
var distorted_r: f32;
|
||||
if (strength >= 0.0) {
|
||||
distorted_r = pow(normalized_r, 1.0 + strength * 1.5) * 0.5;
|
||||
} else {
|
||||
let abs_s = abs(strength);
|
||||
distorted_r = pow(normalized_r, 1.0 / (1.0 + abs_s * 1.5)) * 0.5;
|
||||
}
|
||||
|
||||
let theta = atan2(centered.y, centered.x);
|
||||
let distorted_uv = vec2f(0.5) + vec2f(cos(theta), sin(theta)) * distorted_r;
|
||||
|
||||
if (distorted_uv.x < 0.0 || distorted_uv.x > 1.0 || distorted_uv.y < 0.0 || distorted_uv.y > 1.0) {
|
||||
return vec4f(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
return textureSample(input_texture, input_sampler, distorted_uv);
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
fn hash21(p: vec2f) -> f32 {
|
||||
var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
|
||||
p3 = p3 + dot(p3, vec3f(p3.y, p3.z, p3.x) + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
let block_size = max(uniforms.scalars[0].y, 1.0);
|
||||
|
||||
let block_uv = floor(input.tex_coord * (uniforms.resolution / block_size));
|
||||
let block_hash = hash21(block_uv);
|
||||
|
||||
var uv = input.tex_coord;
|
||||
|
||||
let shift_threshold = 1.0 - intensity * 0.8;
|
||||
if (block_hash > shift_threshold) {
|
||||
let shift = (hash21(block_uv + vec2f(42.0, 0.0)) - 0.5) * intensity * 0.15;
|
||||
uv = vec2f(uv.x + shift, uv.y);
|
||||
}
|
||||
|
||||
let rgb_shift = intensity * 0.008;
|
||||
let r = textureSample(input_texture, input_sampler, vec2f(uv.x + rgb_shift, uv.y)).r;
|
||||
let g = textureSample(input_texture, input_sampler, uv).g;
|
||||
let b = textureSample(input_texture, input_sampler, vec2f(uv.x - rgb_shift, uv.y)).b;
|
||||
let a = textureSample(input_texture, input_sampler, uv).a;
|
||||
|
||||
return vec4f(r, g, b, a);
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let texel_size = vec2f(1.0, 1.0) / uniforms.resolution;
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
let threshold = uniforms.scalars[0].y / 100.0;
|
||||
let radius = uniforms.scalars[1].x;
|
||||
|
||||
let center = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let luminance = dot(center.rgb, vec3f(0.2126, 0.7152, 0.0722));
|
||||
|
||||
if (luminance < threshold) {
|
||||
return center;
|
||||
}
|
||||
|
||||
let samples = 16.0;
|
||||
var bloom = vec3f(0.0);
|
||||
let step = radius / samples;
|
||||
|
||||
for (var i = -samples; i <= samples; i = i + 1.0) {
|
||||
for (var j = -samples; j <= samples; j = j + 1.0) {
|
||||
let offset = vec2f(i, j) * step * texel_size;
|
||||
let sample_color = textureSample(input_texture, input_sampler, input.tex_coord + offset);
|
||||
let sample_lum = dot(sample_color.rgb, vec3f(0.2126, 0.7152, 0.0722));
|
||||
if (sample_lum >= threshold) {
|
||||
bloom = bloom + sample_color.rgb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let total = (samples * 2.0 + 1.0) * (samples * 2.0 + 1.0);
|
||||
bloom = bloom / total;
|
||||
|
||||
let result = mix(center.rgb, center.rgb + bloom * intensity, intensity);
|
||||
|
||||
return vec4f(clamp(result, vec3f(0.0), vec3f(1.0)), center.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
const PI: f32 = 3.141592653589793;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let dot_size = max(uniforms.scalars[0].x, 1.0);
|
||||
let angle = uniforms.scalars[0].y * PI / 180.0;
|
||||
|
||||
let aspect = uniforms.resolution.x / uniforms.resolution.y;
|
||||
let grid_uv = vec2f(
|
||||
input.tex_coord.x * aspect,
|
||||
input.tex_coord.y,
|
||||
);
|
||||
|
||||
let rotated_x = grid_uv.x * cos(angle) - grid_uv.y * sin(angle);
|
||||
let rotated_y = grid_uv.x * sin(angle) + grid_uv.y * cos(angle);
|
||||
|
||||
let grid_spacing = dot_size * 2.0 / uniforms.resolution.y;
|
||||
let cell = vec2f(
|
||||
floor(rotated_x / grid_spacing),
|
||||
floor(rotated_y / grid_spacing),
|
||||
);
|
||||
|
||||
let cell_center = (cell + vec2f(0.5)) * grid_spacing;
|
||||
|
||||
let inv_cos = cos(-angle);
|
||||
let inv_sin = sin(-angle);
|
||||
let sample_uv = vec2f(
|
||||
cell_center.x * inv_cos - cell_center.y * inv_sin,
|
||||
cell_center.x * inv_sin + cell_center.y * inv_cos,
|
||||
);
|
||||
|
||||
let sample_coord = vec2f(sample_uv.x / aspect, sample_uv.y);
|
||||
|
||||
if (sample_coord.x < 0.0 || sample_coord.x > 1.0 || sample_coord.y < 0.0 || sample_coord.y > 1.0) {
|
||||
return vec4f(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
let color = textureSample(input_texture, input_sampler, sample_coord);
|
||||
let luminance = dot(color.rgb, vec3f(0.2126, 0.7152, 0.0722));
|
||||
|
||||
let dist = length(vec2f(rotated_x, rotated_y) - cell_center);
|
||||
let max_radius = grid_spacing * 0.5;
|
||||
let dot_radius = max_radius * (1.0 - luminance);
|
||||
|
||||
let mask = 1.0 - smoothstep(dot_radius - 0.5, dot_radius + 0.5, dist);
|
||||
|
||||
return vec4f(color.rgb * mask, color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
fn rgb_to_hsl(c: vec3f) -> vec3f {
|
||||
let max_c = max(max(c.r, c.g), c.b);
|
||||
let min_c = min(min(c.r, c.g), c.b);
|
||||
let l = (max_c + min_c) * 0.5;
|
||||
var h = 0.0;
|
||||
var s = 0.0;
|
||||
if (max_c != min_c) {
|
||||
let d = max_c - min_c;
|
||||
if (l > 0.5) {
|
||||
s = d / (2.0 - max_c - min_c);
|
||||
} else {
|
||||
s = d / (max_c + min_c);
|
||||
}
|
||||
if (max_c == c.r) {
|
||||
h = (c.g - c.b) / d + select(6.0, 0.0, c.g >= c.b);
|
||||
} else if (max_c == c.g) {
|
||||
h = (c.b - c.r) / d + 2.0;
|
||||
} else {
|
||||
h = (c.r - c.g) / d + 4.0;
|
||||
}
|
||||
h = h / 6.0;
|
||||
}
|
||||
return vec3f(h, s, l);
|
||||
}
|
||||
|
||||
fn hsl_to_rgb(hsl: vec3f) -> vec3f {
|
||||
if (hsl.y == 0.0) {
|
||||
return vec3f(hsl.z);
|
||||
}
|
||||
let h = hsl.x;
|
||||
let s = hsl.y;
|
||||
let l = hsl.z;
|
||||
let q = select(l * (1.0 + s), l + s - l * s, l < 0.5);
|
||||
let p = 2.0 * l - q;
|
||||
let r = hue_to_rgb(p, q, h + 1.0 / 3.0);
|
||||
let g = hue_to_rgb(p, q, h);
|
||||
let b = hue_to_rgb(p, q, h - 1.0 / 3.0);
|
||||
return vec3f(r, g, b);
|
||||
}
|
||||
|
||||
fn hue_to_rgb(p: f32, q: f32, t_in: f32) -> f32 {
|
||||
var t = t_in;
|
||||
if (t < 0.0) { t += 1.0; }
|
||||
if (t > 1.0) { t -= 1.0; }
|
||||
if (t < 1.0 / 6.0) { return p + (q - p) * 6.0 * t; }
|
||||
if (t < 1.0 / 2.0) { return q; }
|
||||
if (t < 2.0 / 3.0) { return p + (q - p) * (2.0 / 3.0 - t) * 6.0; }
|
||||
return p;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let angle = uniforms.scalars[0].x;
|
||||
|
||||
let hsl = rgb_to_hsl(color.rgb);
|
||||
let rotated_h = fract(hsl.x + angle);
|
||||
let rotated = hsl_to_rgb(vec3f(rotated_h, hsl.y, hsl.z));
|
||||
|
||||
return vec4f(rotated, color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
const PI: f32 = 3.141592653589793;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let segments = max(uniforms.scalars[0].x, 2.0);
|
||||
let angle_offset = uniforms.scalars[0].y * PI / 180.0;
|
||||
|
||||
let centered = input.tex_coord - vec2f(0.5);
|
||||
let r = length(centered);
|
||||
var theta = atan2(centered.y, centered.x) + angle_offset;
|
||||
|
||||
let segment_angle = 2.0 * PI / segments;
|
||||
theta = abs(mod(theta, segment_angle) - segment_angle * 0.5);
|
||||
|
||||
let uv = vec2f(0.5) + vec2f(cos(theta), sin(theta)) * r;
|
||||
|
||||
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
|
||||
return vec4f(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
return textureSample(input_texture, input_sampler, uv);
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let axis = uniforms.scalars[0].x;
|
||||
let position = uniforms.scalars[0].y / 100.0;
|
||||
|
||||
var uv = input.tex_coord;
|
||||
|
||||
if (axis < 0.5) {
|
||||
if (uv.x > position) {
|
||||
uv.x = position - (uv.x - position);
|
||||
}
|
||||
} else {
|
||||
if (uv.y > position) {
|
||||
uv.y = position - (uv.y - position);
|
||||
}
|
||||
}
|
||||
|
||||
uv = clamp(uv, vec2f(0.0), vec2f(1.0));
|
||||
|
||||
return textureSample(input_texture, input_sampler, uv);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let pixel_size = max(uniforms.scalars[0].x, 1.0);
|
||||
let aspect = uniforms.resolution.x / uniforms.resolution.y;
|
||||
|
||||
let cell_x = pixel_size / uniforms.resolution.x;
|
||||
let cell_y = pixel_size / uniforms.resolution.y;
|
||||
|
||||
let snapped_x = floor(input.tex_coord.x / cell_x) * cell_x + cell_x * 0.5;
|
||||
let snapped_y = floor(input.tex_coord.y / cell_y) * cell_y + cell_y * 0.5;
|
||||
|
||||
let snapped_uv = vec2f(clamp(snapped_x, 0.0, 1.0), clamp(snapped_y, 0.0, 1.0));
|
||||
|
||||
return textureSample(input_texture, input_sampler, snapped_uv);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let levels = uniforms.scalars[0].x;
|
||||
|
||||
if (levels < 2.0) {
|
||||
return color;
|
||||
}
|
||||
|
||||
let step = 1.0 / (levels - 1.0);
|
||||
let r = round(color.r * (levels - 1.0)) * step;
|
||||
let g = round(color.g * (levels - 1.0)) * step;
|
||||
let b = round(color.b * (levels - 1.0)) * step;
|
||||
|
||||
return vec4f(r, g, b, color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
let count = max(uniforms.scalars[0].y, 1.0);
|
||||
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
|
||||
let line_y = input.tex_coord.y * count;
|
||||
let line = smoothstep(0.4, 0.5, fract(line_y));
|
||||
|
||||
let darkness = 1.0 - line * intensity;
|
||||
|
||||
return vec4f(color.rgb * darkness, color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let shadows_adj = uniforms.scalars[0].x / 100.0;
|
||||
let highlights_adj = uniforms.scalars[0].y / 100.0;
|
||||
|
||||
let luminance = dot(color.rgb, vec3f(0.2126, 0.7152, 0.0722));
|
||||
|
||||
let shadow_weight = 1.0 - smoothstep(0.0, 0.5, luminance);
|
||||
let highlight_weight = smoothstep(0.5, 1.0, luminance);
|
||||
|
||||
let shadow_correction = shadow_weight * shadows_adj * 0.5;
|
||||
let highlight_correction = highlight_weight * highlights_adj * 0.5;
|
||||
|
||||
let shadow_result = color.rgb + shadow_correction;
|
||||
let highlight_result = shadow_result - highlight_correction;
|
||||
|
||||
return vec4f(clamp(highlight_result, vec3f(0.0), vec3f(1.0)), color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
let texel_size = vec2f(1.0, 1.0) / uniforms.resolution;
|
||||
|
||||
let center = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let top = textureSample(input_texture, input_sampler, input.tex_coord + vec2f(0.0, -texel_size.y));
|
||||
let bottom = textureSample(input_texture, input_sampler, input.tex_coord + vec2f(0.0, texel_size.y));
|
||||
let left = textureSample(input_texture, input_sampler, input.tex_coord + vec2f(-texel_size.x, 0.0));
|
||||
let right = textureSample(input_texture, input_sampler, input.tex_coord + vec2f( texel_size.x, 0.0));
|
||||
|
||||
let neighbors = top.rgb + bottom.rgb + left.rgb + right.rgb;
|
||||
let sharpened = center.rgb * 5.0 - neighbors;
|
||||
|
||||
let result = mix(center.rgb, sharpened, intensity);
|
||||
|
||||
return vec4f(clamp(result, vec3f(0.0), vec3f(1.0)), center.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let color = textureSample(input_texture, input_sampler, input.tex_coord);
|
||||
let intensity = uniforms.scalars[0].x / 100.0;
|
||||
let tint_r = uniforms.vectors[0].x;
|
||||
let tint_g = uniforms.vectors[0].y;
|
||||
let tint_b = uniforms.vectors[0].z;
|
||||
|
||||
let tint_color = vec3f(tint_r, tint_g, tint_b);
|
||||
let luminance = dot(color.rgb, vec3f(0.2126, 0.7152, 0.0722));
|
||||
let tinted = mix(vec3f(luminance), tint_color, 0.5);
|
||||
let result = mix(color.rgb, tinted, intensity);
|
||||
|
||||
return vec4f(clamp(result, vec3f(0.0), vec3f(1.0)), color.a);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) tex_coord: vec2f,
|
||||
}
|
||||
|
||||
struct EffectUniforms {
|
||||
resolution: vec2f,
|
||||
_pad_res: vec2f,
|
||||
scalars: array<vec4f, 2>,
|
||||
vectors: array<vec4f, 2>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var input_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1) var input_sampler: sampler;
|
||||
@group(1) @binding(0) var<uniform> uniforms: EffectUniforms;
|
||||
|
||||
@fragment
|
||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
let amplitude = uniforms.scalars[0].x / 100.0;
|
||||
let frequency = uniforms.scalars[0].y;
|
||||
|
||||
let wave_x = sin(input.tex_coord.y * frequency * 6.28318530718) * amplitude * 0.02;
|
||||
let wave_y = cos(input.tex_coord.x * frequency * 6.28318530718) * amplitude * 0.02;
|
||||
|
||||
let distorted_uv = vec2f(
|
||||
clamp(input.tex_coord.x + wave_x, 0.0, 1.0),
|
||||
clamp(input.tex_coord.y + wave_y, 0.0, 1.0),
|
||||
);
|
||||
|
||||
return textureSample(input_texture, input_sampler, distorted_uv);
|
||||
}
|
||||
Loading…
Reference in New Issue