您的位置:首页 > 其它

DirectX学习笔记(六):Direct3D中模拟实现光照下的物体绘制

2016-09-15 15:17 489 查看

前言:

一般来讲,光照的使用有助于增强场景的真实感,也有助于描述实体的形状和立体感。在Direct3D中,Direct3D会将顶点送入光照计算引擎,依据光源类型,材质,以及物体表面想对与光源的朝向,计算出每个顶点的颜色值。这样,我们就无需自行指定顶点的颜色值。

1.光照的组成:

光照分为三种:

1.环境光:这种类型的光经过其他表面反射到达物体的表面,并且照亮整个场景。

2.漫射光:这种类型光沿着特定的方向传播。当它到达某一表面的时候,将沿着各个方向均匀反射。

3.镜面光:这种类型的光沿着特定的方向传播。当此类的光到达一个表面时,将严格沿着另一个方向传播,从而形成只能在一定角度范围内才能观察到的高亮度照射。

光照的存储

光照的存储可以使用D3DCOLOR和D3DCOLORVALUE结构来表示(但是一般都采用D3DCOLOR结构使用,而且,描述光照的颜色时,D3DXCOLOR结构中的Alpha值都会被忽略。)

如:红色:

D3DXCOLOR red(1.0f, 0.0f,0.0f,1.0f);


2.材质:

材质在光照中的用处:

我们所观察到的物体的颜色是由该物体所反射的光的颜色决定的,例如:

一个红色的球体反射了全部的红色入射光,并吸收了所有的非红色入射光,所以该物体呈现红色。

或者说:如果一个只能发出蓝色光的光源在照射一个红色物体时,则由于蓝色光被吸收,而反射的红色光为零,所以该物体无法被照亮。当一个物体吸收了所有的入射光时,那么久会呈现黑色,如果y一个物体能够完全的反射红色,蓝色,绿色光,那么久会呈现白色。

我们利用D3DMATERIAL9来存储材质数据:

typedef struct _D3DMATERIAL8 {
D3DCOLORVALUE   Diffuse; //材质对漫射光的反射率
D3DCOLORVALUE   Ambient;//材质对环境光的反射率
D3DCOLORVALUE   Specular;//材质对镜面光的反射率
D3DCOLORVALUE   Emissive;用于增强物体的亮度,使其看起来好像自发光
float           Power;//指定镜面高光的锐度,越大则高光点的锐度越大
} D3DMATERIAL8;


如何使用呢?

如我们想让一个球体只反射红色光:

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, 1.0f);//没有高亮
red.Power = 5.0f;


3.顶点法线:

在使用光照所绘制的3D场景中,计算物体顶点的颜色值除了需要光源和物体的材质信息外,还需要知道每个顶点的法向量,以便于根据光线的入射方向与法向量的夹角计算发射光线的最终颜色值。

 

在这里提出了顶点法线和面法线这两个概念,我们首先需要清楚的理解并区分这两个概念。

 

面法线很容易理解,即垂直于三角形面的一条法线。那顶点法线又从何而来呢,严格的从法线的定义上来说,其实顶点是不存在法线的,那为何又有顶点法线这个概念的呢?让顶点也拥有法线,是为了在光照计算时,能够知晓光线到达表面时的入射角,以在多面体的表面获得一种平滑的效果。

面法线:



顶点法线:



在一般情况下,顶点法线与面法线的方向是相同的。但是在某些特殊的情况下,顶点法线并不与面法线相同。比如一个近似的球体或圆的顶点法线和面法线就不一致:



对于简单的物体而言,比如立方体和球体,我们完全可以通过观察来得到这些顶点的法向量。然而,对于不规则或者复杂的物体,则需要另寻高招。

对于复杂的物体,我们可以认为每个顶点的法向量与该顶点构成的三角形面的法向量相同。

假设一个三角形由顶点p0,p1,p2这三个顶点构成的,现在我们要计算每个顶点的法向量的话,就是求这个三角形面的法向量罢了。

求法如图:



也就是首先计算位于三角形平面内代表两条边的向量,然后对这两条向量做叉乘运算就可以了。

当然,当我们使用一组三角形渐进来表示曲面时,使用上述方法计算出的顶点法向量将会产生不光滑的效果。因此,另一种计算顶点法向量的方式应运而生——计算法向量的均值(normal averaging):首先我们求出共享该顶点的3个三角形的面法向量,然后取他们的平均值作为该顶点的顶点法向量,如图:

也就是说np = (n1 + n2 + n3) / 3

4.光源:

光源的结构可以用D3DLIGHT9来表示:

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;


Direct3D支持三种光源:

1.点光源:

