【笔记】《WebGL编程指南》学习-第8章光照(3-点光源光)

来源:互联网 发布:手机网络gsm cdma lte 编辑:程序博客网 时间:2024/05/17 02:32

与平行光相比,点光源光发出的光,在三维空间的不同位置上其方向也不同,如下图所示。所以,在对点光源光下的物体进行着色时,需要在每个入射点计算点光源光在该处的方向。

这里写图片描述

前一节根据每个顶点的法向量和平行光入射方向来计算反射光的颜色,这一节还是采用该方法,只不过点光源光的方向不再是恒定不变的,而是根据每个顶点的位置逐一计算。着色器需要知道点光源光自身的所在位置,而不是光的方向。

示例程序 PointLightedCube 是前一节 LightedCube_ambient 示例程序的点光源光版本,显示了一个点光源下的红色立方体。立方体表面仍然是漫反射,环境光保持不变,程序的效果如下图。

这里写图片描述

示例程序(PointLightedCube .js)

与 LightedCube_ambient 相比,顶点着色器中新增加了 u_ModelMatrix 变量和 u_LightPosition 变量,前者表示模型矩阵,后者表示点光源的位置。本例中的光是点光源光而非平行光,所以我们需要用到定光源光的位置,而不是光线方向。为了让你看的更清楚,本例将立方体稍做放大。

PointLightedCube.js

