天天看点

Direct-X学习笔记--光照&材质&DX自带几何体绘制

DX中自带几何体的绘制:

为了方便,DX为我们提供了几种自带的几何体,我们就不需要定义成吨的顶点来绘制一个图形了。

常用的有几种,立方体,茶壶,柱体,球面体。

HRESULT  D3DXCreateTeapot(
  __in   LPDIRECT3DDEVICE9 pDevice,
  __out  LPD3DXMESH *ppMesh,
  __out  LPD3DXBUFFER *ppAdjacency
);
           

上面是其中最简单的创建一个茶壶的代码,其余立方体与这个的创建大同小异,仅仅是在第一个参数之后加上几个尺寸的参数。

例如,我们创建一个立方体:

//生成立方体
	D3DXCreateBox(
		g_pDevice,				//设备对象
		1.0f, 1.0f, 1.0f,		//X,Y,Z方向的长度
		&g_pbox,				//创建的对象的指针
		NULL					//存储绘制网格的三角形索引参数,不使用,置为NULL
		);
           

在绘制时也很简单,只需要一句代码即可绘制:

g_pbox->DrawSubset(0);
           

一个Demo:

创建了一个立方体和一个茶壶,分别绕X轴旋转和Y轴旋转:

// D3DDemo.cpp : 定义应用程序的入口点。
//

#include "stdafx.h"
#include "D3DDemo.h"

#define MAX_LOADSTRING 100

// 全局变量:
HINSTANCE hInst;								// 当前实例
TCHAR szTitle[MAX_LOADSTRING];					// 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING];			// 主窗口类名

// 此代码模块中包含的函数的前向声明:
HWND                g_hWnd;
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);

//---------改造3D窗口需要的内容------------
LPDIRECT3D9 g_pD3D = NULL; //D3D接口指针
LPDIRECT3DDEVICE9 g_pDevice = NULL;//D3D设备指针

//立方体的接口
LPD3DXMESH g_pteapot = NULL;
LPD3DXMESH g_pbox = NULL;


void onCreatD3D()
{
	g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if (!g_pD3D)
		return;

	//检测硬件设备能力的方法
	/*D3DCAPS9 caps;
	ZeroMemory(&caps, sizeof(caps));
	g_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);*/

	//获得相关信息,屏幕大小,像素点属性
	D3DDISPLAYMODE d3ddm;
	ZeroMemory(&d3ddm, sizeof(d3ddm));

	g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);


	//设置全屏模式
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	/*d3dpp.Windowed = false;
	d3dpp.BackBufferWidth = d3ddm.Width;
	d3dpp.BackBufferHeight = d3ddm.Height;*/

	d3dpp.Windowed = true;
	d3dpp.BackBufferFormat = d3ddm.Format;
	d3dpp.BackBufferCount = 1;

	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//交换后原缓冲区数据丢弃

	//是否开启自动深度模板缓冲
	d3dpp.EnableAutoDepthStencil = true;
	//当前自动深度模板缓冲的格式
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;//每个像素点有16位的存储空间,存储离摄像机的距离
	

	g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pDevice);

	if (!g_pDevice)
		return;

	//设置渲染状态,设置启用深度值
	g_pDevice->SetRenderState(D3DRS_ZENABLE, true);

	//设置渲染状态,关闭灯光
	g_pDevice->SetRenderState(D3DRS_LIGHTING, false);

	//设置渲染状态,裁剪模式
	g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

	//g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE) ;

}


void Transform()
{	
	//ViewTransform:取景变换

	D3DXVECTOR3 vEyePt(0.0f, 0.0f, -10.0f);		//摄像机世界坐标
	D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);	//观察点世界坐标
	D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);		//摄像机的上向量,通常为(0.0f, 1.0f, 0.0f)
	D3DXMATRIXA16 matView;						//View变换的矩阵
	//根据上面的结果计算出矩阵,存入矩阵中
	D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
	//进行取景变换
	g_pDevice->SetTransform(D3DTS_VIEW, &matView);

	//ProjectionTransform:投影变换

	D3DXMATRIXA16 matProj;					//投影变换矩阵
	//生成投影变换矩阵,存入上面的矩阵中
	D3DXMatrixPerspectiveFovLH(
		&matProj,	     //输出结果矩阵
		D3DX_PI / 4,	 //视域角度,一般为PI/4
		1.0f,			 //显示屏的长宽比
		1.0f,			 //视截体中近截面距离摄像机的位置
		100.0f			 //视截体中远截面距离摄像机的位置
		);
	//进行投影变换
	g_pDevice->SetTransform(D3DTS_PROJECTION, &matProj);
}

