patriciogonzalezvivo / glsl-sandbox

Prototype complex pipelines directly from a single shader by branching it into stages
MIT License
56 stars 2 forks source link

How to make ping-pong example with GlslSandbox? #8

Closed marioecg closed 1 year ago

marioecg commented 1 year ago

I'm trying to understand how double buffers work using the sandbox workflow, and as practice I want to port this ping-pong example I made a while ago.

Screen Shot 2023-08-14 at 19 50 37

The next shader works for making a similar effect that fades away over time:

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

#if defined(DOUBLE_BUFFER_0)
    vec2 st0 = st;
    st0 -= 0.5;
    st0 *= 0.995;
    st0 += 0.5;

    vec4 prev = texture2D(u_doubleBuffer0, st0);
    vec4 scene = texture2D(u_scene, st);

    color = mix(prev, scene, 0.01);

#elif defined(POSTPROCESSING)
    color = texture2D(u_doubleBuffer0, st);

#endif

    gl_FragColor = color;
}

Screen Shot 2023-08-14 at 19 53 25

What if I want the effect not to fade overtime and persist like in the codesandbox from above? Is it possible with GlslSandbox / glslViewer, if so how would this be?

patriciogonzalezvivo commented 1 year ago

https://github.com/patriciogonzalezvivo/glsl-sandbox/blob/main/examples/000.js#L70-L89

: )

patriciogonzalezvivo commented 1 year ago

Try this other ; )

#define PLATFORM_WEBGL

uniform sampler2D   u_scene;
uniform sampler2D   u_doubleBuffer0;

uniform vec2        u_resolution;
uniform float       u_time;
uniform int         u_frame;

varying vec2        v_texcoord;
varying vec3        v_normal;
varying vec4        v_position;

#include "lygia/math/saturate.glsl"
#include "lygia/space/ratio.glsl"
#include "lygia/space/scale.glsl"
#include "lygia/color/mixOklab.glsl"
#include "lygia/generative/snoise.glsl"

void main() {
    vec4 color = vec4(vec3(0.0), 1.0);
    vec2 pixel = 1.0 / u_resolution;
    vec2 st = gl_FragCoord.xy * pixel;
    vec2 sst = ratio(st, u_resolution); 
    vec2 uv = v_texcoord;

#if defined(BACKGROUND)
    color.a = 0.0;

#elif defined(DOUBLE_BUFFER_0)
    float n = snoise( vec3(sst * (1.5 + sin(u_time)) * 5.0, u_time * 0.5) ) * 0.0025;
    vec2 st0 = scale(st, 0.995 + n);
    color = texture2D(u_doubleBuffer0, st0);

    vec4 scene = texture2D(u_scene, st);
    color.rgb = mixOklab(color.rgb, scene.rgb, step(0.99,scene.a));
    color.a = 1.0;

#elif defined(POSTPROCESSING)
    color = texture2D(u_doubleBuffer0, st);

#else
    color.rgb = v_normal * 0.5 + 0.5;
    color.rg = mix(color.rg, uv, saturate(distance(sst, vec2(0.5))*2. ) );

#endif

    gl_FragColor = color;
}
marioecg commented 1 year ago

Thanks @patriciogonzalezvivo! Love the example using noise. So having a background, setting its alpha to 0 and interpolating with the previous frame and the scene's transparency is key.

Could this work as a generalized basic structure for ping-pong buffers with 3D shaders?

void main() {
    vec4 color = vec4(vec3(0.0), 1.0);
    vec2 pixel = 1.0 / u_resolution;
    vec2 st = gl_FragCoord.xy * pixel;

#if defined(BACKGROUND)
    // If you need a background billboard behind the scene
    // I think this just works for 3D shaders
    // ...

#elif defined(DOUBLE_BUFFER_0)
    // Sample the texture that does ping-pong between 
    // the two buffers (or previous frame)
    // Keep in mind that changes inside here accumulate over time
    vec2 st1 = scale(st, 0.995);
    color.rgb = texture2D(u_doubleBuffer0, st1).rgb;

#elif defined(POSTPROCESSING)
    // Displays the content of the double buffer
    // directly to the screen
    color = texture2D(u_doubleBuffer0, st);

#endif

    gl_FragColor = color;
}


