Drawing Fbo to Screenspace Quad with Shader

I’m trying to draw one object to an Fbo, set that Fbo’s texture to a shader, then draw a screen-sized quad using my shader to apply that Fbo texture to the quad.

I cannot make this work. This is part of a more complicated use-case, but this step is the one that’s really tripping me up. Here’s a minimal example to show just this bit of the process.

A couple notes. I have tried both sampler2D and sampler2DRect in the fragment shader, binding the texture outside of the shader with colorFbo.getTextureReference().bind() — using tex0 as the name in the shader in this case — and setting it with setUniformTexture() inside of the finalShader.begin() — finalShader.end() in draw(). ofDisableArbTex() appears to have no effect.
I have also tried ignoring the camera for the second half of draw() and replacing the screenQuad with ofDrawRectangle(0,0,ofGetWidth(), ofGetHeight()); This still just gives me a window filled with the default solid dark grey.

main.cpp

#include "ofMain.h"
#include "ofApp.h"

//========================================================================
int main( ){
    
    ofGLFWWindowSettings settings;
    settings.setGLVersion(3, 2); //we define the OpenGL version we want to use
    settings.setSize(1024, 768);
    ofCreateWindow(settings);
    // this kicks off the running of my app
    ofRunApp(new ofApp());

}

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    //ofDisableArbTex();
    colorFbo.allocate(ofGetWidth(), ofGetHeight());
    
    sphere.set(2.0f, 5);
    sphere.setPosition(glm::vec3(0,0,0));
    
    camera.setNearClip(0.1f);
    camera.setFarClip(200000.0f);
    camera.move(0,0,15);
    
    screenQuad.setMode(OF_PRIMITIVE_TRIANGLES);
    screenQuad.addVertex(glm::vec3(0,0,0));
    screenQuad.addVertex(glm::vec3(ofGetWidth(),0,0));
    screenQuad.addVertex(glm::vec3(ofGetWidth(),ofGetHeight(),0));
    screenQuad.addVertex(glm::vec3(0,ofGetHeight(),0));
    
    ofIndexType indices[6] = {0,1,2,2,0,3};
    screenQuad.addIndices(indices, 6);
    
    finalShader.load("final");
}

//--------------------------------------------------------------
void ofApp::update(){

}

//--------------------------------------------------------------
void ofApp::draw(){
    colorFbo.begin();
        camera.begin();
            ofSetColor(255,255,255);
            sphere.draw();
        camera.end();
    colorFbo.end();
    
    colorFbo.getTextureReference().bind();
    camera.begin();
    finalShader.begin();
        //finalShader.setUniformTexture("colorTexture", colorFbo.getTexture(0), 1);
        finalShader.setUniform3f("iResolution", ofGetWidth(), ofGetHeight(), 4.0f);
        ofSetColor(255,255,255);
        screenQuad.draw();
    finalShader.end();
    camera.end();
}

ofApp.h

#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();
		
    ofIcoSpherePrimitive sphere;
    
    ofFbo colorFbo;
    
    ofShader finalShader;
    
    ofMesh screenQuad;
    
    ofEasyCam camera;
};

bin/data/final.vert

#version 150

precision highp float;
in vec4 position;
in vec2 texcoord;
uniform mat4 modelViewProjectionMatrix;
out vec2 varyingtexcoord;
void main() {
    varyingtexcoord = texcoord;
    gl_Position = modelViewProjectionMatrix * position;
}

bin/data/final.frag

#version 150

uniform sampler2DRect tex0;
// uniform sampler2DRect colorTexture;
in vec2 varyingtexcoord;
uniform vec3 iResolution;
out vec4 fragColor;

void main(){
    fragColor = texture(tex0, varyingtexcoord);
}

This seems incredibly simple, but for some reason I cannot make this work. Any help would be much appreciated!

Hi @drooJayyy , I’m thinking that the texcoord that comes out of oF and into final.frag is coming from the texture coordinates in screenQuad (an ofMesh). A call of .draw() on something with texture coordinates will send the ones for that particular object out to the shader as texcoord. So try setting the texture coordinates of screenQuad. I’d recommend using normalized values for sampler2D, and non-normalized values for sampler2DRect. You could also use an ofPlanePrimitive for screenQuad, which has some functions for setting texture coordinates.

Also its not a great idea to read and write to the same ofFbo. Using a pair of ofFbo (sometimes called a ping-pong pair) is a nice way to do this, where you can read from one and write to the other. And std::swap() seems to work with ofFbo if you need to swap them.

If you use a sampler2D in the fragment shader, its texture coordinates will be normalized (0.0 - 1.0), and you’ll probably want to call ofDisableArbTex() somewhere in ofApp::setup(). This tells oF to use normalized texcoord for textures, and they’ll show up in the shader that way with the texture. I’m not sure if it matters if the texcoords are coming from an ofMesh, but it will if they are coming from something with an ofTexture in it (ofImage, ofFbo, etc). A sampler2DRect has non-normalized coordinates.

Finally, calling .bind() or .draw() on something (the last thing) with a texture will send it into the shader as tex0. So you should be able to access colorFbo in the shader as tex0, unless you call .bind() or .draw() on something else with a texture before ofShader::end().