Using Simple Technique to Improve Your Lighting Effect
仅供个人学习使用,请勿转载,勿用于任何商业用途。
呵呵,好长时间没有写与渲染相关的东西了,那些高级和复杂的渲染技术,大家还是去看ShaderX吧J。下面要介绍的是一种对最基本的光照方程进行稍稍修改,以显著提高光照效果的方法。数学上,描述基本光照模型的公式如下:
surfaceColor = emissive + ambient + diffuset + specular.
这里只讨论ambient和diffuse项。Ambient其实是方程中最复杂,最难以计算的元素。较为真实的模拟方法需要计算environment map,radiance,spherical harmonic(SH)等等,而最简单的方法则是直接把ambient当做常量。这是人们喜欢走极端的最好证据,高端的游戏大部分使用了前者,而大部分网游仍然使用最简单的方法。由于SH的复杂性,我至今也还没勇气亲自实现,我只希望对最基本的方法进行稍稍修改,让它看起来稍微好一点而已。
如上图左边的模型所示,常量ambient得到的结果非常”丑陋”,除了物体轮廓以外,所有细节都丢失了,甚至无法识别出所渲染的是一个3D模型。如果你对环境贴图,或者hemiphere lighting等理论稍有了解,那么应该知道我们通常假设到达物体的环境光,来自于物体法线所指的半球面区域。这里我们不关心这个半球面上的光照分布如何,并且假设场景中所有光线方向都垂直向下,通过法线与垂直方向的角度,决定物体的受到的环境光。代码非常简单:
float3 light3 = lerp(AmbientColor*0.5f, AmbientColor, saturate(normal.y*0.5+0.5f));
如果法线垂直向上的地方,是获得环境光最多的部分,而垂直向下的部分则是吸收光线最少的。从上图右边的模型可以看到,效果非常明显,简直可以说发生了质的改变。
接下来看diffuse,最常用的计算公式恐怕就是
diffuseColor = lightColor * max ( dot( N, L), 0)
下图最左边的两个模型就是用这种方法渲染的。如果说正面的效果还算不错,那么背光一面的效果简直糟糕透了,所有dot(N,L)为负值的部分都是黑色,和常量ambient一样,所有细节都丢失了,画面看起来非常flat。然而在真实环境中,即使是背光的一面,由于布料本身和周围物体的散射反射,即使是背对光源的物体,也会被照亮的。
我们对公式稍微进行一些修改,让dot(N,L)为负值的部分,亮度也有变化:
diffuse = (dot( N, L) * 0.5 + 0.5) * lightColor;
我们把法线所有方向上的亮度映射到了0,1之间,而不是简单的截取其中一部分。结果如上图中间2个模型所示,可以看到背光的一面效果好多了,所有细节都保留下来。但面朝光线的一部分亮的似乎有些过分,甚至有些难以辨别光线所来的方向了。既然这样,再对光线进行一些衰减好了
diffuse = (dot( N, L) * 0.5 + 0.5) * lightColor;
diffuse *= diffuse;
这里多做了一次平方运算。从上图最右边的2个模型可以看到,正面的光照亮度不但回到了之前正常的水平,平且明暗过度也更加平滑,而背面,所有重要的细节(腰带,绑腿)也没有丢失。
好了,现在把ambient与diffuse合成起来,左边2个模型所用的是传统方法,而右边2则是我们修改过的版本。显然,右边的部分光照不但平滑,立体感也更加明显。特别是对背光面的光照有了很大改善。