该光源在世界坐标系中存在确切的位置,并向所有方向发射光线:
代码实现:
D3DLIGHT9 light;
::ZeroMemory(&light, sizeof(light));
light.Type = D3DLIGHT_POINT;//点光源
light.Ambient = D3DXCOLOR(0.8f, 0.8f, 0.8f, 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(0.0f, 200.0f, 0.0f);
light.Attenuation0 = 1.0f;
light.Attenuation1 = 0.0f;
light.Attenuation2 = 0.0f;
light.Range = 300.0f;
pd3dDevice->SetLight(0, &light); //设置光源
pd3dDevice->LightEnable(0, true); //启用光照


2.方向光:

该光源没有位置,所发射的光线相互平行的沿着某一特定方向传播

代码实现:

D3DLIGHT9 light;
::ZeroMemory(&light, sizeof(light));
light.Type = D3DLIGHT_DIRECTIONAL;//方向光源
light.Ambient = D3DXCOLOR(0.5f, 0.5f, 0.5f, 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.Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
pd3dDevice->SetLight(0, &light); //设置光源
pd3dDevice->LightEnable(0, true); //启用光照

3.聚光灯:

类型与手电筒类似:该光源有位置,其发射的光线呈现锥形沿着特定防线传播。

代码实现:

D3DLIGHT9 light;
::ZeroMemory(&light,sizeof(light));
light.Type          = D3DLIGHT_SPOT;//聚光灯光源
light.Position      = D3DXVECTOR3(100.0f, 100.0f, 100.0f);
light.Direction     = D3DXVECTOR3(-1.0f, -1.0f, -1.0f);
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, 0.3f);
light.Attenuation0  = 1.0f;
light.Attenuation1  = 0.0f;
light.Attenuation2  = 0.0f;
light.Range         = 300.0f;
light.Falloff       = 0.1f;
light.Phi           = D3DX_PI / 3.0f;
light.Theta         = D3DX_PI / 6.0f;
pd3dDevice->SetLight(0,&light); //设置光源
pd3dDevice->LightEnable(0,true); //启用光照


5.完整代码:(可运行)

#include<d3d9.h>
#include<d3dx9math.h>
#include<windows.h>

IDirect3DDevice9* Device = 0; // 一个C++对象,代表了我们用来显示3D图形的物理硬件设备
IDirect3DVertexBuffer9* Pyramid = 0;//顶点缓存

const int Width = 640; //窗口的宽度
const int Height = 480; //高度

//所要用到的颜色:
const D3DXCOLOR      WHITE_LSZ(D3DCOLOR_XRGB(255, 255, 255));
const D3DXCOLOR      BLACK_LSZ(D3DCOLOR_XRGB(0, 0, 0));
const D3DXCOLOR        RED_LSZ(D3DCOLOR_XRGB(255, 0, 0));
const D3DXCOLOR      GREEN_LSZ(D3DCOLOR_XRGB(0, 255, 0));
const D3DXCOLOR       BLUE_LSZ(D3DCOLOR_XRGB(0, 0, 255));
const D3DXCOLOR     YELLOW_LSZ(D3DCOLOR_XRGB(255, 255, 0));
const D3DXCOLOR       CYAN_LSZ(D3DCOLOR_XRGB(0, 255, 255));
const D3DXCOLOR    MAGENTA_LSZ(D3DCOLOR_XRGB(255, 0, 255));

//-----------------------------顶点结构体----------------------------------
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,_nx,_ny,_nz;
static const DWORD FVF;
};
//顶点格式:说明对应于顶点格式的顶点结构包含了位置属性和漫反射颜色属性
//要注意的是,灵活顶点格式标记的指定顺序必须与顶点结构中相应类型数据的顺序保持一致。
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;

//------------------------------以下为窗口过程---------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)//窗口过程
{
switch (msg)
{
case WM_DESTROY://销毁
PostQuitMessage(0); //终止请求
break;
}
//调用缺省的窗口过程来为应用程序没有处理的任何窗口消息提供缺省的处理。
//该函数确保每一个消息得到处理
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}

//----------------------------以下为初始化窗口信息---------------------------------
bool InitWindow(HINSTANCE hInstance, HWND &hwnd, int width, int height)
{
//定义窗口样式
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = 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 = "LszDX";

//窗口注册
RegisterClass(&wc);

//创建窗口
hwnd = ::CreateWindow("LszDX", "LszDX", WS_OVERLAPPEDWINDOW, 0, 0, width, height, 0, 0, hInstance, 0);

//绘制更新窗口
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
return true;
}

//----------------------------以下为初始化Direct3D----------------------------------------
//注意函数的hwnd为传引用
bool InitD3D(HINSTANCE &hInstance, HWND &hwnd, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9** device)
{
//获取IDirect3D9的指针
IDirect3D9* d3d9 = 0;
d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

//检验硬件顶点运算
D3DCAPS9 caps;
d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps);

int vp = 0;
if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

//填充D3DPRESENT_PARAMETERS 结构
D3DPRESENT_PARAMETERS d3dpp;
d3dpp.BackBufferWidth = width;
d3dpp.BackBufferHeight = height;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = windowed;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

//创建IDirect3DDevice9接口
d3d9->CreateDevice(D3DADAPTER_DEFAULT, deviceType, hwnd, vp, &d3dpp, device);
d3d9->Release();
return true;
}

