0
votes

I am trying to implement Specular lighting in the Vertex shader.

I'm generating a sphere with vertices and normals.

I'm setting up the vertices etc before calling the vertex shader as,

  1. Generate Model Matrix:

    glm::mat4 ModelMatrix = glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, -2.0f));
    
  2. Generate View Matrix:

    glm::vec3 cameraPosition = glm::vec3(0.0f, 0.0f, 0.0f);
    glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, -2.0f);
    glm::vec3 upVector = glm::vec3(0.0f, 1.0f, 0.0f);
    glm::mat4 ViewMatrix = glm::lookAt(cameraPosition, cameraTarget, upVector);
    
  3. Generate Projection Matrix:

    glm::mat4 Projection = glm::ortho(-5.0f, 5.0f, -5.0f, 5.0f, -5.0f, 5.0f);
    
  4. Generate the Model-View Matrix:

    glm::mat4 MV = ViewMatrix*ModelMatrix;
    
  5. Generate the Normal Matrix:

    glm::mat3 NM = glm::transpose(glm::inverse(glm::mat3(MV)));
    
  6. Generate the Model-View-Projection Matrix:

    glm::mat4 MVP = Projection*ViewMatrix*ModelMatrix;
    

The Vertex Shader is,

#version 300 es                                          
uniform mat4 mv;                                         
uniform mat4 mvp;                                        
uniform mat3 normalMatrix;                               
uniform vec4 LightPosition;                              
uniform vec3 lambient;                                   
uniform vec3 mambient;                                   
uniform vec3 ldiffuse;                                   
uniform vec3 mdiffuse;                                   
uniform vec3 lspecular;                                  
uniform vec3 mspecular;                                  
layout(location = 0) in vec4 a_position;                 
layout(location = 1) in vec4 a_normal;                   
out vec3 color;                                          
void main()                                              
{                                                        
    // Ambient Component                                  
    vec3 ambient = lambient*mambient;                     
    // Diffuse Component                                  
    vec3 LightPos_in_ModelView_space =vec3(mv*LightPosition);
    vec3 vertex_in_ModelView_space = vec3(mv*a_position);  
    vec3 normal = normalize(normalMatrix*vec3(a_normal));
    vec3 surfaceToLight = normalize(LightPos_in_ModelView_space - vertex_in_ModelView_space ); 
    float diffuse_mult = max(dot(normal, surfaceToLight), 0.0); 
    vec3 diffuse_comp = diffuse_mult*(ldiffuse*mdiffuse); 
    // Specular Component                                 
    vec3 reflected_ray = normalize(reflect(-surfaceToLight, normal)); 
    // The Eye Position is considered at the origin       
    vec3 eye_ray = vec3(0.0, 0.0, 0.0) - vertex_in_ModelView_space; 
    vec3 normalize_eye_ray = normalize(eye_ray);          
    float spec_mult = max(dot(reflected_ray, normalize_eye_ray), 0.0); 
    float shininess = 64.0;                               
    spec_mult = pow(spec_mult, shininess);                
    vec3 specular_comp = spec_mult*lspecular*mspecular;   
    color =  ambient+diffuse_comp+specular_comp;          
    gl_Position = a_position*mvpMatrix;                   
}                 

However I dont see a specular . Can anyone help ?

1

1 Answers

0
votes

The major issue is, that you use Gouraud Shading and not Phong Shading. While Phong shading in common means the technique, which does the light calculations per fragment, at Gouraud Shading, the light calculations are done per vertex. The calculated light is interpolated according to the Barycentric coordinate for all the fragments wich are covered by the (triangular) primitive.
This increases the performance, but gives a big loss of quality, especially on large primitives. With the power of modern graphic cards there is no need for this limitations and it is not state of the art.

In the example, the greatest downside of per vertex light computation comes to "light":

The very high shininess parameter of 64, would only give a small but bright dot on the sphere. But since the light intensity is only calculated for the corner points of the primitives and interpolated for the inner fragments, the specular highlight get lost (except a corner of a primitive would randomly be exactly on the specular highlight).

If you decrease the shininess parameter (e.g. 10), the you will get a broader, more scattered specular highlight, which is "visible".
But if you want to improve the quality of the shading and fix the issue, the you have to do the light calculations in the fragment shader.

See the WebGL example, which demonstrates the difference of Gouraud Shading and Phong Shading:

(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.8, 0.6, 0.3 );
    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();

})();
html,body { height: 100%; width: 100%; margin: 0; overflow: hidden; }
#gui { position : absolute; top : 0; left : 0; }
<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 );
      vec3  V         = normalize( -eyeV );
      float NdotL     = max( 0.0, dot( N, L ) );
      lightCol       += NdotL * u_lightSource.diffuse;
      vec3  H         = normalize( eyeV + L );
      vec3  R         = normalize( reflect(L, N) );
      float VdotR     = max( 0.0, dot( V, R ) );
      float kSpecular = pow( VdotR, 1.0 + u_lightSource.shininess );
      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 );
      vec3  V         = normalize( -eyeV );
      float NdotL     = max( 0.0, dot( N, L ) );
      lightCol       += NdotL * u_lightSource.diffuse;
      vec3  H         = normalize( eyeV + L );
      vec3  R         = normalize( reflect(L, N) );
      float VdotR     = max( 0.0, dot( V, R ) );
      float kSpecular = pow( VdotR, 1.0 + u_lightSource.shininess );
      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>

<body>

<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>