FBO ping pong and reaction diffusion

Hi all, after implementing a super naive version of the reaction diffusion algorithm by Gray Scott, I’ve tried to implement a version using FBOs. On the forum I’ve consulted these threads:
Rewriting Cinder’s Reaction Diffusion example in openFrameworks and How to pass textures to a gl shader? by @morphogencc

this discussion Lost with Textures Shaders and FBO by @patricio and @joshuajnoble

and of course I’ve had a look at the addon that Patricio did https://github.com/patriciogonzalezvivo/ofxFX/blob/master/src/generative/ofxGrayScott.h

Nevertheless, it is still not working. When I launch the app i see the 2 squares that I’m drawing in the update method fading-in pretty fast and then remaining there.

this is the header code:

#pragma once

#include "ofMain.h"
#include "ofxGui.h"

struct pingPongBuffer {
public:
    void allocate( int _width, int _height, int _internalformat = GL_RGBA){
        // Allocate
        for(int i = 0; i < 2; i++){
            FBOs[i].allocate(_width,_height, _internalformat );
            FBOs[i].getTexture().setTextureMinMagFilter(GL_NEAREST, GL_NEAREST);
        }

        // Clean
        clear();
    }

    void swap(){
        std::swap(src,dst);
    }

    void clear(){
        for(int i = 0; i < 2; i++){
            FBOs[i].begin();
            ofClear(0,255);
            FBOs[i].end();
        }
    }

    ofFbo& operator[]( int n ){ return FBOs[n];}
    ofFbo   *src = &FBOs[0];       // Source       ->  Ping
    ofFbo   *dst = &FBOs[1];       // Destination  ->  Pong

private:
    ofFbo   FBOs[2];    // Real addresses of ping/pong FBO´s
};

class ofApp : public ofBaseApp{

public:
	void setup();
	void update();
	void draw();

	void keyPressed(int key);
	void keyReleased(int key);
	void mouseMoved(int x, int y );
	void mouseDragged(int x, int y, int button);
	void mousePressed(int x, int y, int button);
	void mouseReleased(int x, int y, int button);
	void mouseEntered(int x, int y);
	void mouseExited(int x, int y);
	void windowResized(int w, int h);
	void dragEvent(ofDragInfo dragInfo);
	void gotMessage(ofMessage msg);

    ofFbo output;
    pingPongBuffer pingPong;
    ofShader shader;
    string shadersFolder = "shaders_gl3";
    ofImage image;
    ofTexture texture;

    float ru = 0.25f;
    float f = 0.0195f;
    float rv = 0.04f;
    float k = 0.066f;
}

this is the ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    restartButton.addListener(this, &ofApp::restartButtonPressed);
    if (!ofIsGLProgrammableRenderer()) {
        ofLogError("this app supports only open the programmable render pipeline");
        return 1;
    } else {
         shader.load(shadersFolder+"/passthru.vert", shadersFolder+"/grayscott.frag");
    };
    //image.load("img.jpg");
    output.allocate(256, 256);
    pingPong.allocate(256, 256);
}

//--------------------------------------------------------------
void ofApp::update(){
    pingPong.dst->begin();

    ofClear(0,0,0,255);
    //image.getTexture().bind();
    shader.begin();
    shader.setUniformTexture("prevBuffer", pingPong.src->getTexture(), 0);
    shader.setUniformTexture("prevTexture", output, 1);
    shader.setUniform1f( "ru", (float)ru);
    shader.setUniform1f( "rv", (float)rv);
    shader.setUniform1f( "f", (float)f );
    shader.setUniform1f( "k", (float)k );

    ofRect(0,0,10,10);
    ofRect(100,160,10,10);
    shader.end();
    //image.getTexture().unbind();
    pingPong.dst->end();
    pingPong.swap();
}

void ofApp::draw(){
    pingPong.src->draw(0,0);
}

this is the vertex shader

#version 150

uniform mat4 modelViewProjectionMatrix;

in vec4 position;
in vec2 texcoord;

out vec2 vTexCoord;

void main() {
    vTexCoord = texcoord;
    gl_Position = modelViewProjectionMatrix * position;
}

and this is the fragment shader

#version 150
#define KERNEL_SIZE 9    

float kernel[KERNEL_SIZE];    
vec2 offset[KERNEL_SIZE];

uniform sampler2DRect prevBuffer;
uniform sampler2DRect prevTexture; // U := r, V := g, other channels ignored
uniform float ru;          // rate of diffusion of U
uniform float rv;          // rate of diffusion of V    
uniform float f;           // some coupling parameter    
uniform float k;           // another coupling parameter

in vec2 vTexCoord;
out vec4 vFragColor;