void onInit()
{
	//初始化D3D
	onCreatD3D();

	//初始化对象

	//生成茶壶
	D3DXCreateTeapot(
		g_pDevice,				//设备对象
		&g_pteapot,				//创建的对象的指针
		NULL					//通常为NULL
		);

	//生成立方体
	D3DXCreateBox(
		g_pDevice,				//设备对象
		1.0f, 1.0f, 1.0f,		//X,Y,Z方向的长度
		&g_pbox,				//创建的对象的指针
		NULL					//存储绘制网格的三角形索引参数,不使用,置为NULL
		);
		
}

void onDestroy()
{
	if (!g_pDevice)
		g_pDevice->Release();
	g_pDevice = NULL;
}

void onLogic(float fElapsedTime)
{
	
}

void onRender(float fElasedTime)
{
	//前两个参数是0和NULL时,清空整个游戏窗口的内容(清的是后台)
	//第三个是清除的对象:前面表示清除颜色缓冲区,后面表示清除深度缓冲区,D3DCLEAR_STENCIL清空模板缓冲区
	g_pDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,100,100), 1.0f, 0);

	g_pDevice->BeginScene();

	Transform();

	D3DXMATRIXA16 matTY, matTX;
	D3DXMatrixRotationY(&matTY, timeGetTime() / 720.0f);
	D3DXMatrixRotationX(&matTX, timeGetTime() / 720.0f);

	//绘制茶壶
	D3DXMATRIXA16 matTeapot;
	D3DXMatrixTranslation(&matTeapot, 0.0f, 0.0f, 0.0f);
	matTeapot = matTeapot * matTY;
	g_pDevice->SetTransform(D3DTS_WORLD, &matTeapot);
	g_pteapot->DrawSubset(0);

	//绘制立方体
	D3DXMATRIXA16 matBox;
	D3DXMatrixTranslation(&matBox, 1.0f, 1.0f, 0.0f);
	matBox = matBox * matTX;
	
	
	g_pDevice->SetTransform(D3DTS_WORLD, &matBox);

	g_pbox->DrawSubset(0);


	//设置渲染状态为线框填充
	g_pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);


	g_pDevice->EndScene();


	g_pDevice->Present(NULL, NULL, NULL, NULL);
}


int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: 在此放置代码。
	MSG msg;
	HACCEL hAccelTable;

	// 初始化全局字符串
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_D3DDEMO, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// 执行应用程序初始化:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_D3DDEMO));

	

	ZeroMemory(&msg, sizeof(msg));
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			static DWORD dwTime = timeGetTime();
			DWORD dwCurrentTime = timeGetTime();
			DWORD dwElapsedTime = dwCurrentTime - dwTime;
			float fElapsedTime = dwElapsedTime * 0.001f;

			//------------渲染和逻辑部分代码----------
			onLogic(fElapsedTime);
			onRender(fElapsedTime);
			//-----------------------------------------
			if (dwElapsedTime < 1000 / 60)
			{
				Sleep(1000/ 60 - dwElapsedTime);
			}
			dwTime = dwCurrentTime;
		}
	}

	onDestroy();
	return (int) msg.wParam;
}



//
//  函数: MyRegisterClass()
//
//  目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_D3DDEMO));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_D3DDEMO);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   函数: InitInstance(HINSTANCE, int)
//
//   目的: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{

   hInst = hInstance; // 将实例句柄存储在全局变量中

   g_hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!g_hWnd)
   {
      return FALSE;
   }

 

   SetMenu(g_hWnd, NULL);
   ShowWindow(g_hWnd, nCmdShow);
   UpdateWindow(g_hWnd);

   onInit();

   return TRUE;
}

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的: 处理主窗口的消息。
//
//  WM_COMMAND	- 处理应用程序菜单
//  WM_PAINT	- 绘制主窗口
//  WM_DESTROY	- 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND g_hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)
			PostQuitMessage(0);
		break;
	case WM_CLOSE:
		DestroyWindow(g_hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(g_hWnd, message, wParam, lParam);
	}
	return 0;
}
           
Direct-X学习笔记--光照&amp;材质&amp;DX自带几何体绘制

对于世界变换,我们每次绘制一个物体之前都要设置它的世界矩阵,否则会使用上一次绘制时使用的世界矩阵,即出现了“渲染状态泄露”的情况。其实不仅仅是世界矩阵,所有的渲染状态都需要在绘制之前进行设置,如果绘制完,其他对象不需要这种渲染状态,我们就要把渲染状态恢复。

材质:

物体表面的材质决定了其能反射什么颜色的光以及反射的量。材质信息由下面的这个结构体表示:

