安徽房和城乡建设部网站,网站建设公司价,邯郸企业网站建设费用,江门网络培训学院目录
光照原理
光源类型
平行光
点光源
环境光
反射类型
漫反射
漫反射光颜色 计算公式
环境反射
环境反射光颜色
表面的反射光颜色#xff08;漫反射和环境反射同时存在时#xff09;计算公式
平行光下的漫反射
根据光线和法线方向计算入射角θ#xff08;以便…目录
光照原理
光源类型
平行光
点光源
环境光
反射类型
漫反射
漫反射光颜色 计算公式
环境反射
环境反射光颜色
表面的反射光颜色漫反射和环境反射同时存在时计算公式
平行光下的漫反射
根据光线和法线方向计算入射角θ以便求两者点积cosθ
归一化
法线表面的朝向
一个表面具有两个法向量
平面的法向量唯一
示例代码——平行光漫反射LightedCube.js
示例效果
代码详解
顶点着色器部分
JavaScript程序部分
环境光下的漫反射
示例代码——平行光漫反射环境反射LightedCube_ambient.js
示例效果 光照原理
现实世界中的物体被光线照射时会反射一部分光。只有当反射光线进入你的眼睛时你才能够看到物体并辩认出它的颜色。比如白色的盒子会反射白光当白光进入你的眼睛时你才能看到盒子是白色的。
在现实世界中当光线照射到物体上时发生了两个重要的现象见下图
● 根据光源和光线方向物体不同表面的明暗程度变得不一致。
● 根据光源和光线方向物体向地面投下了影子。 在生活中你可能常常会注意到阴影却很少注意到明暗差异。实际上正是明暗差异给了物体立体感虽然难以察觉但它始终存在。虽然上图所示的立方体是纯白色的但我们还是能够辨认它的每个面因为它的每个面受到光照的程度不同。如你所见向着光的表面看上去明亮一些而侧着光或背着光的表面看上去就暗一些。正是有了这些差异立方体看上去才真正像一个立方体。
在三维图形学中术语着色shading的真正含义就是根据光照条件重建“物体各表面明暗不一的效果”的过程。物体向地面投下影子的现象又被称为阴影shadowing。
在讨论着色过程之前考虑两件事 ● 发出光线的光源的类型。 ● 物体表面如何反射光线。 在开始编写代码之前我们先来理解一下上述两个问题。
光源类型
当物体被光线照射时必然存在发出光线的光源。真实世界中的光主要有两种类型平行光directional light类似于自然中的太阳光点光源光point light类似于人造灯泡的光。此外我们还用环境光ambient light来模拟真实世界中的非直射光也就是由光源发出后经过墙壁或其他物体反射后的光。三维图形学还使用一些其他类型的光比如用聚光灯spot light来模拟电筒、车前灯等。至于其他的更加特殊的光源类型可以参考OpenGL ES 2.0 Programming Guide一书 平行光 顾名思义平行光的光线是相互平行的平行光具有方向。平行光可以看作是无限远处的光源比如太阳发出的光。因为太阳距离地球很远所以阳光到达地球时可以认为是平行的。平行光很简单可以用一个方向和一个颜色来定义 点光源 点光源光是从一个点向周围的所有方向发出的光。点光源光可以用来表示现实中的灯泡、火焰等。我们需要指定点光源的位置和颜色。光线的方向将根据点光源的位置和被照射之处的位置计算出来因为点光源的光线的方向在场景内的不同位置是不同的。 环境光 环境光间接光是指那些经光源点光源或平行光源发出后被墙壁等物体多次反射然后照到物体表面上的光。环境光从各个角度照射物体其强度都是一致的。比如说在夜间打开冰箱的门整个厨房都会有些微微亮这就是环境光的作用。环境光不用指定位置和方向只需要指定颜色即可。 现在你已经了解了三种主要的光源类型下面来讨论物体表面反射光线的几种方式。
反射类型
物体向哪个方向反射光反射的光是什么颜色取决于以下两个因素入射光和物体表面的类型。入射光的信息包括入射光的方向和颜色而物体表面的信息包括表面的固有颜色也称基底色和反射特性。
物体表面反射光线的方式有两种漫反射diffuse reflection和环境反射enviroment/ambient reflection。本节的重点是如何根据上述两种信息入射光和物体表面特性来计算出反射光的颜色。本节会涉及一些简单的数学计算。
漫反射
漫反射是针对平行光或点光源而言的。漫反射的反射光在各个方向上是均匀的如下图所示。如果物体表面像镜子一样光滑那么光线就会以特定的角度反射出去但是现实中的大部分材质比如纸张、岩石、塑料等其表面都是粗糙的在这种情况下反射光就会以不固定的角度反射出去。漫反射就是针对后一种情况而建立的理想反射模型。 在漫反射中反射光的颜色取决于入射光的颜色、表面的基底色、入射光与表面形成的入射角。我们将入射角定义为入射光与表面的法线形成的夹角并用θ表示那么漫反射光的颜色可以根据下式计算得到
漫反射光颜色 计算公式 漫反射光颜色入射光颜色×表面基底色× cosθ 式子中入射光颜色指的是点光源或平行光的颜色乘法操作是在颜色矢量上逐分量R、G、B进行的。因为漫反射光在各个方向上都是“均匀”的所以从任何角度看上去其强度都相等如下图所示。
漫反射光各方向均匀 环境反射
环境反射是针对环境光而言的。在环境反射中反射光的方向可以认为就是入射光的反方向。由于环境光照射物体的方式就是各方向均匀、强度相等的所以反射光也是各向均匀的如下图所示。我们可以这样来描述它
环境反射光颜色 环境反射光颜色入射光颜色×表面基底色 这里的入射光颜色实际上也就是环境光的颜色。 当漫反射和环境反射同时存在时将两者加起来就会得到物体最终被观察到的颜色
表面的反射光颜色漫反射和环境反射同时存在时计算公式 表面的反射光颜色漫反射光颜色环境反射光颜色 注意两种反射光并不一定总是存在也并不一定要完全按照上述公式来计算。渲染三维模型时你可以修改这些公式以达到想要的效果。
下面来建立一个示例程序在合适的位置放置一个光源对场景进行着色。首先实现平行光下的漫反射。
平行光下的漫反射
如前所述漫反射的反射光其颜色与入射光在入射点的入射角θ有关。平行光入射产生的漫反射光的颜色很容易计算因为平行光的方向是唯一的对于同一个平面上的所有点入射角是相同的。根据等式——漫反射光颜色 计算平行光入射的漫反射光颜色。
漫反射光颜色入射光颜色×表面基底色× cosθ
上式用到了三项数据 ● 平行入射光的颜色 ● 表面的基底色 ● 入射光与表面形成的入射角θ 入射光的颜色可能是白色的比如阳光也可能是其他颜色的比如隧道中的橘黄色灯光。我们知道颜色可以用RGB值来表示比如标准强度的白光颜色值就是1.01.01.0。物体表面的基底色其实就是“物体本来的颜色”或者说是“物体在标准白光下的颜色”。按照上式公式计算反射光颜色时我们对RGB值的三个分量逐个相乘。
假设入射光是白色1.01.01.0而物体表面的基底色是红色1.00.00.0而入射角θ为0.0即入射光垂直入射根据上式入射光的红色分量R为1.0基底色的红色分量R为1.0入射角余弦值cosθ为1.0那么反射光的红色分量R就可以有如下计算得到 R1.01.01.01.0 类似地我们可以算出绿色分量G和蓝色分量B G1.00.01.00.0 B1.00.01.00.0 根据上面的计算当白光垂直入射到红色物体的表面时漫反射光的颜色就变成了红色1.00.00.0。而如果是红光垂直入射到白色物体的表面时漫反射光的颜色也会是红色。在这两种情况下物体在观察者看来就是红色的这很符合我们在现实世界中的经验。
那么如果入射角θ是90度也就是说入射光与表面平行90度相当于没有没有照射到物体一点都没有“照射”到表面上在这种情况下会怎样呢根据我们在现实世界中的经验物体表面应该完全不反光看上去是黑的。验证一下当θ是90度的时候cosθ的值是0那么根据上面的式子不管入射光的颜色和物体表面基底色是什么最后得到的漫反射光颜色都为0.00.00.0也就是黑色正如我们预期的那样。同样如果θ是60度也就是斜射平行光斜射到物体表面上那么该表面应该还是红色的只不过比垂直入射时暗一些。根据上式cosθ是0.5漫反射光颜色为0.50.00.0即暗红色。
这个简单的例子帮助你了解了如何计算漫反射光的颜色。但是我们并不知道入射角θ是多少只知道光线的方向。下面我们就来通过光线和物体表面的方向来计算入射角θ将漫反射光颜色公式中的θ换成我们更加熟悉的东西。
根据光线和法线方向计算入射角θ以便求两者点积cosθ
在程序中我们没法像前一节最后那样直接说“入射角θ是多少多少度”。我们必须根据入射光的方向和物体表面的朝向即法线方向来计算出入射角。这并不简单因为在创建三维模型的时候我们无法预先确定光线将以怎样的角度照射到每个表面上。但是我们可以确定每个表面的朝向。在指定光源的时候再确定光的方向就可以用这两项信息来计算出入射角了。
幸运的是我们可以通过计算两个矢量的点积来计算这两个矢量的夹角余弦值cosθ两个矢量的点积等同于两个矢量归一化后的夹角的cos值。点积运算的使用非常频繁GLSL ES内置了点积运算函数。在公式中我们使用点符号·来表示点积运算进行点积运算前需对矢量归一化即长度为1。这样cosθ就可以通过下式计算出来 cosθ 光线方向·法线方向 因此上述漫反射光颜色公式可以改写成下式如下所示 漫反射光颜色入射光颜色×表面基底色×光线方向·法线方向 这里有两点需要注意其一光线方向矢量和表面法线矢量的长度必须为1否则反射光的颜色就会过暗或过亮。将一个矢量的长度调整为1同时保持方向不变的过程称之为归一化normalization 。GLSL ES提供了内置的归一化函数你可以直接使用。
归一化 比如矢量n为nx, ny, nz则其长度为|n| nx平方 ny平方 nz平方的开方 对矢量n进行归一化后的结果是nx/m, ny/m, nz/m式中m为n的长度比如矢量2.0, 2.0, 1.0的长度|n| sqrt(9) 3那么其归一化之后就是2.0/3.0, 2.0/3.0, 1.0/3.0 其二这里包括后面所谓的“光线方向”实际上是入射方向的反方向即从入射点指向光源方向因为这样该方向与法线方向的夹角才是入射角如下图所示。 这里用到了表面的法线方向来参与对θ的计算可是我们还不知道法线方向下一节就来研究如何获取表面的法线方向。
法线表面的朝向
物体表面的朝向即垂直于表面的方向又称法线或法向量。法向量有三个分量向量nxnynz表示从原点000指向点nxnynz的方向。比如说向量100表。示x轴正方向向量001表示z轴正方向。涉及到表面和法向量的问题时必须考虑以下两点
一个表面具有两个法向量 在三维图形学中表面的正面和背面取决于绘制表面时的顶点顺序。当你按照v0v1v2v3的顶点顺序绘制了一个平面那么当你从正面观察这个表面时这4个顶点是顺时针的而你从背面观察该表面这4个顶点就是逆时针的即第3章中用来确定旋转方向的“右手法则”。如上图所示该平面正面的法向量是00-1。
平面的法向量唯一
由于法向量表示的是方向与位置无关所以一个平面只有一个法向量。换句话说平面的任意一点都具有相同的法向量。
进一步来说即使有两个不同的平面只要其朝向相同也就是两个平面平行法向量也相同。比方说有一个经过点10989的平面只要它垂直于z轴它的法向量仍然是001和00-1和经过原点并垂直于z轴的平面一样如图。 下图左显示了示例程序中的立方体及每个表面的法向量。比如立方体表面上的法向量表示为n010。 一旦计算好每个平面的法向量接下来的任务就是将数据传给着色器程序。以前的程序把颜色作为“逐顶点数据”存储在缓冲区中并传给着色器。对法向量数据也可以这样做。如上图右所示每个顶点对应3个法向量就像之前每个顶点都对应3个颜色值一样
示例程序LightedCube显示了一个处于白色平行光照射下的红色三角形看下面代码
示例代码——平行光漫反射LightedCube.js
var VSHADER_SOURCE // p288attribute vec4 a_Position;\n attribute vec4 a_Color;\n attribute vec4 a_Normal;\n // 法向量uniform mat4 u_MvpMatrix;\n uniform vec3 u_LightColor;\n // 光线颜色uniform vec3 u_LightDirection;\n // 归一化的世界坐标varying vec4 v_Color;\n void main() {\n gl_Position u_MvpMatrix * a_Position ;\n // 对法向量进行归一化 vec3 normal normalize(a_Normal.xyz);\n // 计算光线方向和法向量的点积即两者归一化后的夹角的余弦值cosθ float nDotL max(dot(u_LightDirection, normal), 0.0);\n // 计算漫反射光的颜色入射光颜色 * 表面基底色 * cosθ vec3 diffuse u_LightColor * a_Color.rgb * nDotL;\n v_Color vec4(diffuse, a_Color.a);\n }\n;var FSHADER_SOURCE #ifdef GL_ES\n precision mediump float;\n #endif\n varying vec4 v_Color;\n void main() {\n gl_FragColor v_Color;\n }\n;function main() {var canvas document.getElementById(webgl);var gl getWebGLContext(canvas);if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return// 设置顶点的坐标、颜色和法向量var n initVertexBuffers(gl);// 设置清除颜色并启用深度测试gl.clearColor(0, 0, 0, 1);gl.enable(gl.DEPTH_TEST);// 获取统一变量的存储位置等等var u_MvpMatrix gl.getUniformLocation(gl.program, u_MvpMatrix);var u_LightColor gl.getUniformLocation(gl.program, u_LightColor);var u_LightDirection gl.getUniformLocation(gl.program, u_LightDirection);// 设置光线颜色白色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); // 见cuon-matrix// 计算模型视图投影矩阵var mvpMatrix new Matrix4(); // 模型视图投影矩阵mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100); // 计算投影矩阵mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0); // 计算视图矩阵// 将模型视图投影矩阵传给u_MvpMatrix变量gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);// 清除颜色和深度缓冲gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0); // 绘制立方体
}function initVertexBuffers(gl) {// Create a cube// v6----- v5// /| /|// v1------v0|// | | | |// | |v7---|-|v4// |/ |/// v2------v3var 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 front1.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 right1.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 up-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 left-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 down1.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 back]);var colors new Float32Array([ // 颜色1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v1-v2-v3 front1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v5-v6-v1 up1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v1-v6-v7-v2 left1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v7-v4-v3-v2 down1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0 // v4-v7-v6-v5 back]);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, // v0-v1-v2-v3 front1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back]);// 顶点的索引var indices new Uint8Array([0, 1, 2, 0, 2, 3, // front4, 5, 6, 4, 6, 7, // right8, 9,10, 8,10,11, // up12,13,14, 12,14,15, // left16,17,18, 16,18,19, // down20,21,22, 20,22,23 // back]);// 将顶点属性写入缓冲区坐标、颜色和法线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();gl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);var a_attribute gl.getAttribLocation(gl.program, attribute);gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);gl.enableVertexAttribArray(a_attribute);gl.bindBuffer(gl.ARRAY_BUFFER, null);return true;
}示例效果 代码详解
注意顶点着色器实现了漫反射光颜色公式 漫反射光颜色入射光颜色×表面基底色×光线方向·法线方向 计算漫反射光颜色需要1入射光颜色2表面基底色3入射光方向4表面法线方向。其中后两者都必须是归一化的即长度为1.0。
顶点着色器部分
顶点着色器中的a_Color变量表示表面基底色第3行a_Normal变量表示表面法线方向第4行u_LightColor变量表示入射光颜色第6行u_LightDirection变量表示入射光方向第7行。注意入射光方向u_LightDirection是在世界坐标系下的而且在传入着色器前已经在JavaScript中归一化了。这样我们就可以避免在顶点着色器每次执行时都对它进行归一化。 有了这些信息就可以开始在顶点着色器中进行计算了。首先对a_Normal进行归一化第12行。严格地说本例通过缓冲区传入的法向量都是已经归一化过的所以实际上这一步可以略去。但是顶点着色器可不知道传入的矢量是否经过了归一化而且这里没有节省开销的理由法向量是逐顶点的所以有这一步总比没有要好 a_Normal变量是vec4类型的使用前三个分量x、y和z表示法线方向所以我们将这三个分量提取出来进行归一化。对vec3类型的变量进行归一化就不必这样做。本例使用vec4类型的a_Normal变量是为了方便对下一个示例程序进行扩展。GLSL ES提供了内置函数normalize对矢量参数进行归一化。归一化的结果赋给了vec3类型的normal变量供之后使用。
接下来根据漫反射光颜色公式计算点积光线方向·法线方向。光线方向存储在u_LightDirection变量中而且已经被归一化了可以直接使用。法线方向存储在之前进行归一化后的结果normal变量中第12行。使用GLSL ES提供的内置函数dot计算两个矢量的点积光线方向·法线方向该函数接收两个矢量作为参数返回它们的点积第14行。 如果点积大于0就将点积赋值给nDotL变量如果其小于0就将0赋给该变量。使用内置函数max完成这个任务将点积和0两者中的较大者赋值给nDotL。
点积值小于0意味着cosθ中的θ大于90度。θ是入射角也就是入射反方向光线方向与表面法向量的夹角θ大于90度说明光线照射在表面的背面上如图8.11所示。此时将nDotL赋为0.0。 现在准备工作都已经就绪了我们在顶点着色器中直接计算漫反射颜色公式第16行。注意a_Color变量即顶点的颜色被从vec4对象转成了vec3对象因为其第4个分量透明度与漫反射颜色公式无关。
实际上物体表面的透明度确实会影响物体的外观。但这时光照的计算较为复杂现在暂时认为物体都是不透明的这样就计算出了漫反射光的颜色diffuse 然后将diffuse的值赋给v_Color变量第17行。v_Color是vec4对象而diffuse是vec3对象需要将第4分量补上为1.0。 顶点着色器运行的结果就是计算出了v_Color变量其值取决于顶点的颜色、法线方向、平行光的颜色和方向。v_Color变量将被传入片元着色器并赋值给gl_FragColor变量。本例中的光是平行光所以立方体上同一个面的颜色也是一致的没有之前出现的颜色渐变效果。
这就是顶点着色器的代码下面来看一下JavaScript程序如何将数据传给顶点着色器并计算式漫反射光颜色公式。
JavaScript程序部分
JavaScript将光的颜色u_LightColor和方向u_LightDirection传给顶点着色器。首先用gl.uniform3f函数将u_LightColor赋值为1.01.01.0表示入射光是白光 下一步是设置光线方向注意光线方向必须被归一化。cuon-matrix.js为Vector3类型提供了normalize函数以实现归一化。该函数的用法非常简单在你想要进行归一化的Vector3对象上调用normalize函数即可第49行。注意JavaScript和GLSL ES中对矢量进行归一化的不同之处。 归一化后的光线方向以Float32Array类型的形式存储在lightDirection对象的elements属性中使用gl.uniform3fv将其分配给着色器中的u_LightDirection变量第50行。
最后在initVertexBuffers函数中为每个顶点定义法向量法向量数据存储在normals数组中第92行然后被initArrayBuffer函数第115行传给了顶点着色器的a_Normal变量。 initArrayBuffer函数的作用是将第3个参数指定的数组normals分配给第2个参数指定的着色器中的变量。
环境光下的漫反射
现在我们已经成功实现了平行光下的漫反射光LightedCube的效果如下图所示。但是下图和现实中的立方体还是有点不大一样特别是右侧表面是全黑的仿佛不存在一样。 虽然程序是严格按照 等式漫反射光颜色 对场景进行光照的但经验告诉我们肯定有什么地方不对劲。在现实世界中光照下物体的各表面的差异不会如此分明那些背光的面虽然会暗一些但绝不至于黑到看不见的程度。实际上那些背光的面是被非直射光即其他物体如墙壁的反射光等照亮的前面提到的环境光就起到了这部分非直射光的作用它使场景更加逼真。因为环境光均匀地从各个角度照在物体表面所以由环境光反射产生的颜色只取决于光的颜色和表面基底色使用 等式环境反射光颜色 计算后我们再来看一下 环境反射光颜色入射光颜色×表面基底色 接下来向示例程序中加入上式中的环境光所产生的反射光颜色如 等式表面的反射光颜色漫反射和环境反射同时存在所示 表面的反射光颜色漫反射光颜色环境反射光颜色 环境光是由墙壁等其他物体反射产生的所以环境光的强度通常比较弱。假设环境光是较弱的白光0.20.20.2而物体表面是红色的1.00.00.0。根据 等式环境反射光颜色由环境光产生的反射光颜色就是暗红色0.20.00.0。同样在蓝色的房间中环境光为0.00.00.2有一个白色的物体即表面基底色为1.01.01.0那么由环境光产生的漫反射光颜色就是淡蓝色0.00.00.2。
示例程序LightedCube_ambient实现了环境光漫反射的效果如下所示。可见完全没有被平行光照到的表面也不是全黑而是呈现较暗的颜色与真实世界更加相符。
示例代码——平行光漫反射环境反射LightedCube_ambient.js
示例程序代码大部分与LightedCube一样只有少量修改如下所示。 顶点着色器中新增了u_AmbientLight变量第10行用来接收环境光的颜色值。接着根据式8.2使用该变量和表面的基底色a_Color计算出反射光的颜色将其存储在ambient变量中第21行。这样我们就即有环境光反射产生的颜色ambient又有了由平行光漫反射产生的颜色diffuse。最后根据 等式表面的反射光颜色漫反射和环境反射同时存在计算物体最终的颜色第23行并存储在v_Color变量中作为物体表面最终显示出的颜色和LightedCube一样。
如你所见与LightedCube相比本例对顶点着色器的v_Color变量加上了ambient变量第23行就使得整个立方体变亮了一些这正是环境光从各个方向均匀照射在立方体上产生的。
示例效果