void main(void)    
{    

    kernel[0] = 0.707106781;    
    kernel[1] = 1.0;    
    kernel[2] = 0.707106781;    
    kernel[3] = 1.0;    
    kernel[4] =-6.82842712;    
    kernel[5] = 1.0;    
    kernel[6] = 0.707106781;    
    kernel[7] = 1.0;    
    kernel[8] = 0.707106781;    

    offset[0] = vec2( -1.0, -1.0);
    offset[1] = vec2(  0.0, -1.0);
    offset[2] = vec2(  1.0, -1.0);

    offset[3] = vec2( -1.0, 0.0);
    offset[4] = vec2(  0.0, 0.0);
    offset[5] = vec2(  1.0, 0.0);

    offset[6] = vec2( -1.0, 1.0);
    offset[7] = vec2(  0.0, 1.0);
    offset[8] = vec2(  1.0, 1.0);

    //vec2 vTexCoord   = gl_FragCoord.xy;
    //vec2 vTexCoord   = gl_TexCoord[0].st;
    vec2 texColor       = texture( prevBuffer, vTexCoord ).rb;
    float srcTexColor   = texture( prevTexture, vTexCoord ).r;

    vec2 laplace        = vec2( 0.0, 0.0 );

    for( int i=0; i<KERNEL_SIZE; i++ ){    
        vec2 tmp    = texture( prevBuffer, vTexCoord + offset[i] ).rb;
        laplace     += tmp * kernel[i];
    }    

    float F     = f + srcTexColor * 0.025 - 0.0005;    
    float K     = k + srcTexColor * 0.025 - 0.0005;    

    float u     = texColor.r;
    float v     = texColor.g  + srcTexColor * 0.5;
    float uvv   = u * v * v;
    float du    = ru * laplace.r - uvv + F * (1.0 - u);
    float dv    = rv * laplace.g + uvv - (F + K) * v;
    u += du*0.6;
    v += dv*0.6;

    vFragColor = vec4( clamp( u, 0.0, 1.0 ), 1.0 - u/v, clamp( v, 0.0, 1.0 ), 1.0 );
}

Does someone have an idea about what I’m doing wrong?
I’ve uploaded the app here: https://github.com/edap/reactionDiffusion/tree/master/reactionDiffusionFbo

Just to clarify, this is the current output:

The 2 reds square are fading in in 1 second. I expect that they should grow, and slowly fulfil the screen. I think that I’m not clearing the FBO somewhere.
Also probably worth mentioning, I’m on mac, using OF nightly 20161107.
Any suggestion is really appreciated.

Well, I find one error. I was not drawing in the Fbo. Now this is fixed,

void ofApp::setup(){
    if (!ofIsGLProgrammableRenderer()) {
        ofLogError("this app supports only open the programmable render pipeline");
        return 1;
    } else {
        shader.load(shadersFolder+"/passthru.vert", shadersFolder+"/grayscott.frag");
    };
    image.load("img.jpg");
    output.allocate(image.getWidth(), image.getHeight(), GL_RGBA);
    pingPong.allocate(image.getWidth(), image.getHeight(), GL_RGBA);

    pingPong.src->begin();
    ofClear(0, 0, 0, 255);
    image.draw(0,0, width, height);
    pingPong.src->end();
}

And then in the update method

