在 3D 空间中模拟现实灯光
在 3D 空间中模拟现实世界的灯光的具体原理和细节绝非本篇文章能够描述清楚的,但是对灯光模型有一定的了解对我们的学习还是很有帮助的。虽然这里没办法深入讲解,但是维基百科中的Phong 着色法给出了一个不错的概要介绍,其中包含了最常用的几种光照模型。
光源类型可以概括成如下三种:
环境光 是一种可以渗透到场景的每一个角落的光。它是非方向光并且会均匀地照射物体的每一个面,无论这个面是朝向哪个方向的。
方向光 是一束从一个固定的方向照射过来的光。这种光的特点可以理解为好像是从一个很遥远的地方照射过来的,然后光线中的每一个光子与其它光子都是平行运动的。举个例子来说,阳光就可以认为是方向光。
点光源光 是指光线是从一个点发射出来的,是向着四面八方发射的。这种光在我们的现实生活中是最常被用到的。举个例子来说,电灯泡就是向各个方向发射光线的。
以我们的需要来看,我们会简化光照模型,只考虑简单的方向光和环境光,不会考虑任何镜面反射和点光源。这样的话,我们只需要在我们使用的环境光上加上照射到旋转立方体的方向光就可以了。在这里可以看到之前的旋转立方体的例子。
虽然可以抛开了点光源和镜面反射,但是关于方向光还是有两点需要注意一下:
需要在每个顶点信息中加入面的朝向法线。这个法线是一个垂直于这个顶点所在平面的向量。
需要明确方向光的传播方向,可以使用一个方向向量来定义。
接着,我们会更新顶点着色器,考虑到环境光,再考虑到方向光(方向光的作用会因为光线方向与面的夹角关系而不同),计算每一个顶点的颜色。实现这一目标的代码如下。
建立顶点法线
首先我们需要做的是建立一个数组来存放立方体所有顶点的法线。由于立方体是一个很简单的物体,所以很容易实现;显然如果是对复杂物体,则法线的计算方法需要更深入的研究。(注:译者调试后发现此处 new WebGLFloatArray(…) 可能应该使用 new Float32Array())
cubeVerticesNormalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer);
var vertexNormals = [
// Front
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
// Back
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
// Top
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
// Bottom
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
// Right
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
// Left
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertexNormals), gl.STATIC_DRAW);
现在我们应该对此非常熟悉了;创建新的 buffer,将它和 gl.ARRAR_BUFFER 绑定在一起,然后通过调用 bufferData() 把我们的顶点法线数组一起传入。
然后我们在 drawScene() 中添加代码,将法线数组和着色器的 attribute 绑定起来以便着色器能够获取到法线数组的信息。
(此处变量 vertexNormalAttribute 应该在 initShader() 函数中声明,并赋值:vertexNormalAttribute = gl.getAttribLocation(shaderProgram, “aVertexNormal”); gl.enableVertexAttribArray(vertexNormalAttribute);
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer);
gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0);
最后,我们需要更新下代码,在着色器中建立和传递法线向量矩阵,用这个矩阵来处理当前立方体相对于光源位置法线向量的转换 :
var normalMatrix = mvMatrix.inverse();
normalMatrix = normalMatrix.transpose();
var nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix");
gl.uniformMatrix4fv(nUniform, false, new WebGLFloatArray(normalMatrix.flatten()));