Direct3D基础——光照的组成、材质、顶点法线、光源

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/DY_1024/article/details/84329923

为了增强所绘制的场景的真实感,我们会为场景增加光照,使用光照来描物体的立体感。使用光照的时候,我们就无需描述顶点的颜色,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 = 禁用
)

猜你喜欢

转载自blog.csdn.net/DY_1024/article/details/84329923