How to configure a shader with multiple render buffers and SwapBuffers (ex shadertoy)?

Hi !

I’m looking for a way to play within openframeworks with shaders from shadertoys which implement the multiple render buffers and SwapBuffer (or ping pong buffer).

I have done some research concerning the way those shaders works but i have to admit i’m a bit lost right now.

Here is an example from shadertoys by “cornusammonis” of reaction-diffusion which use the two technics

So the correct way to handle this would be to create and attach multiple textures to an fbo (2 in this case), ping pong it on the shader “Buf A” and draw it on the shader “Image” ?

Here is one of my attempts (278.8 KB)

1 Like

I reached the same “I’m confused” state as you did. I started exploring ofxFx to do most of the complicated stuff, and it has worked very well for several projects. I couldn’t get ping ponging to work on my system myself, but I blame my lack of coding prowess.

I did rewrite a few items for my own purpose, but the fact one can indicate the number of passes per frame for ping-ponging made things much easier than trying to do it myself. I made use of the reaction-diffusion system, with a number of alterations for my own nefarious needs…


Right, go for ofxFX.
directly copy/pasting shadertoy code into ofxFX will not work as there are some naming differences.
check this

check this instructions for modifying the shader


A very good synopsis on the differences, roymacdonald. I spent a long time second guessing how to get this set up, and your work would have helped a lot.

No prob. Just ping me if you need more help.

Great, thank you both !
I had checked ofxBlur and ofxGpuParticles for the ping pong technique but ofxFX seems the best way.
I will work on this and post the project here if all go well.

So, i’ve worked on the implementation of the shaders in ofxFX.
It’s really cleaver, i think i will only use this add-on for shaders from now on :).

The thing is i can’t make it work and i don’t know why, i don’t have any error printed in my console and i’ve checked carefully the examples.

Anyway here is the code, maybe someone could spot something wrong :

The shader “BufA”

#pragma once

#include "ofMain.h"
#include "ofxFXObject.h"

 "Suture Fluid" by cornusammonis - 2016 April 2015
 License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

class ofxSutureFluid_A : public ofxFXObject {

