Direct3D:对定向光源的代码实现 + 讨论分析

整体流程图
【完整代码见文章结尾】


一、定向光源代码实现

1.1 顶点法线

  使用顶点法线,同一个面的多个顶点的法线就不一定相同,这样通过光栅化处理后,就能在多面体的表面获得一种平滑过渡的光照效果。

// 使用顶点法线
struct Vertex{
	Vertex(float x, float y, float z,
		float nx, float ny, float nz){
		_x  = x;  _y  = y;  _z  = z;
		_nx = nx; _ny = ny; _nz = nz;
	}
    float _x, _y, _z;
    float _nx, _ny, _nz;
	static const DWORD FVF;
};

1.2 灵活顶点格式

  灵活顶点格式(Flexible Vertex Format, FVF)用来描述在顶点缓冲区中的顶点存储格式中包含了哪些属性,可以使程序只使用它需要的顶点数据,排除那些它不需要的组成成分,达到节省内存空间的目的。

// 描述包含位置属性、法向量属性的顶点结构时,灵活顶点格式标志
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;

1.3 设置材质

  首先创建一个ID3DXMesh的全局变量指针,这是是一个三角形图元的网格接口。之后再创建一个D3DMATERIAL9来储存茶壶的材质属性。

ID3DXMesh*   Teapot = 0;
D3DMATERIAL9 TeapotMtrl;

  下面的代码是对茶壶材质的初始化。ZeroMemory开辟内存空间,用0来填充内存区域。D3DMATERIAL9结构体是物体表面对光的反射百分比(没有被吸收的)。物体的颜色=反射环境光+反射漫反射光+反射镜面反射光+自发光

  在本次实验中,目标是制作一个灰色陶瓷质感的茶壶,所以在设置反射颜色的时候的 r, g, b 的值相等。因为是陶瓷质感,可能反射光会多一点,所以镜面光的反射值比漫反射和环境光的值大。当然茶壶不会自发光,所以Emissive的值为0 。Power是高光的锐利值,该值越大表示高光强度与周围亮度相差越大。从图 1 和图 2 的对比中可以直观看出Power值对材质所产生的作用。

// 初始化茶壶材料
::ZeroMemory(&TeapotMtrl, sizeof(TeapotMtrl));
TeapotMtrl = d3d::WHITE_MTRL;	//一个白色茶杯
TeapotMtrl.Diffuse  = D3DXCOLOR(0.1f, 0.1f, 0.1f, 0.5f);	// 漫反射光
TeapotMtrl.Specular = D3DXCOLOR(2.0f, 2.0f, 2.0f, 1.0f);	// 镜面光
TeapotMtrl.Ambient  = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);	// 环境光
TeapotMtrl.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);	// 发光度
TeapotMtrl.Power    = 15.0f;		//高光的锐利值,该值越大表示高光强度与周围亮度相差越大。

// 创建一个茶壶
D3DXCreateTeapot(Device, &Teapot, 0);

Power值对材质所产生的作用

1.4 设置定向光源

  创建一个名为dir的定向光,为其开辟内存空间。设置定向光的颜色,分别设置漫反射、镜面光、环境光的颜色。设置光源的照射方向,从屏幕外侧左上角向中心照射。

// 设置光线
// 设置光的颜色,D3DXCOLOR的四个参数分别是r,g,b,a
D3DLIGHT9 dir;
::ZeroMemory(&dir, sizeof(dir));
dir.Type      = D3DLIGHT_DIRECTIONAL; //定向光源
dir.Diffuse   = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);		// 光源发出的漫反射光的颜色
dir.Specular  = D3DXCOLOR(0.2f, 0.2f, 0.2f, 1.0f);		// 光源发出的镜面光颜色
dir.Ambient   = D3DXCOLOR(0.6f, 0.6f, 0.6f, 1.0f);		// 光源发出的环境光的颜色
dir.Direction = D3DXVECTOR3(9.8f, -6.0f, 0.8f);		// 用向量表示的光源世界坐标照射方向(屏幕外侧左上角,向中心照射)

  设置光源编号和打开光源。

Device->SetLight(0, &dir);			// 设置0号灯
Device->LightEnable(0, true);		//打开0号灯光

1.5 渲染

  使用SetRenderState设置渲染状态,设置重新计算法线方向,自动对法线矢量进行归一化处理。打开镜面光渲染,这样会增加计算时间,但是可以提高渲染效果,图 3 和图 4 中分别展示了打开和关闭镜面光的渲染效果。设置混合因子,使alpha组件决定透明度。

