From f77c33860817ae09d9988ef819446a8e0994a272 Mon Sep 17 00:00:00 2001 From: Abe Pazos Date: Mon, 7 Jul 2025 16:25:31 +0200 Subject: [PATCH] [orx-fx] Directional blur tweaks (#357) --- .../commonMain/kotlin/blur/DirectionalBlur.kt | 28 ++++++---- .../kotlin/distort/DirectionalDisplace.kt | 55 +++++++++++++++++++ .../jvmDemo/kotlin/DemoDirectionalBlur01.kt | 14 +++++ .../kotlin/DemoDirectionalDisplace01.kt | 50 +++++++++++++++++ .../kotlin/DemoDirectionalDisplace02.kt | 45 +++++++++++++++ .../shaders/glsl/blur/directional-blur.frag | 23 +++----- .../glsl/distort/directional-displace.frag | 37 +++++++++++++ 7 files changed, 227 insertions(+), 25 deletions(-) create mode 100644 orx-fx/src/commonMain/kotlin/distort/DirectionalDisplace.kt create mode 100644 orx-fx/src/jvmDemo/kotlin/DemoDirectionalDisplace01.kt create mode 100644 orx-fx/src/jvmDemo/kotlin/DemoDirectionalDisplace02.kt create mode 100644 orx-fx/src/shaders/glsl/distort/directional-displace.frag diff --git a/orx-fx/src/commonMain/kotlin/blur/DirectionalBlur.kt b/orx-fx/src/commonMain/kotlin/blur/DirectionalBlur.kt index 7f605b28..d723db15 100644 --- a/orx-fx/src/commonMain/kotlin/blur/DirectionalBlur.kt +++ b/orx-fx/src/commonMain/kotlin/blur/DirectionalBlur.kt @@ -2,14 +2,14 @@ package org.openrndr.extra.fx.blur -import org.openrndr.draw.* + +import org.openrndr.draw.Filter2to1 import org.openrndr.extra.fx.fx_directional_blur import org.openrndr.extra.fx.mppFilterShader import org.openrndr.extra.parameters.BooleanParameter import org.openrndr.extra.parameters.Description import org.openrndr.extra.parameters.DoubleParameter import org.openrndr.extra.parameters.IntParameter -import org.openrndr.shape.Rectangle /** * Directional blur filter. Takes source image and direction buffer inputs @@ -24,13 +24,13 @@ class DirectionalBlur : Filter2to1(mppFilterShader(fx_directional_blur, "directi var centerWindow: Boolean by parameters /** - * The sample window, default is 5 + * The sample window: how many samples to mix. The default is 5 */ @IntParameter("window size", 1, 25) var window: Int by parameters /** - * Spread multiplier, default is 1.0 + * Spread multiplier: the distance in pixels between sampled pixels. The default is 1.0 */ @DoubleParameter("kernel spread", 1.0, 4.0) var spread: Double by parameters @@ -42,12 +42,22 @@ class DirectionalBlur : Filter2to1(mppFilterShader(fx_directional_blur, "directi var gain: Double by parameters /** - * Should filter use directions perpendicular to those in the direction buffer? + * Should filter use directions perpendicular to those in the direction buffer? default is false */ @BooleanParameter("perpendicular") var perpendicular: Boolean by parameters + /** + * Wrap around left and right edges + */ + @BooleanParameter("wrapX") + var wrapX: Boolean by parameters + /** + * Wrap around top and bottom edges + */ + @BooleanParameter("wrapY") + var wrapY: Boolean by parameters init { window = 5 @@ -55,11 +65,7 @@ class DirectionalBlur : Filter2to1(mppFilterShader(fx_directional_blur, "directi gain = 1.0 perpendicular = false centerWindow = false - } - - override fun apply(source: Array, target: Array, clip: Rectangle?) { - parameters["wrapX"] = false - parameters["wrapY"] = false - super.apply(source, target, clip) + wrapX = false + wrapY = false } } \ No newline at end of file diff --git a/orx-fx/src/commonMain/kotlin/distort/DirectionalDisplace.kt b/orx-fx/src/commonMain/kotlin/distort/DirectionalDisplace.kt new file mode 100644 index 00000000..cba6e9b8 --- /dev/null +++ b/orx-fx/src/commonMain/kotlin/distort/DirectionalDisplace.kt @@ -0,0 +1,55 @@ +@file:Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED") + +package org.openrndr.extra.fx.distort + +import org.openrndr.draw.Filter2to1 +import org.openrndr.extra.fx.fx_directional_displace +import org.openrndr.extra.fx.mppFilterShader +import org.openrndr.extra.parameters.BooleanParameter +import org.openrndr.extra.parameters.Description +import org.openrndr.extra.parameters.DoubleParameter + +/** + * Directional displace filter. Takes source image and direction buffer inputs + */ +@Description("Directional displace") +class DirectionalDisplace : Filter2to1(mppFilterShader(fx_directional_displace, "directional-displace")) { + + /** + * The distance of the sampled pixel. The default is 1.0 + */ + @DoubleParameter("distance", 1.0, 1000.0) + var distance: Double by parameters + + /** + * Post-displace gain, default is 1.0 + */ + @DoubleParameter("gain", 0.0, 4.0) + var gain: Double by parameters + + /** + * Should filter use directions perpendicular to those in the direction buffer? default is false + */ + @BooleanParameter("perpendicular") + var perpendicular: Boolean by parameters + + /** + * Wrap around left and right edges + */ + @BooleanParameter("wrapX") + var wrapX: Boolean by parameters + + /** + * Wrap around top and bottom edges + */ + @BooleanParameter("wrapY") + var wrapY: Boolean by parameters + + init { + distance = 1.0 + gain = 1.0 + perpendicular = false + wrapX = false + wrapY = false + } +} \ No newline at end of file diff --git a/orx-fx/src/jvmDemo/kotlin/DemoDirectionalBlur01.kt b/orx-fx/src/jvmDemo/kotlin/DemoDirectionalBlur01.kt index 1ff50916..d305b18f 100644 --- a/orx-fx/src/jvmDemo/kotlin/DemoDirectionalBlur01.kt +++ b/orx-fx/src/jvmDemo/kotlin/DemoDirectionalBlur01.kt @@ -6,6 +6,19 @@ import org.openrndr.math.smoothstep import kotlin.math.cos import kotlin.math.sin +/** + * Demonstrates how to use [DirectionalBlur] by creating a `direction` + * ColorBuffer in which the red and green components of the pixels point + * in various directions where to sample pixels from. All the pixel colors + * of the ColorBuffer are set one by one using two for loops. + * + * Note the FLOAT32 color type of the buffer to allow for negative values, + * so sampling can happen from every direction. + * + * Every 60 animation frames the `centerWindow` property is toggled + * between true and false to demonstrate how the result changes. + * + */ fun main() = application { program { val db = DirectionalBlur() @@ -31,6 +44,7 @@ fun main() = application { drawer.image(image) } db.window = 10 + db.centerWindow = frameCount % 120 > 60 db.apply(arrayOf(rt.colorBuffer(0), direction), blurred) drawer.image(blurred) } diff --git a/orx-fx/src/jvmDemo/kotlin/DemoDirectionalDisplace01.kt b/orx-fx/src/jvmDemo/kotlin/DemoDirectionalDisplace01.kt new file mode 100644 index 00000000..fa65dbe5 --- /dev/null +++ b/orx-fx/src/jvmDemo/kotlin/DemoDirectionalDisplace01.kt @@ -0,0 +1,50 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.color.rgb +import org.openrndr.draw.ColorType +import org.openrndr.draw.colorBuffer +import org.openrndr.draw.loadImage +import org.openrndr.drawImage +import org.openrndr.extra.fx.distort.DirectionalDisplace +import org.openrndr.extra.noise.simplex +import org.openrndr.extra.shapes.primitives.grid +import kotlin.math.cos + +/** + * Demonstrate how to use [DirectionalDisplace]. + * + * The direction map is populated using `drawImage` instead of + * pixel by pixel. A grid of circles is drawn, each circle with a + * color based on simplex noise. The R and G channels of the colors + * control the direction of the sampling. By animating the sampling + * distance the result oscillates between no-effect and a noticeable one. + */ +fun main() = application { + program { + val displace = DirectionalDisplace() + + val displaced = colorBuffer(width, height) + val direction = drawImage(width, height, type = ColorType.FLOAT32) { + clear(ColorRGBa.BLACK) + bounds.grid(32, 24).flatten().forEach { + fill = rgb( + simplex(133, it.center * 0.004), + simplex(197, it.center * 0.004), + 0.0 + ) + stroke = null + + //rectangle(it) + circle(it.center, 8.0) + } + } + val image = loadImage("demo-data/images/image-001.png") + extend { + displace.distance = 100.0 + 100.0 * cos(seconds) + displace.wrapX = true + displace.wrapX = true + displace.apply(arrayOf(image, direction), displaced) + drawer.image(displaced) + } + } +} \ No newline at end of file diff --git a/orx-fx/src/jvmDemo/kotlin/DemoDirectionalDisplace02.kt b/orx-fx/src/jvmDemo/kotlin/DemoDirectionalDisplace02.kt new file mode 100644 index 00000000..50cc280d --- /dev/null +++ b/orx-fx/src/jvmDemo/kotlin/DemoDirectionalDisplace02.kt @@ -0,0 +1,45 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.color.rgb +import org.openrndr.draw.ColorType +import org.openrndr.draw.colorBuffer +import org.openrndr.draw.loadImage +import org.openrndr.drawImage +import org.openrndr.extra.fx.distort.DirectionalDisplace +import org.openrndr.math.Polar + +/** + * Demonstrate how to use [DirectionalDisplace]. + * + * The program draws 12 overlapping translucent circles on the + * `direction` color buffer to produce new color combinations + * on the overlapping areas. Those colors specify where the + * `DirectionalDisplace` effect will sample pixels from. + */ + +fun main() = application { + program { + val displace = DirectionalDisplace() + + val displaced = colorBuffer(width, height) + val direction = drawImage(width, height, type = ColorType.FLOAT32) { + clear(ColorRGBa.BLACK) + for(x in 0 until 6) { + val offset = Polar(x * 60.0).cartesian + fill = rgb(offset.y, offset.x, 0.0, 0.3) + stroke = null + + val pos = bounds.center - offset * 110.0 + circle(pos, 120.0) + circle(pos, 80.0) + } + } + val image = loadImage("demo-data/images/image-001.png") + extend { + displace.distance = 250.0 + displace.apply(arrayOf(image, direction), displaced) + drawer.image(displaced) + //drawer.image(direction) + } + } +} \ No newline at end of file diff --git a/orx-fx/src/shaders/glsl/blur/directional-blur.frag b/orx-fx/src/shaders/glsl/blur/directional-blur.frag index 24e0db0f..447f8329 100644 --- a/orx-fx/src/shaders/glsl/blur/directional-blur.frag +++ b/orx-fx/src/shaders/glsl/blur/directional-blur.frag @@ -1,8 +1,8 @@ in vec2 v_texCoord0; uniform bool centerWindow; -uniform sampler2D tex0;// image -uniform sampler2D tex1;// blurDirection +uniform sampler2D tex0; // image +uniform sampler2D tex1; // blurDirection uniform vec2 textureSize0; uniform int window; @@ -17,15 +17,11 @@ out vec4 o_color; vec2 wrap(vec2 uv) { vec2 res = uv; - if (wrapX) { - res.x = mod(res.x, 1.0); - } - if (wrapY) { - res.y = mod(res.y, 1.0); - } + if (wrapX) { res.x = fract(res.x); } + if (wrapY) { res.y = fract(res.y); } return res; - } + void main() { vec2 s = textureSize0; s = vec2(1.0 / s.x, 1.0 / s.y); @@ -37,15 +33,14 @@ void main() { } float weight = 0.0; - int start = centerWindow? -window/2 : 0; - int end = centerWindow? window/2 + 1 : window; + int start = centerWindow ? -window / 2 : 0; + int end = centerWindow ? window / 2 + 1 : window; - - for (int x = 0; x < window; ++x) { + for (int x = start; x < end; ++x) { sum += texture(tex0, wrap(v_texCoord0 + float(x) * blurDirection * s * spread)); weight += 1.0; } - vec4 result = (sum/weight) * gain; + vec4 result = (sum / weight) * gain; o_color = result; } \ No newline at end of file diff --git a/orx-fx/src/shaders/glsl/distort/directional-displace.frag b/orx-fx/src/shaders/glsl/distort/directional-displace.frag new file mode 100644 index 00000000..4b4757f0 --- /dev/null +++ b/orx-fx/src/shaders/glsl/distort/directional-displace.frag @@ -0,0 +1,37 @@ +in vec2 v_texCoord0; + +uniform sampler2D tex0; // image +uniform sampler2D tex1; // displaceDirection +uniform vec2 textureSize0; + +uniform float gain; +uniform float distance; + +uniform bool wrapX; +uniform bool wrapY; +uniform bool perpendicular; + +out vec4 o_color; + +vec2 wrap(vec2 uv) { + vec2 res = uv; + if (wrapX) { res.x = fract(res.x); } + if (wrapY) { res.y = fract(res.y); } + return res; +} + +void main() { + vec2 s = textureSize0; + s = vec2(1.0 / s.x, 1.0 / s.y); + + vec4 sum = vec4(0.0, 0.0, 0.0, 0.0); + vec2 blurDirection = texture(tex1, v_texCoord0).xy; + if (perpendicular) { + blurDirection = vec2(-blurDirection.y, blurDirection.x); + } + + vec4 result = texture(tex0, wrap(v_texCoord0 + blurDirection * s * distance)) + * gain; + + o_color = result; +} \ No newline at end of file