How to pass textures to a gl shader?

As an exercise in gl shaders, I’m trying to figure out how to send a texture to the fragment shader, and then use the fragment shader to blur it with a horizontal gaussian blur. Unfortunately, the ofShader documentation is a bit lacking, so I’ve spent a bunch of time reading through the source trying to piece together how to do this, without much success. Right now I can draw an image (a black circle on a white background) and then load the shader; but as soon as I add the shader the screen goes solid black.

I think that something isn’t quite right with

  
  shader.setUniformTexture("inputBuffer", fbo.getTextureReference(),1);   

; but I’m not really sure how else to pass the texture as a uniform.

Any suggestions?

blur_h.frag:

  
  
uniform sampler2D inputBuffer;  
uniform vec2 resolution;  
uniform float time;  
   
const float blurSize = 1.0/512.0;  
   
void main(void)  
{  
   vec4 sum = vec4(0.0);  
   vec2 position = (gl_FragCoord.xy / resolution.xy);  
   // blur in y (vertical)  
   // take nine samples, with the distance blurSize between them  
   sum += texture2D(inputBuffer, vec2(position.x - 4.0*blurSize, position.y)) * 0.05;  
   sum += texture2D(inputBuffer, vec2(position.x - 3.0*blurSize, position.y)) * 0.09;  
   sum += texture2D(inputBuffer, vec2(position.x - 2.0*blurSize, position.y)) * 0.12;  
   sum += texture2D(inputBuffer, vec2(position.x - blurSize, position.y)) * 0.15;  
   sum += texture2D(inputBuffer, vec2(position.x, position.y)) * 0.16;  
   sum += texture2D(inputBuffer, vec2(position.x + blurSize, position.y)) * 0.15;  
   sum += texture2D(inputBuffer, vec2(position.x + 2.0*blurSize, position.y)) * 0.12;  
   sum += texture2D(inputBuffer, vec2(position.x + 3.0*blurSize, position.y)) * 0.09;  
   sum += texture2D(inputBuffer, vec2(position.x + 4.0*blurSize, position.y)) * 0.05;  
   
   gl_FragColor = texture2D(inputBuffer, vec2(position.x, position.y));  
}  
  

testApp.cpp:

  
  
void testApp::setup(){  
  ofEnableSmoothing();  
  ofSetFrameRate(1);  
  //ofSetVerticalSync(true);  
  width = ofGetWidth();  
  height = ofGetHeight();  
  shader.load("blur_h");  
  fbo.allocate(width,height);  
  fbo.begin();  
  ofSetColor(0);  
  ofCircle(width/2, height/2, 100, 100);  
  fbo.end();  
}  
  
//--------------------------------------------------------------  
void testApp::update(){  
  
}  
  
//--------------------------------------------------------------  
void testApp::draw(){  
  fbo.begin();  
  shader.begin();  
  setUniforms();  
  //ofRect(0,0,width,height);  
  shader.end();  
  fbo.end();  
  
  fbo.draw(0,0,width,height);  
}  
  
//--------------------------------------------------------------  
void testApp::setUniforms(){   
  float resolution[] = {width, height};  
  float time = ofGetElapsedTimef();  
  
  shader.setUniform1f("time",time);  
  shader.setUniform2fv("resolution",resolution);  
  shader.setUniformTexture("inputBuffer", fbo.getTextureReference(),0);  
}  
  

Hey scottnla,

Try changing sampler2D to sampler2DRect, and texture2D to texture2DRect?

also you are writing to the same fbo you are reading from which is undefined afaik. Check out ping pong buffers to deal with that

trentbrooks: I tried changing the variables to sampler2DRect and texture2DRect, but there was no visible change in the behavior.

pants: Could you point me towards a good example of a simple ping pong buffer?

nathan

Take a look through examples/gl/GPUParticleSystemExample, it uses a ping pong buffer. They are pretty simple, just draw to one and pass the other one in as your texture. When you’re done call swap().

I tried to incorporate the pingPong buffer to make this work, but to no avail. if I just draw the source buffer after drawing to it in setup(), it works dandy – but as soon as I start using the shader things start breaking. I’m not sure if the problem is the shader at this point?

  
  
void testApp::setup(){  
    ofEnableSmoothing();  
    ofSetFrameRate(60);  
    ofSetVerticalSync(true);  
    width = ofGetWidth();  
    height = ofGetHeight();  
    shader.load("blur_h");  
    pingPong.allocate(width, height);  
      
    //draw the original image we care about  
    pingPong.src->begin();  
    ofSetColor(0);  
    ofCircle(width/2, height/2, 100, 100);  
    ofSetColor(255);  
    pingPong.src->end();  
}  
  
//--------------------------------------------------------------  
void testApp::update(){  
    //begin writing to the destination framebuffer  
    pingPong.dst->begin();  
    shader.begin();  
    //setuniforms  
    setUniforms();  
    shader.end();  
    pingPong.dst->end();  
      
    //swap frame buffers  
    pingPong.swap();  
}  
  
//--------------------------------------------------------------  
void testApp::draw(){  
    pingPong.dst->begin();  
    shader.begin();  
    //setuniforms  
    setUniforms();  
    ofRect(0,0,width,height);  
    shader.end();  
      
    //draw the destination frame buffer  
    pingPong.dst->draw(0,0);  
    pingPong.dst->end();  
      
    //swap frame buffers  
    pingPong.swap();  
}  
  
