在 Direct3D 的光照模型中,光源发出的光由以下 3 种分量或者 3 种类型的光组成:
(1) 环境光(Ambient Light):光经其他表面反射到达物体表面,并照亮整个场景。个人理解:白天阴天的时候为什么还能看见物体?环境光!
(2) 漫射光(Diffuse Light):光沿着特定的方向传播。当它到达某一表面时,将沿着各个方向均匀反射。无论从那个方向来看,表面亮度均相同,所以采用该模型时,无须考虑观察者的位置。
(3) 镜面光(Specular Light):光沿特定方向传播。当此类光到达一个表面时,将严格地沿着另一个方向反射,从而形成只能在一定角度范围内才能观察到的高亮度照射。相对前两种光照类型,镜面光的计算量要大的多,因此默认情况下 Direct3D 不进行镜面反射计算;如果想启动镜面光,必须将绘制状态 D3DRS_SPECULARENABLE 设为 true 。
每种类型的光都可用结构 D3DCOLORVALUE 或 D3DXCOLOR 来表示,这些类型描述了光线的颜色( Alpha 值被忽略)。
在现实世界中,我们所看到的物体颜色是由该物体所反射的光的颜色决定的(物体对光进行的部分吸收)。例如:我的显示器边缘是黑色的,这是由于它吸收了所有非黑色的光,所以它显示为黑色。
Direct3D 通过定义物体的材质(materials)来模拟同样的现象。允许我们定义物体表面对各种颜色光的反射比例。在代码中使用结构 D3DMATERIAL9 来表示。Direct3D 使用 SetMaterial 来设定当前材质。
当物体能收吸收所有的光时,它将显示为黑色;当物体能反射所有的光时,它将显示为白色。
面法线是一个描述多边形朝向的向量。顶点法线描述构成多边形各个顶点的法线。 Direct3D 需要知道顶点的法线方向,以确定光线到达表示时的入射角。
Direct3D 支持三种类型的光源:
(1) 点光源(Point lights),在世界坐标系位置固定,并且向所有的方向发射光线。
(2) 方向光(Directional lights),没有位置信息,发射的光线平行地沿着某一特定的方向传播。
(3) 聚光灯(Spot lights),和手电筒类似,有位置信息,其发射的光线成锥形,沿着特定的方向传播。
在程序代码中,光源用结构 D3DLIGHT9 来表示。
(1) 将光照和颜色整合到框架中,将 DrawColorTriangle 中的GlobalColorConstant.h 添加到框架中。
(2) 新建文件 GlobalMtrlConstant.h ,保存常用材质的常量。
#ifndef __GLOBALMTRLCONSTANCE_H__
#define __GLOBALMTRLCONSTANCE_H__
D3DMATERIAL9 InitMtrl(D3DXCOLOR ambient, D3DXCOLOR diffuse, D3XCOLOR specular,
D3DXCOLOR emissive, float power)
{
D3DMATRIAL9 mtrl;
mtrl.Ambient = ambient;
mtrl.Diffuse = diffuse;
mtrl.Specular = specular;
mtrl.Emissive = emissive;
mtrl.Power = p;
return mtrl;
}
const D3DMATERIAL9 WHITE_MTRL = InitMtrl(WHITE, WHITE, WHITE, BLACK, 8.0f);
const D3DMATERIAL9 RED_MTRL = InitMtrl(RED, RED, RED, BLACK, 8.0f);
const D3DMATERIAL9 GREEN_MTRL = InitMtrl(GREEN, GREEN, GREEN, BLACK, 8.0f);
const D3DMATERIAL9 BLUE_MTRL = InitMtrl(BLUE, BLUE, BLUE, BLACK, 8.0f)
const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 8.0f);
#endif /* __GLOBALMTRLCONSTANCE_H__ */
(3) 新建工程 Lighting ,将框架代码拷贝到新工程中(以下在 Lighting 上操作)。
(4) 新建文件 NormalVertex.h,添加法线顶点的定义。
#ifndef __NORMALVERTEX_H__
#define __NORMALVERTEX_H__
struct NormalVertex
{
public:
NormalVertex()
{
}
NormalVertex(float x, float y, float z, float nx, float ny, float nz)
{
m_x = x; m_y = y; m_z = z;
m_nx = nx; m_ny = ny; m_nz = nz;
}
public:
float m_x, m_y, m_z;
float m_nx, m_ny, m_nz;
static const DWORD FVF;
};
const DWORD NormalVertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;
#endif /* __NORMALVERTEX_H__ */
(5) 向 Direct3DFrame 类中添加成员变量,保存金字塔的定点数据。
IDirect3DVertexBuffer9 * m_pyramid; // 金字塔顶点缓存
(6) 在 Setup 中添加场景光照和其他操作
bool Direct3DFrame::Setup()
{
// 启用光照
m_device->SetRenderState(D3DRS_LIGHTING, true);
// 创建顶点缓存,并指定构成金字塔的三角形单元的顶点,顶点法线预先计算好
m_device->CreateVertexBuffer(
12 * sizeof(NormalVertex),
D3DUSAGE_WRITEONLY,
NormalVertex::FVF,
D3DPOOL_MANAGED,
&m_pyramid,
0);
NormalVertex* nv;
m_pyramid->Lock(0, 0, (void**)&nv, 0);
// font face
nv[0] = NormalVertex(-1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);
nv[1] = NormalVertex( 0.0f, 1.0f, 0.0f, 0.0f, 0.707f, -0.707f);
nv[2] = NormalVertex( 1.0f, 0.0f, -1.0f, 0.0f, 0.707f, -0.707f);
// left face
nv[3] = NormalVertex(-1.0f, 0.0f, 1.0f, -0.707f, 0.707f, 0.0f);
nv[4] = NormalVertex( 0.0f, 1.0f, 0.0f, -0.707f, 0.707f, 0.0f);
nv[5] = NormalVertex(-1.0f, 0.0f, -1.0f, -0.707f, 0.707f, 0.0f);
// right face
nv[6] = NormalVertex( 1.0f, 0.0f, -1.0f, 0.707f, 0.707f, 0.0f);
nv[7] = NormalVertex( 0.0f, 1.0f, 0.0f, 0.707f, 0.707f, 0.0f);
nv[8] = NormalVertex( 1.0f, 0.0f, 1.0f, 0.707f, 0.707f, 0.0f);
// back face
nv[9] = NormalVertex( 1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f);
nv[10] = NormalVertex( 0.0f, 1.0f, 0.0f, 0.0f, 0.707f, 0.707f);
nv[11] = NormalVertex(-1.0f, 0.0f, 1.0f, 0.0f, 0.707f, 0.707f);
m_pyramid->Unlock();
// 设置物体材质
D3DMATERIAL9 mtrl;
mtrl.Ambient = WHITE;
mtrl.Diffuse = WHITE;
mtrl.Specular = WHITE;
mtrl.Emissive = BLACK;
mtrl.Power = 0.5f;
m_device->SetMaterial(&mtrl);
// 创建并启用光源
D3DLIGHT9 dir;
::ZeroMemory(&dir, sizeof(dir));
dir.Type = D3DLIGHT_DIRECTIONAL;
dir.Diffuse = WHITE;
dir.Specular = WHITE * 0.3f;
dir.Ambient = WHITE * 0.6f;
dir.Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
m_device->SetLight(0, &dir);
m_device->LightEnable(0, true);
// 重新规范化法向量,启用镜面光
m_device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
m_device->SetRenderState(D3DRS_SPECULARENABLE, true);
D3DXVECTOR3 position(0.0f, 0.0f, -2.5f);
D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMATRIX v;
D3DXMatrixLookAtLH(&v, &position, &target, &up);
m_device->SetTransform(D3DTS_VIEW, &v);
// Set the projection matrix.
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(
&proj,
D3DX_PI * 0.5f, // 90 - degree
(float)m_width / (float)m_height,
1.0f,
1000.0f);
m_device->SetTransform(D3DTS_PROJECTION, &proj);
return true;
}
(7) 在 Display 添加代码,使金字塔显示和旋转起来
bool Direct3DFrame::Display(float timeDelta)
{
if (m_device) {
D3DXMATRIX yRot;
D3DXMatrixRotationY(&yRot, m_yRotAngle);
m_yRotAngle += timeDelta;
if( m_yRotAngle >= 6.28f ) {
m_yRotAngle = 0.0f;
}
m_device->SetTransform(D3DTS_WORLD, &yRot);
m_device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0);
m_device->BeginScene();
m_device->SetStreamSource(0, m_pyramid, 0, sizeof(NormalVertex));
m_device->SetFVF(NormalVertex::FVF);
m_device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 4);
m_device->EndScene();
m_device->Present(0, 0, 0, 0);
}
return true;
}
(8) 在 Cleanup 中释放资源(在以后的博客中不再提示)。
运行后效果图:
/*******************************************************/
** 本文由 独酌逸醉 原创,转载请注明博客链接,谢谢!
** 小弟初学 Direct3D,文章中如果存在错误,烦请指证,不胜感激!
** 参考书籍:《DirectX 9.0 3D 游戏开发编程基础》
** 时间:2011.11.23
/*******************************************************/