typedef struct D3DMATERIAL9 {
    D3DCOLORVALUE Diffuse;    // 指定表面反射的漫反射光
    D3DCOLORVALUE Ambient;    // 指定表面反射的环境光
    D3DCOLORVALUE Specular;   // 指定表面反射的镜面光
    D3DCOLORVALUE Emissive;   // 表面本身自发光
    float Power;              // 镜面高光
} D3DMATERIAL9, *LPD3DMATERIAL9;
           

我们要做的就是填好这个结构体,然后调用设置材质的函数即可。 看一个例子:

D3DMATERIAL9 mtrl;
	ZeroMemory(&mtrl, sizeof(mtrl));
	mtrl.Ambient = D3DXCOLOR(1.0f, 0.5f, 0.1f, 1.0f); //物体表面对环境光的反射率,为一个颜色值
	mtrl.Diffuse = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f); //物体表面对漫反射光的反射率,为一个颜色值
	mtrl.Specular = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); //物体表面对镜面反射光的反射率,为一个颜色值
	mtrl.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f); //物体自发光的颜色值
	//mtrl.Power  镜面反射指数,该值越大,高光强度和周围亮度相差就越大
	g_pDevice->SetMaterial(&mtrl);
           

这样就设置好了物体的材质。配合下面的光照,就可以绘制出绚丽的3D世界啦!

光照:

之前的程序中,我们都是将光照关闭了。所谓的关闭光照,就是不使用这种模拟现实世界的光照效果,即必须通过光的反射才能看到物体的自然现象。而是没有光也能看见物体。

这里,我们把原来这句话删除即可,因为DX默认是打开光照的。删除这一句,我们就可以用光照与材质来营造一个更加逼真的3D世界了!

//设置渲染状态,关闭灯光
	g_pDevice->SetRenderState(D3DRS_LIGHTING, false);
           

DX中共有如下几种光:

1.环境光(Ambient Light):环 境光没有位置或者方向上的特征,只有一个颜色亮度值,而且不会衰减,所以在所有方向和所有物体表面上投射的环境光的数量是恒定不变的。 设置环境光的方法也比较简单,例如:

g_pDevice->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(20, 20, 100));
           

2.漫反射光(Diffuse Light):这种类型的光沿着特定的方向传播。当它到达某一表面时,将沿着各个方向均匀反射,所以我们无论从哪个方向观察,物体表面的亮度都是相同的,所以采用漫反射这种光照模型时,无需考虑观察者的位置,但是需要考虑漫反射光的空间位置和方向。从一个光源发出的光一般都是这种类型的

3.镜面反射光(Specular Light):沿着特定的方向传播,当此类光到达一个表面时,将严格地沿着另一个方向反射,从而形成只能在一个角度范围内才能观察到的高亮度照射。如果我们移动光源,就会发现镜面亮光区所发生的变化,这意味着镜面反射取决于观察者的角度。我们可以这样来归纳,漫反射与视觉无关,而镜面反射与视觉相关。

需要注意的是,镜面光与其他类型的光相比,计算量要大得多,Direct3D默认情况是把镜面反射关闭的。打开方法如下:

//开启镜面反射
	g_pDevice->SetRenderState(D3DRS_SPECULARENABLE, true);
           

4.自发光(Emissive Light):自发光影响着一个对象的颜色,比如我们可以通过设置自发光的颜色属性,把一些灰暗的材质变得明亮一些。我们可以使用材质的自发光属性来照亮这个对象,而不用在场景中内部添加灯光,从而缩小计算量。自发光属性创建的材质并不发射出能被场景内其他对象反射的光,也就是说,它发出的光并不参与光运算,而为了实现反射光,需要在场景中添加额外的灯光。

关于光源,有这样一个结构体:

typedef struct _D3DLIGHT9
{
    D3DLIGHTTYPE  Type;       // 定义了所要创建的光源类型
    D3DCOLORVALUE Diffuse;    // 该光源所发出的漫射光的颜色
    D3DCOLORVALUE Specular;   // 该光源所发出的镜面光的颜色
    D3DCOLORVALUE Ambient;    // 该光源所发出的环境光的颜色
    D3DVECTOR Position;       // 用于描述光源在世界坐标系中位置的向量。
    D3DVECTOR Direction;      // 一个描述光在世界坐标系中传播方向的向量。
    float Range;              // 光线“消亡”前,所能达到的最大光程。
    float Falloff;            // 仅用于聚光灯。定义了光强从内锥形到外锥形的衰减方式
    float Attenuation0;       // 该衰减变量定义了光强随距离衰减的方式。仅用于点光源和聚光灯
    float Attenuation1;       // 同Attenuation0
    float Attenuation2;       // 同Attenuation0
    float Theta;              // 仅用于聚光灯。指定了内部锥形的圆锥角,单位为弧度
    float Phi;                // 仅用于聚光灯。指定了外部锥形的圆锥角,单位为弧度
}D3DLIGHT9;
           

