webgl 中的着色

Shading in webgl

我一直在尝试在 webgl 中创建阴影,就像在这个图像中一样:在这里我们可以看到一个圆锥体、一个球体和一个光(我们可以用滑块改变他的位置)。

我试图通过从一些 webgl 辅导网站看到多个阴影示例在 html 文件中编写一些代码,但现在,我什至看不到形状。可以肯定我做错了什么,但我只是不知道在哪里。这是我的代码,我还包含一个 link 因为它包含多个文件。提前致谢。

Link: https://wetransfer.com/downloads/cd0f66f2e2866c0d118e95b02e01cb0520200923203442/274553

<html>

<head>
<title>Light and Shading</title>
<meta http-equiv='content-type' content='text/html; charset=ISO-8859-1'>

<!-- CSS Styles //-->
<link href='css/style.css'   type='text/css' rel='stylesheet'>
<link href='css/desert.css'  type='text/css' rel='stylesheet'/>
<link href='css/colorpicker.css'  type='text/css' rel='stylesheet'/>
<link href='css/smoothness/jquery-ui-1.8.13.custom.css' type='text/css' rel='stylesheet' />

<!-- JavaScript Libraries //-->
<script type='text/javascript' src='js/gl-matrix-min.js'></script>
<script type='text/javascript' src='js/jquery-1.5.1.min.js'></script>
<script type='text/javascript' src='js/jquery-ui-1.8.13.custom.min.js'></script> 
<script type='text/javascript' src='js/prettify.js'></script>
<script type='text/javascript' src='js/utils.js'></script>
<script type='text/javascript' src='js/colorpicker.js'></script>
<script type='text/javascript' src='js/codeview.js'></script>

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

    // matrice model-view combinee.
    uniform mat4 uMVMatrix;

    // matrice de projection
    uniform mat4 uPMatrix;

    // matrice des normales.
    uniform mat3 uNMatrix;

    // position de la lumiere.
    uniform vec3 uLightPosition;

    // La normale transformee
    varying vec3 vNormal;

    // la direction vertex-lumiere
    varying vec3 vLightRay;

    // la direction camera-vertex
    varying vec3 vEyeVec;

    uniform vec4 uLightAmbient;
    uniform vec4 uLightDiffuse;
    uniform vec4 uLightSpecular;

    uniform vec4 uMaterialAmbient;
    uniform vec4 uMaterialDiffuse;
    uniform vec4 uMaterialSpecular;


    void main(void) {


    vec4 ambientProduct= uLightAmbient* uMaterialAmbient;
    vec4 diffuseProduct= uLightDiffuse*uMaterialDiffuse;
    vec4 specularProduct= uLightSpecular*uMaterialSpecular;

    vec3 pos = (uMVMatrix*vec4(aVertexPosition, 1.0)).xyz;


    // position de l'oeil/camera.
    const vec3 eyePosition = vec3(0,0,-40);

    //Transformed normal position
    vNormal = normalize((uNMatrix* aVertexNormal).xyz) ;

    //Transformed light position
    vec4 light = uMVMatrix * vec4(uLightPosition,1.0);

    vec3 lightPos = (uMVMatrix * light).xyz;

    //Light position
    vLightRay = normalize(pos - lightPos);

    //Vector Eye
    vEyeVec = -normalize(pos);


    //Final vertex position
    gl_Position = uMVMatrix*uPMatrix* vec4(aVertexPosition, 1.0);

    }
</script>

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

    varying vec3 vNormal;
    varying vec3 vLightRay;
    varying vec3 vEyeVec;

    uniform vec4 ambientProduct;
    uniform vec4 diffuseProduct;
    uniform vec4 specularProduct;
    uniform float uShininess;

    void main(void)
    {

    vec4 diffuse = max(dot( vNormal,vLightRay), 0.0) * diffuseProduct;
    vec3 H = normalize(vLightRay+vEyeVec);
    vec4 specular =
    pow(max(dot(vNormal, H), 0.0), uShininess) * specularProduct;
    if (dot(vLightRay, vNormal) < 0.0)
    specular = vec4(0.0, 0.0, 0.0, 1.0);

    vec4 fColor = ambientProduct + diffuse + specular;
    fColor.a = 1.0;
    gl_FragColor =fColor;

    }
</script>



<script id='code-js' type="text/javascript">

var gl = null; // WebGL context
var prg = null; // The program (shaders)
var c_width = 0; // Variable to store the width of the canvas
var c_height = 0; // Variable to store the height of the canvas

var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
var nMatrix =  mat4.create();      // The normal matrix

var distance = -40;
var animateFlag = false;

var objects = [];   