		passes = 2;
		internalFormat = GL_RGBA32F;
		fragmentShader = STRINGIFY(
								   uniform float     time;           // shader playback time (in seconds)
								   uniform vec3      mouse;
								   uniform vec2      resolution;           // viewport resolution (in pixels)
								   uniform sampler2D backbuffer;          // input channel. XX = 2D/Cube
								   uniform sampler2D tex0;          // input channel. XX = 2D/Cube

								   //bool reset() {
								   //    return texture2D(iChannel3, vec2(32.5/256.0, 0.5) ).x > 0.5;

								   vec2 normz(vec2 x) {
									   return x == vec2(0.0, 0.0) ? vec2(0.0, 0.0) : normalize(x);

								   // reverse advection
								   vec3 advect(vec2 ab, vec2 vUv, vec2 step, float sc) {

									   vec2 aUv = vUv - ab * sc * step;

									   const float _G0 = 0.25; // center weight
									   const float _G1 = 0.125; // edge-neighbors
									   const float _G2 = 0.0625; // vertex-neighbors

									   // 3x3 neighborhood coordinates
									   float step_x = step.x;
									   float step_y = step.y;
									   vec2 n  = vec2(0.0, step_y);
									   vec2 ne = vec2(step_x, step_y);
									   vec2 e  = vec2(step_x, 0.0);
									   vec2 se = vec2(step_x, -step_y);
									   vec2 s  = vec2(0.0, -step_y);
									   vec2 sw = vec2(-step_x, -step_y);
									   vec2 w  = vec2(-step_x, 0.0);
									   vec2 nw = vec2(-step_x, step_y);

									   vec3 uv =    texture2D(backbuffer, fract(aUv)).xyz;
									   vec3 uv_n =  texture2D(backbuffer, fract(aUv+n)).xyz;
									   vec3 uv_e =  texture2D(backbuffer, fract(aUv+e)).xyz;
									   vec3 uv_s =  texture2D(backbuffer, fract(aUv+s)).xyz;
									   vec3 uv_w =  texture2D(backbuffer, fract(aUv+w)).xyz;
									   vec3 uv_nw = texture2D(backbuffer, fract(aUv+nw)).xyz;
									   vec3 uv_sw = texture2D(backbuffer, fract(aUv+sw)).xyz;
									   vec3 uv_ne = texture2D(backbuffer, fract(aUv+ne)).xyz;
									   vec3 uv_se = texture2D(backbuffer, fract(aUv+se)).xyz;

									   return _G0*uv + _G1*(uv_n + uv_e + uv_w + uv_s) + _G2*(uv_nw + uv_sw + uv_ne + uv_se);

								   void main( void )
									const float _K0 = -20.0/6.0; // center weight
									const float _K1 = 4.0/6.0;   // edge-neighbors
									const float _K2 = 1.0/6.0;   // vertex-neighbors
									const float cs = -0.6;  // curl scale
									const float ls = 0.05;  // laplacian scale
									const float ps = -0.8;  // laplacian of divergence scale
									const float ds = -0.05; // divergence scale
									const float dp = -0.04; // divergence update scale
									const float pl = 0.3;   // divergence smoothing
									const float ad = 6.0;   // advection distance scale
									const float pwr = 1.0;  // power when deriving rotation angle from curl
									const float amp = 1.0;  // self-amplification
									const float upd = 0.8;  // update smoothing
									const float sq2 = 0.6;  // diagonal weight

									vec2 vUv = gl_FragCoord.xy / resolution.xy;
									vec2 texel = 1. / resolution.xy;

									// 3x3 neighborhood coordinates
									float step_x = texel.x;
									float step_y = texel.y;
									vec2 n  = vec2(0.0, step_y);
									vec2 ne = vec2(step_x, step_y);
									vec2 e  = vec2(step_x, 0.0);
									vec2 se = vec2(step_x, -step_y);
									vec2 s  = vec2(0.0, -step_y);
									vec2 sw = vec2(-step_x, -step_y);
									vec2 w  = vec2(-step_x, 0.0);
									vec2 nw = vec2(-step_x, step_y);

									vec3 uv =    texture2D(backbuffer, fract(vUv)).xyz;
									vec3 uv_n =  texture2D(backbuffer, fract(vUv+n)).xyz;
									vec3 uv_e =  texture2D(backbuffer, fract(vUv+e)).xyz;
									vec3 uv_s =  texture2D(backbuffer, fract(vUv+s)).xyz;
									vec3 uv_w =  texture2D(backbuffer, fract(vUv+w)).xyz;
									vec3 uv_nw = texture2D(backbuffer, fract(vUv+nw)).xyz;
									vec3 uv_sw = texture2D(backbuffer, fract(vUv+sw)).xyz;
									vec3 uv_ne = texture2D(backbuffer, fract(vUv+ne)).xyz;
									vec3 uv_se = texture2D(backbuffer, fract(vUv+se)).xyz;

									// uv.x and uv.y are the x and y components, uv.z is divergence

									// laplacian of all components
									vec3 lapl  = _K0*uv + _K1*(uv_n + uv_e + uv_w + uv_s) + _K2*(uv_nw + uv_sw + uv_ne + uv_se);
									float sp = ps * lapl.z;

									// calculate curl
									// vectors point clockwise about the center point
									float curl = uv_n.x - uv_s.x - uv_e.y + uv_w.y + sq2 * (uv_nw.x + uv_nw.y + uv_ne.x - uv_ne.y + uv_sw.y - uv_sw.x - uv_se.y - uv_se.x);

									// compute angle of rotation from curl
									float sc = cs * sign(curl) * pow(abs(curl), pwr);

									// calculate divergence
									// vectors point inwards towards the center point
									float div  = uv_s.y - uv_n.y - uv_e.x + uv_w.x + sq2 * (uv_nw.x - uv_nw.y - uv_ne.x - uv_ne.y + uv_sw.x + uv_sw.y + uv_se.y - uv_se.x);
									float sd = uv.z + dp * div + pl * lapl.z;

									vec2 norm = normz(uv.xy);

									vec3 ab = advect(vec2(uv.x, uv.y), vUv, texel, ad);

									// temp values for the update rule
									float ta = amp * ab.x + ls * lapl.x + norm.x * sp + uv.x * ds * sd;
									float tb = amp * ab.y + ls * lapl.y + norm.y * sp + uv.y * ds * sd;

									// rotate
									float a = ta * cos(sc) - tb * sin(sc);
									float b = ta * sin(sc) + tb * cos(sc);

									vec3 abd = upd * uv + (1.0 - upd) * vec3(a,b,sd);

									if (mouse.z > 0.0) {
										vec2 d = gl_FragCoord.xy - mouse.xy;
										float m = exp(-length(d) / 10.0);
										abd.xy += m * normz(d);
									// initialize with noise
									if(mouse.z > 0.0) {
										gl_FragColor = -0.5 + texture2D(tex0, gl_FragCoord.xy / resolution.xy);
									} else {
										//gl_FragColor = clamp(vec4(abd,0.0), -1., 1.);
										abd.z = clamp(abd.z, -1.0, 1.0);
										abd.xy = clamp(length(abd.xy) > 1.0 ? normz(abd.xy) : abd.xy, -1.0, 1.0);
										gl_FragColor = vec4(abd, 0.0);


	void update(){

		for( int i = 0; i < passes ; i++ ){

			shader.setUniform1f("time", ofGetElapsedTimef());
			shader.setUniform2f("resolution", (float)width, (float)height);
			shader.setUniform2f("mouse", (float)ofGetMouseX()/width, (float)ofGetMouseY()/height);
			shader.setUniformTexture("backbuffer", pingPong.src->getTexture(), 0 );
			shader.setUniformTexture("tex0", textures[0].getTexture(), 1 );




The shader “Image”

#pragma once

#include "ofMain.h"
#include "ofxFXObject.h"

 "Suture Fluid" by cornusammonis - 2016 April 2015
 License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

class ofxSutureFluid : public ofxFXObject {

		passes = 1;
		internalFormat = GL_RGBA32F;
		fragmentShader = STRINGIFY(
								   uniform float     time;           // shader playback time (in seconds)
								   uniform vec3      mouse;
								   uniform vec2      resolution;           // viewport resolution (in pixels)
								   uniform sampler2D tex0;          // input channel. XX = 2D/Cube

								   // Visualization of the system in Buffer A

								   void main( void )
									   vec2 texel = 1. / resolution.xy;
									   vec2 uv = gl_FragCoord.xy / resolution.xy;
									   vec3 c = texture2D(tex0, uv).xyz;
									   vec3 norm = normalize(c);

									   vec3 div = vec3(0.1) * norm.z;
									   vec3 rbcol = 0.5 + 0.6 * cross(, vec3(0.5, -0.4, 0.5));
									   gl_FragColor = vec4(rbcol + div, 0.0);



  #pragma once

    #include "ofMain.h"
    #include "ofxSutureFluid.h"
    #include "ofxSutureFluid_A.h"

    class ofApp : public ofBaseApp{

    		void setup();
    		void update();
    		void draw();

    	ofxSutureFluid SutureFluid;
    	ofxSutureFluid_A SutureFluid_A;


#include "ofApp.h"

void ofApp::setup(){



	ofImage background;

	SutureFluid_A.setTexture(background.getTexture(), 1);


void ofApp::update(){


	SutureFluid << SutureFluid_A;



void ofApp::draw(){


The project : (67.7 KB)

Meet the same problem, hope to have a good solution. thank you

Finally i’ve just resolved it :slight_smile: !

The issue was first with the Texture2D.
Openframeworks set automatically “GL_ARB_texture_rectangle : enable” and so the coordinates were incorrect.
I had to add ofDisableArbTex() in setup() in order for it to work.

Secondly the shader originally had 0. in the alpha channel, so naturally it was invisible…

Here is the project : (74.0 KB)

I will try to work on a shader with multiple buffers now but it shouldn’t be an issue.

I have converted this shader which have two buffers I’ve done some tweaks in the code to avoid some errors. (962.9 KB)

I still wonder about this type of shaders as the shader “BufA” is taking back the render buffer from the shader “BufD” to do some sort of loophole. I will try tomorrow and post the code here if i succeed.

spalluel,Thank you very much. Can I use this library to complete, without modification GLSL.

You are welcome :slight_smile:.

No because ofxFX don’t use the same strings for the shaders as stated above by @roymacdonald. And even if, it wouldn’t help. You need to change manually the strings. “iChannel0” for “tex0” etc. Also some shaders will not work even if you convert those strings because the GL renderer can’t read in STRINGIFY() the “#define …” functions, floats etc.

For instance in ofxAnisotropic_A.h i’ve done that :

// a gaussian centered around the point at ‘ab’
// #define e(x,y) exp(-SHARPNESS * dot(vec2(x,y) - ab, vec2(x,y) - ab))

float gaussAB(float x, float y, vec2 ab) {
float result = exp(-SHARPNESS * dot(vec2(x,y) - ab, vec2(x,y) - ab));
return result;

I hope this is clear. I invite you to try by yourself with simple multipass shaders to start.

CamFluid 3.rar (152.5 KB)

I try, but there are problems.
Requesting for help.

I’ve tried to make it work but i’m lost too.
The thing is that if i understand it well, the 2 shaders “BufA” and “BufB” need to have the texture from both in the same pass and i don’t know how to proceed with ofxFX. Here is my attempt : (32.7 KB)

Hi Spalluel
Did you ever figure this out?
I’m trying to get shadertoys with multiple buffers working myself at the minute.

Hi Glenn.

Yes :), it was because the shaders has to be calculated in the same pass, so i modified the add-on ofxFX to add some functions to do so. Here it is

Inside you can find an example “ShadertoyMultipass” with this shader (@zwmin) working and an other “Multiscale Turing Patterns” by cornusammonis which i can’t make to work, i dunno it’s because of the alpha channel which is handled differently by the fbo than Shadertoy. If someone succeed to make it work it would be marvelous to post it here.

The other thing is that my method to allocate all the shaders in the setup() is a wrong way because with a lot of shaders to init, it will over-flood the VRAM… I will work on that today, i’m thinking about using the boolean to allocate the memory once before it trigger the visual and deallocate it when it’s not used.
I will fork the add-on properly on github once it’s clean and that i find the time.


Thanks Seb!
I’m checking all this out now.

I hacked your earlier efforts to get this working,

But then had trouble getting the multi buffer feedback thing working - this is the one I’ve been trying,

Quite similar to the one you’re working on too.

I’ll let you know how I get on.

p.s. I’ve found that sometimes setting the alpha channels to 1.0 in a buffer fixes things.


You are welcome :).

I have succeeded with the reaction-diffusion shader from Flexi too (256.9 KB).
The thing is that the system of reaction-diffusion in “Multiscale Turing Patterns” is based on 4 species which take the RGBA values, and so i really guess it has something to do with the alpha value in the fbo, maybe it’s just a dull thing like a setting to change, i don’t know but all the process seems correct to me.

1 Like

Problem solved for the dynamic allocation/destruction of the fbos. I’ve added a few functions in ofxFX and updated the example to show how to use it with ofxGui thanks to the addlistener() method so that the fbos are created and erased dynamically from the VRAM. ofxFX(Seb).zip (1.2 MB)

I will fork this version of ofxFX on my github as soon as everything is set.