The hydra-convolutions extension adds image convolution functions to Hydra, allowing you to apply blur, sharpen, edge detection, and other kernel-based effects. This is an experimental extension, so pelase read thoroughly.
| Name | Description | Has parameter k |
|---|---|---|
sharpen |
Classic 3x3 sharpen | ✓ |
sharpenMore |
Stronger 3x3 sharpen | ✓ |
lineSharpen |
Horizontal 1x3 sharpen | ✓ |
emboss |
Emboss/relief effect | ✓ |
blur |
Gaussian 3x3 blur | |
blur5 |
Gaussian 5x5 blur | |
blur7 |
Gaussian 7x7 blur | |
boxBlur |
Box 3x3 blur | |
boxBlur5 |
Box 5x5 blur | |
horizontalBlur |
Horizontal motion blur | |
verticalBlur |
Vertical motion blur | |
diagonalBlur |
Diagonal motion blur (top-right to bottom-left) | |
diagonalBlur2 |
Diagonal motion blur (top-left to bottom-right) | |
lineBlur |
Horizontal 1x3 blur | |
lineBlur5 |
Horizontal 1x5 blur | |
sobelY |
Sobel vertical edge detection | |
sobelX |
Sobel horizontal edge detection | |
sobelDiagonal |
Sobel diagonal edge detection | |
sobelDiagonal2 |
Sobel diagonal edge detection (other direction) | |
prewittY |
Prewitt vertical edge detection | |
prewittX |
Prewitt horizontal edge detection | |
prewittDiagonal |
Prewitt diagonal edge detection | |
prewittDiagonal2 |
Prewitt diagonal edge detection (other direction) | |
edge |
Laplacian edge detection |
Each convolution function comes in multiple variants for different use cases:
| Suffix | Applies to | Example |
|---|---|---|
| (none) | RGB channels | blur() |
Luma |
Luminance only | blurLuma() |
OnY |
Y channel (YUV) | blurOnY() |
OnUV |
UV channels (YUV) | blurOnUV() |
OnIQ |
IQ channels (YIQ) | blurOnIQ() |
The Luma variant outputs a grayscale image. The OnY, OnUV, and OnIQ variants apply the convolution to specific colorspace channels while preserving the others.
All convolution functions are source functions, meaning they take a texture as input and return a processed texture.
convolution( texture, jump, amp )
texture: The input texture (default:o0)jump: Pixel jump distance, controls kernel spread (default:1)amp: Output amplitude/strength multiplier (default:1)
osc(20,.1,2).out(o0)
blur(o0).out(o1)Some kernels (sharpen, emboss) have a strength parameter:
convolution( texture, k, jump, amp )
k: Kernel strength parameter (default:1)
osc(20,.1,2).out(o0)
sharpen(o0, 2).out(o1) // stronger sharpening
emboss(o0, 0.5).out(o1) // subtle emboss// Simple blur
noise(20,0).thresh(0,0).out(o0)
blur(o0).out(o1)
// Stronger blur with larger kernel
blur5(o0).out(o1)
// Even stronger
blur7(o0).out(o1)
// Spread the blur further apart
blur(o0, 7).out(o1)// Basic sharpen
osc(70,0)
.blend(voronoi(),.2)
.modulate(noise(10,.03))
.out(o0)
sharpen(o0).out(o1)
// Adjust sharpening strength with k parameter
sharpen(o0, 0.5).out(o1) // subtle
sharpen(o0, 5).out(o1) // strong// Detect vertical edges
osc(30,-.1).kaleid().thresh(.5).out(o0)
sobelY(o0).out(o1)
// Detect horizontal edges
sobelX(o0).out(o1)
// Detect all edges (Laplacian)
edge(o0).out(o1)// Horizontal motion blur
osc(30,-.1).kaleid().thresh(.5).out(o0)
horizontalBlur(o0, 2).out(o1)
// Diagonal streaks
diagonalBlur(o0, 3).out(o1)// Emboss effect
noise(4).repeat(5).out(o0)
emboss(o0, 2).out(o1)// Sharpen only the luminance (analog-like sharpen)
osc(20,.1,2).rotate().repeat(5,4).out(o0)
sharpenOnY(o0,1,2).out(o1)
// Luminance edges
osc(20,.1,2).rotate().repeat(5,5).modulate(voronoi()).out(o0)
edgeLuma(o0,3).out(o1)// Blur then sharpen (unsharp mask-like effect)
setResolution(512,512)
osc(30,-.1).kaleid().thresh(.5).mult(osc(20,.1,2)).out(o0)
blur5(o0,2).out(o1)
sharpen(o1, 4, 2).out(o2)
render(o2)You can define your own convolution kernels using setConvolutionFunction():
setConvolutionFunction({
name: "myBlur",
kernel: [
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]
],
multiplier: 1/9
})
// Now you can use it
osc().out(o0)
myBlur(o0).out(o1)You can also get creative with them:
setResolution(512,512)
setConvolutionFunction({
name: "ringing", // https://www.avartifactatlas.com/artifacts/ringing.html
kernel: [
[ 0, 0, 0, 0, 0, 0, 1.8, -1, 0.75, -.75, .6, -.6, .3 ],
]
})
osc(30,-.1).kaleid().thresh(.5).out(o0)
ringing(o0,4,1).out(o1)| Property | Description |
|---|---|
name |
The function name (required) |
kernel |
2D array of weights (required) |
multiplier |
Value to multiply the result by (optional, default: 1) |
You can use the string "k" in your kernel to create a parameterized convolution:
setConvolutionFunction({
name: "customSharpen",
kernel: [
[0, "-k", 0],
["-k", "(4.0*k)+1.0", "-k"],
[0, "-k", 0]
]
})
// k becomes a parameter
customSharpen(o0, 2).out(o1) // k = 2Note: When you define a custom kernel, all five variants (regular, Luma, OnY, OnUV, OnIQ) are automatically generated.
Note 2: You can technically write any GLSL expression inside the kernels, so you can create non-sensical convolutions that depend on time or whatever you can hack into them.
Larger kernels (5x5, 7x7) are more computationally expensive. Use them sparingly or consider using the line blur variants for directional effects. You can also try different jump values, which can act some-what similarly.
Hydra outputs use double framebuffers (ping-pong buffers). This means each frame is rendered to buffer A, then B, then A again and so on, for all buffers. If you create feedback loops across multiple outputs that depend on each other's previous frame, you will run into a problem. Such a feedback system would actually require Hydra to do a proper renderpass, which it can't do. The two outputs will become desynchronized, this often leads to a visible strobing.
For example, this pattern is prone to that behaviour:
sharpenOnY(o1,1.2).blend(noise(),.01).out(o0)
blur(o0).scale(1.1).out(o1)Because each output updates against its own ping-pong buffer, the feedback loop effectively alternates states between frames, producing a strobe or period-doubling bifurcation in the visual result. Effectively, two different feedback systems are running, one in frames A and the other in frames B.
You can reduce (not eliminate) that instability by adding temporal persistence: mixing a bit of the previous output back into the current update. This softens the desync and produces a more continuous visual transition, for example:
sharpenOnY(o1,1.2)
.blend(noise(),.01)
.blend(o0,.4)
.out(o0)
blur(o0).scale(1.1).out(o1)Here .blend(o0,.4) feeds some of o0 back into its own update so the alternating states are dampened. Note this changes the system's dynamics (it is not a strict fix and your visual would look different without the desync), but it is a practical technique to reduce strobing when using multiple interdependent outputs.