Copyright © MikeFeng QQ: 76848502
D3D支持三种光:环境光(Ambient Light),漫反射(Diffuse Light)和镜面反射(Specular Light)。镜面反射需要更多计算,因此D3D默认关闭镜面反射。可以通过给SetRenderState传D3DRS_AMBIENT来设置环境光。
在现实世界中,我们看到一个红色的物体是因为其他颜色的光线被这个物体吸收了,我们看到的是这个物体反射的红色光。D3D引入材质这个概念来模拟现实。
Typpdef struct D3DMATERIAL9 {
D3DCOLORVALUE Diffuse, Ambient, Specular, Emissive;
Float Power;
} D3DMATERIAL9;
D3DCOLORVALUE是一个由r, g,b,a四个元素组成的色彩结构。
Diffuse, Ambient, Specular分别代表这三种光的反射量,而Emissive让物体使物体更亮,看上去就像本身在发光一样。Power描述镜面反射高亮的程度,值越高越亮。注意如果一个物体吸收所有颜色的光,那么它就是黑色的。可以通过SetMaterial函数来设定材质。
D3DMATERIAL9 blueMaterial;
…
Device->SetMaterial(&blueMaterial)
D3D引入顶点法线和面法线来解决光照问题。面法线描述了一个多边形的朝向,顶点法线的朝向可以自己定义。D3D必须知道顶点法线的方向以便计算它的反射光。
在定义顶点的时候可以加入顶点法线的朝向。
struct Vertex
{
float _x, _y, _z;
float _nx, _ny, _nz;
static const DWORD FVF;
}
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;
顶点颜色的信息被法线信息替代了,因为我们需要根据材质和法线信息自己计算反射光。简单的图形可以由观察得到顶点法线的信息,但是复杂的图形需要通过计算得到。一种计算方法是将顶点法线等同于面法线。对于一个三角形来说,只要叉积的方式求出面发现即可。另一种更科学的方法是法线平均法。例如三棱锥的情况,最上面那个顶点的法线Vn是由包含着个定点的三个侧面的法线V1, V2, V3平均数决定的。即Vn=(V1+V2+V3)/3。前面学过由于矩阵转换可能会导致法线不偏移的现象,我们可以通过
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
来重新归一(Renormalize)法线。
D3D中的光源有3种类型:点光源(向四周发射的光源),平行光源,锥形光源(类似于电筒光)。在锥形光源中有两个角度来描述其大小,一个描述内锥,一个描述外锥。D3D中描述光源的结构是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;
可以通过SetLight函数来设定光源。可以通过LightEnable函数来打开关闭光源。
给一个物体添加光照分为以下几步:
① 启用光照
② 为每个物体创建材质,并在渲染前为这些物体配上材质
③ 创建一个或多个光源,设置并启用它们
④ 启用附加的光照状态,例如镜面高光。
设定光源最好不要放在OnCreateDevice中,因为切换全屏/窗口或者改变窗口大小时会丢失光源信息。应放在OnResetDevice中。
Example 1
Cube Without Lighting
前面有个旋转平面的例子,现在为了要使用光照,立体的东西效果好一点。下面是一个立体方块的例子,和平面的类似,不过顶点定义中多了顶点法线。
// cube.h
#ifndef
__cubeH__
#define
__cubeH__
#include
"dxstdafx.h"
struct
Vertex
{
Vertex
(){}
Vertex
(
float
x
, float y, float z,
float
nx
, float ny, float nz,
float
u
, float v)
{
_x
=
x
; _y = y; _z = z;
_nx
=
nx
; _ny = ny; _nz = nz;
_u
=
u
; _v = v;
}
float
_x
, _y, _z;
float
_nx
, _ny, _nz;
float
_u
, _v; // texture coordinates
};
#define
FVF_VERTEX
(
D3DFVF_XYZ
|
D3DFVF_NORMAL
|
D3DFVF_TEX1
)
class
Cube
{
public
:
Cube
(IDirect3DDevice9* device);
~
Cube
();
HRESULT
OnResetDevice
();
VOID
OnLostDevice
();
bool
OnFrameMove
( double fTime ) ;
bool
OnFrameRender
( D3DMATERIAL9* mtrl, IDirect3DTexture9* tex );
private
:
HRESULT
Create
();
D3DMATERIAL9
m_Mtrl
;
IDirect3DDevice9
* m_device;
IDirect3DVertexBuffer9
* m_vb;
IDirect3DIndexBuffer9
* m_ib;
};
#endif
//__cubeH__
//
其他函数和
plain.h
中类似,因此只列出
Create
函数。其余函数请参照前面的
Plain
类
HRESULT
Cube
::Create()
{
m_device
->CreateVertexBuffer(
24 *
sizeof
(Vertex),
D3DUSAGE_WRITEONLY
,
FVF_VERTEX
,
D3DPOOL_MANAGED
,
&
m_vb
,
NULL
);
Vertex
* v;
m_vb
->Lock(0, 0, (void**)&v, 0);
// build box
// x, y, z, nx, ny, nz, tu, tv
// fill in the front face vertex data
v
[0] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
v
[1] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v
[2] = Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);
v
[3] = Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
// fill in the back face vertex data
v
[4] = Vertex(-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
v
[5] = Vertex( 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
v
[6] = Vertex( 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
v
[7] = Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
// fill in the top face vertex data
v
[8] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
v
[9] = Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
v
[10] = Vertex( 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);
v
[11] = Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
// fill in the bottom face vertex data
v
[12] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f);
v
[13] = Vertex( 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f);
v
[14] = Vertex( 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f);
v
[15] = Vertex(-1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f);
// fill in the left face vertex data
v
[16] = Vertex(-1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v
[17] = Vertex(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v
[18] = Vertex(-1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v
[19] = Vertex(-1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
// fill in the right face vertex data
v
[20] = Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v
[21] = Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v
[22] = Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v
[23] = Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
m_vb
->Unlock();
m_device
->CreateIndexBuffer(
36 *
sizeof
(WORD),
D3DUSAGE_WRITEONLY
,
D3DFMT_INDEX16
,
D3DPOOL_MANAGED
,
&
m_ib
,
0);
WORD
* i = 0;
m_ib
->Lock(0, 0, (void**)&i, 0);
// fill in the front face index data
i
[0] = 0; i[1] = 1; i[2] = 2;
i
[3] = 0; i[4] = 2; i[5] = 3;
// fill in the back face index data
i
[6] = 4; i[7] = 5; i[8] = 6;
i
[9] = 4; i[10] = 6; i[11] = 7;
// fill in the top face index data
i
[12] = 8; i[13] = 9; i[14] = 10;
i
[15] = 8; i[16] = 10; i[17] = 11;
// fill in the bottom face index data
i
[18] = 12; i[19] = 13; i[20] = 14;
i
[21] = 12; i[22] = 14; i[23] = 15;
// fill in the left face index data
i
[24] = 16; i[25] = 17; i[26] = 18;
i
[27] = 16; i[28] = 18; i[29] = 19;
// fill in the right face index data
i
[30] = 20; i[31] = 21; i[32] = 22;
i
[33] = 20; i[34] = 22; i[35] = 23;
m_ib
->Unlock();
return
S_OK
;
}
主函数和旋转平面基本相同,只是声明了一个不同的全局变量:
Cube
* g_pCube = NULL;
在未启用光照模式[
SetRenderState
( D3DRS_LIGHTING, FALSE) ]的情况下效果如图:
Example 2
Cube With Material & Lighting
Step 1
启用光照
( D3D
中默认启用
)
pd3dDevice
->SetRenderState( D3DRS_LIGHTING, TRUE );
Step 2
为每个物体创建材质,并在渲染前为这些物体配上材质
在主回调函数OnCreateDevice中定义光源
在上例Cube的构造函数添加材质代码,使其如下
Cube
::Cube(IDirect3DDevice9* device)
{
// save a ptr to the device
m_device
=
device
;
m_Mtrl
.Ambient = D3DXCOLOR(1,0,0,0);
m_Mtrl
.Diffuse = D3DXCOLOR(0,0,0,0);
m_Mtrl
.Specular = D3DXCOLOR(1,1,1,0);
m_Mtrl
.Emissive = D3DXCOLOR(0,1,0,0);
m_Mtrl.Power = 5.0f;
}
其中m_Mtrl是cube.h中定义的成员变量D3DMATERIAL9,Emissive对于材质来说很重要,因为它决定了我们看见的纹理颜色范围,这里定义的是只显示绿色。
在Cube::OnFrameRender中添加如下代码
bool
Cube
::OnFrameRender( D3DMATERIAL9* mtrl, IDirect3DTexture9* tex)
{
if
( mtrl )
m_device
->SetMaterial(mtrl);
else
if
(&
m_Mtrl
)
m_device
->SetMaterial(&m_Mtrl);
if
( tex )
m_device
->SetTexture(0, tex);
m_device
->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
m_device
->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
m_device
->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
m_device
->SetStreamSource(0, m_vb, 0, sizeof(Vertex));
m_device
->SetIndices(m_ib);
m_device
->SetFVF(FVF_VERTEX);
m_device
->DrawIndexedPrimitive(
D3DPT_TRIANGLELIST
,
0,
0,
24,
0,
12);
return
true
;
}
如果主回调函数中未给此函数设置材质,那么它将使用自己的成员变量m_Mtrl作为材质。
Step 3
创建一个或多个光源,设置并启用它们
在主回调函数OnCreateDevice中添加如下代码
D3DLIGHT9
dir
;
::
ZeroMemory
(&dir, sizeof(dir));
dir
.Type = D3DLIGHT_DIRECTIONAL;
dir
.Diffuse = D3DXCOLOR(1,1,0,1
);
dir
.Specular = D3DXCOLOR(0,0,1
1
);
dir
.Ambient = D3DXCOLOR(1,0,0,1);
dir
.Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
pd3dDevice
->SetLight(0, &dir);
pd3dDevice
->LightEnable(0, true);
上面的参数只允许光源产生蓝色镜面反射光,以及红色+绿色环境光。该光源是平行光源。
在主回调函数OnFrameRender中添加如下代码,以重新归一法线和启用镜面反射
pd3dDevice
->SetRenderState(D3DRS_NORMALIZENORMALS, true);
pd3dDevice
->SetRenderState(D3DRS_SPECULARENABLE, true);
注意不要忘记在适当时间禁用镜面反射,以提高效率。
pd3dDevice
->SetRenderState(D3DRS_SPECULARENABLE, false);
Step 4
启用附加的光照状态
(未设置)
蓝色部分为镜面反射,绿色部分为材质本身反射。因为光源由x正方向照射过来,因此镜面反射只有在右侧才有效果。最终效果如下