纹理平面上的 OpenGL 光照不工作

OpenGL Lighting on texture plane is not working

我想照亮纹理平面,但这行不通。实心球体打光很好,但贴图平面不打光

Whole Image

solid-sphere 上的照明效果很好。

但是,纹理平面上的照明不起作用。 (GL_DECAL,GL_REPLACE;我也试过GL_MODULATE)

这是我的渲染代码片段。 (Whole code on GitHub)

正在加载纹理。

  sf::Image image;

  if (!image.loadFromFile(path))
    return false;
  glGenTextures(1, &id);
  glBindTexture(GL_TEXTURE_2D, id);

  glTexImage2D(
    GL_TEXTURE_2D, 0, GL_RGBA,
    image.getSize().x, image.getSize().y, 0,
    GL_RGBA, GL_UNSIGNED_BYTE,
    image.getPixelsPtr()
  );

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

初始化

  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClearDepth(1.0f);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);
  glShadeModel(GL_SMOOTH);
  //glEnable(GL_CULL_FACE);
  glFrontFace(GL_CCW);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
  glutSetCursor(GLUT_CURSOR_NONE);
  light.Init();

  camera.SetPin((GLfloat)width / 2, (GLfloat)height/2);

显示回调

  adjustPerspective();

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glEnable(GL_LIGHTING);

  glPushMatrix();
  camera.SetLookAt();
  light.On();

  // TODO: dsiplay processing
  for (auto& obj : display_objs)
  {
    glPushMatrix();
    obj->Draw();
    glPopMatrix();
  }
  glPopMatrix();

  // print fps and swap buffers

灯光初始化函数

  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);

  // Set lighting intensity and color
  glLightfv(GL_LIGHT0, GL_AMBIENT, qaAmbientLight);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, qaDiffuseLight);
  glLightfv(GL_LIGHT0, GL_POSITION, qaLightPosition);
  glLightfv(GL_LIGHT0, GL_SPECULAR, qaSpecularLight);
  ////////////////////////////////////////////////

  glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0);// set cutoff angle
  glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, dirVector0);
  glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 10.0); // set focusing strength

Light.On() 函数

  glPushMatrix();
  glTranslatef(2.0, 10.0, 2.0);
  //glRotatef(90, 1, 0, 0);
  glLightfv(GL_LIGHT0, GL_POSITION, qaLightPosition);
  glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, dirVector0);
  glPopMatrix();

  glPushMatrix();
  glDisable(GL_LIGHTING);
  glTranslatef(2.0, 0.0, 2.0);
  glRotatef(-90.0, 1.0, 0.0, 0.0);
  glutWireCone(tan(80.0 / 180.0 * 3.14159265),10.0,20,20);
  glEnable(GL_LIGHTING);
  glPopMatrix();

这是纹理平面绘制函数。

  float tile_x = 0.125;

  glTranslatef(x, y, z);

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, tex.GetId());
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

  glBegin(GL_QUADS);
    // Both of the following cases not work.
    glNormal3f(0, -1, 0);
    glNormal3f(0, 1, 0);
    glTexCoord2f(0.0, 0.0); glVertex3f(0, 0, 0);
    glTexCoord2f(height*tile_x, 0.0); glVertex3f(0, 0, width);
    glTexCoord2f(height*tile_x, width*tile_x); glVertex3f(height, 0, width);
    glTexCoord2f(0.0, width*tile_x); glVertex3f(height, 0, 0);
  glEnd();

  glDisable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);

我改变了矢量方向,改变了glTexEnvf属性,改变了代码的顺序,但没有修复错误。我认为我的代码中存在根本错误,但我找不到它。为什么会发生这种情况,我该如何解决?

I want to light to the texture plane but this is not work. Light on solid sphere is very well, but texture plane is not light.

这是由图元上的片段 Gouraud Shading model of the OpenGLs standard light model. 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 引起的问题。

这意味着在您的情况下,灯光是针对地面四边形的角计算的。这种如此计算的光被插值到其间的所有片段。拐角处的法向量与光向量的夹角趋于90°。因此,整个地面四边形看起来几乎没有点亮。

由于光照是按顶点计算的,因此会针对比四边形的 4 个角更多的位置计算光照,并且质量会提高。请注意,球体上的光看起来几乎是完美的,因为球体由许多围绕其形状的顶点组成。

试试下面的代码,它将四边形分割成小块:

int   tiles = 5;
float u_max = height*tile_x;
float v_max = width*tile_x

glBegin(GL_QUADS);
glNormal3f(0, 1, 0);

for (int x=0; x < tiles; ++x)
{
    for (int y=0; y < tiles; ++y)
    {
        x0 = (float)x/(float)tiles;
        x1 = (float)(x+1)/(float)tiles;
        y0 = (float)y/(float)tiles;
        y1 = (float)(y+1)/(float)tiles;

        glTexCoord2f(u_max*x0, v_max*y0);  glVertex3f(height*x0, 0, widht*y0);
        glTexCoord2f(u_max*x1, v_max*y0);  glVertex3f(height*x0, 0, widht*y1);
        glTexCoord2f(u_max*x1, v_max*y1);  glVertex3f(height*x1, 0, widht*y1);
        glTexCoord2f(u_max*x0, v_max*y1);  glVertex3f(height*x1, 0, widht*y0);
    }
}
glEnd();

当然你也可以自己写shader实现逐片光照。但是已弃用的固定功能管线 OpenGL 标准光照模型不支持按片段光照。

查看WebGL示例中的区别:

(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.disable(gl.CULL_FACE);
    
    
    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.lightPos", [0.0, 0.0, 0.25] )
    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, -1.5, 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 = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0];
    var sphere_nv  = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0];
    var sphere_col = [0.8, 0.6, 0.3, 0.8, 0.6, 0.3, 0.8, 0.6, 0.3, 0.8, 0.6, 0.3];
    var sphere_inx = [0, 1, 2, 0, 2, 3];
    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, 2, 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  lightPos;
      vec3  ambient;
      vec3  diffuse;
      vec3  specular;
      float shininess;
  };

  uniform TLightSource u_lightSource;
  
  vec3 Light( vec3 eyeV, vec3 N, vec3 P )
  {
      vec3  lightCol  = u_lightSource.ambient;
      vec3  L         = normalize( u_lightSource.lightPos-P );
      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 ) * sign(vertNV.z);
      vertCol       = inCol * Light( eyeV, normalV, vertPos );
      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  lightPos;
  vec3  ambient;
  vec3  diffuse;
  vec3  specular;
  float shininess;
};

uniform TLightSource u_lightSource;

vec3 Light( vec3 eyeV, vec3 N, vec3 P )
{
  vec3  lightCol  = u_lightSource.ambient;
  vec3  L         = normalize( u_lightSource.lightPos - P );
  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 ) * sign(vertNV.z);
  vec3 color   = vertCol * Light( eyeV, normalV, vertPos );
  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="10"/></td>
</tr></table></form>
<canvas id="canvas" style="border: none;" width="100%" height="100%"></canvas>