注:文章译自http://wgld.org/,原作者杉本雅広(doxas),文章中如果有我的额外说明,我会加上[lufy:],另外,鄙人webgl研究还不够深入,一些专业词语,如果翻译有误,欢迎大家指正。
平行光源的弱点
上次挑战了一下从平行光源发出的光。平行光源的光的方向是固定的。而且,为了模拟这些,需要用到模型变换矩阵的逆矩阵,以及需要向模型数据中加入法线情报等等。
平行光源的计算负担比较小,在一定程度上模拟了光照效果,在3D模拟世界中经常被用到。但是,平行光源也有弱点,阴面的部分,就是没有被光照到的部分是无法完美的模拟。
比如上次的demo中的定点着色器,仔细观察的话,获取光向量和法线的内积的部分,实际上是取巧的。
>上次的demo中的一部分代码
float diffuse = clamp(dot(normal, invLight), 0.1, 1.0);
这里使用了GLSL的内置函数clamp,这个函数是将参数的数值限制到指定的范围之内,上面的代码,结果会限制在0.1 ~ 1.0之间。
但是,获取光向量和法线的内积,根据使用场合的不同,可能会出现负数,而使用了clamp函数,即使有负数,也会被指定的最小值0.1代替,假设把clamp的范围设置为0.0 ~ 1.0之间会是什么效果呢,试着运行一下的话,出现下图效果。
这样,完全没有和光发生碰撞的地方会变成完全的黑色。这样会导致无法分清楚模型的轮廓,这就是平行光源的缺点。
就像上次的demo那样,将光照系数的范围设定的大一些,在一定程度上可以解决上述问题。但是使用环境光源的话,可以彻底解决这个问题。
什么是环境光源
环境光是模拟现实世界中的自然光的漫反射。现实世界中,从太阳或是照明器械等发射出的光,遇到物体或者大气中的灰尘等遮挡而发生反射,将世界照亮。比如说,在一个漆黑的屋子里面,只需要一个灯泡,背对着灯泡的话,就会看到自己的影子映射到床或者墙壁上,而自己的身体虽然没有直接被光照到,但是也应该能看得见吧。
由于墙壁和屋顶,以及床和大气中的灰尘等对灯泡发出的光的反射,即使是没有直接被光照到的部分也会受到光的影响。这样,呈现光的漫反射的就是环境光源了。
环境光源用于照亮三维空间中的所有部分。就是说,不是根据顶点的不同处理attribute变量,而是向着色器中传递uniform变量。进一步说,环境光最终影响的是在context中输出的颜色,处理包含四个元素的颜色情报。
>定义环境光的例子
var ambientColor = [0.1,0.1,0.1,1.0];
使用环境光源的时候,需要注意颜色的亮度。环境光照的是全部,比如上面的代码中指定的0.1,如果全都换成1.0的话,模型就会变成全白了。和平行光源不一样,所以要注意。
环境光的颜色,最好是限制在0.2左右以下,这次的demo使用的是0.1。
顶点着色器和javascript的修改
接着,看一下各个代码部分的修改。先从顶点着色器开始看。
>顶点着色器代码
attribute vec3 position; attribute vec3 normal; attribute vec4 color; uniform mat4 mvpMatrix; uniform mat4 invMatrix; uniform vec3 lightDirection; uniform vec4 ambientColor; varying vec4 vColor; void main(void){ vec3 invLight = normalize(invMatrix * vec4(lightDirection, 0.0)).xyz; float diffuse = clamp(dot(normal, invLight), 0.0, 1.0); vColor = color * vec4(vec3(diffuse), 1.0) + ambientColor; gl_Position = mvpMatrix * vec4(position, 1.0); }
上次追加了一个uniform变量,就是vec4型的变量ambientColor。环境光是在平行光源等一连串的计算结束之后,最后输出颜色的阶段开始添加的。
这里,如果不使用加法而使用乘法的话,整个画面都会变暗,所以要特别注意。
接下来,修改主程序。
说起来,只是把环境光作为参数传给顶点着色器,追加的东西还是挺少的。
首先在程序中定义环境光的参数。
>增加环境光参数
var ambientColor = [0.1,0.1,0.1,1.0];
接着,为了正确的传给顶点着色器,追加获取着色器的uniformLocation的部分。
// 将uniformLocation保存到数组中 var uniLocation = new Array(); uniLocation[0] = gl.getUniformLocation(prg, ‘mvpMatrix‘); uniLocation[1] = gl.getUniformLocation(prg, ‘invMatrix‘); uniLocation[2] = gl.getUniformLocation(prg, ‘lightDirection‘); uniLocation[3] = gl.getUniformLocation(prg, ‘ambientColor‘);
接着,作为uniform变量在持续循环的时候传给着色器就行了。
>向着色器中传送环境光的参数
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix); gl.uniformMatrix4fv(uniLocation[1], false, invMatrix); gl.uniform3fv(uniLocation[2], lightDirection); gl.uniform4fv(uniLocation[3], ambientColor);
这样,就完成了顶点着色器和javascript程序的修改了。
其实,因为这次引入了环境光源,所以平行光源部分的光照系数设定成了0.0 ~ 1.0,没有被平行光源照到的部分,就会使用纯粹的环境光来照射。
总结
环境光,模拟了自然界的光的漫反射,弥补了平行光源的缺点。一般,这两种光会同时使用。只使用环境光的话,无法表现出模型的凹凸,只使用平行光源的话,阴影过于严重无法分清模型的轮廓。
3D模拟中的扩散光的代表就是环境光和平行光。这次的demo也实现到了这一步。下次会介绍一下反射光。
点击下面的连接,可以确认一下今天的内容。
同时使用平行光源和环境光源照射的圆环体
转载请注明:转自lufy_legend的博客http://blog.csdn.net/lufy_legend