// SetRenderState的两个参数:要改变的状态,状态值
// 打开高光并重新正规化
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);	// 设置渲染状态重新计算法线方向
Device->SetRenderState(D3DRS_SPECULARENABLE, true);		// 打开镜面光渲染

// 设置混合因子,使alpha组件决定透明度
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

镜面光的渲染效果

1.6 设置摄像机

  设置摄像机的观察方向、摄像机的位置、摄像机的向上的方向。图 5和图 6是对up参数的对比分析。

// 设置摄像机
D3DXVECTOR3 pos(0.0f, 0.0f, -3.0f);		//眼睛的位置,观察的方向
D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);	//摄影机的前进和后退,上下左右
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);		//向上的方向
D3DXMATRIX V;
D3DXMatrixLookAtLH(&V, &pos, &target, &up);
Device->SetTransform(D3DTS_VIEW, &V);

up参数的对比分析

1.7 设置投影

  使用D3DXMatrixPerspectiveFovLH进行投影变换,其中分别设置了y轴视角、宽高比、近裁面和远裁面。

// 设置投影矩阵。
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(
		&proj,
		D3DX_PI * 0.26f, // 90 - degree,y轴视角
		(float)Width / (float)Height,	// 宽高比
		1.0f,		//近裁面
		1000.0f);	//远裁面
Device->SetTransform(D3DTS_PROJECTION, &proj);

y轴视角与远近裁面的分析


二、实验分析

2.1 三种光源对比

  使用d3dUtility.h中的光源初始化函数,分别简单实现了三种光源,进行效果的对比。光源的方向均为从屏幕内侧右上角向中心照射。光源的颜色均为蓝色。从图 11、图 12、图 13 的对比中可以看出三种光的区别,但是因为点光源和聚光灯光源都是近似的发散性光源,所以在图中的区别不大。

D3DXVECTOR3 position(9.8f, 6.0f, 0.8f);		 //屏幕内侧右上角
D3DXVECTOR3 direction(-9.8f, -6.0f, -0.8f);  //屏幕内侧右上角,向中心照射
D3DXCOLOR blue(0.0f, 0.0f, 1.0f, 1.0f);

// 蓝色聚光灯
D3DLIGHT9 blue_spot = d3d::InitSpotLight(&position, &direction, &blue);
// 蓝色点光源
D3DLIGHT9 blue_point = d3d::InitPointLight(&position, &blue);
// 蓝色定向光源
D3DLIGHT9 blue_direct = d3d::InitDirectionalLight(&direction, &blue);

三种光源对比

2.2 同时打开两个定向光

  图 14 中展示的是同时打开两个定向光的情况,一个是最初在实验中设置的白色定向光,从屏幕外侧左上角向中心照射,另一个是上文中提到的蓝色定向光,从屏幕内侧右上角向中心照射。

Device->SetLight(0, &dir);			// 设置0号灯
Device->LightEnable(0, true);		//打开0号灯光
Device->SetLight(1, &blue_direct);	// 设置1号灯
Device->LightEnable(1, true);		//打开1号灯光

同时打开两个定向光

2.3 对茶壶材质的参数调整

  对漫反射参数的调整,从图中可以看出,漫反射值过大会产生局部过曝现象。另外diffuse光是方向性的,所以diffuse光的入射角度决定了整体反射的强度,当入射光线跟顶点法向量平行时,diffuse反射是最强的,即为图 16中过曝部分。
对漫反射参数的调整
  对镜面反射参数的调整,从图中可以看出,当镜面反射的参数过小时,材质会感觉变的粗糙,有一种磨砂的质感,当镜面反射的参数变大时,材质的质感会更加光滑。
对镜面反射参数的调整
  对环境光的参数进行调整,环境反射是非方向性的,当环境反射很小时会导致材质变暗。
对环境光的参数进行调整
  对茶壶漫反射透明度参数的调整,D3DXCOLOR结构体的最后一个参数是alpha值,区间在[0, 1]之间。由下图可以看出alpha值对物体材质的影响。
对茶壶漫反射透明度参数的调整

2.4 对定向光的参数调整

  为了使效果更加明显,只打开0号光源,将另外一个蓝色的定向光关闭。

  对漫反射光的参数调整,从图中可以看出光源强度对物体局部亮度产生的影响。
对漫反射光的参数调整
  对镜面光的参数进行调整,可以看出光源镜面光过强时会对物体产生剧烈的过曝现象。但是这样的变化只产生在物体的局部表面,即朝向光的方向的表面,而背面则不会产生任何影响,由此可以证明镜面光是有很强方向性的。
