Kuranes
Kuranes

Reputation: 197

Switching shaders in webgl

I have a problem with a program written in webgl and I don't know how to debug it because the browser console shows no errors. Webgl is drawing nothing at all.

I have the following set of shaders:

<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;

uniform sampler2D uSampler;

varying vec2 vTextureCoord;    
varying vec3 vEye;
varying vec3 vNormal;
uniform vec3 uLightDirection;    // Vector direccion de la luz
uniform vec3 uDirectionalColor; // Color de la luz direcional

uniform vec3 uColShadeless;
uniform vec3 uAmbientColor;
uniform float uKAmbiente;
uniform vec3 uColDifuso;
uniform float uKDifuso;
uniform vec3 uColEspecular;
uniform float uKEspecular;
uniform float uGlossiness;

void main(void) 
{
    vec3 normal = normalize(vNormal);
    float mLambert = max(dot(normal, uLightDirection), 0.0);
    vec3 vLuzLambert = uDirectionalColor * mLambert;

    vec3 r = 2.0 * max(dot(normal, uLightDirection), 0.0) * normal - uLightDirection;
    //vec3 r = reflect(uLightDirection, normal); // <- Da glossines del otro lado también
    float specular = pow(max(dot(r, normalize(vEye)), 0.0), uGlossiness) ;
    vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
    vec3 componenteShadeless = uColShadeless * textureColor.rgb * uColDifuso;             // luz autoiluminada * colores difusos
    vec3 componenteAmbiente = uKAmbiente * uAmbientColor * textureColor.rgb * uColDifuso; // k% * luz ambiente * colores difusos
    vec3 componenteDifusa = uKDifuso * textureColor.rgb * uColDifuso * vLuzLambert;
    vec3 componenteEspecular = uKEspecular * specular * uColEspecular ;
    gl_FragColor = vec4(componenteShadeless + componenteAmbiente + componenteDifusa + componenteEspecular, textureColor.a);
}
</script>

<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;

uniform mat4 uViewMatrix;
uniform mat4 uModelMatrix;
uniform mat4 uPMatrix;
uniform mat3 uNMatrix;

varying vec2 vTextureCoord;
varying vec3 vEye;
varying vec3 vNormal;

uniform vec2 aUVOffset;

void main(void) 
{

        // Transformamos al vértice al espacio de la cámara
        vec4 pos_camera_view = uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
        // Transformamos al vértice al espacio de la proyección
        gl_Position = uPMatrix * pos_camera_view;
        // Coordenada de textura
        vTextureCoord.x = aTextureCoord.x + aUVOffset.x;
        vTextureCoord.y = aTextureCoord.y + aUVOffset.y;

        // Para iluminación
        vEye = -vec3(pos_camera_view.xyz);
        vNormal = uNMatrix * aVertexNormal;
}
</script>

<script id="vs" type="x-shader/x-vertex">
        attribute vec3 aPositionL;
        attribute vec3 aNormalL;
        attribute vec3 aTangentL;
        attribute vec2 aTexCoord;

        uniform mat4 uMatrixMVP;
        uniform mat4 uMatrixMV;

        varying vec4 vPositionV;
        varying vec3 vNormalV;
        varying vec3 vTangentV;
        varying vec2 vTexCoord;

        attribute vec3 aVertexPosition;
        attribute vec3 aVertexNormal;
        attribute vec2 aTextureCoord;

        uniform mat4 uViewMatrix;
        uniform mat4 uModelMatrix;
        uniform mat4 uPMatrix;
        uniform mat3 uNMatrix;
        varying vec3 vNormal;

        uniform vec2 aUVOffset;
        void main(void) 
        {

        // Transformamos al vértice al espacio de la cámara
        vec4 pos_camera_view = uViewMatrix * uModelMatrix * vec4(aPositionL, 1.0);
        // Transformamos al vértice al espacio de la proyección
        gl_Position = uPMatrix * pos_camera_view;

        vNormal = uNMatrix * aVertexNormal;
        vPositionV = uMatrixMV * vec4(aPositionL, 1.0);
        vNormalV = (uMatrixMV * vec4(aNormalL, 0.0)).xyz;
        vTangentV = (uMatrixMV * vec4(aTangentL, 0.0)).xyz;
        vTexCoord = aTexCoord;
}
</script>

