Direct3D中的光照
程序员文章站
2022-07-13 10:15:27
...
一、概述
为了增强所绘制场景的真实感,我们可以为场景增加光照。光照也有助于描述实体形状和立体感。使用光照时,我们无需自行指定顶点的颜色值:Direct3D会将顶点送入光照计算引擎,依据光源类型、材质以及物体表面相对于光源的朝向,计算出每个顶点的颜色值。基于某种光照模型计算出各顶点的颜色,会使绘制结果更加逼真。
二、光照的组成
在Direct3D的光照模型中,光源发出的光由以下3个分量或3种类型的光组成。
●环境光(Ambient Light)这种类型的光经其他表面反射到达物体表面,并照亮整个场景。物体之所以会被照亮,是由于其他物体对光的反射。要想以较低代价粗略地模拟这类反射光,环境光是一个很好的选择。
●漫射光(Diffuse Light)这种类型的光沿着特定的方向传播。当它到达某一表面时,将沿着各个方向均匀反射。由于漫射光沿所有点的方向均匀反射,无论从哪个方位观察,表面亮度均相同,所以采用该模型时,无需考虑观察者的位置。
●镜面光(Specular Light)这种类型的光沿特定的方向传播。当此类光到达一个表面时,将严格地沿着另一个方向反射,从而形成只能在一定角度范围内才能观察到的高亮度照射。镜面光与其他类型的光相比,计算量要大很多。默认状态下,Direct3D不进行镜面反射计算:如果想启用镜面光,必须将绘制状态D3DRS_SPECULARENABLE设为TRUE。
每种类型的光都可用结构D3DCOLORVALUE或D3DXCOLOR来表示,这些类型描述了光线的颜色。描述光线颜色时,D3DXCOLOR类中的Alpha值都将被忽略。
三、材质
在显示世界中,我们所观察到的物体的颜色是由该物体所反射的光的颜色决定的。例如一个纯红色的球体反射了全部的红色入射光,并吸收了所有非红色的光,所以该球体呈现为红色。Direct3D通过定义物体的材质来模拟同样的现象。材质允许我们定义物体表面对各种颜色光的反射比例。
材质的定义如下:
typedef struct D3DMATERIAL9{
D3DCOLORVALUE Diffuse;
D3DCOLORVALUE Ambient;
D3DCOLORVALUE Specular;
D3DCOLORVALUE Emissive;
float Power;
}D3DMATERIAL9, *LPD3DMATERIAL9;
●Diffuse 指定材质对漫射光的反射率。
●Ambient 指定材质对环境光的反射率。
●Specular 指定材质对镜面光的反射率。
●Emissive 该分量用于增强物体的亮度,使之看起来好像可以自己发光。
●Power 指定镜面高光点的锐度,该值越大,高光点的锐度越大。
顶点结构中不含有材质属性,但我们必须对当前材质进行设定。
如下函数用来对当前材质进行设定:
IDirect3DDevice9::SetMaterial(CONST D3DMATERIAL9* pMaterial)。
要绘制几个具有不同材质的物体,我们可以这样书写代码:
D3DMATERIAL9 blueMaterial, redMaterial;
Device->SetMaterial(&blueMaterial);
drawSphere();
Device->SetMaterial(&redMaterial);
drawSphere();
四、顶点法线
面法线是一个描述多边形朝向的向量。
顶点法线正是基于上述思路而产生的,但它并不用于指定每个多边形的法向量。顶点法线描述的是构成多边形的各个顶点的法线。
面法线和顶点法线如下图所示:
Direct3D需要知道顶点的法线方向,以确定光线到达表面时的入射角。而且,由于光照计算是对每个顶点进行的,所以Direct3D需要知道表面在每个顶点处的局部朝向(法线方向)。顶点法线与表面法线不一定相同。如下图所示:
对于网格面,计算顶点法线最简单的做法就是求出三角形的面法向量,并将该面法向量作为各定点的法向量。当用三角形单元逼近表示曲面时,将面法向量作为构成该面片的顶点法线向量不可能产生很平滑的效果。一种更好的求取顶点法向量的方法是计算法向量均值。为求出顶点 V 的顶点法向量,我们需要求出共享顶点 V 的所有三角形的面法向量,顶点 V 的法向量可由这些面法向量取均值得到。
五、光源
Direct3D支持3种光源:
●点光源(Point lights)该光源在世界坐标系中有固定的位置,并向所有的方向发射光线。
●方向光(Directional lights)该光源没有位置信息,所发射的光线互相平行地沿某一特定方向传播。
●聚光灯(Spot lights)这种类型的光源与手电筒类似:该光源有位置信息,其发射的光线呈锥形沿着特定方向传播。该锥形可用两个角度:θ 和 φ 来描述。θ 描述了内部锥形,φ 描述了外部锥形。
代码中光源的定义如下:
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;
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;
●Type定义了所要创建的光源类型。该参数可取一下3种枚举值:D3DLIGHT_POINT、D3DLIGHT_SPOT、D3DLIGHT_DIRECTIONAL。
●Diffuse 该光源所发出的漫射光的颜色。
●Ambient该光源所发出的环境光的颜色。
●Specular 该光源所发出的镜面光的颜色。
●Position 用于描述光源在世界坐标系中位置的向量。对于方向光,该参数无意义。
●Direction 一个描述光在世界坐标系中传播方向的向量,使用时应将其单位化。对于点光源,该参数无意义。
●Range 光线“消亡”前,所能达到的最大光程。对于方向光,该参数无意义。
●Falloff 该值仅用于聚光灯。该参数定义了光强从内锥形到外锥形的衰减方式,该参数一般取为1。
●Attenuation0、Attenuation1、Attenuation2这些衰减变量定义了光强随距离衰减的方式。这些变量仅用于电光源和聚光灯。Attenuation0、Attenuation1、Attenuation2分别表示光的常量、线性、2次距离衰减系数。
●Theta 仅用于聚光灯。指定了内部锥形的圆锥角,单位为弧度。
●Phi 仅用于聚光灯。指定了外部锥形的圆锥角,单位为弧度。
当D3DLIGHT9实例初始化完毕后,需要在Direct3D所维护的一个光源内部列表中对所要使用的光源进行注册:
Device->SetLight(0, &light);
一旦光源注册成功,我们就可以对其开关状态进行控制:
Device->LightEnable(0, true);
默认状态下,光照是已经启用了的。
注意光照计算模型(简称光照模型)和光源类型的区别。光照模型说明图形工作系统以什么样的方法计算灯光照射在物体上的颜色值,它只是一种计算方法,而不是具体的灯光。光源则定义了三维场景中具体的灯光,包括位置、方向、强度等信息。相同的光源可以根据物体表面材质的不同,通过不同的光照模型显示。三种光源,每种光源都可以发出三种光。
六、示例代码
以下代码绘制了一个黄色的金字塔。
#include <Windows.h>
#include <mmsystem.h>
#include <d3dx9.h>
#pragma warning( disable : 4996 )
#include <strsafe.h>
#pragma warning( default : 4996 )
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
struct CUSTOMVERTEX
{
CUSTOMVERTEX(){}
CUSTOMVERTEX(float ax, float ay, float az, float anx, float any, float anz)
{
x = ax, y = ay, z = az, nx = anx, ny = any, nz = anz;
}
FLOAT x, y, z;
FLOAT nx, ny, nz;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL)
HRESULT InitD3D( HWND hWnd )
{
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.EnableAutoDepthStencil = FALSE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
return S_OK;
}
HRESULT InitGeometry()
{
if( FAILED( g_pd3dDevice->CreateVertexBuffer( 12 * sizeof( CUSTOMVERTEX ),
0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL ) ) )
{
return E_FAIL;
}
CUSTOMVERTEX* pVertices;
if( FAILED( g_pVB->Lock( 0, 0, ( void** )&pVertices, 0 ) ) )
return E_FAIL;
pVertices[0] = CUSTOMVERTEX(0.0f, 1.0f, 0.0f, -0.57735f, -0.57735f, -0.57735f);
pVertices[1] = CUSTOMVERTEX( 1.0f, 0.0f, 0.0f, -0.57735f, -0.57735f, -0.57735f);
pVertices[2] = CUSTOMVERTEX( 0.0f, 0.0f, 1.0f, -0.57735f, -0.57735f, -0.57735f);
pVertices[3] = CUSTOMVERTEX(0.0f, 1.0f, 0.0f, 0.57735f, -0.57735f, -0.57735f);
pVertices[4] = CUSTOMVERTEX( 0.0f, 0.0f, 1.0f, 0.57735f, -0.57735f, 0.57735f);
pVertices[5] = CUSTOMVERTEX(-1.0f, 0.0f, 0.0f, 0.57735f, -0.57735f, -0.57735f);
pVertices[6] = CUSTOMVERTEX(0.0f, 1.0f, 0.0f, 0.57735f, -0.57735f, 0.57735f);
pVertices[7] = CUSTOMVERTEX(-1.0f, 0.0f, 0.0f, 0.57735f, -0.57735f, 0.57735f);
pVertices[8] = CUSTOMVERTEX(0.0f, 0.0f, -1.0f, 0.57735f, -0.57735f, 0.57735f);
pVertices[9] = CUSTOMVERTEX(0.0f, 1.0f, 0.0f, -0.57735f, -0.57735f, 0.57735f);
pVertices[10] = CUSTOMVERTEX( 0.0f, 0.0f, -1.0f, -0.57735f, -0.57735f, 0.57735f);
pVertices[11] = CUSTOMVERTEX(1.0f, 0.0f, 0.0f, -0.57735f, -0.57735f, 0.57735f);
g_pVB->Unlock();
return S_OK;
}
VOID Cleanup()
{
if( g_pVB != NULL )
g_pVB->Release();
if( g_pd3dDevice != NULL )
g_pd3dDevice->Release();
if( g_pD3D != NULL )
g_pD3D->Release();
}
VOID SetupMatrices()
{
D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
D3DXMATRIXA16 matView;
D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
D3DXMATRIXA16 matProj;
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI / 4, 1.0f, 1.0f, 100.0f );
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
}
VOID SetupLights()
{
D3DMATERIAL9 mtrl;
ZeroMemory( &mtrl, sizeof( D3DMATERIAL9 ) );
mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
mtrl.Diffuse.b = mtrl.Ambient.b = 0.0f;
mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
g_pd3dDevice->SetMaterial( &mtrl );
D3DXVECTOR3 vecDir;
D3DLIGHT9 light;
ZeroMemory( &light, sizeof( D3DLIGHT9 ) );
light.Type = D3DLIGHT_DIRECTIONAL;
light.Diffuse.r = 1.0f;
light.Diffuse.g = 1.0f;
light.Diffuse.b = 1.0f;
vecDir = D3DXVECTOR3( 1, 0, 0 );
D3DXVec3Normalize( ( D3DXVECTOR3* )&light.Direction, &vecDir );
g_pd3dDevice->SetLight( 0, &light );
g_pd3dDevice->LightEnable( 0, TRUE );
g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
}
VOID Render()
{
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET,
D3DCOLOR_XRGB( 0, 0, 255 ), 1.0f, 0 );
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
SetupLights();
SetupMatrices();
g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) );
g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );
g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 4 );
g_pd3dDevice->EndScene();
}
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
{
WNDCLASSEX wc =
{
sizeof( WNDCLASSEX ), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle( NULL ), NULL, NULL, NULL, NULL,
L"D3D Tutorial", NULL
};
RegisterClassEx( &wc );
HWND hWnd = CreateWindow( L"D3D Tutorial", L"D3D Tutorial 04: Lights",
WS_OVERLAPPEDWINDOW, 100, 100, 600, 600,
NULL, NULL, wc.hInstance, NULL );
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
if( SUCCEEDED( InitGeometry() ) )
{
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );
MSG msg;
ZeroMemory( &msg, sizeof( msg ) );
while( msg.message != WM_QUIT )
{
if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
Render();
}
}
}
UnregisterClass( L"D3D Tutorial", wc.hInstance );
return 0;
}
上一篇: Direct3D融合技术