为了增强所绘制的场景的真实感,我们会为场景增加光照,使用光照来描物体的立体感。使用光照的时候,我们就无需描述顶点的颜色,Direct3D会将顶点送入光照计算引擎,然后引擎根据光源类型,材质以及物体表面和相对于光源的朝向等等,计算出内个顶点的颜色值。基于某种光照模型然后计算出顶点的颜色,效果更nice。
环境光
这种类型的光经过其他物体表面反射到达所观察物体,并且照亮场景。比如:某一个物体的一部分被照亮,但是该物体并没有处在光源的直接照射下,之所以被照亮,是因为有其他物体反射光源的光,然后到达自己身上,再被照亮的。这类光叫做环境光。
漫射光
这种类型的光沿着特定的方向传播。当漫射光到达一定表面的时候,沿着物体表面的所有方向均匀反射,所以无论从哪一个方向观察,表面的亮度都是相同的,所以采用漫射光的模型的时候,不需要考虑观察者的位置。所以漫射光方程中只需要考虑光的传播方向以及表面的朝向。从一个光源发出的光,大多就是这种类型的光。
镜面光
这类光沿着特定的方向传播,当光线到达表面之后,然后严格的按照另外一个方向进行,从而形成只能在一定角度才能观察到的高亮度照射,所以在镜面光方程中我们不仅要光照的入射方向和物体的表面朝向,还要考虑观察者的方向,镜面光可以模拟物体中的高光点,例如:当光线照射到物体表面所形成的的比较亮的照射
镜面光的计算量比环境光和漫射光的计算量要大得多,所以Direct3D专门为其设置了开关选项,默认情况下,Direct3D是不计算镜面光的反射的。
使用状态机将有关状态设置为true,就会将镜面光照打开:
Device->SetRenderState(D3DRS_SOECULARENABLE,true);
在现实世界中,物体的颜色是由其反射的光的颜色决定的。一个物体反射所有的红色光,吸收所有的非红色光,那么这个物体就呈现于红色光。Direct3D中允许我们通过定义物体的材质来模拟同样的现象。材质允许我们定义物体的表面对各种光的反射比例,在代码中使用D3DMATERIAL9来表示:
typedef struct D3DMATERIAL9
{
D3DCOLORVALUE Diffuse; //指定材质对漫射光的反射率
D3DCOLORVALUE Ambient; //指定材质对环境光的反射率
D3DCOLORVALUE Specular; //指定材质对镜面光的反射率
D3DCOLORVALUE Emissive; //该分量用于增强物体的亮度,使之看起来可以自己发光
float Power; //指定镜面高光的锐度,该值越大,高光点的锐度越大
}D3DMATERIAL9,*LPD3DMATERIAL9;
假如现在有一个红色的球体,我们想让他只去反射红色的光,应该向下面这样定义:
D3DMATERIAL9 red;
::ZeroMemory(&red, sizeof(red));
red.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);
red.Ambient = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);
red.Specular = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);
red.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 0.0f);//用于增强物体亮度的分量
red.Power = 5.0f; //指物体高光点的锐度
上面这个例子中,我们将绿色和蓝色光的反射率设置为0,意思就是:对绿色光和蓝色光全部吸收,对红色光的反射率为100%。也就是说我们可以控制材质在各种类型光的照射下,那种颜色的光会被反射。
注意的一点:如果使用一个蓝色光去照射一个只反射红色的球体的话,因为蓝色光被全部吸收,红色光为0,没有反射出来的红色光,所以球体是不能被照亮的。但一个球体吸收全部的颜色的时候成:黑色。当一个物体可以百分之百的反射红色光、绿色光、蓝色光的时候,物体成白色。
为了减轻我们初始化的工作,我们可以再d3dUtility.h/cpp中添加如下使用函数以及全局材质质量:
const D3DXCOLOR WHITE(D3DCOLOR_XRGB(255, 255, 255));
const D3DXCOLOR BLACK(D3DCOLOR_XRGB(0,0,0));
const D3DXCOLOR RED(D3DCOLOR_XRGB(255, 0, 0));
const D3DXCOLOR GREEN(D3DCOLOR_XRGB(0,255,0));
const D3DXCOLOR BLUE(D3DCOLOR_XRGB(0,0,255));
const D3DXCOLOR YELLOW(D3DCOLOR_XRGB(255,255,0));
const D3DXCOLOR CYAN(D3DCOLOR_XRGB(0,255,255));
const D3DXCOLOR MAGENTA(D3DCOLOR_XRGB(255,0,255));
D3DMATERIAL9 d3d::IniMtrl(D3DCOLOR a, D3DCOLOR d, D3DCOLOR s, D3DCOLOR e, float p)
{
D3DMATERIAL9 mtrl;
mtrl.Ambient = a;
mtrl.Diffuse = d;
mtrl.Specular = s;
mtrl.Emissive = e;
mtrl.Power = p;
return mtrl;
}
//下面的五种材质都是物体亮度和镜面高光点的锐度为0的材质
//白色材质,因为对环境光,漫反射光,镜面光的反射率都是反射白色光
const D3DMATERIAL9 WHITE_MTRL = InitMtrl(WHITE, WHITE, WHITE, BLACK,8.0f);
const D3DMATERIAL9 RED_MTRL = InitMtrl(RED, RED, RED, BLACK, 8.0f);
const D3DMATERIAL9 GREEN_MTRL = InitMtrl(GREEN, GREEN, GREEN, BLACK, 8.0f);
const D3DMATERIAL9 BLUE_MTRL = InitMtrl(BLUE, BLUE, BLUE, BLACK, 8.0f);
const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 8.0f);
因为顶点的结构中不包含材质的属性,所以我们必须要对当前绘制的图元的材质进行设定:
//定义一个蓝色材质和一个红色材质
D3DMATERIAL9 blueMaterial, redMaterial;
//省略一些舒适化材质内容的代码
//这里省去的就是在材质结构体里面填充他的镜面光,漫反射光、环境光之中的反射率
.
.
.
//设置材质
Device->SetMaterial(&blueMaterial);
//画一个球,现在画出来的就是蓝色球
drawsphere();
//设置材质
Device->SetMaterial(&redMaterial);
//画一个球,现在画出来的就是红色球
drawsphere();
面法线是描述一个多边形朝向的向量。顶点法线是描述构成多边形顶点的法线。
Direct3D需要知道每个顶点的朝向,也就是每个顶点的顶点法向量,来计算光照达到表面的入射角。
一个多边形的顶点法线和面法线不一定就是相同的,下面就是例子:
为了描述一个带顶点法线的顶点,我们的顶点结构就必须要改变:
struct Vertex{
float x,y,z;
float _nx,_ny,_nz;
static const DWORD FVF;
};
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL
在结构里面,我们去掉了顶点的颜色信息,因为我们可以通过光照来计算颜色的信息。
对于简单的物体,比如立方体和球体,我们可以直接的观察得到顶点的法线,而对于比较复杂物体,我们就需要通过一种方法来计算。
假定有一个三角形,由顶点p0,p1,p2构成,现在我们来计算每一个顶点的法线:n0,n1,n2。
最简单的方法就是三角形的面法线,然后将这个面法线当做每个顶点的顶点法线。
首先我们来计算三角形内部的两个向量:
p1 - p0 = u;
p2 - p0 = v;
则面法线就是:
n = u × v;
由于每一个顶点的发向量都相等,那么n0 = n1 = n2 = n;
下面是一个C语言写的计算三个顶点的所构成的面的面法向量。该函数中假设指定的顶点的顺序是顺时针绕序,如果是逆时针绕序的话,计算出来的法向量就是相反方向的。
void ComputeNormal (D3DVECTOR3 * p0,D3DVECTOR3 * p1,D3DVECTOR3 * p2,D3DVECTOR3 * pOut)
{
D3DVECTOR3 u = *p1 - *p0;
D3DVECTOR3 v = *p2 - *p0;
//求叉积
D3DVec3Cross(out,&u,&v);
//返回3D向量的规格化向量
D3DVec3Normal(out,out);
}
但是当使用三角形表面逼近表示曲面的时候,将单个的面法向量当做顶点的法向量不可能产生一个平滑的向量。现在我们的改进方法就是求出所有使用这个点的那些面的法向量,然后进行求均值。例如:为了求出点v的顶点法向量Vn,我们需要求出所有共享顶点V的三角形的面法向量,然后对这些面法向量求均值。
Vn = 1/3(n0 + n1 + n2)
再变换完成之后,可能有一些顶点法向量已经不是规范化的了,所以做好的方法就是改变绘制状态来使得所有的向量重新规范化。
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
Direct3D支持三种光源的形式。
点光源:该光源在世界坐标系中的位置固定,并均匀的向所有方向发射光线。
方向光:该光源没有位置,发射的光线互相平行,沿着某一特定方向传播
聚光灯:这种光源和手电筒类似,,发出的光程锥形,沿着特定的方向传播,锥形有两个角度φ和θ,θ描述内部角度,φ描述外部角度
代码中的光源结构使用结构体:D3DLIGHT9表示。
typedef struct D3DLIGHT9 {
D3DLIGHTTYPE Type; //光源类型:D3DLIGHT_POINT、D3DLIGHT_SPOT(聚光灯)、 D3DLIGHT_DIRECTIONAL
D3DCOLORVALUE Diffuse; //光源发出的漫反射光的颜色
D3DCOLORVALUE Specular; //光源发出的镜面光的颜色
D3DCOLORVALUE Ambient; //光源发出的环境光的颜色
D3DVECTOR Position; //光源在世界坐标中的位置,对于方向光该参数无意义,
D3DVECTOR Direction; //描述光在世界坐标中的传播方向的向量,对于点光源该参数无意义
float Range; //光线消亡之前,所能达到的最大的光程,对于方向光,该参数无意义
float Falloff; //该值仅用于聚光灯,该参数定义了光强从内锥形到外锥形的衰减,一般取:1.0f
float Attenuation0; //光线的常量衰减系数
float Attenuation1; //光线的线性衰减系数
float Attenuation2; //光线的2次距离衰减系数
float Theta; //仅用于聚光灯。指定了内部锥形的圆锥角,单位是:弧度
float Phi; //仅用于聚光灯。指定了外部锥形的圆锥角,单位是:弧度
}D3DLIGHT9, *LPD3DLIGHT;
与材质的结构体:D3DMATERIAL9的结构体初始化类似,当您需要一个比较简单的光源的时候,初始化是很麻烦的一件事,我们就可以在d3dUtility.h/.cpp中加入初始化的函数:
namespace d3d
{
D3DLIGHT9 InitDirectjonaLight(D3DXVECTOR3* direction, D3DXCOLOR* color);
D3DLIGHT9 InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color);
D3DLIGHT9 InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DCOLOR* color);
}
//简单实现一下方向光:
D3DLIGHT9 d3d::InitDirectionLight(D3DVECTOR3* direction, D3DXCOLOR* color)
{
D3DLIGHT9 Light;
::ZeroMemory(&Light, sizeof(Light));
Light.Type = D3DLIGHT_DIRECTIONAL;
Light.Ambient = *color * 0.4f; //光源发出的环境光的颜色
Light.Diffuse = *color; //光源发出的漫反射光的颜色
Light.Specular = *color * 0.6f; //光源发出的镜面光的颜色
Light.Direction = *direction; //描述光源在世界坐标系中传播方向的向量
return Light;
}
创建一个平行于x轴的方向光:
D3DXVECTOR3 dir(1.0f, 0.0f, 0.0f);
D3DXCOLOR c = d3d::WHITE;
D3DLIGHT9 dirLight = d3d::InitDirectionalLight(&dir, &c);
当我们对D3DLIGHT9的对象实例化完毕之后,我们需要在Direct3D所维护的一个光源内部列表中对所有我们要使用的光源进行注册。
//对使用的光源进行注册
Device->SetLight(
0, //要设置的灯光列表中的元素
&light //要设置的灯光,刚才已经初始化好之后的
)
//一旦光源注册成功,我们就可以对其进行开关状态进行控制
Device->LightEnable(
0, //灯光列表中的元素启用或者禁用
true //true = 启用,false = 禁用
)