我们在设置光照的时候,只需要填充这样一个结构体,就可以设置相应的光源了。

DX中共有3种光源: 1.点光源(Point Light):有颜色和位置,但是没有方向。所有方向光都一样。即点光源是一个从中心向各个方向发相等强度光线的光源。 设置方法:

<span style="white-space:pre">	</span>D3DLIGHT9 light;
	ZeroMemory(&light, sizeof(light));
	light.Type = D3DLIGHT_POINT;	                      //光源类型
	light.Ambient = D3DXCOLOR(0.6f, 0.6f, 0.6f, 1.0f);    //光源的环境光颜色值
	light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);	  //光源的漫反射光颜色值<span style="white-space:pre">			</span>		light.Specular = D3DXCOLOR(0.3f, 0.3f, 0.3f, 1.0f);	  //光源的镜面反射光颜色值
	light.Position = D3DXVECTOR3(0.0f,20.0f, 0.0f);      //光源的位置
	light.Attenuation0 = 1.0f;							  //衰减系数0
	light.Attenuation1 = 0.0f;							  //衰减系数1
	light.Attenuation2 = 0.0f;							  //衰减系数2
	light.Range = 300.0f;								  //光源的光照范围

<span style="white-space:pre">	</span>//设置光照
	g_pDevice->SetLight(0, &light);
	//启用光照
	g_pDevice->LightEnable(0, true);
           
Direct-X学习笔记--光照&amp;材质&amp;DX自带几何体绘制

2.方向光源(Directional Light):方向光源是从无穷远处发出的一组平行的,均匀的光线,有颜色和方向,但不会衰减。 设置方法:

<span style="white-space:pre">	</span>D3DLIGHT9 light;
	ZeroMemory(&light, sizeof(light));

	//平行光源
	light.Type = D3DLIGHT_DIRECTIONAL;	                  //光源类型
	light.Ambient = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);       //光源的环境光颜色值<span style="white-space:pre">				</span>		light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);	  //光源的漫反射光颜色值
	light.Specular = D3DXCOLOR(0.3f, 0.3f, 0.3f, 1.0f);	  //光源的镜面反射光颜色值
	light.Position = D3DXVECTOR3(0.0f,20.0f, 0.0f);       //光源的位置

<span style="white-space:pre">	</span>//设置光照
	g_pDevice->SetLight(0, &light);
	//启用光照
	g_pDevice->LightEnable(0, true);
           
Direct-X学习笔记--光照&amp;材质&amp;DX自带几何体绘制

3.聚光灯光源(Spot Light): 聚光灯发出的光由一个明亮的内椎体(inner cone)和大一点的外椎体(outer cone)组成。显然内锥体中的光是最亮的,内锥体到外椎体外围的光强逐渐衰减,到了外椎体以外,已经衰减得没有光了。 设置聚光灯光源:

<span style="white-space:pre">	</span>D3DLIGHT9 light;
	ZeroMemory(&light, sizeof(light));

<span style="white-space:pre">	</span>//聚光灯光源
		light.Type = D3DLIGHT_SPOT;	                          //光源类型
		light.Ambient = D3DXCOLOR(0.3f, 0.3f, 0.3f, 1.0f);    //光源的环境光颜色值
		light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);	  //光源的漫反射光颜色值
		light.Specular = D3DXCOLOR(0.3f, 0.3f, 0.3f, 1.0f);	  //光源的镜面反射光颜色值
		light.Position = D3DXVECTOR3(100.0f,100.0f, 100.0f);  //光源的位置
		light.Direction = D3DXVECTOR3(-1.0f, -1.0f, -1.0f);   //光源方向
		light.Attenuation0 = 1.0f;							  //衰减系数0
		light.Attenuation1 = 0.0f;							  //衰减系数1
		light.Attenuation2 = 0.0f;							  //衰减系数2
		light.Range = 300.0f;								  //光源的光照范围
		light.Falloff = 0.1f;								  //聚光灯特有的参数
		light.Phi = D3DX_PI / 3.0f;							  //聚光灯特有的参数
		light.Theta = D3DX_PI / 6.0f;						  //聚光灯特有的参数

<span style="white-space:pre">	</span>//设置光照
	g_pDevice->SetLight(0, &light);
	//启用光照
	g_pDevice->LightEnable(0, true);
           
Direct-X学习笔记--光照&amp;材质&amp;DX自带几何体绘制

还是很漂亮的,不同的光源,有不同的效果。

最后再总结一下三种光源的区别(结构体包含的属性):

点光源:    颜色,位置,半径,衰减率

平行光源:颜色,方向

聚光灯:    颜色,位置,方向,内椎,外锥,内外锥衰减,衰减率