Using ofMesh and ofShader together

I’m working on an application that imports ofMesh objects from an external file, and then texture maps them. Using ofMesh and ofImage, this works pretty well:

However, I’d like to use a shader to modify the textures. if I enable an ofShader to texture the meshes (similar to ofBook tutorial), then my objects disappear! (I’m drawing the Wireframe on top of the textured versions to help me locate where everything should be).

I’m not entirely sure what’s going on / why this isn’t just working as a passthrough shader, though my best guess is that openFrameworks’ default values for texcoord probably isn’t what I want. Does anyone have experience with using shaders to produce textures for an ofMesh and can provide some guidance on how to handle this?

Vertex Shader:

#version 150

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 textureMatrix;
uniform mat4 modelViewProjectionMatrix;

in vec4 position;
in vec4 color;
in vec4 normal;
in vec2 texcoord;

out vec2 varyingtexcoord;

void main() {
    varyingtexcoord = vec2(texcoord.x, texcoord.y);
    gl_Position = modelViewProjectionMatrix * position;
}

Fragment Shader:

#version 150

uniform sampler2D image;
in vec2 varyingtexcoord;
out vec4 outputColor;

void main() {
    outputColor = texture2D(image, varyingtexcoord);
}

ofApp.cpp

void ofApp::setup(){
    ofSetLogLevel(OF_LOG_VERBOSE);

    ofEnableNormalizedTexCoords();
    ofDisableArbTex();

    mModel = new ofxAssimpModelLoader();
    mModel->loadModel("models/screens.3ds");

    for (int i = 0; i < mModel->getMeshCount(); i++) {
        ofMesh mesh = mModel->getMesh(i);
        mMeshes.push_back(std::make_shared<ofMesh>(mesh));

        std::shared_ptr<ofImage> img = std::make_shared<ofImage>();
        char filename[100];
        sprintf(filename, "images/%d.png", i);
        bool result = img->load(filename);
        if (!result) {
            std::printf("Image failed to load!");
        }
        mImages.push_back(img);
    }

    mShader.load("glsl/passthru.vert", "glsl/tint.frag");
}

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

}

//--------------------------------------------------------------
void ofApp::draw(){
    ofBackground(50, 50, 50);

    ofPushMatrix();
    ofTranslate(ofGetWidth() / 2.0, ofGetHeight() / 2.0, 0.0);
    ofScale(5.0, 5.0, 5.0);

    for (int i = 0; i < mModel->getMeshCount(); i++) {
        mShader.begin();  // <---- THESE LINES OF CODE BREAK THINGS :(
        mImages[i]->bind();
        mMeshes[i]->draw();
        mImages[i]->unbind();
        mShader.end();
    }

    for (auto mesh : mMeshes) {
        mesh->drawWireframe();
    }

    ofPopMatrix();
}

Hi there!

Without testing, I think you have two changes to do.

First, if you call ofDisableArbTex it already enables normalized texture coordinates. Since you are using normalized coords, you need to declare your uniform as sampler2D and not as sampler2DRect. This is your main issue.

And I think your for loop should be something more like this:

mShader.begin();

for (int i = 0; i < mModel->getMeshCount(); i++) { 
        mShader.setUniformTexture("image", mImages[i]->getTextureReference(), 0);
        mMeshes[i]->draw();        
 }

mShader.end();

This will work as you intend. Since you are doing the binding through the shader, you don’t need to bind it.

Unfortunately that didn’t seem to be the solution; I removed the extra ofNormalizedTextureCoordinates() call and switched to sampler2D, but I still get empty meshes with no texture.

did you try moving the bind and unbind calls for the texture inside the shader calls?

I have; it seems like even simple fragment shaders like gl_FragColor = vec4(1,0,0,1); don’t work, so I’m inclined to think there’s something odd going on with coordinates being fed to the shader or perhaps some GL setting.

Some more testing shows that even using ofDrawRectangle produces the same behavior of the rectangle “disappearing”, which suggests some problem further up the pipeline.

Hmmm. Definitely what feels like a bit of magic happening here.

It looks like the default attributes weren’t getting set properly (even when changing how the ProgrammableRenderer runs), and when I ran just my fragment shader:

    mShader.setupShaderFromFile(GL_FRAGMENT_SHADER, "glsl/tint.frag");
    mShader.linkProgram();

And used this shader, using the built-in types rather than the the default attributes

uniform sampler2D image;

void main (void) {
    gl_FragColor = texture2D(image, gl_TexCoord[0].st);
}

everything worked. For the short term, I guess I’m all set, but it seems like I need to put some time into reading about how this works more in-depth to understand what really went wrong.

A few recommendations:

If you do ofShader::load(), it will do everything for you.

If you do ofShader::setupShaderFromFile() you need to call ofShader::linkProgram(). And if you changed the main.cpp to openGL 3+, you also have to call ofShader::bindDefaults().

So… If you are using #version 150, it means you are in openGL 3. So you can’t do gl_FragColor = vec4(1,0,0,1). You always have to use an out vec4.

Furthermore, I had some issues with a Windows + Nividia GPU where I couldn’t, at all, use ints only floats. So, to be extra cautious, I recommend you to do outputColor = vec4(1.0, 0.0, 0.0 ,1.0). Shaders love float, ints… Not so much.

(Just to be clear, it wasn’t on OF. I was doing a few shaders for some folks working on GameMaker.)

What I wrote previously should work with no issues. Did you change the openGL version in main to 3.2 or something similiar?

I think I was having some issue with OpenGL3 compatibility; I tried to follow some of the other examples that would compile and run, but I think that I was still forgetting to set something – probably ofShader::bindDefaults().

Just testing it – it looks like bindDefaults was the issue, and if I convert everything back to OpenGL3, it runs just fine. Oddly enough, the ofBook chapter doesn’t mention bindDefaults but runs OpenGL 3+.

Thanks so much for your help!

1 Like