/**
* The program contains a series of instructions that tell the Graphic Processing Unit (GPU)
* what to do with every vertex and fragment that we pass it. 
* The vertex shader and the fragment shader together are called the program.
*/
function initProgram() {
var fragmentShader      = utils.getShader(gl, "shader-fs");
var vertexShader        = utils.getShader(gl, "shader-vs");

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

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

gl.useProgram(prg);

prg.aVertexPosition     = gl.getAttribLocation(prg, "aVertexPosition");
prg.aVertexNormal       = gl.getAttribLocation(prg, "aVertexNormal");

prg.uPMatrix            = gl.getUniformLocation(prg, "uPMatrix");
prg.uMVMatrix           = gl.getUniformLocation(prg, "uMVMatrix");
prg.uNMatrix            = gl.getUniformLocation(prg, "uNMatrix");


prg.uMaterialAmbient    = gl.getUniformLocation(prg, "uMaterialAmbient");
prg.uMaterialDiffuse    = gl.getUniformLocation(prg, "uMaterialDiffuse");
prg.uMaterialSpecular   = gl.getUniformLocation(prg, "uMaterialSpecular");
prg.uShininess          = gl.getUniformLocation(prg, "uShininess");


prg.uLightPosition      = gl.getUniformLocation(prg, "uLightPosition");
prg.uLightAmbient       = gl.getUniformLocation(prg, "uLightAmbient");
prg.uLightDiffuse       = gl.getUniformLocation(prg, "uLightDiffuse");
prg.uLightSpecular      = gl.getUniformLocation(prg, "uLightSpecular");
}


function initLights(){
//Light uniforms
gl.uniform3fv(prg.uLightPosition,[4.5,3.0,15.0]);        
gl.uniform4f(prg.uLightAmbient ,1.0,1.0,1.0,1.0);
gl.uniform4f(prg.uLightDiffuse,1.0,1.0,1.0,1.0);
gl.uniform4f(prg.uLightSpecular,1.0,1.0,1.0,1.0);

//Object Uniforms
gl.uniform4f(prg.uMaterialAmbient, 0.1,0.1,0.1,1.0);
gl.uniform4f(prg.uMaterialDiffuse, 0.5,0.8,0.1,1.0);
gl.uniform4f(prg.uMaterialSpecular, 0.6,0.6,0.6,1.0);
gl.uniform1f(prg.uShininess, 200.0);

}


/**
* Creates an AJAX request to load the scene asynchronously
*/
function loadScene(){
loadObject('models/plane.json');
loadObject('models/cone.json','cone');
loadObject('models/sphere.json','sphere');
loadObject('models/smallsph.json','lightsource');
}

function getObject(alias){
for(var i=0; i<objects.length; i++){
if (alias == objects[i].alias) return objects[i];
}
return null;
}

/**
* Ajax and JSON in action
*/ 

function loadObject(filename,alias){
    var request = new XMLHttpRequest();
    console.info('Requesting ' + filename);
    request.open("GET",filename);
    
    request.onreadystatechange = function() {
    if (request.readyState == 4) {
        if(request.status == 404) {
            console.info(filename + ' does not exist');
        }
        else {
var o = JSON.parse(request.responseText);
o.alias = (alias==null)?'none':alias;
            handleLoadedObject(filename,o);
        }
    }
    }
    request.send();
}

/**
* Creates the buffers that contain the geometry of the object
*/
function handleLoadedObject(filename,object) {
    
    console.info(filename + ' has been retrieved from the server');
    
    var vertexBufferObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object.vertices), gl.STATIC_DRAW);
    
        
    var normalBufferObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(calcNormals(object.vertices, object.indices)), gl.STATIC_DRAW);
    
    var indexBufferObject = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object.indices), gl.STATIC_DRAW);
        
    
    object.vbo = vertexBufferObject;
    object.ibo = indexBufferObject;
    object.nbo = normalBufferObject;

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    gl.bindBuffer(gl.ARRAY_BUFFER,null);
    
    objects.push(object);
} 

