9.5 增加文理和光照效果
现在,我们已经知道怎样使用位置缓冲区和索引缓冲区,来创建一个简单的3D模型,让我们通过封装我们的模型,再增加环境光和平行光来创建带有阴影的表面,来制作一个木箱。本节介绍创建纹理的纹理缓冲区和处理光照效果所需要的法线缓冲区。
操作步骤
按照以下步骤,创建一个旋转的、并具有光照的木箱:
1. 链接到glMatrix库和WebGL包装器:
<script type="text/javascript" src="glMatrix-1.0.1.min.js"> </script>
<script type="text/javascript" src="WebGL.js"> </script>
2. 定义initBuffers()函数,该函数初始化立方体的位置缓冲区、法线缓冲区、纹理缓冲区、和索引缓冲区:
function initBuffers(gl){
var cubeBuffers = {};
cubeBuffers.positionBuffer = gl.createArrayBuffer([
// Front face
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
// Back face
-1, -1, -1,
-1, 1, -1,
1, 1, -1,
1, -1, -1,
// Top face
-1, 1, -1,
-1, 1, 1,
1, 1, 1,
1, 1, -1,
// Bottom face
-1, -1, -1,
1, -1, -1,
1, -1, 1,
-1, -1, 1,
// Right face
1, -1, -1,
1, 1, -1,
1, 1, 1,
1, -1, 1,
// Left face
-1, -1, -1,
-1, -1, 1,
-1, 1, 1,
-1, 1, -1
]);
cubeBuffers.normalBuffer = gl.createArrayBuffer([
// Front face
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
// Back face
0, 0, -1,
0, 0, -1,
0, 0, -1,
0, 0, -1,
// Top face
0, 1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0,
// Bottom face
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, -1, 0,
// Right face
1, 0, 0,
1, 0, 0,
1, 0, 0,
1, 0, 0,
// Left face
-1, 0, 0,
-1, 0, 0,
-1, 0, 0,
-1, 0, 0
]);
cubeBuffers.textureBuffer = gl.createArrayBuffer([
// Front face
0, 0,
1, 0,
1, 1,
0, 1,
// Back face
1, 0,
1, 1,
0, 1,
0, 0,
// Top face
0, 1,
0, 0,
1, 0,
1, 1,
// Bottom face
1, 1,
0, 1,
0, 0,
1, 0,
// Right face
1, 0,
1, 1,
0, 1,
0, 0,
// Left face
0, 0,
1, 0,
1, 1,
0, 1
]);
cubeBuffers.indexBuffer = gl.createElementArrayBuffer([
0, 1, 2, 0, 2, 3, // Front face
4, 5, 6, 4, 6, 7, // Back face
8, 9, 10, 8, 10, 11, // Top face
12, 13, 14, 12, 14, 15, // Bottom face
16, 17, 18, 16, 18, 19, // Right face
20, 21, 22, 20, 22, 23 // Left face
]);
return cubeBuffers;
}
3. 定义stage()函数,该函数设置透视矩阵,把模型-视图矩阵设置到单位矩阵,平移立方体,旋转立方体,使能光照,设置环境光,设置平行光,把位置缓冲区、法线缓冲区、纹理缓冲区、索引缓冲区发送到显卡,最后,调用drawElements()方法绘制立方体:
function stage(gl, cubeBuffers, crateTexture, angle){
// set field of view at 45 degrees
// set viewing range between 0.1 and 100 units away.
gl.perspective(45, 0.1, 100.0);
gl.identity();
// 平移模型-视图矩阵
gl.translate(0, 0.0, -5);
//绕x轴旋转模型-视图矩阵(使盒子向下倾斜)
gl.rotate(Math.PI * 0.15, 1, 0, 0);
//绕y轴旋转模型-视图矩阵
gl.rotate(angle, 0, 1, 0);
//使能光照
gl.enableLighting();
gl.setAmbientLighting(0.5, 0.5, 0.5);
gl.setDirectionalLighting(-0.25, -0.25, -1, 0.8, 0.8, 0.8);
gl.pushPositionBuffer(cubeBuffers);
gl.pushNormalBuffer(cubeBuffers);
gl.pushTextureBuffer(cubeBuffers, crateTexture);
gl.pushIndexBuffer(cubeBuffers);
gl.drawElements(cubeBuffers);
}
4. 定义init()方法,该方法初始化木纹,设置stage()函数,并启动动画:
function init(gl, crateTexture){
var cubeBuffers = initBuffers(gl); var angle = 0;
gl.initTexture(crateTexture); gl.setStage(function() {
// update angle
var angularVelocity = Math.PI / 4; // radians / second
var angleEachFrame = angularVelocity * this.getTimeInterval() / 1000;
angle += angleEachFrame;
this.clear();
stage(this, cubeBuffers, crateTexture, angle);
});
gl.start();
}
5. 定义loadTexture()函数,该函数新建一个纹理对象,新建一个图像对象,初始化纹理,并在纹理图像加载完成后启动动画:
function loadTexture(gl){
var crateTexture = gl.getContext().createTexture();
crateTexture.image = new Image();
crateTexture.image.onload = function() {
init(gl, crateTexture);
};
crateTexture.image.src = "crate.jpg";
}
6. 页面加载完成后,初始化WebGL包装器对象,把着色器设置为“TEXTURE_DIRECTIONAL_LIGHTING”,并加载纹理:
window.onload = function(){
var gl = new WebGL("myCanvas", "experimental-webgl");
gl.setShaderProgram("TEXTURE_DIRECTIONAL_LIGHTING");
loadTexture(gl);
};
7. 在HTML文档的body部分嵌入canvas标签:
<canvas id="myCanvas" width="600" height="250" style="border:1px solid black;">
</canvas>
工作原理
本节介绍了纹理缓冲区和法线缓冲区的概念。纹理缓冲区允许我们为一个3D模型的每一个面定义纹理图像的方向和大小。要定义木箱的纹理缓冲区,我们需要把纹理图像的四个角,映射到立方体每个面的四个角上。
为了使用WebGL处理光照效果,我们需要使用法线缓冲区,来定义组成立方体的各个面的法线。法线是垂直于平面的向量,例如,地板的法线是竖直向上的,天花板的法线是竖直向下的。一旦我们的法线缓冲区定义完成,就该设置环境光和平行光了。
尽管使用WebGL可以实现很多其他类型的光照效果,本节我们主要介绍两种最常用的光照效果——环境光和平行光,它们可以一起使用,也可以单独使用:
- 环境光是指一个房间或现实世界中普遍的光照情况,使用RGB来定义。环境光的值为[0,0,0]的房间是完全黑暗的,环境光的值为[1,1,1]的房间是完全明亮的。比方说,一个房间的环境光的值为[1,0,0],则该房间将被红光照亮。
- 平行光使3D模型面向光源的面比较亮,而背对光源的面比较暗。平行光一般用来模拟从很远的地方发出的很强的光源,如太阳光。
如果要同时使用纹理和平行光,我们可以使用setShaderProgram()方法来把着色器设置为“TEXTURE_ DIRECTIONAL_LIGHTING”,并使用enableLighting()方法来使能光照。最后,我们使用setAmbientLighting()方法设置环境光,使用setDirectionalLighting()方法设置平行光。
相关参考
- 第5章 创建Animation类