What if we want this same scale effect in a 2D shader that shows a square SDF. How would that be?

patriciogonzalezvivo commented 1 year ago

It's key to that particular effect on where the pixels expands through the space. Ping-pong is the overall use of two buffers where the output of one pass is the input of the following.

From that code you just share what is missing is the scene. There is no explicit content for the material of the geometry and in the double buffer you are not mixing the previous pass with nothing new.

The original example I provided have a 2D example in doubleBuffer0... that then is use as texture for the cube's material

patriciogonzalezvivo commented 1 year ago

I just added two 2D examples:

And simplify the previous one, so it's clear that the main shader in glslViewer is used for the geometry material

This makes the interface more compact

marioecg commented 1 year ago

Thanks for putting the new examples @patriciogonzalezvivo! It makes more sense to me now.

Sometimes for my work I like exploring 3D scenes that I pass through a double buffer to make a poisson fill effect. How would you implement it in this workflow?

marioecg commented 1 year ago

I think I figured it out following a very similar example from above, but if you have a different way of using poisson fill I'll be curious to know!

#define PLATFORM_WEBGL

uniform sampler2D   u_scene;
uniform sampler2D   u_doubleBuffer0;

uniform vec2        u_resolution;

#include "lygia/morphological/poissonFill.glsl"

void main() {
    vec2 pixel = 1.0 / u_resolution;
    vec2 st = gl_FragCoord.xy * pixel;
    vec4 color = vec4(vec3(0.0), 1.0);

#if defined(BACKGROUND)
    color.a = 0.0;

#elif defined(DOUBLE_BUFFER_0)
    vec4 prevFrame = poissonFill(u_doubleBuffer0, u_doubleBuffer0, st, pixel, false);
    vec4 scene = texture2D(u_scene, st);

    color = mix(prevFrame, scene, scene.a);

#elif defined(POSTPROCESSING)
    color = texture2D(u_doubleBuffer0, st);

#endif

    gl_FragColor = color;
}
patriciogonzalezvivo commented 1 year ago

Poisson fill uses another structure that is not just a simple double buffer but a double pyramid convolution (one pyramid for down sampling and other for up sampling). That kind of structure is supported on GlslViewer in this way, but not yet on glsl-sandbox.

If you are curious on how it's implemented you can start here in vera which is my C++/OpenGL framework on which glslViewer is implemented. You can see there how multiple set's of buffers are created... one half the size of the previous one (for the down sampling pyramid) and the other set is each double the size of the previous (for the up sampling pyramid).

Following the same branching logic I use only one single shader for going through both pyramids. But their logic is different depending at what point is.

https://github.com/patriciogonzalezvivo/vera/blob/main/include/vera/shaders/poissonfill.h

I have been using it in GlslViewer for all sort of things and is pretty handy for all sort of things.

I just made a visualization example here for you to see how it works https://github.com/patriciogonzalezvivo/lygia_examples/blob/main/morphological_poissonFill.frag

test

patriciogonzalezvivo commented 1 year ago

Also in GlslViewer if you press p (you see all the passes) and can see how the actual pyramids look (top right corner)

image

patriciogonzalezvivo commented 1 year ago

To clarify, that visualization basically cut the upsampling short, so you can see how the borders are expanding but actually glslViewer does the entire poisson fill going through the entire pyramids convolution in one single render pass

marioecg commented 1 year ago

Oh I see!! Very insightful, thanks for sharing and putting up the examples Patricio. I need to read more on pyramid convolutions.

Now I realize that in my poisson fill example I'm using the formula not ideally, it's just doing the down sampling and that's it — no upscaling and pyramid convolutions are being taken into account. So it falls short or is not being used in all its possibilities. I do get a similar result though as it renders over time (it takes > 5 secs) but less appealing than your visualization.

screenshot

screenshot

screenshot