<script id="fs" type="x-shader/x-fragment">
        #ifdef GL_ES
        precision highp float;
        #endif

        uniform sampler2D uColorSampler;
        uniform sampler2D uNormalSampler;
        uniform float uTime;

        varying vec4 vPositionV;
        varying vec3 vNormalV;
        varying vec3 vTangentV;
        varying vec2 vTexCoord;

        void main(void) {
            vec3 diffuse = texture2D(uColorSampler, vTexCoord).rgb;
            vec3 normalT = texture2D(uNormalSampler, vTexCoord).xyz;
            normalT.y = 1.0 - normalT.y;
            normalT = 2.0 * normalT - vec3(1.0, 1.0, 1.0);
            normalT.z *= 10.0;

            vec3 binormalV = cross(vNormalV, vTangentV);
            vec3 normalV = normalT.x * vTangentV + normalT.y * binormalV + normalT.z * vNormalV;

            normalV = normalize(normalV);
            vec3 lightV = normalize(vec3(10.0 * cos(uTime), 10.0, 10.0 * sin(uTime)));

            float d = dot(normalV, lightV);
            float s = dot(reflect(-lightV, normalV), normalize(-vPositionV.xyz));
            s = pow(s, 30.0);

            vec3 color = diffuse * (0.1 + 0.5 * d + 0.4 * s);

            gl_FragColor = vec4(color, 1.0);
        }

So vs and fs are shaders used to draw a surface with a normal map texture. I use shader-fs and shader-vs for the rest of the code.

The following code is used to init shaders and change them:

function initShaders()
{
    var fragmentShader = getShader(gl, "shader-fs");
    var vertexShader = getShader(gl, "shader-vs");

    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert("Could not initialise shaders");
    }

    initShaders2();
}

function changeShaderBasic()
{

    gl.useProgram(shaderProgramMap);

    gl.disableVertexAttribArray(shaderProgramMap.aPositionL);
    gl.disableVertexAttribArray(shaderProgramMap.aNormalL);
    gl.disableVertexAttribArray(shaderProgramMap.aTangentL);
    gl.disableVertexAttribArray(shaderProgramMap.aTexCoord);

    gl.useProgram(shaderProgram);

    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
    gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);

    shaderProgram.vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal");
gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute);

    shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
    shaderProgram.ViewMatrixUniform = gl.getUniformLocation(shaderProgram, "uViewMatrix");
    shaderProgram.ModelMatrixUniform = gl.getUniformLocation(shaderProgram, "uModelMatrix");
    shaderProgram.nMatrixUniform = gl.getUniformLocation(shaderProgram, "uNMatrix");
    shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");

    shaderProgram.ambientColorUniform = gl.getUniformLocation(shaderProgram, "uAmbientColor"); // Color ambiente
    shaderProgram.lightingDirectionUniform = gl.getUniformLocation(shaderProgram, "uLightDirection"); // Direccion de la luz
    shaderProgram.directionalColorUniform = gl.getUniformLocation(shaderProgram, "uDirectionalColor"); // Color de la luz

    shaderProgram.shadelessColorUniform = gl.getUniformLocation(shaderProgram, "uColShadeless");
    shaderProgram.ambientKUniform = gl.getUniformLocation(shaderProgram, "uKAmbiente");
    shaderProgram.diffuseColorUniform = gl.getUniformLocation(shaderProgram, "uColDifuso");
    shaderProgram.diffuseKUniform = gl.getUniformLocation(shaderProgram, "uKDifuso");
    shaderProgram.specularColorUniform = gl.getUniformLocation(shaderProgram, "uColEspecular");
    shaderProgram.specularKUniform = gl.getUniformLocation(shaderProgram, "uKEspecular");
    shaderProgram.specularGlossiness = gl.getUniformLocation(shaderProgram, "uGlossiness");    

    shaderProgram.uvOffsetUniform = gl.getUniformLocation(shaderProgram, "aUVOffset");

}



