javascripthtmlwebglshading

Phong and Gouraud Shading WebGL


I read that that in Gouraud Shading, the color for the fragment is computed in the Vertex Shader. Whereas, in Phong Shading, the color for the fragment is computed in the Fragment Shader.

In this implementation , which of these are we using ?

I did not understand exactly the difference between them. Could anyone help me ? Thanks

<button id = "ButtonX">Rotate X</button>
<button id = "ButtonY">Rotate Y</button>
<button id = "ButtonZ">Rotate Z</button>
<button id = "ButtonT">Toggle Rotation</button>
<button id="Direction">Change Direction</button>
<button id="OrthoPersp">Change Ortho/Persp</button>


<div>Traslation on X  <input id="slideX" type="range"
    min="-1" max="1" step="0.1" value="0" />
    </div>

<div>Traslation on Y  <input id="slideY" type="range"
    min="-1" max="1" step="0.1" value="0" />
     </div>

<div>Traslation on Z  <input id="slideZ" type="range"
    min="-1" max="1" step="0.1" value="0" />
     </div>

<div>Scaling on X  <input id="ScalingX" type="range"
    min="0" max="1" step="0.1" value="0" />
     </div>
<div>Scaling on Y <input id="ScalingY" type="range"
    min="0" max="1" step="0.1" value="0" />
     </div>
<div>Scaling on Z  <input id="ScalingZ" type="range"
    min="0" max="1" step="0.1" value="0" />
     </div>

<div>
    zNear Min<input id="zNearSlider" type="range" min="0.00" max="2.8" step="0.1" value="0.3">
        Max
</div>

<div>
    zFar Min<input id="zFarSlider" type="range" min="3" max="10" step="3.0" value="3">
        Max
</div>







<script id="vertex-shader" type="x-shader/x-vertex">

attribute  vec4 vPosition;
attribute  vec4 vColor;

varying vec4 fColor;

//uniform vec3 theta;

// Point 2 -> Move the matrices
// Per spostare le matrici le abbiamo dovuto dichiarare nel file GLSL come uniform
// le matrici rx ry e rz sono rispettivamente le matrici di rotazione sugli assi
uniform mat4 rx;
uniform mat4 ry;
uniform mat4 rz;

// Points 3 -> Traslation Matrix
uniform mat4 traslation;
// Points 3 -> Scaling Matrix
uniform mat4 scaling;

//Point 4 -> MV and P matrices
uniform mat4 modelView;
uniform mat4 projection;

//Poinit 6 -> Light Source

attribute vec4 vNormal;
uniform vec4 ambientProduct, diffuseProduct, specularProduct;
uniform vec4 lightPosition;
uniform float shininess;

void main()
{
    // Compute the sines and cosines of theta for each of
    //   the three axes in one computation.
    //vec3 angles = radians( theta );
    //vec3 c = cos( angles );
    //vec3 s = sin( angles );

    // Remember: the matrices are column-major
    /*
    mat4 rx = mat4( 1.0,  0.0,  0.0, 0.0,
            0.0,  c.x,  s.x, 0.0,
            0.0, -s.x,  c.x, 0.0,
            0.0,  0.0,  0.0, 1.0 );

    mat4 ry = mat4( c.y, 0.0, -s.y, 0.0,
            0.0, 1.0,  0.0, 0.0,
            s.y, 0.0,  c.y, 0.0,
            0.0, 0.0,  0.0, 1.0 );


    mat4 rz = mat4( c.z, s.z, 0.0, 0.0,
            -s.z,  c.z, 0.0, 0.0,
            0.0,  0.0, 1.0, 0.0,
            0.0,  0.0, 0.0, 1.0 );
     */

    //fColor = vColor;
    // ORDINE : scaling -> rotazione -> traslation

    //gl_Position = projection*modelView*scaling *rz * ry * rx * traslation *vPosition ;
    //gl_Position.z = -gl_Position.z;

    //Point 6
    vec3 pos = -(modelView * vPosition).xyz;
    vec3 light = lightPosition.xyz;
    vec3 L = normalize( light - pos );
    vec3 E = normalize( -pos );
    vec3 H = normalize( L + E );
    vec3 N = normalize( (modelView*vNormal).xyz);

    vec4 ambient = ambientProduct;

    float Kd = max( dot(L, N), 0.0 );
    vec4 diffuse = Kd*diffuseProduct;

    float Ks = pow( max(dot(N, H), 0.0), shininess );
    vec4 specular = Ks * specularProduct;

    if( dot(L, N) < 0.0 ) {
        specular = vec4(0.0, 0.0, 0.0, 1.0);
    }

    fColor = ambient + diffuse + specular;
    fColor.a = 1.0;


    gl_Position = projection*modelView*scaling *rz * ry * rx * traslation *vPosition ;
    gl_Position.z = -gl_Position.z;




    // *******************

}
</script>