/**
* Main rendering function. Called every 500ms according to WebGLStart function (see below)
*/
function drawScene() {
    gl.clearColor(0.3,0.3,0.3, 1.0);
    gl.clearDepth(100.0);
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);
    gl.viewport(0, 0, c_width, c_height);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    mat4.perspective(60, c_width / c_height, 0.1, 1000.0, pMatrix);

    try{
        gl.enableVertexAttribArray(prg.aVertexPosition);
        gl.enableVertexAttribArray(prg.aVertexNormal);
        for (var i = 0; i < objects.length; i++){
            var object = objects[i];
            mat4.identity(mvMatrix);
            mat4.translate(mvMatrix, [0.0, 0.0, distance]); //Sets the camera to a reasonable distance to view the part
            mat4.rotate(mvMatrix, 30*Math.PI/180, [1,0,0]);
            mat4.rotate(mvMatrix, angle*Math.PI/180, [0,1,0]);
            if (object.alias == 'lightsource'){
                var lightPos = gl.getUniform(prg, prg.uLightPosition);
                mat4.translate(mvMatrix,lightPos);
                
            }
            
            gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
            gl.uniformMatrix4fv(prg.uPMatrix, false, pMatrix);
            mat4.set(mvMatrix, nMatrix);
            mat4.inverse(nMatrix);
            mat4.transpose(nMatrix);
            
            gl.uniformMatrix4fv(prg.uNMatrix, false, nMatrix);
            gl.uniform4fv(prg.uMaterialAmbient, object.ambient);
            gl.uniform4fv(prg.uMaterialDiffuse, object.diffuse);
            gl.uniform4fv(prg.uMaterialSpecular, object.specular);
            gl.bindBuffer(gl.ARRAY_BUFFER, object.vbo);
            gl.vertexAttribPointer(prg.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(prg.aVertexPosition);
            
            gl.bindBuffer(gl.ARRAY_BUFFER, object.nbo);
            gl.vertexAttribPointer(prg.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(prg.aVertexNormal);
            
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);
            gl.drawElements(gl.TRIANGLES, object.indices.length, gl.UNSIGNED_SHORT,0);
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
        }
    }
    catch(err){
        alert(err);
        message(err.description);
    }
}






var lastTime = 0;
var angle = 0;
/**
* Updates the angle of rotation by a little bit each time
*/
function animate() {
    var timeNow = new Date().getTime();
    if (lastTime != 0) {
        var elapsed = timeNow - lastTime;
        if (animateFlag) angle += (90 * elapsed) / 10000.0;
    }
    lastTime = timeNow;
}

/**
* Render Loop
*/
function renderLoop() {
    requestAnimFrame(renderLoop);
    drawScene();
animate();
}

/**
* Entry point. This function is invoked when the page is loaded
*/
function runWebGLApp() {
//Obtains a WebGL context
gl = utils.getGLContext("canvas-element-id");
//Initializes the program (shaders) 
    initProgram();
    //Initializes lights
    initLights();
    //Load Scene
loadScene();
    //Renders the scene!
    renderLoop();
}
</script>
</head>

<body onLoad='runWebGLApp()'>
<div id='top'>

<div id='contents'>
<div id='canvasContainer'>
<canvas id='canvas-element-id' width='480' height='400'>
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>