function initShaders2() {

            var vs = getShader(gl,'vs');
            var fs = getShader(gl,'fs');

            shaderProgramMap = gl.createProgram();
            gl.attachShader(shaderProgramMap, vs);
            gl.attachShader(shaderProgramMap, fs);
            gl.linkProgram(shaderProgramMap);

            if (!gl.getProgramParameter(shaderProgramMap, gl.LINK_STATUS)) {
                alert('Could not link the shader normal program.');
                return;
            }
}


function changeShaderNormal()
{
    gl.useProgram(shaderProgram);
    gl.disableVertexAttribArray(shaderProgram.vertexPositionAttribute);
    gl.disableVertexAttribArray(shaderProgram.textureCoordAttribute);
    gl.disableVertexAttribArray(shaderProgram.vertexNormalAttribute);

    gl.useProgram(shaderProgramMap);

    shaderProgramMap.ViewMatrixUniform = gl.getUniformLocation(shaderProgramMap, "uViewMatrix");
    shaderProgramMap.ModelMatrixUniform = gl.getUniformLocation(shaderProgramMap, "uModelMatrix");
    shaderProgramMap.aPositionL = gl.getAttribLocation(shaderProgramMap, 'aPositionL');
    gl.enableVertexAttribArray(shaderProgramMap.aPositionL);
    shaderProgramMap.aNormalL = gl.getAttribLocation(shaderProgramMap, 'aNormalL');
    gl.enableVertexAttribArray(shaderProgramMap.aNormalL);
    shaderProgramMap.aTangentL = gl.getAttribLocation(shaderProgramMap, 'aTangentL');
    gl.enableVertexAttribArray(shaderProgramMap.aTangentL);
    shaderProgramMap.aTexCoord = gl.getAttribLocation(shaderProgramMap, 'aTexCoord');
    gl.enableVertexAttribArray(shaderProgramMap.aTexCoord);
    shaderProgramMap.uMatrixMVP = gl.getUniformLocation(shaderProgramMap, 'uMatrixMVP');
    shaderProgramMap.uMatrixMV = gl.getUniformLocation(shaderProgramMap, 'uMatrixMV');
    shaderProgramMap.uColorSampler = gl.getUniformLocation(shaderProgramMap, 'uColorSampler');
    shaderProgramMap.uNormalSampler = gl.getUniformLocation(shaderProgramMap, 'uNormalSampler');
    shaderProgramMap.uTime = gl.getUniformLocation(shaderProgramMap, 'uTime');
}

So I call the function "changeShaderBasic" first, and when I want to draw a surface with a normal map I do something like this:

changeShaderNormal();
*Draw the surface*
changeShaderBasic();

If I delete that part of the code it works- So the shaders shader-fs and shader-vs seem to be working fine. The problem seems to be the other shaders (vs and fs) or the function that switchs between shaders.

I have omited the part of the code which the program uses to create textures.

I don't know what I am doing wrong and I don't know how to find the problem.

Upvotes: 3

Views: 867

Answers (1)

Kenney
Kenney

Reputation: 9093

The problem is that some shader parameters of shaderProgramMap are not enabled. For example, there is

shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

and

gl.disableVertexAttribArray(shaderProgram.vertexPositionAttribute);

but nothing for shaderProgramMap, which' script also has the aVertexPosition attribute.

When you're starting to get these kinds of problems, where you can't find what's wrong with code you wrote, it is because the code is no longer clear: the overview is lost. There's duplicate code: the same thing is done twice, namely to compile, link, and configure a Shader. Then, it's time to refactor: extract all duplicate code, and parameterize what's different.

