Giter VIP home page Giter VIP logo

Comments (26)

davepagurek avatar davepagurek commented on June 14, 2024 3

Here's an updated dither using @aferriss's random function: https://editor.p5js.org/davepagurek/sketches/fth7UYyXI

I also experimented with making better use of the random function being inside the loop and actually using a different random number per sample, but honestly it didn't look any better. That's convenient at least, because we can take the random function out of the loop and have it run a bit faster!

from p5.js.

davepagurek avatar davepagurek commented on June 14, 2024 2

One more update: @SableRaf and I have been working on versions that don't drift to the side when the blur radius is greater than the max samples.

Here's his with even spacing for 64 samples (we can always turn this up though): https://editor.p5js.org/SableRaf/sketches/QpkhsI94y
image

Here's a fork of that that I added dithering to, still 64 samples: https://editor.p5js.org/davepagurek/sketches/3aD5WBynW
image

from p5.js.

aferriss avatar aferriss commented on June 14, 2024 2

Looks great to me! The multiple pass solution was something we had also explored early on but abandoned in favor of something simple, which we could later revisit. It does seem like its more difficult to achieve parity with the CPU blur when using more than 1 pass.

I like the dithering solution as well but it does add some barely perceptible noise. Personally I don't mind it but wonder how others feel.

For the dithering shader, I've had better luck with this snippet in making more natural looking noise that doesn't create artifacts and doesn't use a sin function. Also can we move that random calculation outside the for loop and save some calculations?