//顶点着色器程序var VSHADER_SOURCE =    'attribute vec4 a_Position;'+    'attribute vec4 a_Color;'+    'attribute vec4 a_Normal;'+    //法向量    'uniform mat4 u_ModelMatrix;'+  //模型矩阵    'uniform mat4 u_NormalMatrix;'+  //用来变化法向量的矩阵    'uniform mat4 u_MvpMatrix;'+    'uniform vec3 u_LightColor;'+    //光线颜色    'uniform vec3 u_LightPosition;'+    //光源位置-世界坐标    'uniform vec3 u_AmbientLight;'+    //环境光颜色    'varying vec4 v_Color;'+    'void main(){'+    'gl_Position = u_MvpMatrix * a_Position;'+    //对法向量进行归一化    'vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));'+    //计算顶点的世界坐标    'vec4 vertexPosition = u_ModelMatrix * a_Position;'+    //计算光线方向并归一化    'vec3 lightDirection = normalize(u_LightPosition - vec3(vertexPosition));'+    //计算法向量和光线方向的点积    'float nDotL = max(dot(lightDirection, normal), 0.0);'+    //计算漫反射光的颜色    'vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;'+    //计算环境光产生的反射颜色    'vec3 ambient = u_AmbientLight * a_Color.rgb;'+    'v_Color = vec4(diffuse + ambient, a_Color.a);'+    '}';//片元着色器程序var FSHADER_SOURCE=    '#ifdef GL_ES\n' +    'precision mediump float;\n' +    '#endif\n' +    'varying vec4 v_Color;' +    'void main() {'+    'gl_FragColor = v_Color;'+    '}';function main() {    //获取canvas元素    var canvas = document.getElementById("webgl");    if(!canvas){        console.log("Failed to retrieve the <canvas> element");        return;    }    //获取WebGL绘图上下文    var gl = getWebGLContext(canvas);    if(!gl){        console.log("Failed to get the rendering context for WebGL");        return;    }    //初始化着色器    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){        console.log("Failed to initialize shaders.");        return;    }    //设置顶点位置    var n = initVertexBuffers(gl);    if (n < 0) {        console.log('Failed to set the positions of the vertices');        return;    }    //指定清空<canvas>颜色    gl.clearColor(0.0, 0.0, 0.0, 1.0);    gl.enable(gl.DEPTH_TEST);    //获取 u_MvpMatrix 、u_LightColor u_LightDirection u_AmbientLight 变量的存储位置    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');    var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');    var u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');    var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');    var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');    var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');    if(!u_MvpMatrix || !u_NormalMatrix || !u_LightPosition || !u_LightColor || !u_LightPosition || !u_AmbientLight){        console.log("Failed to get the storage location");        return;    }    //设置光线颜色    gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);    gl.uniform3f(u_LightPosition, 2.3, 4.0, 3.5);    gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);    var modelMatrix = new Matrix4();    var mvpMatrix = new Matrix4();    var normalMatrix = new Matrix4();    modelMatrix.setRotate(90, 0, 1, 0);    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);    mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);    mvpMatrix.lookAt(6, 6, 14, 0, 0, 0, 0, 1, 0);    mvpMatrix.multiply(modelMatrix);    gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);    normalMatrix.setInverseOf(modelMatrix);    normalMatrix.transpose();    gl.uniformMatrix4fv(u_NormalMatrix, false, modelMatrix.elements);    gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT);    //绘制立方体    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);}function initVertexBuffers(gl) {    //    v6----- v5    //   /|      /|    //  v1------v0|    //  | |     | |    //  | |v7---|-|v4    //  |/      |/    //  v2------v3    var vertices = new Float32Array([   //顶点坐标        2.0, 2.0, 2.0,  -2.0, 2.0, 2.0,  -2.0,-2.0, 2.0,   2.0,-2.0, 2.0, // v0-v1-v2-v3        2.0, 2.0, 2.0,   2.0,-2.0, 2.0,   2.0,-2.0,-2.0,   2.0, 2.0,-2.0, // v0-v3-v4-v5        2.0, 2.0, 2.0,   2.0, 2.0,-2.0,  -2.0, 2.0,-2.0,  -2.0, 2.0, 2.0, // v0-v5-v6-v1        -2.0, 2.0, 2.0,  -2.0, 2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0,-2.0, 2.0, // v1-v6-v7-v2        -2.0,-2.0,-2.0,   2.0,-2.0,-2.0,   2.0,-2.0, 2.0,  -2.0,-2.0, 2.0, // v7-v4-v3-v2        2.0,-2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0, 2.0,-2.0,   2.0, 2.0,-2.0  // v4-v7-v6-v5    ]);    var colors = new Float32Array([     // 颜色        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0    ]);    var normals = new Float32Array([    // 法向量        0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,        1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,        0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,        -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,        0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,        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 indices = new Uint8Array([       // 顶点索引        0, 1, 2,   0, 2, 3,        4, 5, 6,   4, 6, 7,        8, 9,10,   8,10,11,        12,13,14,  12,14,15,        16,17,18,  16,18,19,        20,21,22,  20,22,23    ]);    if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;    if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) return -1;    if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;    //创建缓冲区对象    var indexBuffer = gl.createBuffer();    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);    return indices.length;}function initArrayBuffer(gl, attribute, data, num, type) {    var buffer = gl.createBuffer();    if(!buffer){        console.log("Failed to create thie buffer object");        return -1;    }    //将缓冲区对象保存到目标上    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);    //向缓存对象写入数据    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);    var a_attribute = gl.getAttribLocation(gl.program,attribute);    if(a_attribute < 0){        console.log("Failed to get the storage location of " + attribute);        return -1;    }    gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);    gl.enableVertexAttribArray(a_attribute);    return true;}

最关键的变化发生在顶点着色器中。首先使用模型矩阵变换顶点坐标,获得顶点在世界坐标系中的坐标,以便计算点光源光在顶点处的方向。点光源向四周发射光线,所以顶点出的额光线方向是由点光源光坐标减去顶点坐标而得到的矢量。点光源在世界坐标系中的坐标已经传给了着色器中的 u_LightPositio,而前面也已经计算出了顶点在世界坐标系中的坐标,这样就将计算出了光线方向矢量 lightDirection。注意,需要使用 normaliza()进行归一化,以保证光线方向矢量的长度为1.0。最后, 计算光线方向矢量与法向量的点积,从而计算处每个顶点的颜色。

运行程序,你会发现效果更加逼真了但是,如果仔细观察还是能发现一个问题:立方体表面由不自然的线条。

出现该现象的原因在第5章讨论过的内插过程中提到过。你应该还记得,WebGL 系统会根据顶点的颜色,内插处表面上每个片元的额颜色,实际上,点光源光照射到一个表面上,所产生的效果与简单使用4个顶点颜色内插处的效果并不完全相同,所以为了使效果更加逼真,我们需要对表面的每一个点,而不仅仅是4个顶点计算光照效果。如果使用一个球体,二者的差异可能会更加明显。

