How to tweak the default vertex and fragment shaders?

Hi! I’d like to make changes to the default shader currently being used. I would like to save the vert + frag source to a text file, make changes, then use my modified versions.

The programmable renderer offers getCurrentShader() but I think ofGLRenderer doesn’t.

How can I get the shader sources currently in use? Maybe logging them at the bottom of ofMaterial::initShaders() ?

Update: by studying the code I get the impression ofMaterial is for programmable renderer only. Is that the case?

You can’t modify a shader that is compiled already, you can create one with the same source modify it and use it though.

ofMAterial works with gl2 but doesn’t use shaders in that case and only uses the vertex phong shading that old opengl offered. in gl3+ it uses custom shaders that do per fragment phong shading.

If you want to modify how it works under gl3+ you can use ofMaterialSettings to set it up: https://openframeworks.cc/documentation/gl/ofMaterialSettings/ and the postFragment property to specify shader source that will be executed after the light contribution and reflectance calculations

Thank you. I didn’t want to modify a compiled shader, just get its source, save it to disk, make changes, then use it.

In a nutshell, what I’m trying to do is deform shapes (vertex shaders) while still using ofxPostProcessing. I tried a fork of ofxPostProcesssing that works with programmable pipeline, but couldn’t make SSAO look good.

Then took the other route: reconstructed the #120 shaders by looking at the phong files and the preprocessing that oF does. My new shaders load without errors but output nothing, even with gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);. As soon as I comment out mynewshader.begin() and mynewshader.end() my sculpture is there with its textures and two lights.

When using my newly created shader, is modelViewProjectionMatrix still set by oF? light uniforms? camera uniforms? Just trying to find out why there’s no rendering while.

I realized that I had to change

gl_Position = modelViewProjectionMatrix * position;

to

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

to see a flat color. Now I need to figure out what are all the correct names for these:

uniform mat4 modelViewMatrix;
uniform mat4 modelMatrix;
uniform mat4 textureMatrix;
uniform mat4 normalMatrix;
uniform vec4 mat_ambient;
uniform vec4 mat_diffuse;
uniform vec4 mat_specular;
uniform vec4 mat_emissive;
uniform float mat_shininess;
uniform vec4 global_ambient;

Are those uniform set by openFrameworks?

yes but really if you are going to use shaders it’s much better to use gl3 / the programmable renderer

I tried making ofxPostProcessing SSAO work with the programmable render. I’m using this fork: https://github.com/leozimmerman/ofxPostProcessing/tree/programmable

Many shaders already work, but SSAO does not. This is my non-working attempt:

/*
 *  SSAOPass.h
 *
 *  Copyright (c) 2013, satcy, http://satcy.net
 *  All rights reserved. 
 *  
 *  Redistribution and use in source and binary forms, with or without 
 *  modification, are permitted provided that the following conditions are met: 
 *  
 *  * Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *  * Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution. 
 *  * Neither the name of Neil Mendoza nor the names of its contributors may be used 
 *    to endorse or promote products derived from this software without 
 *    specific prior written permission. 
 *  
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 *  POSSIBILITY OF SUCH DAMAGE. 
 *
 */
#include "SSAOPass.h"