void ofApp::update(){
    pingPong.dst->begin();
    shader.begin();
    shader.setUniformTexture("prevBuffer", pingPong.src->getTexture(), 0);
    shader.setUniformTexture("prevTexture", output, 1);
    shader.setUniform1f( "ru", (float)ru);
    shader.setUniform1f( "rv", (float)rv);
    shader.setUniform1f( "f", (float)f );
    shader.setUniform1f( "k", (float)k );
    ofRect(0,0,width, height);
    shader.end();
    pingPong.dst->end();
    pingPong.swap();

I think ofRect is simply doing what in @patricio addon is called renderFrame https://github.com/patriciogonzalezvivo/ofxFX/blob/master/src/ofxFXObject.cpp#L442

This is the image that should be affected by the reaction diffusion (sorry i did not have enough fantasy while adding colors on gimp :wink: )

But when i launch the app is fading from green to red and then it remains red

I do not know if it is a shader’s problem or something related to the way I’m using the 2 fbos.

If someone could just confirm that the way I’m feeding the buffers is correct, that would also be good.

Hey @edapx,

I think I found a solution. I had similar issues when passing textures to my shaders and I think the problem is that your are passing in your src texture in location 0. Instead you can do the following:

void ofApp::update(){
    pingPong.dst->begin();
    shader.begin();
    shader.setUniformTexture("prevTexture", output.getTexture(), 1);
    shader.setUniform1f( "ru", (float)ru);
    shader.setUniform1f( "rv", (float)rv);
    shader.setUniform1f( "f", (float)f );
    shader.setUniform1f( "k", (float)k );
    pingPong.src->draw(0, 0); // draw the source texture here!!!
    shader.end();
    pingPong.dst->end();
    pingPong.swap();
}

In your fragment shader, you can then access the texture through uniform sampler2DRect tex0. So replacing uniform sampler2DRect prevBuffer yields the following fragment shader.

#version 150
#define KERNEL_SIZE 9    

float kernel[KERNEL_SIZE];    
vec2 offset[KERNEL_SIZE];

uniform sampler2DRect tex0; // <-- ping pong source texture
uniform sampler2DRect prevTexture; // U := r, V := g, other channels ignored
uniform float ru;          // rate of diffusion of U
uniform float rv;          // rate of diffusion of V    
uniform float f;           // some coupling parameter    
uniform float k;           // another coupling parameter

in vec2 vTexCoord;
out vec4 vFragColor;

void main(void)    
{    
    kernel[0] = 0.707106781;    
    kernel[1] = 1.0;    
    kernel[2] = 0.707106781;    
    kernel[3] = 1.0;    
    kernel[4] =-6.82842712;    
    kernel[5] = 1.0;    
    kernel[6] = 0.707106781;    
    kernel[7] = 1.0;    
    kernel[8] = 0.707106781;    

    offset[0] = vec2( -1.0, -1.0);
    offset[1] = vec2(  0.0, -1.0);
    offset[2] = vec2(  1.0, -1.0);

    offset[3] = vec2( -1.0, 0.0);
    offset[4] = vec2(  0.0, 0.0);
    offset[5] = vec2(  1.0, 0.0);

    offset[6] = vec2( -1.0, 1.0);
    offset[7] = vec2(  0.0, 1.0);
    offset[8] = vec2(  1.0, 1.0);

    vec2 texColor      = texture(tex0, vTexCoord).rb;
    float srcTexColor   = texture( prevTexture, vTexCoord ).r;

    vec2 laplace        = vec2( 0.0, 0.0 );

    for( int i=0; i<KERNEL_SIZE; i++ ){    
        vec2 tmp    = texture( tex0, vTexCoord + offset[i] ).rb;
        laplace     += tmp * kernel[i];
    }    

    float F     = f + srcTexColor * 0.025 - 0.0005;    
    float K     = k + srcTexColor * 0.025 - 0.0005;    

    float u     = texColor.r;
    float v     = texColor.g  + srcTexColor * 0.5;
    float uvv   = u * v * v;
    float du    = ru * laplace.r - uvv + F * (1.0 - u);
    float dv    = rv * laplace.g + uvv - (F + K) * v;
    u += du*0.6;
    v += dv*0.6;

    vFragColor = vec4( clamp( u, 0.0, 1.0 ), 1.0 - u/v, clamp( v, 0.0, 1.0 ), 1.0 );
}

I ran a quick test and results are very different. :slight_smile: And just to make sure I cleared all buffers in setup():

void ofApp::setup(){
    // check and allocation...
    
    pingPong.clear();
    
    pingPong.src->begin();
    image.draw(0,0);
    pingPong.src->end();
    
    output.begin();
    ofClear(0, 0, 0, 255);
    output.end();
}

Hope this helps. Hence I’m new to writing shaders myself I can’t really explain why assigning a texture to location 0 does not work or is problematic at least.

Thank you so much @dcb, it works :wink:
Compared to the code that you posted, I’ve just changed the update method adding the texture from the pingpong.src

    pingPong.dst->begin();
    shader.begin();
    shader.setUniformTexture("prevTexture", pingPong.src->getTexture(), 0 );
    shader.setUniformTexture("tex0", output.getTexture(), 1 );
    shader.setUniform1f( "ru", (float)ru);
    shader.setUniform1f( "rv", (float)rv);
    shader.setUniform1f( "f", (float)f );
    shader.setUniform1f( "k", (float)k );
    pingPong.src->draw(0, 0); // draw the source texture here!!!
    shader.end();
    pingPong.dst->end();
    pingPong.swap();

and this part in the fragment shader

    vec2 texColor      = texture(prevTexture, vTexCoord).rb;
    float srcTexColor  = texture(tex0, vTexCoord ).r;

    vec2 laplace        = vec2( 0.0, 0.0 );

    for( int i=0; i<KERNEL_SIZE; i++ ){
        vec2 tmp    = texture( prevTexture, vTexCoord + offset[i] ).rb;
        laplace     += tmp * kernel[i];
    }

I’ve added this piece of code to see if the pingpong.src was working

void ofApp::mousePressed(int x, int y, int button){
    pingPong.src->begin();
    ofSetColor(ofNoise( ofGetElapsedTimef() )*255);
    ofDrawCircle(x, y, 3);
    pingPong.src->end();
}

And it works. I’ve pushed the code to the repository linked above in case you want to have a look at the code. In the next days I will add a gui to play with some presets.

Cool! Please let me know when the code is online – just checked the repo, but it was a day old.

Hey @dcb, it’s online, with a GUI to test different presets.
bye

Hey @edapx.

Thank you for notifying me. I just played around with it a little bit. Looks really neat. :+1:

One thing though that I can’t get to work: loading your presets in JSON format. I get ofXml errors from the Serializer. And I tried it in oF 0.9.3 and 0.9.7, same result – I’m on OS X. Is there maybe an addon missing so ofxGui loads JSON files? I was not aware that is possible. Or is this maybe a Mac issue?

Yes, ofxGui support json files. That’s strange, I’m also on OS X and I’m using the nightly release from 20161107, it looks like is is 0.9.7. Probably it contain some fixes?
I will try later with the latest one not from nightly and I will let you know.

It seems to be a nightly release feature. I just downloaded the release and now it works on my computer too.