<div id='bottom'>
<table style='padding=0px'>
<tr>
<td>X:</td><td id='slider-x-value' width='30px'>4.5</td><td width='150px'><div id='slider-x'/></td>
</tr>
<tr>
<td>Y:</td><td id='slider-y-value'  width='30px'>3.0</td><td width='150px'><div id='slider-y'/></td>
</tr>
<tr>
<td>Z:</td> <td id='slider-z-value'  width='30px'>15.0</td><td width='150px'><div id='slider-z'/></td>
</tr>
</table>
</div>
<script>cview.run(cview.MODE_VIEW);</script>
<script> 
$('#slider-shininess').slider({value:200, min:1, max:300, step:1, slide:updateShininess});
$('#slider-x').slider({value:4.5, min:-50, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#slider-y').slider({value:3.0, min:0, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});
$('#slider-z').slider({value:15.0, min:-50, max:50, step:0.1, slide:updateLightPosition, change:updateLightPosition});


$('#animate-btn').button();
$('#animate-btn').click(
function(){
    if ($('#animate-btn:checked').val()==null){
        animateFlag = false;
    }
    else{
        animateFlag = true;
    }
});
function updateShininess(){
    var v = $('#slider-shininess').slider("value");
    gl.uniform1f(prg.uShininess, v);
    $('#slider-shininess-value').html(v);
}



function updateLightPosition(){
    var x = $('#slider-x').slider("value");
    var y = $('#slider-y').slider("value");
    var z = $('#slider-z').slider("value");
    gl.uniform3fv(prg.uLightPosition, [x,y,z]);
    $('#slider-x-value').html(x);
    $('#slider-y-value').html(y);
    $('#slider-z-value').html(z);
}


function updateDistance(){
    var d = $('#slider-distance').slider("value");
    $('#slider-distance-value').html(distance);
    distance = -d;
}



function updateObjectColor(alias, r,g,b){
    var object = getObject(alias);
    if (object != null){
        object.diffuse = [r,g,b,1.0];
    }

}

$('#colorSelectorSphere').ColorPicker({
onSubmit: function(hsb, hex, rgb, el) {
        $(el).val(hex);
        $(el).ColorPickerHide();

    },
color: '#00ff00',
onShow: function (colpkr) {
        $(colpkr).fadeIn(500);
        return false;
    },
onHide: function (colpkr) {
        $(colpkr).fadeOut(500);
        return false;
    },
onChange: function (hsb, hex, rgb) {
        $('#colorSelectorSphere div').css('backgroundColor', '#' + hex);
        updateObjectColor('sphere',rgb.r/256,rgb.g/256,rgb.b/256);
    },
    
onBeforeShow: function (colpkr) {
        $(this).ColorPickerSetColor('rgb(0.5,0.8,0.1)');
    }
})

$('#colorSelectorCone').ColorPicker({
onSubmit: function(hsb, hex, rgb, el) {
        $(el).val(hex);
        $(el).ColorPickerHide();

    },
color: '#00ff00',
onShow: function (colpkr) {
        $(colpkr).fadeIn(500);
        return false;
    },
onHide: function (colpkr) {
        $(colpkr).fadeOut(500);
        return false;
    },
onChange: function (hsb, hex, rgb) {
        $('#colorSelectorCone div').css('backgroundColor', '#' + hex);
        updateObjectColor('cone',rgb.r/256,rgb.g/256,rgb.b/256);
    },
    
onBeforeShow: function (colpkr) {
        $(this).ColorPickerSetColor('rgb(0.8,0.1,0.5)');
    }
}) 


// Calcule les normales des vertex. La normale de chaque vertex est
// la moyenne des triangles voisins.
//
// vertices: la liste des vertex.
// ind: la liste des indices.
// retour: la liste des normales par vertex.
function calcNormals(vertices, ind){
  var x=0; 
  var y=1;
  var z=2;
    var v1 = [], v2 = [], thisNormal = [];

    // initialiser la liste des normales.
  var ns = [];
  for(var i=0;i<vertices.length;i++)
    {
    ns[i]=0.0;
  }

  for(var i=0;i<ind.length;i=i+3){
    //v1 = p1 - p0
    v1[x] = vertices[3*ind[i+1]+x] - vertices[3*ind[i]+x];
    v1[y] = vertices[3*ind[i+1]+y] - vertices[3*ind[i]+y];
    v1[z] = vertices[3*ind[i+1]+z] - vertices[3*ind[i]+z];

    // v2 = p2 - p1
    v2[x] = vertices[3*ind[i+2]+x] - vertices[3*ind[i]+x];
    v2[y] = vertices[3*ind[i+2]+y] - vertices[3*ind[i]+y];
    v2[z] = vertices[3*ind[i+2]+z] - vertices[3*ind[i]+z];            

    // N = v2 x v1 (cross product).
    thisNormal[x] = v1[y]*v2[z] - v1[z]*v2[y];
    thisNormal[y] = v1[z]*v2[x] - v1[x]*v2[z];
    thisNormal[z] = v1[x]*v2[y] - v1[y]*v2[x];

    for(j=0;j<3;j++)
        {
            // N += thisNormal. on additionne les normales. 
      ns[3*ind[i+j]+x] =  ns[3*ind[i+j]+x] + thisNormal[x];
      ns[3*ind[i+j]+y] =  ns[3*ind[i+j]+y] + thisNormal[y];
      ns[3*ind[i+j]+z] =  ns[3*ind[i+j]+z] + thisNormal[z];
    }
  }

  // Normalisation.
  for(var i=0;i<vertices.length;i=i+3){ 

    var nn=[];
        var len = 0;
        for(var j = 0; j < 3; j++)
        {
            nn[j] = ns[i+j];
            len += nn[j] * nn[j];
        }

        // La norme de la normale.
        len = Math.sqrt(len);
    if (len == 0) 
            len = 0.00001;

        for(var j = 0; j < 3; j++)
        ns[i+j] = nn[j] / len;

        console.log(len);
  }

  return ns;
}

</script>
</body>
</html>

老实说,要涵盖的问题太多了。试图让你的代码工作。首先,您确实需要学习如何制作最小的回购协议。您发布的代码没有运行,引用了几个不存在的脚本和不存在的数据。

代码所基于的教程似乎很旧。 2020 年没有人使用 XMLHttpRequest。没有 requestAnimFrame 的功能,它是 requestAnimationFrame。我认为这是 2011 年的 polyfill 遗留下来的。它仍在使用 <body onload="">,很少有人再使用了。它还使用 new Date().getTime(),因为时间被传递到 requestAnimationFrame,所以没有理由使用它。它正在调用一些函数来获取 webgl 上下文,但在 2020 年也没有理由这样做。它显然也在使用旧版本的 glMatrix,因为 the current version 使用不同的 API。在当前版本中,每个函数都采用一个矩阵来存储结果作为第一个参数。同样在当前版本中,perspective 以弧度表示视野。我不知道它过去是否以度为单位,但代码正在传递度数。

我将 XHR 代码更改为 return 一个立方体。 (“最小完整可验证示例”(mcve) 的最小和完整部分示例 - 我认为现在 S.O 称它们为“最小可复制示例”。我还删除了对 ColorPicker 的所有引用(制作最小回购的另一个例子)

代码应该在 the JavaScript console 中出现错误。你检查过 JavaScript 控制台了吗?特别是 uNMatrix 是一个 mat3 但代码调用 gl.uniformMatrix4fv 来设置它是一个错误。

使用 webgl-lint 指出几个制服没有被设置,包括“ambientProduct”、“diffuseProduct”、“specularProduct”,还有几个被设置的不存在(那部分不一定是错误)但是顶点着色器中有几个制服实际上没有使用,例如颜色似乎设置为

gl.uniform4fv(prg.uMaterialAmbient, object.ambient);
gl.uniform4fv(prg.uMaterialDiffuse, object.diffuse);
gl.uniform4fv(prg.uMaterialSpecular, object.specular);

但是着色器中没有使用这些制服(它们出现在着色器中,但如果您查看代码,您将看不到任何效果)

然后,当我最终消除所有错误时,剩下的问题是 gl_Position 的数学运算将 2 个矩阵倒置。它应该是 projection * modelView * position 但它有 modelView * projection * position

但是,此外,所有 3 个模型都绘制在相同的位置。也许这些模型中的几何图形处于不同的位置。我不知道,但通常你会根据循环索引或一些位置数组或每个对象数据,以某种形式给你绘制的每个东西它自己的位置。

无论如何,屏幕上出现了一些东西,但已经花了 45 分钟,我不想尝试修复照明。相反,我建议您阅读一些稍微更新的教程,例如 this one 及其链接到的教程。

var mat4 = glMatrix.mat4;
var gl = null; // WebGL context
var prg = null; // The program (shaders)
var c_width = 480; // Variable to store the width of the canvas
var c_height = 400; // Variable to store the height of the canvas

var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
var nMatrix = mat4.create(); // The normal matrix

var distance = -40;
var animateFlag = false;

var objects = [];

const utils = {
  getShader(gl, id) {
    const elem = document.getElementById(id);
    const type = /vertex/.test(elem.type) ?
      gl.VERTEX_SHADER :
      gl.FRAGMENT_SHADER;
    const sh = gl.createShader(type);
    gl.shaderSource(sh, elem.text);
    gl.compileShader(sh);
    if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
      throw new Error(gl.getShaderInfoLog(sh));
    }
    return sh;
  },
};

/**
 * The program contains a series of instructions that tell the Graphic Processing Unit (GPU)
 * what to do with every vertex and fragment that we pass it. 
 * The vertex shader and the fragment shader together are called the program.
 */
function initProgram() {
  var fragmentShader = utils.getShader(gl, "shader-fs");
  var vertexShader = utils.getShader(gl, "shader-vs");

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

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

  gl.useProgram(prg);

  prg.aVertexPosition = gl.getAttribLocation(prg, "aVertexPosition");
  prg.aVertexNormal = gl.getAttribLocation(prg, "aVertexNormal");

  prg.uPMatrix = gl.getUniformLocation(prg, "uPMatrix");
  prg.uMVMatrix = gl.getUniformLocation(prg, "uMVMatrix");
  prg.uNMatrix = gl.getUniformLocation(prg, "uNMatrix");


  prg.uMaterialAmbient = gl.getUniformLocation(prg, "uMaterialAmbient");
  prg.uMaterialDiffuse = gl.getUniformLocation(prg, "uMaterialDiffuse");
  prg.uMaterialSpecular = gl.getUniformLocation(prg, "uMaterialSpecular");
  prg.uShininess = gl.getUniformLocation(prg, "uShininess");


  prg.uLightPosition = gl.getUniformLocation(prg, "uLightPosition");
  prg.uLightAmbient = gl.getUniformLocation(prg, "uLightAmbient");
  prg.uLightDiffuse = gl.getUniformLocation(prg, "uLightDiffuse");
  prg.uLightSpecular = gl.getUniformLocation(prg, "uLightSpecular");
  
  prg.ambientProduct = gl.getUniformLocation(prg, "ambientProduct");
  prg.diffuseProduct = gl.getUniformLocation(prg, "diffuseProduct");
  prg.specularProduct = gl.getUniformLocation(prg, "specularProduct");
}


function initLights() {
  //Light uniforms
  gl.uniform3fv(prg.uLightPosition, [4.5, 3.0, 15.0]);
  gl.uniform4f(prg.uLightAmbient, 1.0, 1.0, 1.0, 1.0);
  gl.uniform4f(prg.uLightDiffuse, 1.0, 1.0, 1.0, 1.0);
  gl.uniform4f(prg.uLightSpecular, 1.0, 1.0, 1.0, 1.0);

  //Object Uniforms
  gl.uniform4f(prg.uMaterialAmbient, 0.1, 0.1, 0.1, 1.0);
  gl.uniform4f(prg.uMaterialDiffuse, 0.5, 0.8, 0.1, 1.0);
  gl.uniform4f(prg.uMaterialSpecular, 0.6, 0.6, 0.6, 1.0);
  gl.uniform1f(prg.uShininess, 200.0);
}


/**
 * Creates an AJAX request to load the scene asynchronously
 */
function loadScene() {
  loadObject('models/plane.json');
  loadObject('models/cone.json', 'cone');
  loadObject('models/sphere.json', 'sphere');
  loadObject('models/smallsph.json', 'lightsource');
}

function getObject(alias) {
  for (var i = 0; i < objects.length; i++) {
    if (alias == objects[i].alias) return objects[i];
  }
  return null;
}

/**
 * Ajax and JSON in action
 */

const vertices = [
  -1, -1,  1,
   1, -1,  1,
  -1,  1,  1,
   1,  1,  1,
   1, -1,  1,
   1, -1, -1,
   1,  1,  1,
   1,  1, -1,
   1, -1, -1, 
  -1, -1, -1,
   1,  1, -1, 
  -1,  1, -1,
  -1, -1, -1,
  -1, -1,  1,
  -1,  1, -1,
  -1,  1,  1,
   1,  1, -1,
  -1,  1, -1,
   1,  1,  1,
  -1,  1,  1,
   1, -1,  1,
  -1, -1,  1,
   1, -1, -1,
  -1, -1, -1,
];
let modelNum = 0
function loadObject(filename, alias) {
  setTimeout(() => {
    const o = {
      alias: (alias == null) ? 'none' : alias,
      ambient: [0.1, 0.1, 0.1, 1],
      diffuse: [Math.random(), Math.random(), Math.random(), 1],
      specular: [1, 1, 1, 1],
      // to make up for the fact the code does not have different positions
      // for each model we'll move the vertices (bad)
      vertices: vertices.map((v, i) => i % 3 === 0 ? v + modelNum * 3 : v),
      indices: [
        0, 1, 2, 2, 1, 3,
        4, 5, 6, 6, 5, 7,
        8, 9, 10, 10, 9, 11,
        12, 13, 14, 14, 13, 15,
        16, 17, 18, 18, 17, 19,
        20, 21, 22, 22, 21, 23,
      ],
    };
    handleLoadedObject(filename, o);
    ++modelNum;
  });
}

/**
 * Creates the buffers that contain the geometry of the object
 */
function handleLoadedObject(filename, object) {

  //console.info(filename + ' has been retrieved from the server');

  var vertexBufferObject = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBufferObject);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(object.vertices), gl.STATIC_DRAW);


  var normalBufferObject = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferObject);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(calcNormals(object.vertices, object.indices)), gl.STATIC_DRAW);

  var indexBufferObject = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferObject);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(object.indices), gl.STATIC_DRAW);


  object.vbo = vertexBufferObject;
  object.ibo = indexBufferObject;
  object.nbo = normalBufferObject;

  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  objects.push(object);
}