这里写图片描述

如你所见,左图中球体暗部与亮部的分界不是很自然,而右侧的就自然多了。


更逼真:逐片元光照

乍一听,要在表面的每一点上计算光照产生的颜色,似乎是个不可能完成的任务。但实际上,我们只需要逐片元地进行计算。片元着色器总算要排上用场了。

示例程序是 PointLightedCube_perFragment,效果如下图所示。

这里写图片描述

示例程序(PointLightedCube_perFragment.js)

与 PointLightedCube.js 相比,只有着色器部分被修改了,计算光照效果的逻辑从顶点着色器移到了片元着色器中。

//顶点着色器程序var VSHADER_SOURCE =    'attribute vec4 a_Position;'+    'attribute vec4 a_Color;'+    'attribute vec4 a_Normal;'+    //法向量    'varying vec3 v_Position;'+    'varying vec4 v_Color;'+    'varying vec3 v_Normal;'+    //法向量    'uniform mat4 u_ModelMatrix;'+  //模型矩阵    'uniform mat4 u_NormalMatrix;'+  //用来变化法向量的矩阵    'uniform mat4 u_MvpMatrix;'+    'void main(){'+    'gl_Position = u_MvpMatrix * a_Position;'+    //计算顶点的世界坐标    'v_Position = vec3(u_ModelMatrix * a_Position);'+    'v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));'+    'v_Color = a_Color;'+    '}';//片元着色器程序var FSHADER_SOURCE=    '#ifdef GL_ES\n' +    'precision mediump float;\n' +    '#endif\n' +    'uniform vec3 u_LightColor;'+    //光线颜色    'uniform vec3 u_LightPosition;'+    //光源位置-世界坐标    'uniform vec3 u_AmbientLight;'+    //环境光颜色    'varying vec3 v_Position;'+    'varying vec4 v_Color;'+    'varying vec3 v_Normal;'+    //法向量    'void main() {'+    //对法向量进行归一化    'vec3 normal = normalize(v_Normal);'+    //计算光线方向并归一化    'vec3 lightDirection = normalize(u_LightPosition - v_Position);'+    //计算法向量和光线方向的点积    'float nDotL = max(dot(lightDirection, normal), 0.0);'+    //计算漫反射光的颜色    'vec3 diffuse = u_LightColor * vec3(v_Color) * nDotL;'+    //计算环境光产生的反射颜色    'vec3 ambient = u_AmbientLight * v_Color.rgb;'+    'gl_FragColor = vec4(diffuse + ambient, v_Color.a);'+    '}';function main() {    //获取canvas元素    var canvas = document.getElementById("webgl");    if(!canvas){        console.log("Failed to retrieve the <canvas> element");        return;    }    //获取WebGL绘图上下文    var gl = getWebGLContext(canvas);    if(!gl){        console.log("Failed to get the rendering context for WebGL");        return;    }    //初始化着色器    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){        console.log("Failed to initialize shaders.");        return;    }    //设置顶点位置    var n = initVertexBuffers(gl);    if (n < 0) {        console.log('Failed to set the positions of the vertices');        return;    }    //指定清空<canvas>颜色    gl.clearColor(0.0, 0.0, 0.0, 1.0);    gl.enable(gl.DEPTH_TEST);    //获取 u_MvpMatrix 、u_LightColor u_LightDirection u_AmbientLight 变量的存储位置    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');    var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');    var u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');    var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');    var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');    var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');    if(!u_MvpMatrix || !u_NormalMatrix || !u_LightPosition || !u_LightColor || !u_LightPosition || !u_AmbientLight){        console.log("Failed to get the storage location");        return;    }    //设置光线颜色    gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);    gl.uniform3f(u_LightPosition, 2.3, 4.0, 3.5);    gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);    var modelMatrix = new Matrix4();    var mvpMatrix = new Matrix4();    var normalMatrix = new Matrix4();    modelMatrix.setRotate(90, 0, 1, 0);    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);    mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);    mvpMatrix.lookAt(6, 6, 14, 0, 0, 0, 0, 1, 0);    mvpMatrix.multiply(modelMatrix);    gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);    normalMatrix.setInverseOf(modelMatrix);    normalMatrix.transpose();    gl.uniformMatrix4fv(u_NormalMatrix, false, modelMatrix.elements);    gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT);    //绘制立方体    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);}function initVertexBuffers(gl) {    //    v6----- v5    //   /|      /|    //  v1------v0|    //  | |     | |    //  | |v7---|-|v4    //  |/      |/    //  v2------v3    var vertices = new Float32Array([   //顶点坐标        2.0, 2.0, 2.0,  -2.0, 2.0, 2.0,  -2.0,-2.0, 2.0,   2.0,-2.0, 2.0, // v0-v1-v2-v3        2.0, 2.0, 2.0,   2.0,-2.0, 2.0,   2.0,-2.0,-2.0,   2.0, 2.0,-2.0, // v0-v3-v4-v5        2.0, 2.0, 2.0,   2.0, 2.0,-2.0,  -2.0, 2.0,-2.0,  -2.0, 2.0, 2.0, // v0-v5-v6-v1        -2.0, 2.0, 2.0,  -2.0, 2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0,-2.0, 2.0, // v1-v6-v7-v2        -2.0,-2.0,-2.0,   2.0,-2.0,-2.0,   2.0,-2.0, 2.0,  -2.0,-2.0, 2.0, // v7-v4-v3-v2        2.0,-2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0, 2.0,-2.0,   2.0, 2.0,-2.0  // v4-v7-v6-v5    ]);    var colors = new Float32Array([     // 颜色        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0,        1, 0, 0,   1, 0, 0,   1, 0, 0,  1, 0, 0    ]);    var normals = new Float32Array([    // 法向量        0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,        1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,        0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,        -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,        0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,        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 indices = new Uint8Array([       // 顶点索引        0, 1, 2,   0, 2, 3,        4, 5, 6,   4, 6, 7,        8, 9,10,   8,10,11,        12,13,14,  12,14,15,        16,17,18,  16,18,19,        20,21,22,  20,22,23    ]);    if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;    if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) return -1;    if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;    //创建缓冲区对象    var indexBuffer = gl.createBuffer();    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);    return indices.length;}function initArrayBuffer(gl, attribute, data, num, type) {    var buffer = gl.createBuffer();    if(!buffer){        console.log("Failed to create thie buffer object");        return -1;    }    //将缓冲区对象保存到目标上    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);    //向缓存对象写入数据    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);    var a_attribute = gl.getAttribLocation(gl.program,attribute);    if(a_attribute < 0){        console.log("Failed to get the storage location of " + attribute);        return -1;    }    gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);    gl.enableVertexAttribArray(a_attribute);    return true;}