<script id="fragment-shader" type="x-shader/x-fragment">

precision mediump float;

varying vec4 fColor;

void
main()
{
    gl_FragColor = fColor;

}
</script>

<script type="text/javascript" src="../Common/webgl-utils.js"></script>
<script type="text/javascript" src="../Common/initShaders.js"></script>
<script type="text/javascript" src="../Common/MV.js"></script>
<script type="text/javascript" src="Homework1.js"></script>



<body>
<canvas id="gl-canvas" width="1024" height="1024">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>
</html>

Solution

  • I did not understand exactly the difference between them. Could anyone help me ?

    The technique used in the code snippet of the question is Gouraud Shading.

    In common Phong shading means the technique, which does the light calculations per fragment, in compare at Gouraud Shading, the light calculations ar done per vertex.

    This means that at Phong shading the light calcualtions ar done in the fragment shader (Not to be confused with Phong reflection model).

    Phong shading:
    phong

    At Gouraud Shading the light calculations are done in the vertex shader, for the corners of the primitives (corners of the triangles). The calculated light is (either in a perspective correct manner or linearly) interpolated for all the fragments covered by the primitive, according to the Barycentric coordinate. This increases the performance, but gives a big loss of quality, especially on large primitives and strong specular highlights.

    Gouraud Shading:
    gouraud

    Note, the light is not linear distributed on a surface. If the light is only calculated for some samples and interpolated in between them, this causes flat stains.

    See the example, which compares the 2 techniques:

    (function loadscene() {
    
    var resize, gl, gouraudDraw, phongDraw, vp_size;
    var bufSphere = {};
    
    function render(delteMS){
    
        var shading = document.getElementById( "shading" ).value;
        var shininess = document.getElementById( "shininess" ).value;
        var ambientCol = [0.2, 0.2, 0.2];
        var diffuseCol = [0.6, 0.6, 0.6];
        var specularCol = [0.8, 0.8, 0.8];
    
        Camera.create();
        Camera.vp = vp_size;
            
        gl.enable( gl.DEPTH_TEST );
        gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
        gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
    
        gl.enable(gl.CULL_FACE);
        gl.cullFace(gl.BACK);
        //gl.frontFace(gl.CW);
        gl.frontFace(gl.CCW);
        
        var progDraw = shading == 0 ? gouraudDraw : phongDraw;;
        // set up draw shader
        ShaderProgram.Use( progDraw.prog );
        ShaderProgram.SetUniformM44( progDraw.prog, "u_projectionMat44", Camera.Perspective() );
        ShaderProgram.SetUniformM44( progDraw.prog, "u_viewMat44", Camera.LookAt() );
        ShaderProgram.SetUniformF3( progDraw.prog, "u_lightSource.lightDir", [-1.0, -0.5, -2.0] )
        ShaderProgram.SetUniformF3( progDraw.prog, "u_lightSource.ambient", ambientCol )
        ShaderProgram.SetUniformF3( progDraw.prog, "u_lightSource.diffuse", diffuseCol )
        ShaderProgram.SetUniformF3( progDraw.prog, "u_lightSource.specular", specularCol )
        ShaderProgram.SetUniformF1( progDraw.prog, "u_lightSource.shininess", shininess )
        var modelMat = IdentityMat44()
        modelMat = RotateAxis( modelMat, CalcAng( delteMS, 13.0 ), 0 );
        modelMat = RotateAxis( modelMat, CalcAng( delteMS, 17.0 ), 1 );
        ShaderProgram.SetUniformM44( progDraw.prog, "u_modelMat44", modelMat );
        
        // draw scene
        VertexBuffer.Draw( bufSphere );
       
        requestAnimationFrame(render);
    }
    
    function resize() {
        //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
        vp_size = [window.innerWidth, window.innerHeight]
        canvas.width = vp_size[0];
        canvas.height = vp_size[1];
        gl.viewport( 0, 0, vp_size[0], vp_size[1] );
    }
    
    function initScene() {
    
        canvas = document.getElementById( "canvas");
        gl = canvas.getContext( "experimental-webgl" );
        if ( !gl )
          return null;
    
        gouraudDraw = {}
        gouraudDraw.prog = ShaderProgram.Create( 
          [ { source : "gouraud-shader-vs", stage : gl.VERTEX_SHADER },
            { source : "gouraud-shader-fs", stage : gl.FRAGMENT_SHADER }
          ],
          [ "u_projectionMat44", "u_viewMat44", "u_modelMat44", 
            "u_lightSource.lightDir", "u_lightSource.ambient", "u_lightSource.diffuse", "u_lightSource.specular", "u_lightSource.shininess", ] );
        if ( gouraudDraw.prog == 0 )
          return;  
        gouraudDraw.inPos = gl.getAttribLocation( gouraudDraw.prog, "inPos" );
        gouraudDraw.inNV  = gl.getAttribLocation( gouraudDraw.prog, "inNV" );
        gouraudDraw.inCol = gl.getAttribLocation( gouraudDraw.prog, "inCol" );
    
        phongDraw = {}
        phongDraw.prog = ShaderProgram.Create( 
          [ { source : "phong-shader-vs", stage : gl.VERTEX_SHADER },
            { source : "phong-shader-fs", stage : gl.FRAGMENT_SHADER }
          ],
          [ "u_projectionMat44", "u_viewMat44", "u_modelMat44", 
            "u_lightSource.lightDir", "u_lightSource.ambient", "u_lightSource.diffuse", "u_lightSource.specular", "u_lightSource.shininess", ] );
        if ( phongDraw.prog == 0 )
          return;
        phongDraw.inPos = gl.getAttribLocation( phongDraw.prog, "inPos" );
        phongDraw.inNV  = gl.getAttribLocation( phongDraw.prog, "inNV" );
        phongDraw.inCol = gl.getAttribLocation( phongDraw.prog, "inCol" );
        
        // create cube
        var layer_size = 16, circum_size = 32;
        var rad_circum = 1.0;
        var rad_tube = 0.5;
        var sphere_pts = [];
        var sphere_nv = [];
        var sphere_col = [];
        sphere_pts.push( 0.0, 0.0, -2.0 );
        sphere_nv.push( 0.0, 0.0, -1.0 );
        sphere_col.push( 0.8, 0.6, 0.3 );
        for ( var i_l = 1; i_l < layer_size; ++ i_l ) {
            var angH = (1.0 - i_l / layer_size) * Math.PI;
            var h = Math.cos( angH );
            var r = Math.sin( angH );
            for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
                var circumX = Math.cos(2 * Math.PI * i_c / circum_size);
                var circumY = Math.sin(2 * Math.PI * i_c / circum_size);
                sphere_pts.push( r * circumX * 2.0, r * circumY * 2.0, h * 2.0 );
                sphere_nv.push( r * circumX, r * circumY, h );
                sphere_col.push( 0.8, 0.6, 0.3 );
            }
        }
        sphere_pts.push( 0.0, 0.0, 2.0 );
        sphere_nv.push( 0.0, 0.0, 1.0 );
        sphere_col.push( 0.75, 0.75, 0.75 );
        var sphere_inx = [];
        for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
            sphere_inx.push( i_c+1, 0, (i_c+1) % circum_size + 1 )
        }
        for ( var i_l = 0; i_l < layer_size-2; ++ i_l ) {
            var l1 = i_l * circum_size + 1;
            var l2 = (i_l+1) * circum_size + 1
            for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
                var i_n = (i_c+1) % circum_size;
                sphere_inx.push( l1+i_c, l1+i_n, l2+i_c, l1+i_n, l2+i_n, l2+i_c );
            }
        }
        for ( var i_c = 0; i_c < circum_size; ++ i_c ) {
            var i_start = 1 + (layer_size-2) * circum_size;
            var i_n = (i_c+1) % circum_size;
            sphere_inx.push( i_start + i_c, i_start + i_n, sphere_pts.length/3-1 );
        }
        bufSphere = VertexBuffer.Create(
        [ { data : sphere_pts, attrSize : 3, attrLoc : gouraudDraw.inPos },
          { data : sphere_nv, attrSize : 3, attrLoc : gouraudDraw.inNV },
          { data : sphere_col, attrSize : 3, attrLoc : gouraudDraw.inCol } ],
          sphere_inx );
          
        window.onresize = resize;
        resize();
        requestAnimationFrame(render);
    }
    
    function Fract( val ) { 
        return val - Math.trunc( val );
    }
    function CalcAng( deltaTime, intervall ) {
        return Fract( deltaTime / (1000*intervall) ) * 2.0 * Math.PI;
    }
    function CalcMove( deltaTime, intervall, range ) {
        var pos = self.Fract( deltaTime / (1000*intervall) ) * 2.0
        var pos = pos < 1.0 ? pos : (2.0-pos)
        return range[0] + (range[1] - range[0]) * pos;
    }    
    function EllipticalPosition( a, b, angRag ) {
        var a_b = a * a - b * b
        var ea = (a_b <= 0) ? 0 : Math.sqrt( a_b );
        var eb = (a_b >= 0) ? 0 : Math.sqrt( -a_b );
        return [ a * Math.sin( angRag ) - ea, b * Math.cos( angRag ) - eb, 0 ];
    }
    
    glArrayType = typeof Float32Array !="undefined" ? Float32Array : ( typeof WebGLFloatArray != "undefined" ? WebGLFloatArray : Array );
    
    function IdentityMat44() {
      var m = new glArrayType(16);
      m[0]  = 1; m[1]  = 0; m[2]  = 0; m[3]  = 0;
      m[4]  = 0; m[5]  = 1; m[6]  = 0; m[7]  = 0;
      m[8]  = 0; m[9]  = 0; m[10] = 1; m[11] = 0;
      m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
      return m;
    };
    
    function RotateAxis(matA, angRad, axis) {
        var aMap = [ [1, 2], [2, 0], [0, 1] ];
        var a0 = aMap[axis][0], a1 = aMap[axis][1]; 
        var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
        var matB = new glArrayType(16);
        for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
        for ( var i = 0; i < 3; ++ i ) {
            matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
            matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
        }
        return matB;
    }
    
    function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }
    function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
    function Normalize( v ) {
        var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
        return [ v[0] / len, v[1] / len, v[2] / len ];
    }
    
    var Camera = {};
    Camera.create = function() {
        this.pos    = [0, 3, 0.0];
        this.target = [0, 0, 0];
        this.up     = [0, 0, 1];
        this.fov_y  = 90;
        this.vp     = [800, 600];
        this.near   = 0.5;
        this.far    = 100.0;
    }
    Camera.Perspective = function() {
        var fn = this.far + this.near;
        var f_n = this.far - this.near;
        var r = this.vp[0] / this.vp[1];
        var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
        var m = IdentityMat44();
        m[0]  = t/r; m[1]  = 0; m[2]  =  0;                              m[3]  = 0;
        m[4]  = 0;   m[5]  = t; m[6]  =  0;                              m[7]  = 0;
        m[8]  = 0;   m[9]  = 0; m[10] = -fn / f_n;                       m[11] = -1;
        m[12] = 0;   m[13] = 0; m[14] = -2 * this.far * this.near / f_n; m[15] =  0;
        return m;
    }
    Camera.LookAt = function() {
        var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
        var mx = Normalize( Cross( this.up, mz ) );
        var my = Normalize( Cross( mz, mx ) );
        var tx = Dot( mx, this.pos );
        var ty = Dot( my, this.pos );
        var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos ); 
        var m = IdentityMat44();
        m[0]  = mx[0]; m[1]  = my[0]; m[2]  = mz[0]; m[3]  = 0;
        m[4]  = mx[1]; m[5]  = my[1]; m[6]  = mz[1]; m[7]  = 0;
        m[8]  = mx[2]; m[9]  = my[2]; m[10] = mz[2]; m[11] = 0;
        m[12] = tx;    m[13] = ty;    m[14] = tz;    m[15] = 1; 
        return m;
    } 
    
    var ShaderProgram = {};
    ShaderProgram.Create = function( shaderList ) {
        var shaderObjs = [];
        for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
            var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
            if ( shderObj == 0 )
                return 0;
            shaderObjs.push( shderObj );
        }
        var progObj = this.LinkProgram( shaderObjs )
        if ( progObj != 0 ) {
            progObj.attribIndex = {};
            var noOfAttributes = gl.getProgramParameter( progObj, gl.ACTIVE_ATTRIBUTES );
            for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {
                var name = gl.getActiveAttrib( progObj, i_n ).name;
                progObj.attribIndex[name] = gl.getAttribLocation( progObj, name );
            }
            progObj.unifomLocation = {};
            var noOfUniforms = gl.getProgramParameter( progObj, gl.ACTIVE_UNIFORMS );
            for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {
                var name = gl.getActiveUniform( progObj, i_n ).name;
                progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
            }
        }
        return progObj;
    }
    ShaderProgram.AttributeIndex = function( progObj, name ) { return progObj.attribIndex[name]; } 
    ShaderProgram.UniformLocation = function( progObj, name ) { return progObj.unifomLocation[name]; } 
    ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); } 
    ShaderProgram.SetUniformI1  = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1i( progObj.unifomLocation[name], val ); }
    ShaderProgram.SetUniformF1  = function( progObj, name, val ) { if(progObj.unifomLocation[name]) gl.uniform1f( progObj.unifomLocation[name], val ); }
    ShaderProgram.SetUniformF2  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform2fv( progObj.unifomLocation[name], arr ); }
    ShaderProgram.SetUniformF3  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform3fv( progObj.unifomLocation[name], arr ); }
    ShaderProgram.SetUniformF4  = function( progObj, name, arr ) { if(progObj.unifomLocation[name]) gl.uniform4fv( progObj.unifomLocation[name], arr ); }
    ShaderProgram.SetUniformM33 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix3fv( progObj.unifomLocation[name], false, mat ); }
    ShaderProgram.SetUniformM44 = function( progObj, name, mat ) { if(progObj.unifomLocation[name]) gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); }
    ShaderProgram.CompileShader = function( source, shaderStage ) {
        var shaderScript = document.getElementById(source);
        if (shaderScript)
          source = shaderScript.text;
        var shaderObj = gl.createShader( shaderStage );
        gl.shaderSource( shaderObj, source );
        gl.compileShader( shaderObj );
        var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
        if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
        return status ? shaderObj : null;
    } 
    ShaderProgram.LinkProgram = function( shaderObjs ) {
        var prog = gl.createProgram();
        for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
            gl.attachShader( prog, shaderObjs[i_sh] );
        gl.linkProgram( prog );
        status = gl.getProgramParameter( prog, gl.LINK_STATUS );
        if ( !status ) alert("Could not initialise shaders");
        gl.useProgram( null );
        return status ? prog : null;
    }
    
    var VertexBuffer = {};
    VertexBuffer.Create = function( attributes, indices ) {
        var buffer = {};
        buffer.buf = [];
        buffer.attr = []
        for ( var i = 0; i < attributes.length; ++ i ) {
            buffer.buf.push( gl.createBuffer() );
            buffer.attr.push( { size : attributes[i].attrSize, loc : attributes[i].attrLoc } );
            gl.bindBuffer( gl.ARRAY_BUFFER, buffer.buf[i] );
            gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( attributes[i].data ), gl.STATIC_DRAW );
        }
        buffer.inx = gl.createBuffer();
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, buffer.inx );
        gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );
        buffer.inxLen = indices.length;
        gl.bindBuffer( gl.ARRAY_BUFFER, null );
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
        return buffer;
    }
    VertexBuffer.Draw = function( bufObj ) {
      for ( var i = 0; i < bufObj.buf.length; ++ i ) {
            gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.buf[i] );
            gl.vertexAttribPointer( bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0 );
            gl.enableVertexAttribArray( bufObj.attr[i].loc );
        }
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
        gl.drawElements( gl.TRIANGLES, bufObj.inxLen, gl.UNSIGNED_SHORT, 0 );
        for ( var i = 0; i < bufObj.buf.length; ++ i )
           gl.disableVertexAttribArray( bufObj.attr[i].loc );
        gl.bindBuffer( gl.ARRAY_BUFFER, null );
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
    }
    
    initScene();
    
    })();
    <style>
    html,body {
        height: 100%;
        width: 100%;
        margin: 0;
        overflow: hidden;
    }
    #gui {
        position : absolute;
        top : 0;
        left : 0;
    }
    </style>
    <script id="gouraud-shader-vs" type="x-shader/x-vertex">
      precision mediump float;
      
      attribute vec3 inPos;
      attribute vec3 inNV;
      attribute vec3 inCol;
      
      varying vec3 vertPos;
      varying vec3 vertNV;
      varying vec3 vertCol;
      
      uniform mat4 u_projectionMat44;
      uniform mat4 u_viewMat44;
      uniform mat4 u_modelMat44;
    
      struct TLightSource
      {
          vec3  lightDir;
          vec3  ambient;
          vec3  diffuse;
          vec3  specular;
          float shininess;
      };
    
      uniform TLightSource u_lightSource;
      
      vec3 Light( vec3 eyeV, vec3 N )
      {
          vec3  lightCol  = u_lightSource.ambient;
          vec3  L         = normalize( -u_lightSource.lightDir );
          float NdotL     = max( 0.0, dot( N, L ) );
          lightCol       += NdotL * u_lightSource.diffuse;
          vec3  H         = normalize( eyeV + L );
          float NdotH     = max( 0.0, dot( N, H ) );
          float kSpecular = ( u_lightSource.shininess + 2.0 ) * pow( NdotH, u_lightSource.shininess ) / ( 2.0 * 3.14159265 );
          lightCol       += kSpecular * u_lightSource.specular;
          return lightCol; 
      }
      
      void main()
      {
          vec3 modelNV  = mat3( u_modelMat44 ) * normalize( inNV );
          vertNV        = mat3( u_viewMat44 ) * modelNV;
          vec4 modelPos = u_modelMat44 * vec4( inPos, 1.0 );
          vec4 viewPos  = u_viewMat44 * modelPos;
          vertPos       = viewPos.xyz / viewPos.w;
          vec3 eyeV     = normalize( -vertPos );
          vec3 normalV  = normalize( vertNV );
          vertCol       = inCol * Light( eyeV, normalV );
          gl_Position   = u_projectionMat44 * viewPos;
      }
      </script>
      
      <script id="gouraud-shader-fs" type="x-shader/x-fragment">
      precision mediump float;
      
      varying vec3 vertPos;
      varying vec3 vertNV;
      varying vec3 vertCol;
      
      void main()
      {
          gl_FragColor = vec4( vertCol, 1.0 );
      }
      </script>
    
    <script id="phong-shader-vs" type="x-shader/x-vertex">
    precision mediump float;
    
    attribute vec3 inPos;
    attribute vec3 inNV;
    attribute vec3 inCol;
    
    varying vec3 vertPos;
    varying vec3 vertNV;
    varying vec3 vertCol;
    
    uniform mat4 u_projectionMat44;
    uniform mat4 u_viewMat44;
    uniform mat4 u_modelMat44;
    
    void main()
    {
      vec3 modelNV  = mat3( u_modelMat44 ) * normalize( inNV );
      vertNV        = mat3( u_viewMat44 ) * modelNV;
      vertCol       = inCol;
      vec4 modelPos = u_modelMat44 * vec4( inPos, 1.0 );
      vec4 viewPos  = u_viewMat44 * modelPos;
      vertPos       = viewPos.xyz / viewPos.w;
      gl_Position   = u_projectionMat44 * viewPos;
    }
    </script>
    
    <script id="phong-shader-fs" type="x-shader/x-fragment">
    precision mediump float;
    
    varying vec3 vertPos;
    varying vec3 vertNV;
    varying vec3 vertCol;
    
    struct TLightSource
    {
      vec3  lightDir;
      vec3  ambient;
      vec3  diffuse;
      vec3  specular;
      float shininess;
    };
    
    uniform TLightSource u_lightSource;
    
    vec3 Light( vec3 eyeV, vec3 N )
    {
      vec3  lightCol  = u_lightSource.ambient;
      vec3  L         = normalize( -u_lightSource.lightDir );
      float NdotL     = max( 0.0, dot( N, L ) );
      lightCol       += NdotL * u_lightSource.diffuse;
      vec3  H         = normalize( eyeV + L );
      float NdotH     = max( 0.0, dot( N, H ) );
      float kSpecular = ( u_lightSource.shininess + 2.0 ) * pow( NdotH, u_lightSource.shininess ) / ( 2.0 * 3.14159265 );
      lightCol       += kSpecular * u_lightSource.specular;
      return lightCol; 
    }
    
    void main()
    {
      vec3 eyeV    = normalize( -vertPos );
      vec3 normalV = normalize( vertNV );
      vec3 color   = vertCol * Light( eyeV, normalV );
      gl_FragColor = vec4( color, 1.0 );
    }
    </script>
    
    <form id="gui" name="inputs"><table><tr>
        <td><font color= #CCF>Shading:</font></td> 
        <td><select id="shading">>
            <option value="0">Gouraud</option>
            <option value="1">Phong</option>
        </select></td>
        </tr><tr>
        <td><font color= #CCF>Shininess:</font></td>
        <td><input type="range" id="shininess" min="0" max="100" value="20"/></td>
    </tr></table></form>
    <canvas id="canvas" style="border: none;" width="100%" height="100%"></canvas>