namespace itg
{
    SSAOPass::SSAOPass(const ofVec2f& aspect, bool arb, float cameraNear, float cameraFar, float fogNear, float fogFar, bool fogEnabled, bool onlyAO, float aoClamp, float lumInfluence) :
        cameraNear(cameraNear), cameraFar(cameraFar), fogNear(fogNear), fogFar(fogFar), fogEnabled(fogEnabled), onlyAO(onlyAO), aoClamp(aoClamp), lumInfluence(lumInfluence), RenderPass(aspect, arb, "SSAO")
    {

        ostringstream oss;
        oss << "#version 330" << endl << this->progVertShaderSrc;
        shader.setupShaderFromSource(GL_VERTEX_SHADER, oss.str());

        string fragShaderSrc = STRINGIFY(
            in vec2 vUv;
            out vec4 fragColor;

            uniform float cameraNear;
            uniform float cameraFar;

            uniform float fogNear;
            uniform float fogFar;

            uniform bool fogEnabled;		// attenuate AO with linear fog
            uniform bool onlyAO; 		// use only ambient occlusion pass?

            uniform vec2 size;			// texture width, height
            uniform float aoClamp; 		// depth clamp - reduces haloing at screen edges

            uniform float lumInfluence;  // how much luminance affects occlusion

            uniform sampler2D tDiffuse;
            uniform sampler2D tDepth;

            //const float PI = 3.14159265;
            const float DL = 2.399963229728653; // PI * ( 3.0 - sqrt( 5.0 ) )
            const float EULER = 2.718281828459045;

            // helpers

            float width = size.x; 	// texture width
            float height = size.y; 	// texture height

            float cameraFarPlusNear = cameraFar + cameraNear;
            float cameraFarMinusNear = cameraFar - cameraNear;
            float cameraCoef = 2.0 * cameraNear;

            // user variables

            const int samples = 16; 		// ao sample count
            const float radius = 5.0; 	// ao radius

            const bool useNoise = false; 		 // use noise instead of pattern for sample dithering
            const float noiseAmount = 0.0003; // dithering amount

            const float diffArea = 0.4; 		// self-shadowing reduction
            const float gDisplace = 0.4; 	// gauss bell center

            const vec3 onlyAOColor = vec3( 1.0, 0.7, 0.5 );
            //const vec3 onlyAOColor = vec3( 1.0, 1.0, 1.0 );,


            // RGBA depth

            float unpackDepth( const in vec4 rgba_depth ) {
                const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );
                float depth = dot( rgba_depth, bit_shift );
                return depth;
            }

            // generating noise / pattern texture for dithering

            vec2 rand( const vec2 coord ) {

                vec2 noise;

                if ( useNoise ) {
                 
                 float nx = dot ( coord, vec2( 12.9898, 78.233 ) );
                 float ny = dot ( coord, vec2( 12.9898, 78.233 ) * 2.0 );
                 
                 noise = clamp( fract ( 43758.5453 * sin( vec2( nx, ny ) ) ), 0.0, 1.0 );
                 
                } else {
                 
                 float ff = fract( 1.0 - coord.s * ( width / 2.0 ) );
                 float gg = fract( coord.t * ( height / 2.0 ) );
                 
                 noise = vec2( 0.25, 0.75 ) * vec2( ff ) + vec2( 0.75, 0.25 ) * gg;
                 
                }

                return ( noise * 2.0  - 1.0 ) * noiseAmount;

            }

            float doFog() {
                //vec2 vUv = gl_TexCoord[0].st;
                float zdepth = unpackDepth( texture( tDepth, vUv ) );
                float depth = -cameraFar * cameraNear / ( zdepth * cameraFarMinusNear - cameraFar );

                return smoothstep( fogNear, fogFar, depth );

            }

            float readDepth( const in vec2 coord ) {

                //return ( 2.0 * cameraNear ) / ( cameraFar + cameraNear - unpackDepth( texture2D( tDepth, coord ) ) * ( cameraFar - cameraNear ) );,
                return cameraCoef / ( cameraFarPlusNear - unpackDepth( texture( tDepth, coord ) ) * cameraFarMinusNear );


            }

            float compareDepths( const in float depth1, const in float depth2, inout int far ) {

                float garea = 2.0; 						 // gauss bell width
                float diff = ( depth1 - depth2 ) * 100.0; // depth difference (0-100)

                // reduce left bell width to avoid self-shadowing

                if ( diff < gDisplace ) {
                 
                 garea = diffArea;
                 
                } else {
                 
                 far = 1;
                 
                }

                float dd = diff - gDisplace;
                float gauss = pow( EULER, -2.0 * dd * dd / ( garea * garea ) );
                return gauss;

            }

            float calcAO( float depth, float dw, float dh ) {
                //vec2 vUv = gl_TexCoord[0].st;
                float dd = radius - depth * radius;
                vec2 vv = vec2( dw, dh );
                vec2 coord1 = vUv + dd * vv;
                vec2 coord2 = vUv - dd * vv;

                float temp1 = 0.0;
                float temp2 = 0.0;
                int far = 0;
                temp1 = compareDepths( depth, readDepth( coord1 ), far );

                // DEPTH EXTRAPOLATION

                if ( far > 0 ) {
                 temp2 = compareDepths( readDepth( coord2 ), depth, far );
                 temp1 += ( 1.0 - temp1 ) * temp2;
                }
                return temp1;
            }
            void main() {
                //vec2 vUv = gl_TexCoord[0].st;
                vec2 noise = rand( vUv );
                float depth = readDepth( vUv );
                float tt = clamp( depth, aoClamp, 1.0 );
                float w = ( 1.0 / width )  / tt + ( noise.x * ( 1.0 - noise.x ) );
                float h = ( 1.0 / height ) / tt + ( noise.y * ( 1.0 - noise.y ) );
                float pw;
                float ph;
                float ao;
                float dz = 1.0 / float( samples );
                float z = 1.0 - dz / 2.0;
                float l = 0.0;
                for ( int i = 0; i <= samples; i ++ ) {
                 float r = sqrt( 1.0 - z );
                 pw = cos( l ) * r;
                 ph = sin( l ) * r;
                 ao += calcAO( depth, pw * w, ph * h );
                 z = z - dz;
                 l = l + DL;
                }
                ao /= float( samples );
                ao = 1.0 - ao;
                if ( fogEnabled ) {
                 ao = mix( ao, 1.0, doFog() );
                }
                vec3 color = texture( tDiffuse, vUv ).rgb;
                vec3 lumcoeff = vec3( 0.299, 0.587, 0.114 );
                float lum = dot( color.rgb, lumcoeff );
                vec3 luminance = vec3( lum );
                vec3 final = vec3( color * mix( vec3( ao ), vec3( 1.0 ), luminance * lumInfluence ) );
                if ( onlyAO ) {
                    final = onlyAOColor * vec3( mix( vec3( ao ), vec3( 1.0 ), luminance * lumInfluence ) );
                }
                //final = vec3(0.5 * depth); // tmp
                fragColor = vec4( final, 1.0 );
            }
        );
        