为了逐片元地计算光照,你需要知道:

  1. 片元在世界坐标下的坐标
  2. 片元处表面的法向量。

可以在顶点着色器中,将顶点的世界坐标和法向量以 varying 变量的形式传入片元着色器,偏远着色器中的同名变量就已经是内插后的逐片元值了。

顶点着色器使用模型矩阵乘以顶点坐标计算处顶点的世界坐标,将其赋值给 v_Position 变量,经过内插过程后,片元着色器就获得了逐片元的 v_Position 变量,也就是片元的世界坐标。类似的,顶点着色器将顶点的法向量赋值给 v_Normal 变量,经过内插,片元着色器就获得了逐片元的 v_Normal 变量,即片元的法向量。

片元着色器计算光照效果的方法与 PointLightedCube.js 相同。首先对法向量 v_Normal 进行归一化,因为内插后法向量可能不再是 1.0了;然后,计算片远处的光线方向并对其归一化;接着计算法向量与光线方向的点积;最后分别计算点光源光和环境光产生的反射光颜色,并将两个结果加起来,赋值给 gl_FragColor,片元就会显示为这个颜色。

如果场景中有超过一个点光源,那么就需要在片元着色器中计算每一个点光源,环境光对片元的颜色贡献,并将它们全部加起来。

阅读全文
0 0
原创粉丝点击