今天又想了一下,不知道,自己这样写这个博客是否有用,真的关于DirectX3D,图形图像技术在国内一直没有很好的普及。所以这个行业的就业人口也比较少。大部分都是使用别人的游戏引擎。而且,3D技术本身,也比较难于理解,想游刃有余的使用,更加难。有时想,何必了,当别人下班都在休息的时候,我未必这么累了,可能这样付出,也未必有所得。而且,目前,都是基于DirectX3D 游戏开发基础的书籍的例子代码在用。而且,这个应该是一个很长的过程。我应该这样想,就当这是自己的一个爱好,仅此而已。就像有的人喜欢看电视,有的人喜欢玩游戏一样。我也应该把这当成一种消遣的方式。爱好不需要所得的,如果所有的东西,都想着如何从中得到。这样你永远做不好。
今天,我们看一下,关于游戏中的光源的使用和材质的使用。材质就是,表示物体对光源的反射情况。如果全部反射红光,物体就呈现出红色。光源的类型,有点光源,方向光,和聚光灯。我们用这三种光源来模拟现实中的光。光源发出的光,一般由环境光,漫射光和镜面光组成。
我们还是来看代码:DirectX3D.h
#ifndef __DirectX3DH__
#define __DirectX3DH__
#include <d3dx9.h>
#include <string>
namespace d3d
{
bool InitD3D(
HINSTANCE hInstance, // [in] Application instance.
int width, int height, // [in] Backbuffer dimensions.
bool windowed, // [in] Windowed (true)or full screen (false).
D3DDEVTYPE deviceType, // [in] HAL or REF
IDirect3DDevice9** device);// [out]The created device.
int EnterMsgLoop(
bool (*ptr_display)(float timeDelta));
LRESULT CALLBACK WndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
template<class T> void Release(T t)
{
if( t )
{
t->Release();
t = 0;
}
}
template<class T> void Delete(T t)
{
if( t )
{
delete t;
t = 0;
}
}
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) );
//
// Lights
//
/*
D3DLIGHT9 InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color);
D3DLIGHT9 InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color);
D3DLIGHT9 InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color);
//
// Materials
//
D3DMATERIAL9 InitMtrl(D3DXCOLOR a, D3DXCOLOR d, D3DXCOLOR s, D3DXCOLOR e, float p);
const D3DMATERIAL9 WHITE_MTRL = InitMtrl(WHITE, WHITE, WHITE, BLACK, 2.0f);
const D3DMATERIAL9 RED_MTRL = InitMtrl(RED, RED, RED, BLACK, 2.0f);
const D3DMATERIAL9 GREEN_MTRL = InitMtrl(GREEN, GREEN, GREEN, BLACK, 2.0f);
const D3DMATERIAL9 BLUE_MTRL = InitMtrl(BLUE, BLUE, BLUE, BLACK, 2.0f);
const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 2.0f);
*/
}
#endif
DirectX3D.cpp
#include "DirectX3D.h"
bool d3d::InitD3D(
HINSTANCE hInstance,
int width, int height,
bool windowed,
D3DDEVTYPE deviceType,
IDirect3DDevice9** device)
{
//
// Create the main application window.
//
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)d3d::WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = "Direct3D9App";
if( !RegisterClass(&wc) )
{
::MessageBox(0, "RegisterClass() - FAILED", 0, 0);
return false;
}
HWND hwnd = 0;
hwnd = ::CreateWindow("Direct3D9App", "Direct3D9App",
WS_EX_TOPMOST,
0, 0, width, height,
0 /*parent hwnd*/, 0 /* menu */, hInstance, 0 /*extra*/);
if( !hwnd )
{
::MessageBox(0, "CreateWindow() - FAILED", 0, 0);
return false;
}
::ShowWindow(hwnd, SW_SHOW);
::UpdateWindow(hwnd);
//
// Init D3D:
//
//第一步
//要初始化IDirect3D 首先必须获取IDirect3D9的指针,使用一个专门的Direct3D函数就可以很容易做到
IDirect3D9 * _d3d9;
//这个对象的主要有两个用途:设备枚举以及创建IDirect3DDevice9类型的对象。设备枚举是指获取系统中可用的的每块图形卡的
//性能,显示模型,格式以及其他信息。这个函数调用失败会返回一个NULL指针。
if(NULL == (_d3d9 = Direct3DCreate9(D3D_SDK_VERSION))){
return FALSE;
}
//第二步
//创建一个代表主显卡的IDirect3DDevice9类型对象时,必须指定使用该对象进行顶点运算的类型。如果可以,我们希望使用硬件顶点运算
//但是由于并非所有的显卡都支持硬件顶点运算,我们必须首先检查图形卡是否支持该类型的运算。
//要进行检查,必须先根据主显卡的性能参数初始化一个IDirect3DDevice9类型的对象。我们使用如下方法来完成初始化:
/*
HRESULT IDirect3D9:GetDeviceCaps(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DCAPS9 * pCaps;
)
Adapter : 指定物理显卡的序号。
DeviceType:指定设备类(例如硬件设备(D3DDEVTYPE_HAL)或软件设备(D3DDEVTYPE_REF));
pCaps 返回已初始化的设备性能结构实例。
*/
D3DCAPS9 caps;
int vp = 0; //代表顶点如何操作
_d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT){
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
else
{
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
//第三步是填充D3DPRESENT_PARAMETER结构
//该结构用于指定所要创建的IDirect3DDevice9类型对象的一些特性,该结构定义如下:
/*
typedef struct _D3DPRESENT_PARAMETERS_{
UINT BackBufferWidth;
UINT BackBufferHeight;
UINT BackBufferFormat;
UINT BackBufferCount;
D3DMULTISAMPLE_TYPE MultiSampleType;
DWORD MultiSampleQuality;
D3DSWAPEFFECT SwapEffect;
HWND hDeviceWindow;
BOOL Windowed;
BOOL EnableAutoDepthStencil;
D3DFORMAT AutoDepthStencilFormat;
DWORD Flags;
UINT FullScreen_RefreshRateInHz;
UINT PresentationInterval;
};
*/
/*
BackBufferWidth: 后台缓存中表面的宽度,单位为像素。
BackBufferHeight:后台缓存中表面的高度,单位为像素。
BackBufferFormat:后台缓存的像素格式(如32位像素格式:D3DFMT_A8R8G8B8);
BackBufferCount: 所需使用的后台缓存的个数,通常指定为1,表明我们仅需要一个后台缓存。
MultiSampleType: 后台缓存所使用的多重采样类型。
MultiSampleQuality:多重采样的质量水平。
SwapEffect:D3DSWAPEFFECT 枚举类型的一个成员。该枚举类型指定了交换链中的缓存的页面置换方式。指定D3DSWAPEFFECT_DISCARD时效率最高。
hDeviceWindow:与设备相关的窗口句柄。指定了所要进行绘制的应用程序窗口。
Windowed:为true时表示窗口模式,false时为全屏模式
EnableAutoDepthStencil:设为true,则Direct3D自动创建并维护深度缓存或模板缓存。
AutoDepthStencilFormat:深度缓存或模板缓存的像素格式(例如,用24位表示深度并将8位保留供模板缓存使用,D3DFMT_D24S8).
Flags:一些附加的特性。可以指定为0,表示无标记,或D3DPRESENTFLAG集合中的一个成员,其中两个成员较常用。
D3DPRESENTFLAG_LOCKABLE_DEPTHBUFFER 指定为可锁定的后台缓存。注意,使用一个可锁定的后台缓存会降低性能。
D3DPRESENTFLAG_DISCARD_DEPTHBUFFER 指定当下一个后台缓存提交时,哪个深度或模块缓存将被丢弃。丢弃的意思是深度或模板缓存存储区
中的内容别丢弃或无效。这样可以提升性能。
FullScreen_RefreshRateInHz: 刷新频率,如果想使用默认的刷新频率,则可将该参数指定为D3DPRESENT_RATE_DEFAULT;
PresentationInterval:D3DPRESENT集合的一个成员,其中有两个比较常用。
D3DPRESENT_INTERVAL_IMMEDIATE 立即提交。
D3DPRESENT_INTERVAL_DEFAULT 由Direct3D来选择提交频率,通常该值等于刷新频率。
*/
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.BackBufferWidth = 800;
d3dpp.BackBufferHeight = 600;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = true;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = 0;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
//第四步 创建IDirectDevice9类型的对象
/*
HRESULT IDirect3D9::CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9 ** ppReturnedDeviceInterface
);
Adapter:指定我们希望用已创建的IDirect3DDevice9对象代表哪块物理显卡。
DeviceType:指定需要使用的设备类型()如,硬件设备用D3DDEVTYPE_HAL,或D3DDEVTYPE_REF代表软件设备。
hFocusWindow:与设备相关的窗口句柄。通常情况下是指设备所要进行绘制的目标窗口。
为了达到预期的目的,该句柄与D3DPRESENT_PARAMETER结构的数据成员hDeviceWindow应为同一个句柄。
BehaviorFlags:该参数可为D3DCREATE_HARDWARE_VERTEXPROCESSING或D3DCREATE_SOFTWARE_VERTEXPROCESSING.
pPresentationParameters:一个已经完成初始化的D3DPRESENT_PARAMETERS类型的实例,该实例定义了设备的一些特性。
ppReturnedDeviceInterface:返回所创建的设备。
*/
if(FAILED(_d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd, vp, &d3dpp, device)))
return FALSE;
_d3d9->Release();
return TRUE;
}
int d3d::EnterMsgLoop( bool (*ptr_display)(float timeDelta) )
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG));
static float lastTime = (float)timeGetTime();
while(msg.message != WM_QUIT)
{
if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
float currTime = (float)timeGetTime();
float timeDelta = (currTime - lastTime)*0.001f;
ptr_display(timeDelta);
lastTime = currTime;
}
}
return msg.wParam;
}
在看一下wmain.cpp
#include "DirectX3D.h"
#include <fstream>
#include <vector>
//
// Globals
//
IDirect3DDevice9* Device = 0;
D3DXMATRIX World;
IDirect3DVertexBuffer9 * Pyramid = 0;//顶点缓存的指针
//初始化两个全局常量,以定义屏幕的分辨率。r
const int Width = 800;
const int Height = 600;
//接下来,定义灵活的顶点格式和该结构的灵活顶点格式。我们去掉了表示颜色的成员变量。
//这是因为我们将用光照来计算顶点的颜色。
struct Vertex
{
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;
//
// Framework Functions
//
//面我们在Setup函数里面创建顶点缓存和索引缓存,
//然后对缓存进行锁定,这里我们没有用到索引缓存
bool Setup()
{
// Nothing to setup in this sample.
//启用光照,默认状态下,光照就是启用的。
Device->SetRenderState(D3DRS_LIGHTING,true);
//创建顶点缓存
Device->CreateVertexBuffer(
12*sizeof(Vertex),
D3DUSAGE_WRITEONLY,
Vertex::FVF,
D3DPOOL_MANAGED,
&Pyramid,
0);
//回忆一下这个函数的参数
/*
第一个参数是缓存空间的大小,这里分配3个顶点结构的字节数。
第二个参数是使用缓存的属性,这里用的只写属性,后面如果读就会有问题。
第三个参数,顶点的灵活顶点格式。
第四个参数,容纳缓存的内存池。
*/
Vertex * v;//定义一个顶点结构的指针,用来装从Triangle得到的分配的顶点空间。
Pyramid->Lock(0,0,(void**)&v,0);
//回忆一下lock的参数,第一个是锁定的VB的偏移,第二个是锁定的字节数,第四个是锁定的方式。
//下面给顶点赋值。增加了顶点颜色信息。
//前面
v[0] = Vertex(-1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);
v[1] = Vertex( 0.0f, 1.0f, 0.0f, 0.0f, 0.707f, -0.707f);
v[2] = Vertex( 1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);
//左面
v[3] = Vertex(-1.0f, 0.0f, 1.0f, -0.707f, 0.707f, 0.0f);
v[4] = Vertex( 0.0f, 1.0f, 0.0f, -0.707f, 0.707f, 0.0f);
v[5] = Vertex(-1.0f, 0.0f, -1.0f, -0.707f, 0.707f, 0.0f);
//右面
v[6] = Vertex( 1.0f, 0.0f, -1.0f, 0.707f, 0.707f, 0.0f);
v[7] = Vertex( 0.0f, 1.0f, 0.0f, 0.707f, 0.707f, 0.0f);
v[8] = Vertex( 1.0f, 0.0f, 1.0f, 0.707f, 0.707f, 0.0f);
//下面
v[9] = Vertex( 1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f);
v[10] = Vertex( 0.0f, 1.0f, 0.0f, 0.0f, 0.707f, 0.707f);
v[11] = Vertex(-1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f);
Pyramid->Unlock();
//创建并启用材质
/*
回忆一下,材质,就是物体对光的反射情况,所以它的数据成员也就是跟其相关。
Diffuse 对慢射光的反射率。
Ambient 对环境光的反射率
Specular 对镜面光的反射率
Emissive 该分量增强物体的亮度
Power 指定镜面高光点的锐度,值越大,高光点的锐度越大。
*/
D3DMATERIAL9 mtrl;
mtrl.Ambient = d3d::RED;
mtrl.Diffuse = d3d::WHITE;
mtrl.Specular = d3d::BLUE;
mtrl.Emissive = d3d::BLACK;
mtrl.Power = 5.0f;
Device->SetMaterial(&mtrl);
/*创建一个光源,并启用它:
我们还是先回忆以下光源结构体成员
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, *LPD3DLIGHT;
*/
D3DLIGHT9 dir;
::ZeroMemory(&dir,sizeof(dir));
dir.Type = D3DLIGHT_DIRECTIONAL;
dir.Diffuse = d3d::WHITE*0.3f; // 30%的漫射白色光
dir.Specular = d3d::BLUE; // 蓝色镜面光
dir.Ambient = d3d::WHITE*0.6f; //60%的白色环境光
dir.Direction = D3DXVECTOR3(0.0f,-1.0f,0.0f); //光的方向是沿Y轴的负方向
Device->SetLight(0,&dir);
Device->LightEnable(0,true);
Device->SetRenderState(D3DRS_NORMALIZENORMALS,true);
Device->SetRenderState(D3DRS_SPECULARENABLE,true);
D3DXVECTOR3 pos(0.0f, 1.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);
//
// Set the projection matrix.
//
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(
&proj,
D3DX_PI * 0.5f, // 90 - degree
(float)Width / (float)Height,
1.0f,
1000.0f);
Device->SetTransform(D3DTS_PROJECTION, &proj);
return true;
}
//将之前分配的内存进行清理,也就是顶点缓存和索引缓存
void Cleanup()
{
d3d::Release<IDirect3DVertexBuffer9*>(Pyramid);
// Nothing to cleanup in this sample.
}
/*
这里的Display在两个不同的位置以两种不同的着色模式分别绘制了Triangle。每个三角形的位置由世界变换矩阵World来控制
*/
bool Display(float timeDelta)
{
if( Device ) // Only use Device methods if we have a valid device.
{
D3DXMATRIX yRot;
static float y = 0.0f;
D3DXMatrixRotationY(&yRot, y);
y += timeDelta;
if( y >= 6.28f )
y = 0.0f;
Device->SetTransform(D3DTS_WORLD, &yRot);
//清屏
Device -> Clear(0,0,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,0x00000000,1.0f,0);
//所有的绘制方法都必须在BeginScene和EndScene中进行调用
Device->BeginScene();
Device->SetFVF(Vertex::FVF);
//SetStreamSource函数,第一个参数是,资源号,比如有几个顶点缓存,第二个参数,顶点缓存的指针接口,
//第三个参数,是顶点缓存数据结构中的偏移字节。第四个是,顶点缓存的大小。
Device->SetStreamSource(0,Pyramid,0,sizeof(Vertex));
// 第一种模式 DrawPrimitive这个函数,专门用于绘制没有索引信息的图元。
/* 第一个参数是,图元的类型,
typedef enum D3DPRIMITIVETYPE {
D3DPT_POINTLIST = 1,
D3DPT_LINELIST = 2,
D3DPT_LINESTRIP = 3,
D3DPT_TRIANGLELIST = 4,
D3DPT_TRIANGLESTRIP = 5,
D3DPT_TRIANGLEFAN = 6,
D3DPT_FORCE_DWORD = 0x7fffffff
} D3DPRIMITIVETYPE, *LPD3DPRIMITIVETYPE;
第二个参数,顶点数据流中标识顶点数据读取起点的元素的索引。该参数赋予我们一定的自由度
使得,我们可以只对顶点缓存中的某一部分进行绘制。
第三个参数,所要绘制的图元数量。
关于D3DRS_SHADEMODE,一般规定了我们如何利用顶点的颜色来计算构成图元的像素的颜色,目前一般我们
使用两种着色模式,分别是平面着色D3DSHADE_FLAT ,另一种是,D3DSHADE_GOURAUD。
D3DSHADE_FLAT 着色,每个图元的每个像素都被一直地赋予该图元的第一个顶点所指定的颜色。屏幕颜色
容易呈现出块状,这是因为个颜色之间没有平滑的过度。另一种更好的着色模式是D3DSHADE_GOURAUD,也
称平滑着色,在这种模式下,图元中的各像素的颜色值由各顶点的颜色经线性插值得到。
*/
Device->DrawPrimitive(D3DPT_TRIANGLELIST,0,4);
Device->EndScene();
// Swap the back and front buffers.
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,
640, 480, 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;
}
看一下,程序运行的截图:
每日总结:
1,DirectX3D支持3中类型的光源模型,方向光,点光源以及聚光灯。每种光源可以发出3种光:环境光,漫射光和镜面光。
2,表面的材质定义了光线与其到达表面的交互方式(即表面能反射何种颜色的光以及反射多少,以确定表面的颜色).
3, 顶点法线用于定义顶点的方向。DirectX3D借助顶点法向量来确定光线到达顶点时的入射角。在某些情况下,顶点法向量与由其构成的三角形单元的法向量相等,但如果要用三角形单元组逼近表示光滑的曲面(如球体,圆柱体等)时,情况就未必如此了。