        oss.str(""); // clear
        oss << "#version 330" << endl;
        oss << fragShaderSrc;

        shader.setupShaderFromSource(GL_FRAGMENT_SHADER, oss.str());
        shader.linkProgram();        
    }
    

    void SSAOPass::render(ofFbo& readFbo, ofFbo& writeFbo, ofTexture& depthTex)
    {
        writeFbo.begin();
                
        shader.begin();
        
        shader.setUniformTexture("tDiffuse", readFbo.getTexture(), 0);
        shader.setUniformTexture("tDepth", depthTex, 1);
        shader.setUniform2f("size", writeFbo.getWidth(), writeFbo.getHeight());
        shader.setUniform1f("cameraNear", cameraNear);
        shader.setUniform1f("cameraFar", cameraFar);
        shader.setUniform1f("fogNear", fogNear);
        shader.setUniform1f("fogFar", fogFar);
        shader.setUniform1i("fogEnabled", fogEnabled ? 1 : 0 );
        shader.setUniform1i("onlyAO", onlyAO ? 1 : 0);
        shader.setUniform1f("aoClamp", aoClamp);
        shader.setUniform1f("lumInfluence", lumInfluence);
        
        texturedQuad(0, 0, writeFbo.getWidth(), writeFbo.getHeight());
        
        shader.end();
        writeFbo.end();
    }
}

What it currently does is darkening the rendered image about 5%.
The non-programmable version works fine.

I had to change the example a bit. Without an ofMaterial the light was not having any effect.
I also made the boxes overlap to notice the SSAO.

#include "ofApp.h"

void ofApp::setup()
{
    ofBackground(0);
    ofSetVerticalSync(true);
    ofSetFrameRate(60);
    ofSetSmoothLighting(true);
    ofEnableNormalizedTexCoords();
    ofDisableArbTex();

    ofSetCoordHandedness(OF_RIGHT_HANDED);
    
    post.init(ofGetWidth(), ofGetHeight());
    post.createPass<PixelatePass>()->setEnabled(false);
    post.createPass<RGBShiftPass>()->setEnabled(false);
    post.createPass<KaleidoscopePass>()->setEnabled(false);
    post.createPass<FxaaPass>()->setEnabled(false);
    post.createPass<BloomPass>()->setEnabled(false);

    auto ssao = post.createPass<SSAOPass>();
    ssao->setEnabled(false);
    //ssao->setOnlyAO(true);
    //ssao->setArb(false);
    //ssao->setLumInfluence(1.5f);
    //ssao->setFogFar(200);
    //ssao->setCameraFar(200);


    for (unsigned i = 0; i < NUM_BOXES; ++i){
        posns.push_back(ofVec3f(ofRandom(-100, 100), ofRandom(-100, 100), ofRandom(-100, 100)));
        cols.push_back(ofColor::fromHsb(255.0f * i / NUM_BOXES, 155, 155, 255));
    }
    boxMesh = ofMesh::box(50, 50, 50);

    light.setPointLight();
    light.setPosition(500, 500, 1000);
    light.setDiffuseColor(ofFloatColor(0.95f, 0.9f, 0.7f));
    
}
//--------------------------------------
void ofApp::update(){
    ofSetWindowTitle(ofToString(ofGetFrameRate()));

}
//--------------------------------------
void ofApp::draw(){
    
    glPushAttrib(GL_ENABLE_BIT);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

    //ofEnableDepthTest();
    light.enable();
    ofEnableLighting();
        
    post.begin(cam);
    drawScene();
    post.end();

    glPopAttrib();
    //ofDisableDepthTest();
    ofDisableLighting();
        
    post.debugDraw();
    
    drawHelp();
}
//--------------------------------------
void ofApp::drawScene(){
    
    for (unsigned i = 0; i < posns.size(); ++i){

        material.begin();
        material.setDiffuseColor(cols[i]);
        //ofSetColor(cols[i]);
        ofPushMatrix();
        ofTranslate(posns[i]);
            boxMesh.draw();
        ofPopMatrix();
        material.end();

    }
    
    //ofDrawAxis(100);

}
//--------------------------------------
void ofApp::drawHelp(){
    
    ofPushStyle();
    
    ofSetColor(255);
    
    ofDrawBitmapString("Is Programmable: " + ofToString(ofIsGLProgrammableRenderer()), 10, 20);
    
    for (unsigned i = 0; i < post.size(); ++i){
        if (post[i]->getEnabled()) ofSetColor(0, 255, 255);
        else ofSetColor(255, 0, 0);
        ostringstream oss;
        oss << i << ": " << post[i]->getName() << (post[i]->getEnabled()?" (on)":" (off)");
        
        ofDrawBitmapString(oss.str(), 10, 30 * (i + 2));
    }
    
    ofPopStyle();
}
//--------------------------------------