//--------------------------------------------------------------  
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.setUniformTexture("prevBuffer", pingPong.src->getTextureReference(), 0);  
}  
  

With the PingPong struct taken from the GPU Particle System example:

  
  
// Struct for doing PingPong quickly and easy  
//  
// Because on GPU you can«t write over the texture that you are reading we are  
// using to pair of ofFbo attached together on what we call pingPongBuffer   
// Learn more about Ping-Pong at:  
//  
// [http://www.comp.nus.edu/~ashwinna/docs/PingPong-FBO.pdf](http://www.comp.nus.edu/~ashwinna/docs/PingPong-FBO.pdf)  
// [http://www.seas.upenn.edu/~cis565/fbo.htm#setupgl4](http://www.seas.upenn.edu/~cis565/fbo.htm#setupgl4)  
//  
struct pingPongBuffer {  
public:  
    void allocate( int _width, int _height, int _internalformat = GL_RGBA, float _dissipation = 1.0f){  
        // Allocate  
        for(int i = 0; i < 2; i++){  
            FBOs[i].allocate(_width,_height, _internalformat );  
            FBOs[i].getTextureReference().setTextureMinMagFilter(GL_NEAREST, GL_NEAREST);  
        }  
          
        // Clean  
        clear();  
          
        // Set everything to 0  
        flag = 0;  
        swap();  
        flag = 0;  
    }  
      
    void swap(){  
        src = &(FBOs[(flag)%2]);  
        dst = &(FBOs[++(flag)%2]);  
    }  
      
    void clear(){  
        for(int i = 0; i < 2; i++){  
            FBOs[i].begin();  
            ofClear(0,255);  
            FBOs[i].end();  
        }  
    }  
      
    ofFbo& operator[](%20int%20n%20){ return FBOs[n];}  
      
    ofFbo   *src;       // Source       ->  Ping  
    ofFbo   *dst;       // Destination  ->  Pong  
private:  
    ofFbo   FBOs[2];    // Real addresses of ping/pong FBO«s    
    int     flag;       // Integer for making a quick swap  
};  
  

and the shader:

  
  
uniform sampler2DRect prevBuffer; // the texture2D with the scene you want to blur  
uniform vec2 resolution;  
uniform float time;  
  
void main(void)  
{  
    float blurSize = 1.0/resolution.y;  
    vec4 sum = vec4(0.0);  
    vec2 position = (gl_FragCoord.xy / resolution.xy);  
    // blur in y (vertical)  
    // take nine samples, with the distance blurSize between them  
    sum += texture2DRect(prevBuffer, vec2(position.x - 4.0*blurSize, position.y)) * 0.05;  
    sum += texture2DRect(prevBuffer, vec2(position.x - 3.0*blurSize, position.y)) * 0.09;  
    sum += texture2DRect(prevBuffer, vec2(position.x - 2.0*blurSize, position.y)) * 0.12;  
    sum += texture2DRect(prevBuffer, vec2(position.x - blurSize, position.y)) * 0.15;  
    sum += texture2DRect(prevBuffer, vec2(position.x, position.y)) * 0.16;       
    sum += texture2DRect(prevBuffer, vec2(position.x + blurSize, position.y)) * 0.15;  
    sum += texture2DRect(prevBuffer, vec2(position.x + 2.0*blurSize, position.y)) * 0.12;  
    sum += texture2DRect(prevBuffer, vec2(position.x + 3.0*blurSize, position.y)) * 0.09;  
    sum += texture2DRect(prevBuffer, vec2(position.x + 4.0*blurSize, position.y)) * 0.05;  
    gl_FragColor = sum;  
}  
  

coupla things:

  1. ARB is enabled by default, which means your texture coordinates aren’t normalized. Change these in your shader:
  
  
    float blurSize = 1.0;    
    vec2 position = gl_FragCoord.xy;  
  

you also don’t need to pass in resolution to normalize the coords. You can have a look here for more info on arb on/off and tex coords http://forum.openframeworks.cc/t/textured-mesh-shader-problem/13026/0

  1. in draw() you need to swap these 2 lines
  
  
    pingPong.dst->draw(0,0);    
    pingPong.dst->end();    
//becomes         
    pingPong.dst->end();    
    pingPong.dst->draw(0,0);    
  

  1. in setup() you are drawing the circle black, which is also the default colour in your shader. Change it to white or something non-zero otherwise the texture values will always be zero.

  2. a minor thing is that the stuff in update isn’t doing anything. You can remove it all, or move the stuff in draw() to update(), keeping the final pingPong->draw() in testApp::draw()

What happens is you set up your fbo and shader, then you need to send the vertex data to open gl. Each vertex gets sent to the vertex shader, then each pixel within the shape the vertexes define will run through the fragment shader. You are doing this correctly in testApp::draw() with the call to ofRect(0, 0, width, height), which sends 4 vertexes the size of the screen to be rendered through the pipeline (i.e. your shader).

When i am using ping pong fbos i usually call swap as soon as i finish rendering, and always draw src, so you might want an update/draw like this:

  
  
//--------------------------------------------------------------  
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);  
}  
  

Thanks so much for your help!

And for letting me flail a bit, because I definitely learned much more that way. Your explanations were really helpful.

thanks again!