float random(vec2 p){
	vec3 p3  = fract(vec3(p.xyx) * .1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}
float r = random(gl_FragCoord.xy); // no need to normalize coords

Nice work everyone!!

from p5.js.

wong-justin avatar wong-justin commented on June 14, 2024 1

I think blur is the hardest filter to match exactly with CPU mode.

The main difficulties trying to recreate the Gaussian blur were:

  • making a kernel size that could change on any frame according to the user's parameter, which could mean hard coding many different kernels in one shader, or compiling on the fly in the middle of runtime

  • alternatively using a small kernel and repeating passes for more blur, but the extra passes were slowing things down with all the back-and-forth between layers

Happy to hear other thoughts and approaches though. It would be nice to have a better blur.

from p5.js.

wong-justin avatar wong-justin commented on June 14, 2024 1

Ah ok, thanks for the resources and ideas. I can take the next week or two to dig in and learn and see what I can come up with.

from p5.js.

davepagurek avatar davepagurek commented on June 14, 2024 1

I dug a bit more into the code for the existing CPU blur:

  • It looks like it's not actually doing a Gaussian blur at all -- the influence of each pixel is parabolic as you go away from the center
  • It's doing a two-pass blur, one for each axis
  • The radius you pass in is not the number of samples, nor the radius of the blur -- it's something closer to Οƒ in a Gaussian blur, where the conventional wisdom is that at 3Οƒ away, it's "basically" 0 influence, so you can stop your kernel there. Similarly, the existing CPU blur increases the kernel radius to 3.5 times the number you pass in (which means the number of samples per axis is double that.)

Putting all those together into @SableRaf's scaffolding, I got this: https://editor.p5js.org/davepagurek/sketches/x6wNXyzxQ

Comparison:
blur-comparison

from p5.js.

wong-justin avatar wong-justin commented on June 14, 2024 1

The latest demo looks fantastic! @davepagurek no I didn't have a PR for blur in progress, feel free to make it.

from p5.js.

patriciogonzalezvivo avatar patriciogonzalezvivo commented on June 14, 2024 1

Hi! Sorry for jumping a bit late to the party.
I gave it a try to a Bartlett (bilinear filter) and the diff is pretty minimal

bartlett

barlett_13

difference
barlett_13_diff


#ifdef GL_ES
precision mediump float;
#endif

uniform vec2        u_resolution;
uniform vec2        u_mouse;

uniform sampler2D   u_tex0;
uniform vec2        u_tex0Resolution;

uniform sampler2D   u_tex1;
uniform vec2        u_tex1Resolution;

varying vec2        v_texcoord;

#include "lygia/filter/bartlett.glsl"

void main (void) {
    vec3 color = vec3(0.0);
    vec2 pixel = 1.0/u_resolution.xy;
    vec2 tex0Pixel = 1.0/u_tex0Resolution;
    vec2 st = gl_FragCoord.xy * pixel;
    vec2 uv = v_texcoord;

    vec2 mouse = u_mouse * pixel;

    vec4 blur = bartlett(u_tex0, st, tex0Pixel, 13);

    vec4 control = texture2D(u_tex1, st);
    vec4 delta = abs(FNC - control);

    color = mix(blur.xyz, delta.xyz, step(mouse.x, st.x));

    gl_FragColor = vec4(color,1.0);
}

from p5.js.

davepagurek avatar davepagurek commented on June 14, 2024

Instead of having a hardcoded kernel, we could generate the kernel value dynamically per sample based on the distance from the center. I'm not sure how much of a performance impact this would have compared to using hardcoded values, but if it's not significant, it would avoid the complexity of managing different shaders.

To loop over a variable number of pixels, to circumvent the issue where loops have to have constant bounds, I generally see people loop up to a max value (max radius, in this case) and then break if the loop index equals the real loop limit.

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

I asked Patricio Gonzales-Vivo (creator of Lygia and co-creator of the Book of Shaders). He shared the following insights:

If matching what’s on the cpu is the goal you could upload the weight you use them to procedurally write the kernel convolution shader. Also for reference in lygia I calculate them on the fly https://lygia.xyz/filter/gaussianBlur/2D Is not incredibly performant but is not the worst on the world. Is the goal efficiency or accuracy?

I'd argue that accuracy should be essential since we're replacing the CPU blur with the GPU blur. I believe minimizing discrepancies would be beneficial. As for performance, we could test it and see how costly this type of blur is. I'm guessing it should still be significantly faster than CPU blur but some benchmarking would help πŸ€“

An example of all 2D shaders in Lygia, including 2D gausian blur, using p5.js can be found here: https://editor.p5js.org/patriciogonzalezvivo/sketches/XCkTzoyB3

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

Just one more resource that may be helpful:

https://github.com/SableRaf/Filters4Processing/blob/master/sketches/GaussianBlur/data/gaussianBlur.glsl

Taken from: http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html

from p5.js.

wong-justin avatar wong-justin commented on June 14, 2024

Here's an initial draft: https://editor.p5js.org/jwong/sketches/Cn2z2G-to
The weight function needs some adjusting, and I haven't thought about matching CPU output yet. Supposedly with the right math, I think it could be tuned pretty close to the original.

But this is a single shader, single pass gaussian blur using the break technique (I think I've wrapped my head around it now thanks to everybody's help)

Note that performance drops kinda quickly. Some rough manual benchmarks from my machine:

steps / kernel size / parameter --> 10 20 30 40
current shader BLUR, fps 60 60 57 50
new linked BLUR, fps 60 30 10 7

from p5.js.

aferriss avatar aferriss commented on June 14, 2024

I think the current implementation could be modified to continue using the two pass solution, which should keep things much more performant. Here's another implementation of the gaussian kernel setup for a two pass renderer

https://editor.p5js.org/aferriss/sketches/IS4P5RdAo

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

Thanks @wong-justin and @aferriss! How do these two approaches compare with the CPU blur in terms of quality?

from p5.js.

aferriss avatar aferriss commented on June 14, 2024

Here's a few comparisons I put together, with the GPU blur on top and CPU on the bottom of each image. None of them match perfectly, and to be honest I think we will have a pretty difficult time matching the cpu blur exactly. I think we may need to make a tradeoff here between performance and visual appeal. That being said we can keep tinkering away at this and hopefully get as close as possible. I think of the options presented here the one I shared yesterday seems the closest, but would love to hear other opinions!

currentcpublur

justincpublur

adamcpublur

raphcpublur

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

@aferriss I agree that your version looks the best of the ones there.

Another approach suggested here is to use a single precalculated kernel and apply the blur multiple times with two ping-pong framebuffers.

I've made a quick attempt based on your sketch, setting the samples relatively low, but calling filter(s) multiple times per frame. The results seem really encouraging in terms of both performance and visual quality.

Edit: I also tried a version of the hardcoded kernel from the article: https://editor.p5js.org/SableRaf/sketches/NTy_zPyPX

image

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

Another useful reference. This article does a systematic exploration of GPU blur, with regard to performance and quality across platforms:
https://venturebeat.com/pc-gaming/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms/

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

I've done further exploration using a shader to visualize the lightness as a graph, inspired by the figures in the article from VentureBeat. You can find the test sketch here: https://editor.p5js.org/SableRaf/sketches/6it1LpFq8

We can see that Adam's blur is pretty close but it has a bias towards the bottom right direction that becomes more visible at higher samples.

The multipass blur is also quite close but it explodes at extreme number of passes, I suppose because of edge effects. (edit: I'm not sure about that image, looks like I may have made a mistake while generating it, I'll have to get back to it)

This is not a super rigorous benchmark but I hope it will be helpful.

By the way, I'm testing in Chrome Version 116.0.5845.187 on macOS Ventura 13.4.

CPU Blur filter(BLUR,20,false) (ground truth)

image

Current GPU Blur filter(BLUR,20)

image

Multipass blur (200 passes)

image

Adam's blur (60 samples)

image

Adam's blur (80 samples)

image

Multipass blur (400 passes)

image

from p5.js.

davepagurek avatar davepagurek commented on June 14, 2024

I think the offset in Adam's might be from this line:

float x2 =  (i - samples * 0.5);

If we're using e.g. samples=3, then we get:

i x2
0 -1.5
1 -0.5
2 1.5

If we instead use x2 = i - (samples-1.) * 0.5, we get:

i x2
0 -1
1 0
2 1

This only works when samples is odd, but if we define samples = blurRadius * 2 + 1 (so a radius of 1 means it averages the pixel to the left and the pixel to the right with the current pixel) then this would always be true.

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

Hey y'all, with some help from @davepagurek and others, I've built a more advanced benchmark for blur. Feel free to play around with it to get a better feel for the parameter space and performance. Hold click and move the cursor to see the profile change based on the radius and number of passes.

Feel free to experiment and let me know what you think.

Notes

  • The shader version is based on Adam's, with the proposed fix by Dave and using only odd sample counts.
  • CPU blur only refreshes when the radius changes so don't use this sketch to evaluate the performance of CPU blur.

Interactive demo

https://editor.p5js.org/SableRaf/sketches/KFye28jYn

Instructions

  • Press W to toggle between WebGL and classic CPU blur.
  • Press G to toggle displaying the grid overlay
  • Move your cursor left and right to change the blur radius
  • Move your cursor up and down to change the number of passes (WebGL mode only)
  • Left Click to toggle the luminosity profile graph (green pixels).

Examples

Adding extra passes helps with the smoothness of the shader blur. You can see that the sharp edges almost completely disappear when applying the blur twice.

img_2023920132945_radius-21_passes-1_graph-OFF_grid-OFF

img_2023920132949_radius-21_passes-2_graph-OFF_grid-OFF

More passes help with the shape of the blur.

img_2023920133659_radius-71_passes-1_graph-ON_grid-ON

img_202392013375_radius-71_passes-2_graph-ON_grid-ON

img_2023920133751_radius-71_passes-4_graph-ON_grid-ON

The CPU blur does get a more rounded shape at higher radii but shows some discontinuities (unless those are caused by my graphing shader, but they don't appear on the GPU blur).
img_2023920135951_radius-65_passes-11_graph-ON_grid-ON_webgl-OFF

To get this kind of roundedness on the GPU a higher number of passes is necessary.
img_2023920135925_radius-95_passes-11_graph-ON_grid-ON_webgl-ON

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

Thanks @davepagurek that looks great!

@aferriss and @wong-justin what do you think? Should we go with that last one?

from p5.js.

davepagurek avatar davepagurek commented on June 14, 2024

I like the dithering solution as well but it does add some barely perceptible noise. Personally I don't mind it but wonder how others feel.

Do we have any easy ways of passing more than one parameter to a filter shader? I think letting people use more samples could make sense (e.g. for exports), but I'm not sure that we have a great API for it yet.

from p5.js.

davepagurek avatar davepagurek commented on June 14, 2024

Also I'll try adding your dithering changes after work πŸ™‚

from p5.js.

aferriss avatar aferriss commented on June 14, 2024

Do we have any easy ways of passing more than one parameter to a filter shader? I think letting people use more samples could make sense (e.g. for exports), but I'm not sure that we have a great API for it yet.

Currently you can only pass a single value to the shader, but there's no reason why it couldn't be extended to receive an object of parameters. I think the function signature is already a little complicated...

filter(customShader);
filter(FILTER_ENUM);
filter(FILTER_ENUM, parameter);
filter(FILTER_ENUM, parameter, useWebGL);

@wong-justin wrote this function to deal with the parameters

function parseFilterArgs(...args) {

from p5.js.

SableRaf avatar SableRaf commented on June 14, 2024

Looks great! I'd say the original issue is fully addressed by the latest shader version. Should we roll with that and leave the parameter discussion for a separate issue if necessary?

from p5.js.

davepagurek avatar davepagurek commented on June 14, 2024

I think that makes sense. @wong-justin were you already working on adding blur filter changes into your updates? If not I can make a PR with these changes.

from p5.js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.