There are many possible steps. One is to abstract the shader parameters: the attributes and uniforms.
The code below will produce a parameter hashmap with information about all the parameters in the shader source. Note, it may not cover all possibilities, but it is merely to give you an idea.

function initShaderParams( gl, shaderProgram, vs, fs )
{
    /** We'll be returning this: */
    var params = {
        uniform:{},
        attribute:{},

        uniforms: function(arr) {
            for ( var a in arr )
                if ( params.uniform[a] )
                    params.uniform[ a ].set( arr[a] );
                else throw new Error("unknown uniform '"+a+"' referenced in shader " + vs+"+"+fs
                    + ";\navailable uniforms: " + Object.keys(params.uniform)
                );
        },

        enable: function() {
            for ( var a in this.attribute )
                gl.disableVertexAttribArray( this.attribute[a] );
        }

        // disable: ....
    };


    /** returns a function to set the value of a uniform given it's type */
    function getUniformFN( type ) {
        switch ( type ) {
            case 'vec2': return function(v){gl.uniform2f( this.loc, false, v[0], v[1] );};
            case 'mat4': return function(v){gl.uniformMatrix4fv( this.loc, false, v );};
            default:
                throw new Error("unknown uniform type " + type + " in shader " + vs+"+"+fs );
        }
    }

    /** same, for attributes. */
    function getAttributeFN( type ) {
        switch ( type ) {
            case 'vec2': return function(v){ gl.bindBuffer( gl.ARRAY_BUFFER, v ); gl.vertexAttribPointer( this.loc, 2, gl.FLOAT, false, 0, 0 ); };
            case 'vec3': return function(v){ gl.bindBuffer( gl.ARRAY_BUFFER, v ); gl.vertexAttribPointer( this.loc, 3, gl.FLOAT, false, 0, 0 ); };
            case 'vec4': return function(v){ gl.bindBuffer( gl.ARRAY_BUFFER, v ); gl.vertexAttribPointer( this.loc, 4, gl.FLOAT, false, 0, 0 ); };
            default:
                throw new Error("unknown uniform type " + type + " in shader " + vs+"+"+fs );
        }
    }

    /** Utility method to map a regex callback */
    function regexMap(regex, text, callback) {
        while ((result = regex.exec(text)) != null)
            callback(result);
    }

    // extract parameters:

    var src = vs + fs;

    regexMap(/(uniform)\s+(\w+)\s+(\w+)(\[\d+\])?\s*;/g, src, function(groups) {
        var loc = gl.getUniformLocation( shaderProgram, groups[3] );
        if ( loc == null ) {
            console.warn("declared ", groups[0], " not used" );
            return;
        }

        params.uniform[ groups[3] ] = {
            type: groups[2],
            loc:  loc,
            set:  getUniformFN( groups[2] )
       };
    } );

    regexMap(/(attribute)\s+(\w+)\s+(\w+)\s*;/g, src, function(groups) {
        var loc = gl.getAttribLocation ( shaderProgram, groups[3] );
        if ( loc == -1 ) {
            console.warn("declared ", groups[0], " not used" );
            return;
        }
        params.attribute[ groups[3] ] = {
            type: groups[2],
            loc:  loc,
            set:  getAttributeFN( groups[2] )
        };
    } );

    return params;
}

Calling this method you'll have access to the shader parameters, and you can iterate over them, or set them all at once:

var params = initShaderParams( gl, shaderProgram, vsSource, fsSource );

params.uniforms( {
    uViewMatrix: ...,
    uModelMatrix: ...,
    ...
} );

That way, the code becomes more clear, and it will make it easier to spot what is missing. You could even write a function to do it for you: calculate the intersection of the params.uniforms keys and the given parameters to see if all of them are specified, or keep track of whether they are set using a setter function. Of course, there are many other ways; you could also create a Shader class and override methods.

Copy-paste coding leads to unmaintainable code. As a rule of thumb, whenever you have to program something twice, you're doing it once too often.

Upvotes: 3

Related Questions