【笔记】《WebGL编程指南》学习-第8章光照(2-运动物体光照效果)

来源:互联网 发布:米格31知乎 编辑:程序博客网 时间:2024/05/29 03:10

在程序 LightedTranslatedRotatedCube 中,立方体先绕 z 轴顺时针旋转了90度,然后沿着 y 轴平移了0.9个单位。场景中的光照情况与前一节的 LightedCube_ambient 一样,既有平行光又有环境光。结果如下图:

这里写图片描述

立方体旋转时,每个表面的法向量也会随之变化。在下图中,我们沿着 z 轴负方向观察一个立方体,最左边是立方体的初始状态,途中标出了立方体右侧面的法向量(1, 0, 0),它指向 x 轴正方向,然后对该立方体进行变换,观察右侧面法向量随之变化的情况。

这里写图片描述

由上图可知:

  • 平移变换不会改变法向量,因为平移不会改变物体的方向。
  • 旋转变换会改变法向量,因为旋转改变了物体的方向。
  • 缩放变换对法向量的影响较为复杂。如你所见,最右侧的图显示了立方体先旋转了45度,再在y轴上拉伸至原来的2倍的情况。此时法向量改变了,因为表面的朝向改变了。但是,如果缩放比例在所有轴上都一直的话,那么法向量就不会变化。最后,即使物体在某些轴上的缩放比例并不一致,法向量也并不一定会变化,比如将最左侧图中的立方体在y轴方向上拉伸两倍,法向量就不会变化。

显然,在对物体进行不同变化时,法向量的变化情况较为复杂。这时候,数学公式就会派上用场了。


魔法矩阵:逆转置矩阵

在第4章中曾讨论过,对顶点进行变换的矩阵称为模型矩阵。如何计算变换之后的法向量呢?只要将变换之前的法向量乘以模型矩阵的逆转置矩阵即可。所谓逆转置矩阵,就是逆矩阵的转置。

逆矩阵的含义是,如果矩阵 M 的逆矩阵是 R,那么 R * M 或 M * R 的结果都是单位矩阵。转置的意思是,将矩阵的行列进行调换。这里将逆转置矩阵的用法总结如下:

规则:用法向量乘以模型矩阵的逆转置矩阵,就可以求得变换后的法向量。

求逆转置矩阵的两个步骤:

  1. 求原矩阵的逆矩阵
  2. 将上一步求得的逆矩阵进行转置。

Matrix4 对象提供了边界的方法来完成上述任务。

这里写图片描述

假设模型矩阵存储在 modelMatrix 对象中,那么下面这段代码将会计算它的 逆转置矩阵,并将其存储在 normalMatrix 对象中:

这里写代码片

下面来看看示例程序 LightedTranslatedRotatedCube.js 的代码。改程序使立方体绕 z 轴顺时针旋转90度,然后沿 y 轴平移0.9个单位,并且处于平行光和环境光的照射下。立方体在变换之前,与 LightedCube_ambient 中的立方体完全相同。


示例程序(LightedTranslatedRotatedCube.js)

LightedTranslatedRotatedCube.js

//顶点着色器程序var VSHADER_SOURCE =    'attribute vec4 a_Position;'+    'attribute vec4 a_Color;'+    'attribute vec4 a_Normal;'+    //法向量    'uniform mat4 u_MvpMatrix;'+    'uniform mat4 u_NormalMatrix;'+  //用来变化法向量的矩阵    'uniform vec3 u_LightColor;'+    //光线颜色    'uniform vec3 u_LightDirection;'+    //归一化的世界坐标    'uniform vec3 u_AmbientLight;'+    //环境光颜色    'varying vec4 v_Color;'+    'void main(){'+    'gl_Position = u_MvpMatrix * a_Position;'+    //对法向量进行归一化    'vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));'+    //计算法向量和光线方向的点积    'float nDotL = max(dot(u_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);    //获取 uniform 变量的存储位置    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');    var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');    var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');    var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');    var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');    if(!u_MvpMatrix || !u_NormalMatrix || !u_LightColor || !u_LightDirection || !u_AmbientLight){        console.log("Failed to get the storage location");        return;    }    //设置光线颜色    gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);    //设置光线方向    var lightDirection = new Vector3([0.5, 3.0, 4.0]);    lightDirection.normalize();    gl.uniform3fv(u_LightDirection, lightDirection.elements);    //传入环境光颜色    gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);    var modelMatrix = new Matrix4();  //模型矩阵    var mvpMatrix = new Matrix4();    var normalMatrix = new Matrix4();  //用来变换法向量的矩阵    modelMatrix.setTranslate(0, 1, 0);    modelMatrix.rotate(90, 0, 0, 1);    mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);    mvpMatrix.lookAt(-7, 2.5, 6, 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, normalMatrix.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([   //顶点坐标        1.0, 1.0, 1.0,  -1.0, 1.0, 1.0,  -1.0,-1.0, 1.0,   1.0,-1.0, 1.0,  // v0-v1-v2-v3        1.0, 1.0, 1.0,   1.0,-1.0, 1.0,   1.0,-1.0,-1.0,   1.0, 1.0,-1.0,  // v0-v3-v4-v5        1.0, 1.0, 1.0,   1.0, 1.0,-1.0,  -1.0, 1.0,-1.0,  -1.0, 1.0, 1.0,  // v0-v5-v6-v1        -1.0, 1.0, 1.0,  -1.0, 1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0,-1.0, 1.0,  // v1-v6-v7-v2        -1.0,-1.0,-1.0,   1.0,-1.0,-1.0,   1.0,-1.0, 1.0,  -1.0,-1.0, 1.0,  // v7-v4-v3-v2        1.0,-1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0, 1.0,-1.0,   1.0, 1.0,-1.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;}

顶点着色器的流程与 LightedCube_ambient 类似,区别在于,本例根据前述的规则先用模型矩阵的逆转置矩阵对 a_Normal 进行了变换,再赋值给 normal,而不是直接赋值:

 'vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));'+

a_Normal 是 vec4 类型的,a_NormalMatrix 是 mat4 类型的,两者可以相乘,其结果也是 vec4 类型。我们只需要知道结果的前三个分量,所以使用 vec3()函数取其前3个分量,转为 vec3 类型。你也可以使用 .xyz 来这样做,比如这样写:(u_NomalMatrix*a_Normal).xyz。现在你已经了解了在物体旋转和平移时,如何变换每个顶点的法向量了。下面来看在 JS 代码中如何计算传给着色器的 u_NormalMatrix 变量的矩阵。

u_NormalMatrix 是模型矩阵的逆转置矩阵。实例中立方体先绕 z 轴旋转再沿 y 轴平移,所以首先使用 setTranslate()和 rotate()计算出模型矩阵;接着求模型矩阵的逆矩阵,再对结果进行转置,得到逆转置军阵 normalMatrix;最后,将逆转置军阵传给着色器中的 u_NormalMatrix 变量。gl.uniformMatrix4fv()函数的第2个参数指定是否对矩阵矩形转置。

   normalMatrix.setInverseOf(modelMatrix);    normalMatrix.transpose();    gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);

与 LightedCube_ambient 相比,立方体各个表面的颜色没有改变,只是位置上移动了一段距离,这是因为:

  1. 平移没有改变法向量;
  2. 旋转虽然改变了法向量,但这里恰好旋转了90度,原来的前面现在处在右侧面的位置上,所以立方体看上去没有变化
  3. 场景中的光照条件不会随着立方体位置的变化而变化
  4. 漫反射光在个方向上是均匀的。
阅读全文
0 0
原创粉丝点击