3D游戏编程入门(二十)灯光材质
恩,现在上不了网了,所以也专心复习了。不废话。
另外需要补充的是,即本章节起,将参考部分其他D3D相关书籍来复习了,因为我确实不知道如何去空头讲述了,自己学的也不扎实,无法条理的叙述。但依旧大幅度的对原教程进行补充和说明。
灯光的组成
在D3D中,我们的灯光分为三类。
- 环境光。
这个我们不能去字面的理解。它不是仅仅说我们现实环境中的这种光,实在上这是一种理想状态下的光,我们假使它是可以照射每一个角落的,无论任何地方,透明或不透明,被遮挡或未被遮挡,我们都将认为环境光可以照到。它影响整个游戏屏幕的光暗程度。
- 漫反射。
学过物理光学基础的朋友可以想到,光照在物体上是有两种状态的,一种是漫反射,一种是镜面反射,而实际上就是这样。漫反射是完全均匀的进行反射的,各个角度都是如此,所以我们不必去考虑观察者恶毒角度,我们仅仅需要关心的就是灯光光源的位置和物体本身的采制,在游戏中,它用来模拟真实的灯光反射
- 镜面反射。
当光线照射到比较光泽的物体表面上时,将发生镜面反射,它将光线按照一定的方向反射回去,此时在物体表面产生高光地带,但是我们这时需要考虑观察者的角度,光线入射方向以及物体材质的属性,一般我们仅在表面光泽的物体上设置这种灯光。 由于镜面反射进行的计算比较多,所以我们系统默认的是关闭的,而环境光和漫反射是默认开启的,当我们需要使用竟面反射时,我们需要调用响应的函数开启它。
D3DDevice->SetRenderState( D3DRS_SPECULARENABLE, ture );
我们对每一个灯光可以设置一个结构来控制它,或者我们调用D3DX中提供的D3DXCOLOR来设置
D3DXCOLOR blueAmbient( 0.0f, 0.0f, 0.0f, 1.0f );
上面是一个蓝色的环境光,参数依次为RGBA(限制为0.0f-1.0f之间的浮点数)
材质
我们看到物体表面的色彩,并非单纯由灯光来控制的,我们可以想想,一面红色的墙壁,我们之所以看到它是红的,是因为它反射着红色的光,但我们不能由此确定灯光是红色的,此时它显示红色则是由于它的材质影响。
我们在D3D中可以自己定义我们物体的材质,使用一个D3DMATERIAL9结构来存放材质相关信息。
typedef struct D3DMATERIAL9
{
D3DCOLORVALUE Diffuse, Ambient, Specular, Emissive;
float Power;
} D3DMATERIAL9;
其中Diffuse是漫射光反射数量,Ambient是环境光反射数量,Specular是镜面反射数量,Emissive是自发光信息,Power是镜面反射强度。
我们假如想设置一个蓝色的金属质感盒子,可以如下设置其材质。
D3DMATERIAL9 blueBox;
ZeroMemory( &bleBox, sizeof(blueBox) ); //清空此结构的内存
blueBox.Diffuse = D3DXCOLOR( 0.0f, 0.0f, 1.0f, 1.0f ); //只漫反射蓝色光
blueBox.Ambient = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ); //完全反射环境光(白光)
blueBox.Specular = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ); //镜面反射所有光(白光)
blueBox.Emissive = D3DXCOLOR( 0.0f, 0.0f, 0.0f, 1.0f ); //又不是电灯泡,不会自发光
blueBox.Power = 5.0f //镜面反射强度,其实还可以调大些。
顶点法线
这个忘记在D3D相关几何基础中说到了。
但是我提到了面法线,那是一个垂直于一个面的向量。顶点法线其实也与其一样,不过它仅仅是一个比较特别的面法线,它经过面的顶点,并且垂直与点所在的平面。
这时聪明的朋友可能会想到,我们的一个立方体,每一个顶点就被3个面使用,那么哪条是顶点法线呢?
实际上,这三个都是顶点法线,不过它仅仅是分不同面的对应法线而已。
我之前说过,D3D图元中是完全不存在圆的,但实际上我们可能很多物体是圆的,那是如何实现的呢?接触过3D建模的朋友可能会清楚些,我们是使用三角行面来模拟圆形的,但实际上我们若是不对顶点进行判断,则在设置灯光时,我们会发现球体面是一块一块的,原因是我们在渲染时参照的是顶点法线。而一个点处的顶点法线是不同的,所以才有棱角出现,现在假如我们将一个顶点处的所有顶点法线进行统一的平衡化,我们就会获得一个相对圆滑的球体。因为我们面与邻接面的渲染依据一致了。
首先我们来设置顶点法线,这需要我们更改我们的灵活顶点格式,使顶点中能够保存我们的 顶点信息
struct Vertex
{
float x, y, z; //顶点坐标
float nx, ny, nz; //顶点法线
}
当然在声明FVF格式时,也需要更改如下
#define D3DFVF_CUSTOMVERTEX ( D3DFVF_XYZ|D3DFVF_NORMAL )
当我们想将一点的法线设置为统一平衡化时,我们可以调用下函数开启
D3DDevice->SetRenderState( D3DRS_NORMALIZENORMALS, true );
这样,我们的球体就更加平滑了
光源
我们需要清楚的是,光源和灯光是完全不同的概念,灯光我们更倾向于我们物体方面的设置,而光源,我们仅仅需要考虑的是光源本身的位置强度,以及其发射出来的光线,这和我们的物体是没有任何关系的。
光源我们也是分为三类的:
- 点光源。
就象我们高中物理中设置的小灯泡一样,它是由一点发射出来的发散周围的光线。可以想象是一个极小的灯炮。
- 平行光。
就是一组平行照射的光线,这种光源我们不关心它的位置。我们可以理解太阳光到了地球就是一组平行光
- 聚光灯。
我们可以理解为是一个手电筒,它是仅在一组圆锥范围内的灯光。我们需要设置它的内弧灯光角度和外弧灯光角度。
在代码中,我们的光源相关信息也是存储在一个结构中的,我们可以调用D3DLIGHT9结构来实现。
typedef struct D3DLIGHT9
{
D3DLIGHTTYPE Type;
D3DCOLORVALUE Diffuse;
D3DCOLORVALUE Specular;
D3DCOLORVALUE Ambient;
D3DVECTOR Position;
D3DVECTOR Direction;
float Range;
float Falloff;
float Attenuation0;
float Attenuation1;
float Attenuation2;
float Theta;
float Phi;
} D3DLIGHT9;
- 其中Type就是那三项光源类型,分别被宏定义D3DLIGHT_PIONT,D3DLIGHT_SPOT,D3DLIGHT_DIRECTIONAL
- Diffuse,Specular,Ambient则分别表示此光源射出的漫射光颜色,镜面光颜色,环境光颜色
- Position是代表此光源在世界空间中的位置
- Direction是描述该光源的照射方向,当然点光源由于是四向照射,所以此值不设置。
- Range是灯光最大照射范围,当然,由于平行光理论上是无限传播的,我们不可对其设置此项。
- Falloff是内外弧灯光衰减,仅有聚光灯可以设置。
- Attenuation0,1,2分别是代表光线的各种衰减,我们很少使用,一般不进行设置。
- Tgeta,Phi分别是聚光灯的内弧,外弧角度。
当我们将灯光结构定义好了之后,我们需要注册灯光
D3DDevice->SetLight( 0, &light );
其中第一个是灯光编号,第二个是灯光的结构地址
注册完毕后,我们开启灯光,就万事大吉了
D3DDevice->LightEnable( 0, true );
其中第一个参数是我们刚才设置的灯光编号,第二个参数表示我们开启了这个灯光,设置为flase则代表关闭此灯光。
总结
我们一般设置灯光可用时需要按照以下步骤来设置
- 开启灯光设置,允许使用灯光
- 将材质设置好,并将其渲染到某物体上
- 设置灯光结构
- 注册并开启灯光。