对镜面光的参数进行调整
  对环境光的参数进行改变,从图中的对比可以看出环境光是没有方向性的,它与上面两种光不同,它的值改变会对整个环境中物体产生亮度上的影响,但是上面两种光只会对照射方向产生影响。
对环境光的参数进行改变
  对定向光的方向进行参数改变,改变其z值,从图中可以看出光线的方向产生了变化。
对定向光的方向进行参数改变


三、实验总结

  在本实验中主要实现了定向光的案例,其中有两个比较关键的地方:设置茶壶材质、设置光源参数。

  材质是物体的一个基本属性,它反映了物体表面对光的反射百分比,其中主要有5个参数Diffuse(漫反射),Ambient(环境光反射),Specular(镜面反射),Emissive(发光),Power(高光的锐利值),通过对这些参数的调整,可以模拟出各种材质。

  光源的参数设置主要储存在D3DLIGHT9结构体中。和材质相同的是它也包括上述的三种光,除此之外,它还有光源的位置、光源的方向、光的衰减度等参数。在本身实验中主要使用的是定向光,定向光是类似太阳光的平行光,没有发出的光源位置,只有光线的方向。


四、附录

DirectionaLight.cpp完整代码

//////////////////////////////////////////////////////////////////////////////////////////////////
// 
// File: DirectionaLight.cpp
// 
// Author: Frank Luna (C) All Rights Reserved
//
// System: AMD Athlon 1800+ XP, 512 DDR, Geforce 3, Windows XP, MSVC++ 7.0 
//
// Desc: Renders a semi transparent teapot using alpha blending.  In this 
//       sample, the alpha is taken from the material's diffuse alpha value.
//       You can increase the opaqueness with the 'Q' key and can descrease
//       it with the 'E' key.          
//////////////////////////////////////////////////////////////////////////////////////////////////

#include "d3dUtility.h"

// Globals
IDirect3DDevice9* Device = 0; 
const int Width  = 640;
const int Height = 480;
 
ID3DXMesh*   Teapot = 0;
D3DMATERIAL9 TeapotMtrl;

// 使用顶点法线
struct Vertex{
	Vertex(float x, float y, float z,
		float nx, float ny, float nz){
		_x  = x;  _y  = y;  _z  = z;
		_nx = nx; _ny = ny; _nz = nz;
	}
    float _x, _y, _z;
    float _nx, _ny, _nz;
	static const DWORD FVF;
};
// 描述包含位置属性、法向量属性的顶点结构时,灵活顶点格式标志
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;


