Phong着色法简介:
上一讲我们通过基本的光照计算,渲染出了一个球体。这个球体已经能基本体现出漫反射光、镜面光的效果。但是凡事就怕比较,如果对比一下下面两幅图,你还会觉得左边的好看吗?(左边的是我们上一讲画的)。
哈哈,现在各位一定会觉得上一讲我们画的球体,简直是弱爆了。问题在哪里?两个球体蓝色的部分似乎差不了太多,但是白色的镜面光部分,右边的球体明显要逼真很多,而左边球体的镜面光部分就显得很挫。
造成这种现象的原因,是因为我们只是计算了每个顶点的光照颜色(在顶点着色器里),而对于球体上的每个像素,我们只是简单的通过差值计算颜色的近似值。我们没有对每个像素进行光照计算。对于漫反射光,差值算法可以相对准确的近似每个像素颜色,但是对于较为锐利的镜面光,这种差值算法就力不从心了。所以左边球体看起来就不是很逼真。如果我们能针对球体上的每个像素,分别带入光照模型的公式,计算其光照颜色。则我们的球体就会像右边的一样逼真。这就是我们这一讲要学习的Phong着色算法。
Shader算法实现:
上一讲中,我们主要的工作都是在顶点着色器中实现的。而Phong算法既然是一种Per-Pixel Lighting(针对每个像素的光照计算),它的很大一部分代码都需要在像素着色器中完成。首先,既然要在像素着色器中应用上一讲的光照模型,有几个向量我们必须要拿到,包括Normal、LightDir、HalfVect三个向量(三个向量的含义请参看上一讲)。但是在像素着色器里想要拿到法线等向量,似乎是办不到的。不过我们可以用一些技巧,在顶点着色器里返回这三个向量,通过顶点着色器向像素着色器传入这三个向量。这样说读者未必理解,我们先看看代码,首先把VS_OUTPUT结构体的定义改成:
struct VS_OUTPUT
{
float4 Position : POSITION0;
float4 LightDir : TEXCOORD0;
float3 Normal : TEXCOORD1;
float3 HalfVect : TEXCOORD2;
};
这个结构体定义的就是顶点着色器的返回类型。其中LightDir、Normal、HalfVect成员分别用来存储三个向量。注意,这里的用途标记TEXCOOR0、TEXCOOR1、TEXCOOR2并不是真的表示他们是纹理坐标,而只是用来告诉DX9,我们借用GPU里用来存储纹理坐标的寄存器来存储各种向量。
下面我们再看一下顶点着色器的算法:
VS_OUTPUT vs_main(float4 Pos : POSITION0, float3 Normal : NORMAL)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.Position = mul(Pos, MatWVP);
output.Normal = Normal;
//计算光线入射方向
float3 LightDir = LightPos - Pos;
float Dist = length(LightDir);
//将光线入射向量存储在VS_OUTPUT的LightDir的成员的前三个分量中
//而用w分量存储距离衰减系数DistAtten
output.LightDir.xyz = LightDir / Dist;
output.LightDi