//投影变换
//设置绘制状态
bool InitTr()
{
Device->SetRenderState(D3DRS_LIGHTING, true);

//创建顶点缓存
Device->CreateVertexBuffer(12 * sizeof(Vertex), D3DUSAGE_WRITEONLY, Vertex::FVF, D3DPOOL_MANAGED, &Pyramid, 0);

//访问顶点缓存,将顶点数据写入
Vertex* vertices;
Pyramid->Lock(0, 0, (void**)&vertices, 0);
// front face
vertices[0] = Vertex(-1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);
vertices[1] = Vertex(0.0f, 1.0f, 0.0f, 0.0f, 0.707f, -0.707f);
vertices[2] = Vertex(1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);

// left face
vertices[3] = Vertex(-1.0f, 0.0f, 1.0f, -0.707f, 0.707f, 0.0f);
vertices[4] = Vertex(0.0f, 1.0f, 0.0f, -0.707f, 0.707f, 0.0f);
vertices[5] = Vertex(-1.0f, 0.0f, -1.0f, -0.707f, 0.707f, 0.0f);

// right face
vertices[6] = Vertex(1.0f, 0.0f, -1.0f, 0.707f, 0.707f, 0.0f);
vertices[7] = Vertex(0.0f, 1.0f, 0.0f, 0.707f, 0.707f, 0.0f);
vertices[8] = Vertex(1.0f, 0.0f, 1.0f, 0.707f, 0.707f, 0.0f);

// back face
vertices[9] = Vertex(1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f);
vertices[10] = Vertex(0.0f, 1.0f, 0.0f, 0.0f, 0.707f, 0.707f);
vertices[11] = Vertex(-1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f);

Pyramid->Unlock();

//创建物体的材质:
D3DMATERIAL9 matrl;
matrl.Ambient = WHITE_LSZ;
matrl.Diffuse = WHITE_LSZ;
matrl.Specular = WHITE_LSZ;
matrl.Emissive = BLUE_LSZ;
matrl.Power = 5.0f;

Device->SetMaterial(&matrl);

//创建一个方向光:
D3DLIGHT9 dir;
::ZeroMemory(&dir, sizeof(dir));
dir.Type = D3DLIGHT_DIRECTIONAL;
dir.Diffuse = WHITE_LSZ;
dir.Specular = WHITE_LSZ *0.3f;
dir.Ambient = WHITE_LSZ * 0.6f;
dir.Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f);

//设置和使用灯光:
Device->SetLight(0, &dir);
Device->LightEnable(0, 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);

//投影变换
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI * 0.5f, (float)Width / (float)Height, 1.0f, 1000.0f);
Device->SetTransform(D3DTS_PROJECTION, &proj);

//设置绘制状态
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
Device->SetRenderState(D3DRS_SPECULARENABLE, true);

return true;
}

//-----------------------以下为对Cube进行绘制-------------------------------------
bool Display()
{
MSG msg;
ZeroMemory(&msg, sizeof(MSG));//用0来填充消息可类比为:memset()函数
while (msg.message != WM_QUIT) //退出
{
//PeekMessage函数是以查看的方式从系统中获取消息
//并将该消息(如果存在)放于指定的结构
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
//PM_REMOVE:PeekMessage处理后,消息从队列里除掉。

//TranslateMessage函数将虚拟键消息转换为字符消息。
//字符消息被寄送到调用线程的消息队列里,
//当下一次线程调用函数GetMessage或PeekMessage时被读出。
//TranslateMessage只能用于转换调用GetMessage或PeekMessage接收的消息。

TranslateMessage(&msg);

//DispatchMessage函数
//该函数分发一个消息给窗口程序。
//通常消息从GetMessage函数获得。
//消息被分发到回调函数(过程函数),作用是消息传递给操作系统,
//然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息
DispatchMessage(&msg);
}
else
{
if (Device)
{
static float y = 0.0f;
//旋转:
D3DXMATRIX Ry;
D3DXMatrixRotationY(&Ry, y);
y = y + 0.0001f;

Device->SetTransform(D3DTS_WORLD, &Ry);

//绘制场景:

Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0);
Device->BeginScene();
Device->SetStreamSource(0, Pyramid, 0, sizeof(Vertex));
Device->SetFVF(Vertex::FVF);
Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);
Device->EndScene();
Device->Present(0, 0, 0, 0);
}
}
}
return true;
}

//-------------------------Main函数-----------------------------------------------
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd)
{
HWND hwnd = 0;

//1.窗口创建
InitWindow(hinstance, hwnd, Width, Height);

//2.初始化Direct3D
InitD3D(hinstance, hwnd, Width, Height, true, D3DDEVTYPE_HAL, &Device);

//3.初始化cube信息
InitTr();

//4.进行显示绘制
Display();

//5.释放指针
Device->Release();
return 0;
}




6.运行效果:(可自行旋转)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  DirectX