/**
 * Main rendering function. Called every 500ms according to WebGLStart function (see below)
 */
function drawScene() {
  gl.clearColor(0.3, 0.3, 0.3, 1.0);
  //gl.clearDepth(100.0);
  gl.enable(gl.DEPTH_TEST);
  gl.depthFunc(gl.LEQUAL);
  gl.viewport(0, 0, c_width, c_height);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  mat4.perspective(pMatrix, 60 * Math.PI / 180, c_width / c_height, 0.1, 1000.0);

  gl.enableVertexAttribArray(prg.aVertexPosition);
  gl.enableVertexAttribArray(prg.aVertexNormal);
  for (var i = 0; i < objects.length; i++) {
    var object = objects[i];
    mat4.identity(mvMatrix);
    mat4.translate(mvMatrix, mvMatrix, [0.0, 0.0, distance]); //Sets the camera to a reasonable distance to view the part
    mat4.rotate(mvMatrix, mvMatrix, 30 * Math.PI / 180, [1, 0, 0]);
    mat4.rotate(mvMatrix, mvMatrix, angle * Math.PI / 180, [0, 1, 0]);
    if (object.alias == 'lightsource') {
      var lightPos = gl.getUniform(prg, prg.uLightPosition);
      mat4.translate(mvMatrix, mvMatrix, lightPos);

    }

    gl.uniformMatrix4fv(prg.uMVMatrix, false, mvMatrix);
    gl.uniformMatrix4fv(prg.uPMatrix, false, pMatrix);
    mat4.set(mvMatrix, nMatrix);
    mat4.invert(nMatrix, nMatrix);
    mat4.transpose(nMatrix, nMatrix);

    const t3 = glMatrix.mat3.create();
    glMatrix.mat3.fromMat4(t3, nMatrix);
    gl.uniformMatrix3fv(prg.uNMatrix, false, t3);
    gl.uniform4fv(prg.ambientProduct, object.ambient);
    gl.uniform4fv(prg.diffuseProduct, object.diffuse);
    gl.uniform4fv(prg.specularProduct, object.specular);
    gl.bindBuffer(gl.ARRAY_BUFFER, object.vbo);
    gl.vertexAttribPointer(prg.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(prg.aVertexPosition);

    gl.bindBuffer(gl.ARRAY_BUFFER, object.nbo);
    gl.vertexAttribPointer(prg.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(prg.aVertexNormal);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, object.ibo);
    gl.drawElements(gl.TRIANGLES, object.indices.length, gl.UNSIGNED_SHORT, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
  }
}

var lastTime = 0;
var angle = 0;
/**
 * Updates the angle of rotation by a little bit each time
 */
function animate() {
  var timeNow = new Date().getTime();
  if (lastTime != 0) {
    var elapsed = timeNow - lastTime;
    if (animateFlag) angle += (90 * elapsed) / 10000.0;
  }
  lastTime = timeNow;
}

/**
 * Render Loop
 */
function renderLoop() {
  drawScene();
  animate();
  requestAnimationFrame(renderLoop);
}

/**
 * Entry point. This function is invoked when the page is loaded
 */
function runWebGLApp() {
  //Obtains a WebGL context
  gl = document.getElementById("canvas-element-id").getContext('webgl');
  //Initializes the program (shaders) 
  initProgram();
  //Initializes lights
  initLights();
  //Load Scene
  loadScene();
  //Renders the scene!
  renderLoop();
}

$('#slider-shininess').slider({
  value: 200,
  min: 1,
  max: 300,
  step: 1,
  slide: updateShininess
});
$('#slider-x').slider({
  value: 4.5,
  min: -50,
  max: 50,
  step: 0.1,
  slide: updateLightPosition,
  change: updateLightPosition
});
$('#slider-y').slider({
  value: 3.0,
  min: 0,
  max: 50,
  step: 0.1,
  slide: updateLightPosition,
  change: updateLightPosition
});
$('#slider-z').slider({
  value: 15.0,
  min: -50,
  max: 50,
  step: 0.1,
  slide: updateLightPosition,
  change: updateLightPosition
});


$('#animate-btn').button();
$('#animate-btn').click(
  function() {
    if ($('#animate-btn:checked').val() == null) {
      animateFlag = false;
    } else {
      animateFlag = true;
    }
  });

function updateShininess() {
  var v = $('#slider-shininess').slider("value");
  gl.uniform1f(prg.uShininess, v);
  $('#slider-shininess-value').html(v);
}



function updateLightPosition() {
  var x = $('#slider-x').slider("value");
  var y = $('#slider-y').slider("value");
  var z = $('#slider-z').slider("value");
  gl.uniform3fv(prg.uLightPosition, [x, y, z]);
  $('#slider-x-value').html(x);
  $('#slider-y-value').html(y);
  $('#slider-z-value').html(z);
}


function updateDistance() {
  var d = $('#slider-distance').slider("value");
  $('#slider-distance-value').html(distance);
  distance = -d;
}



function updateObjectColor(alias, r, g, b) {
  var object = getObject(alias);
  if (object != null) {
    object.diffuse = [r, g, b, 1.0];
  }

}

// Calcule les normales des vertex. La normale de chaque vertex est
// la moyenne des triangles voisins.
//
// vertices: la liste des vertex.
// ind: la liste des indices.
// retour: la liste des normales par vertex.
function calcNormals(vertices, ind) {
  var x = 0;
  var y = 1;
  var z = 2;
  var v1 = [],
    v2 = [],
    thisNormal = [];

  // initialiser la liste des normales.
  var ns = [];
  for (var i = 0; i < vertices.length; i++) {
    ns[i] = 0.0;
  }

  for (var i = 0; i < ind.length; i = i + 3) {
    //v1 = p1 - p0
    v1[x] = vertices[3 * ind[i + 1] + x] - vertices[3 * ind[i] + x];
    v1[y] = vertices[3 * ind[i + 1] + y] - vertices[3 * ind[i] + y];
    v1[z] = vertices[3 * ind[i + 1] + z] - vertices[3 * ind[i] + z];

    // v2 = p2 - p1
    v2[x] = vertices[3 * ind[i + 2] + x] - vertices[3 * ind[i] + x];
    v2[y] = vertices[3 * ind[i + 2] + y] - vertices[3 * ind[i] + y];
    v2[z] = vertices[3 * ind[i + 2] + z] - vertices[3 * ind[i] + z];

    // N = v2 x v1 (cross product).
    thisNormal[x] = v1[y] * v2[z] - v1[z] * v2[y];
    thisNormal[y] = v1[z] * v2[x] - v1[x] * v2[z];
    thisNormal[z] = v1[x] * v2[y] - v1[y] * v2[x];

    for (j = 0; j < 3; j++) {
      // N += thisNormal. on additionne les normales. 
      ns[3 * ind[i + j] + x] = ns[3 * ind[i + j] + x] + thisNormal[x];
      ns[3 * ind[i + j] + y] = ns[3 * ind[i + j] + y] + thisNormal[y];
      ns[3 * ind[i + j] + z] = ns[3 * ind[i + j] + z] + thisNormal[z];
    }
  }

  // Normalisation.
  for (var i = 0; i < vertices.length; i = i + 3) {

    var nn = [];
    var len = 0;
    for (var j = 0; j < 3; j++) {
      nn[j] = ns[i + j];
      len += nn[j] * nn[j];
    }

    // La norme de la normale.
    len = Math.sqrt(len);
    if (len == 0)
      len = 0.00001;

    for (var j = 0; j < 3; j++)
      ns[i + j] = nn[j] / len;
  }

  return ns;
}

runWebGLApp();
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet">
<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    attribute vec3 aVertexNormal;

    // matrice model-view combinee.
    uniform mat4 uMVMatrix;

    // matrice de projection
    uniform mat4 uPMatrix;

    // matrice des normales.
    uniform mat3 uNMatrix;

    // position de la lumiere.
    uniform vec3 uLightPosition;

    // La normale transformee
    varying vec3 vNormal;

    // la direction vertex-lumiere
    varying vec3 vLightRay;

    // la direction camera-vertex
    varying vec3 vEyeVec;

    uniform vec4 uLightAmbient;
    uniform vec4 uLightDiffuse;
    uniform vec4 uLightSpecular;

    uniform vec4 uMaterialAmbient;
    uniform vec4 uMaterialDiffuse;
    uniform vec4 uMaterialSpecular;


    void main(void) {


    vec4 ambientProduct= uLightAmbient* uMaterialAmbient;
    vec4 diffuseProduct= uLightDiffuse*uMaterialDiffuse;
    vec4 specularProduct= uLightSpecular*uMaterialSpecular;

    vec3 pos = (uMVMatrix*vec4(aVertexPosition, 1.0)).xyz;


    // position de l'oeil/camera.
    const vec3 eyePosition = vec3(0,0,-40);

    //Transformed normal position
    vNormal = normalize((uNMatrix* aVertexNormal).xyz) ;

    //Transformed light position
    vec4 light = uMVMatrix * vec4(uLightPosition,1.0);

    vec3 lightPos = (uMVMatrix * light).xyz;

    //Light position
    vLightRay = normalize(pos - lightPos);

    //Vector Eye
    vEyeVec = -normalize(pos);


    //Final vertex position
    gl_Position = uPMatrix*uMVMatrix* vec4(aVertexPosition, 1.0);

    }
</script>

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

    varying vec3 vNormal;
    varying vec3 vLightRay;
    varying vec3 vEyeVec;

    uniform vec4 ambientProduct;
    uniform vec4 diffuseProduct;
    uniform vec4 specularProduct;
    uniform float uShininess;

    void main(void)
    {

    vec4 diffuse = max(dot( vNormal,vLightRay), 0.0) * diffuseProduct;
    vec3 H = normalize(vLightRay+vEyeVec);
    vec4 specular =
    pow(max(dot(vNormal, H), 0.0), uShininess) * specularProduct;
    if (dot(vLightRay, vNormal) < 0.0)
    specular = vec4(0.0, 0.0, 0.0, 1.0);

    vec4 fColor = ambientProduct + diffuse + specular;
    fColor.a = 1.0;
    gl_FragColor =fColor;

    }
</script>



<div id='top'>

<div id='contents'>
<div id='canvasContainer'>
<canvas id='canvas-element-id' width='480' height='400'>
Your browser does not support the HTML5 canvas element.
</canvas>
</div>
</div>

<div id='bottom'>
<table style='padding=0px'>
<tr>
<td>X:</td><td id='slider-x-value' width='30px'>4.5</td><td width='150px'><div id='slider-x'/></td>
</tr>
<tr>
<td>Y:</td><td id='slider-y-value'  width='30px'>3.0</td><td width='150px'><div id='slider-y'/></td>
</tr>
<tr>
<td>Z:</td> <td id='slider-z-value'  width='30px'>15.0</td><td width='150px'><div id='slider-z'/></td>
</tr>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/gl-matrix@3.3.0/gl-matrix-min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js"
  data-gman-debug-helper='
    {
      "warnUndefinedUniforms": false
    }
  '
></script>