第九作坊中的像素光照实现

像素光照
与固定管线不同,使用可编程管线可以实现灵活多样的渲染效果。然而事物总有两面,使用可编程管线就必须编写Shader程序。本节的内容将以分析实现各种效果的Shader为主,在此之前有必要简单说明Shader的使用方法。下面的伪代码说明了如何使用ID3DXEFFECT接口执行Shader程序。
 
使用D3DXCreateEffectFromFile读取并编译Shader
使用effect->Begin设置渲染状态
使用effect->BeginPass装入当前pass的Shader程序①
渲染需要渲染的东西②
使用effect->EndPass清空装入的Shader①
使用effect->End恢复渲染状态

①BeginPass/EndPass是在开始和结束一个 Pass时必须执行的操作,由于无法得知其内部实现,它的具体功能只是一种推测。

②这里渲染的实体都将“通过”当前的Shader,D3D会自动的将顶点缓冲的信息在适当的时候装入相应的寄存器。Shader中就可以通过访问寄存器访问这些数据了。

像素光照
光照处理一直是游戏中十分抢眼的部分,真实眩目的光照效果往往可以将游戏的图像效果提升一个档次。像素光照是指像素级别的光照,即每个像素都将执行光照运算,其产生的效果远远超过顶点级别的光照。在FFP中只能使用顶点光照。正如前面提到的,Shader分为VS,PS两种,VS将对每个顶点执行,而PS则为每个像素执行。一个Effect对象可以封装多个VS、PS。下面的Shader程序使用HLSL编写,实现了具有环境光、漫反射、镜面反射的光照效果。
 
struct VS_INPUT
{
   float4 Position : POSITION;①
   float4 normal:NORMAL;
   float2 tex0:TEXCOORD;
};
struct VS_OUTPUT
{
 float4 Position:POSITION;①
 float2 tex0:TEXCOORD0;
 float4 colorA:TEXCOORD1;
   float4 colorD:TEXCOORD2;
   float4 colorS:TEXCOORD3;
};
VS_OUTPUT vs_main( VS_INPUT input )②
{
VS_OUTPUT Output;
Output.Position =mul(input.Position,matWorldViewProjection);③
Output.tex0=input.tex0;④
 

     这里定义了VS的输入结构,需要说明的是“POSITION”是一个semantics (语意)。语意的作用就是标识寄存器。这行代码表示:VS_ INPUT的Position成员将从POSI TION寄存器中获得。而寄存器中的数值已经在Shader调用之前由D3D准备好了。相应的VS_ OUTPUT中的语意则表示Shader执行完毕之后运算结果将放到哪个寄存器中。这里的运算结果将在之后的阶段里在顶点之间执行线性插值,然后插值结果将被PS使用。

     这里是VS主过程的声明,定义了输入输出的类型。
     将顶点变换到投影空间。3D投影是十分复杂的过程,幸运的是D3D已经将这个过程做的很好,只要几行代码就可以生成正确的矩阵,并完成设置。这里的matWorldViewProjection是由应用程序在渲染过程开始之前通过ID3DXEFFECT接口设置的。它是包含了世界、观察、投影变换的矩阵。
     直接将贴图坐标返回。
     由于所有的光照运算都是在世界空间进行的,而默认情况下法线是在模型空间中的,所以需要将法线变换到世界空间中。该行将世界矩阵的位移量清0,(_41,_42,_43分别表示矩阵的第4行第1、2、3列,这正是保存位移量的地方)这是因为法线是向量而不是顶点,对向量进行位移变换的错误的。

     这里执行了法线和光线的点乘。Normalize将向量规格化,dot执行点乘。它们都是HLSL的内置函数。

     计算光线的反射向量。使用公式:(参考[7]:P 53 Programing VertexShaders : RacorX3)
R=2*(N.L)*N-L。式中:N——法线向量;L——光线向量;R——反射向量。

Output.colorA=ka*lightAmbient*MaterialAmbient;⑧
Output.colorD=kd*lightdiffuse*(max(0,-DotNLInWorld))*MaterialDiffuse;⑨
float DotRV=dot(normalize(posinw-vViewPosition),Vreflect);
Output.colorS=ks*lightspecular*pow(max(0,DotRV),ns)*max(0,-DotNLInWorld/abs(DotNLInWorld))*MaterialSpecular;⑩
 return( Output );}⑪
struct PS_INPUT
{
 float2 tex0:TEXCOORD0;
 float4 colorA:TEXCOORD1;
   float4 colorD:TEXCOORD2;
   float4 colorS:TEXCOORD3;
};
float4 ps_main(PS_INPUT input) : COLOR0 ⑫
{
float4 finalcolor;
float4 diffusecolor=tex2D(DiffuseSampler,input.tex0);⑬
finalcolor=((input.colorA+input.colorD)*diffusecolor+input.colorS)*lightpower;⑭
return finalcolor;
}

     将环境光和物体材质混合,并乘以环境光系数(ka,kd,ks分别为光源的环境光系数、漫反射系数、镜面反射系数、具体数值由应用程序设置)。

     计算漫反射强度,使用公式:D=(-N.L) 式中:
D——漫反射强度;N——法线向量;L——光线向量;
再将光源的漫反射颜色和材质混合,并乘以漫反射强度和漫反射系数。
     计算镜面反射强度,使用公式:(R.V)^ns,式中:
R——反射向量;ns——镜面反射指数;V——观察方向;
ns越大表示物体表面越光滑镜面反射的强度越大,同时光照面积越小;V可以利用观察者的位置和当前顶点位置求出。代码中乘以max(0, -DotNLInWorld/abs(DotNLInWorld))的作用是:保证只有当N.L为负的时候才会出现镜面反射,否则镜面反射为0。因为当N.L为正时意味着N和L同向,所以此时当前像素在物体的背光面,不会发生镜面反射。
⑪  将VS_OUTPUT结构返回。
⑫  这里是PS主过程,定义了输入变量和输出变量。和VS不同的是PS的输出变量必须为float4,该值将会传递到渲染流水线的下一个阶段进行AlphaBlend等运算,并最终输出到屏幕上。另外,PS中的输入数据一般是从VS的输出中获得的,但是在VS、PS之间并不是直接的参数传递。从3.2节开始处的图2-4可以看出VS、PS之间还有一个叫做Primitive Processing的过程,该过程中处理实体信息,包括对每个顶点的VS输出进行插值,并送入PS的输入寄存器。所以,PS的输入就是当前像素所在三角形的各个顶点的VS输出的插值结果。在本程序中VS输出的3个颜色值都会经历这个过程并送到PS中。这样的最大好处是:将大量的运算放在VS中,运算结果交给PS使用。由于VS的执行效率比PS高的多,大大加快了处理速度,同时由于PS的输入是在顶点间“平滑”过度的,所以不用担心产生多边形之间出现“颜色突变”的现象。
⑬  对纹理贴图采样。采样是指:根据给出的贴图坐标按照一定的方式取出指定贴图的颜色值。这里使用2D采样取得与当前像素对应的颜色值。
⑭  最终,将所有的三种颜色迭加在一起,并用lightpower控制光照的整体强度。
上图展示了像素光照的渲染效果。
 
图a,c光照方向均沿着x轴正方向。
图b环境光系数,漫反射系数,镜面反射系数,镜面反射指数分别设置为0.1,1, 0,2。其他各图设置均为0.1,1,1,2。
图a,b,d分别为不同角度的观察结果.
通过上面的Shader代码,介绍了实现像素光照的方法以及VS,PS之间传递参数的方法.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值