// 用来完成例子所需的所有设置,例如完成资源分配、设备性能检测以及程序状态设置等工作
bool Setup(){
	// 初始化茶壶材料
	::ZeroMemory(&TeapotMtrl, sizeof(TeapotMtrl));
	TeapotMtrl = d3d::WHITE_MTRL;	//一个白色茶杯
	TeapotMtrl.Diffuse  = D3DXCOLOR(0.1f, 0.1f, 0.1f, 0.5f);	// 漫反射光
	TeapotMtrl.Specular = D3DXCOLOR(2.0f, 2.0f, 2.0f, 1.0f);	// 镜面光
	TeapotMtrl.Ambient  = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);	// 环境光
	TeapotMtrl.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);	// 发光度
	TeapotMtrl.Power    = 15.0f;		//高光的锐利值,该值越大表示高光强度与周围亮度相差越大。

	// 创建一个茶壶
	D3DXCreateTeapot(Device, &Teapot, 0);

	// 设置光线
	// 设置光的颜色,D3DXCOLOR的四个参数分别是r,g,b,a
	D3DLIGHT9 dir;
	::ZeroMemory(&dir, sizeof(dir));
	dir.Type      = D3DLIGHT_DIRECTIONAL; //定向光源
	dir.Diffuse   = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);		// 光源发出的漫反射光的颜色
	dir.Specular  = D3DXCOLOR(0.2f, 0.2f, 0.2f, 1.0f);		// 光源发出的镜面光颜色
	dir.Ambient   = D3DXCOLOR(0.6f, 0.6f, 0.6f, 1.0f);		// 光源发出的环境光的颜色
	dir.Direction = D3DXVECTOR3(9.8f, -6.0f, 0.8f);		// 用向量表示的光源世界坐标照射方向(屏幕外侧左上角,向中心照射)

	D3DXVECTOR3 position(9.8f, 6.0f, 0.8f);		 //屏幕内侧右上角
	D3DXVECTOR3 direction(-9.8f, -6.0f, -0.8f);  //屏幕内侧右上角,向中心照射
	D3DXCOLOR blue(0.0f, 0.0f, 1.0f, 1.0f);

	// 蓝色聚光灯
	D3DLIGHT9 blue_spot = d3d::InitSpotLight(&position, &direction, &blue);
	// 蓝色点光源
	D3DLIGHT9 blue_point = d3d::InitPointLight(&position, &blue);
	// 蓝色定向光源
	D3DLIGHT9 blue_direct = d3d::InitDirectionalLight(&direction, &blue);

	Device->SetLight(0, &dir);			// 设置0号灯
	Device->LightEnable(0, true);		//打开0号灯光
	Device->SetLight(1, &blue_direct);	// 设置1号灯
	Device->LightEnable(1, true);		//打开1号灯光

	// SetRenderState的两个参数:要改变的状态,状态值
	// 打开高光并重新正规化
	Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);	// 设置渲染状态重新计算法线方向
	Device->SetRenderState(D3DRS_SPECULARENABLE, true);		// 打开镜面光渲染

	// 设置混合因子,使alpha组件决定透明度
	Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

	// 设置摄像机
	D3DXVECTOR3 pos(0.0f, 0.0f, -3.0f);		//眼睛的位置,观察的方向
	D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);	//摄影机的前进和后退,上下左右
	D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);		//向上的方向
	D3DXMATRIX V;
	D3DXMatrixLookAtLH(&V, &pos, &target, &up);
	Device->SetTransform(D3DTS_VIEW, &V);

	// 设置投影矩阵。
	D3DXMATRIX proj;
	D3DXMatrixPerspectiveFovLH(
			&proj,
			D3DX_PI * 0.26f, // 90 - degree,y轴视角
			(float)Width / (float)Height,	// 宽高比
			1.0f,		//近裁面
			1000.0f);	//远裁面
	Device->SetTransform(D3DTS_PROJECTION, &proj);

	return true;
}

// 用于释放Setup函数中所分配的资源,例如释放在堆上占用的内存空间
void Cleanup(){	
	d3d::Release<ID3DXMesh*>(Teapot);
}

// 该函数包含了所有的渲染代码以及那些在每帧之间控制程序的操作代码
bool Display(float timeDelta){
	if( Device ){
		// 通过键盘输入增加/减少alpha值
		if( ::GetAsyncKeyState('Q') & 0x8000f )
			TeapotMtrl.Diffuse.a += 0.001f;
		if( ::GetAsyncKeyState('E') & 0x8000f )
			TeapotMtrl.Diffuse.a -= 0.001f;
		// 透明度[0,1]区间
		if(TeapotMtrl.Diffuse.a > 1.0f)
			TeapotMtrl.Diffuse.a = 1.0f;
		if(TeapotMtrl.Diffuse.a < 0.0f)
			TeapotMtrl.Diffuse.a = 0.0f;

		// Render 渲染
		Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
		Device->BeginScene();

		Device->SetMaterial(&TeapotMtrl);//设置设备的材料属性
		Device->SetTexture(0, 0);	//将纹理分配给设备的舞台
		Teapot->DrawSubset(0);		//绘制网格的子集

		Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);	// 设置支持透明度的渲染

		Device->EndScene();
		Device->Present(0, 0, 0, 0);
	}
	return true;
}


// WndProc
LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch( msg ){
	case WM_DESTROY:
		::PostQuitMessage(0);
		break;		
	case WM_KEYDOWN:
		if( wParam == VK_ESCAPE )
			::DestroyWindow(hwnd);
		break;
	}
	return ::DefWindowProc(hwnd, msg, wParam, lParam);
}


// WinMain
int WINAPI WinMain(HINSTANCE hinstance,
				   HINSTANCE prevInstance, 
				   PSTR cmdLine,
				   int showCmd){
	if(!d3d::InitD3D(hinstance,Width, Height, true, D3DDEVTYPE_HAL, &Device)){
		::MessageBox(0, "InitD3D() - FAILED", 0, 0);
		return 0;
	}
		
	if(!Setup()){
		::MessageBox(0, "Setup() - FAILED", 0, 0);
		return 0;
	}
	d3d::EnterMsgLoop( Display );
	Cleanup();
	Device->Release();
	return 0;
}

【如需要其它配置文件代码,可私信博主】

猜你喜欢

转载自blog.csdn.net/SongXJ_01/article/details/106022418
今日推荐