void ofApp::keyPressed(int key)
{
    unsigned idx = key - '0';
    if (idx < post.size()) post[idx]->setEnabled(!post[idx]->getEnabled());
}

I leave it here in case someone wants to try :slight_smile:

1 Like

Hi! I found it quite challenging to take a working project that uses a few lights and textures and then make changes to the vertex and fragment shaders.

The first challenge I had was to get the source of the shader that was actually running, since it is compiled when the program runs by oF. I slowly reconstructed it by looking at ofMaterial and ofGLProgrammableRenderer.

I spent a few hours debugging until I figured out that oF does not set normalMatrix, so all shapes where black. I had to add the following line before rendering each transformed shape:

fx.setUniformMatrix4f("normalMatrix", ofGetCurrentRenderer()->getCurrentNormalMatrix());

Then I figured out that the texture uniform is not tex0 but src_tex_unit0.

Any suggestions on how to get access to the shaders of a working project and make changes to them (not on run time, but while developing)?

Not sure what you mean with “shaders of a working project” the shaders that OF use internally are in the OF source in the shaders folder mostly inside GL. The problems you seem to have are related to uploading the correct uniforms to the graphics card.

I have mostly figured it out, but I was thinking it would be nice to make the process easier for others too.

With “shaders of a working project” I mean: I create an oF project. It uses some lights, textures, 3D models. It works great (1).

But how can I modify the shaders being used? There are pieces of them in phong.vert, phong.frag and in various files in the GL folder. I’m wondering if the process of going from default shaders to tweaking them could be easier by describing how to copy the used shaders into two files (vert, frag), load them, and what oF functions need to be called so the behavior is identical to (1).

This set me back too. It seems that the normalMatrix is only uploaded if a material is enabled and then the normal matrix is uploaded to that material shader.

I noticed in ofMaterialSettings there is a postFragment string that can be set, which gets called after all the lighting and material calculations. And there are customUniforms that you can access from that function. This seems like what you might be after for a fragment shader, but didn’t notice anything for vertex shaders.

Yes the normal matrix is not calculated unless there’s lights cause it’s not used otherwise so it would be pretty expensive to calculate it for every draw call.

I was mentioning postFragment and setUniforms before, it’s documented mostly in ofMAterialSettings https://openframeworks.cc/documentation/gl/ofMaterialSettings/ and it’s the supported way to modify the behaviour of a material, we could add other entry points to add shader routines that would be run at different stages but documenting the inner workings of OF seems a little out of the scope, if only cause we already have trouble documenting the public API :slight_smile:

ofMaterial uses a shader intenally to calculate phong shading but it’s not supposed to be used as a shader, there’s really lots of examples of how to do phong shading that you can copy paste if you want to modify them more than what a few entry points in the material would allow

I ended up using the OF shaders as a starting point, simplified them and then added my own features (animated vert+frag mesh deformations).

Some things that were not obvious to me were: if I load my own shaders, how much will OF still do for me? Can I still use lights? ofSetColor, ofNoFill, transformations, ofxPostProcessing? Which attributes and uniforms will be set? I think both extremes are easy (either using the default rendering or using your own shaders) but combining the two was not trivial.

I was assuming it should be easy because with Processing it is: the shader files can be found in a folder and therefore cloning + tweaking them is trivial (if you know GLSL) but OF constructs more optimized shaders for different targets.

I still wish there was a method to save the shader sources currently in use so you can more easily move from default shaders to customized ones.

Thanks @arturo and @NickHardeman :slight_smile: I’ll post a video of the results soon.

1 Like