Rewriting Cinder's Reaction Diffusion example in openFrameworks

In my efforts to learn shaders better, I’m going through a bunch of simple classic shader examples and trying to reimplement them in openFrameworks. My latest re-implementation attempt is Cinder’s RDiffusion reaction-diffusion shader example.

I’ve sorted out how to get pingPong buffering working, but I’m confused by the addition of the the extra FBO ‘prevTexture’ in the shader. My understanding of how the three FBOs should interact is as follows:

-the two pingPong buffers swap data back and form, with the red channel being the data for the concentration of ‘u’ and the green channel is the concentration of ‘v’. It then would make sense that there exists a third buffer (prevTexture, or in my example, the fbo ‘output’) that takes the raw data and then allows you to visualize the data contained in the pingPong buffer without altering it.

However, this doesn’t seem to be the case. I can’t find where in the Cinder examples the extra texture is used except to load an initial image – why can’t the initial image just be loaded directly to the source buffer in the pingPong buffer?

In addition, the coloring scheme seems inconsistent. The fragColor seems to define u and red and v was blue (with 1.0 - u/v as green), but from the *incoming* texture, v is taken to be green. Is this intentional?

Furthermore, the addition of this texture buffer seems to break my example, but if I remove all references to prevTexture in the shader the shader outputs something resembling reaction-diffusion, but much more boring. It still doesn’t seem as dynamic and interesting as the cinder example.

Can someone help me understand what this extra buffer does, and why it’s so important to making the shader work well?

thanks!

nathan lachenmyer

testApp.cpp:

  
  
//--------------------------------------------------------------  
void testApp::setup(){  
    ofEnableSmoothing();  
    ofSetFrameRate(60);  
    ofSetVerticalSync(true);  
    width = ofGetWidth();  
    height = ofGetHeight();  
    shader.load("reaction");  
    output.allocate(width,height);  
    pingPong.allocate(width, height);  
      
    //draw the original image we care about  
    pingPong.src->begin();  
    ofSetColor(255, 128, 0);  
    ofCircle(ofRandom(width), ofRandom(height), 100);  
    ofCircle(ofRandom(width), ofRandom(height), 100);  
    ofCircle(ofRandom(width), ofRandom(height), 100);  
    pingPong.src->end();  
  
    //parameters  
    f = 0.0195f;  
    k = 0.066f;  
    dU = 0.25f;  
    dV = 0.04f;  
}  
  
//--------------------------------------------------------------  
void testApp::update(){  
    pingPong.dst->begin();  
    shader.begin();  
    setUniforms();  
    ofRect(0,0,width,height);  
    shader.end();  
    pingPong.dst->end();  
    pingPong.swap();  
}  
  
//--------------------------------------------------------------  
void testApp::draw(){  
   pingPong.src->draw(0,0);  
}  
  
//--------------------------------------------------------------  
void testApp::setUniforms(){  
    float resolution[] = {width, height};  
    float time = ofGetElapsedTimef();  
      
    //pass the time, resolution, and source buffer as uniforms to the shader  
    shader.setUniform1f("time",time);  
    shader.setUniform2fv("resolution",resolution);  
    shader.setUniform1f("ru",dU);  
    shader.setUniform1f("rv",dV);  
    shader.setUniform1f("f",f);  
    shader.setUniform1f("k",k);  
    shader.setUniformTexture("prevBuffer", pingPong.src->getTextureReference(), 0);  
    shader.setUniformTexture("prevTexture", output, 0);  
}  
  

reaction.frag:

  
  
float kernel[9];  
vec2 offset[9];  
                                     
uniform sampler2DRect prevBuffer;  
uniform sampler2DRect prevTexture;  
  
uniform float diffU;  
uniform float diffV;  
uniform float f;  
uniform float k;  
  
void main(void){  
float kernel[9];  
vec2 offset[9];  
                                     
uniform sampler2DRect prevBuffer;  
uniform sampler2DRect prevTexture;  
  
uniform float diffU;  
uniform float diffV;  
uniform float f;  
uniform float k;  
  
void main(void){  
     vec2 st   = gl_FragCoord.xy;  
     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 = texture2DRect(prevBuffer, st).rb;  
     float srcTexColor = texture2DRect(prevTexture, st).r;  
                                         
     vec2 lap = vec2( 0.0, 0.0 );                                         
  
     for( int i=0; i < 9; i++ ){  
       vec2 tmp = texture2DRect( prevBuffer, st + offset[i] ).rb;  
       lap += 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 = diffU * lap.r - uvv + F * (1.0 - u);  
     float dv = diffV * lap.g + uvv - (F + K) * v;  
                                         
     u += du * 0.6;  
     v += dv * 0.6;  
              
     gl_FragColor = vec4(clamp( u, 0.0, 1.0 ), 1.0 - u/v ,clamp( v, 0.0, 1.0 ), 1.0);  
}  
  

One thing to note, this:

  
 shader.setUniformTexture("prevBuffer", pingPong.src->getTextureReference(), 0);    
    shader.setUniformTexture("prevTexture", output, 0);  

uses the same texture location, it should be:

  
 shader.setUniformTexture("prevBuffer", pingPong.src->getTextureReference(), 0);    
    shader.setUniformTexture("prevTexture", output, 1);  

I think that second texture is just to have the previous frames values visible in the shader for calculations, since they’re just being used for lookups (as far as I can tell from the shader you posted here).