终于开始了3D里面最激动人的一刻,前面,我们一直在自己用程序定义顶点,然后在上色,或者再贴纹理,总是那么点东西,很不好看,也许大家会有这样的疑问,如果老是那么去画,复杂的模型我们怎么能画的出来?比如一个房子,一个椅子。的确,这样的模型用程序区画出来是很难的,这次我们就来解决这个问题,用的是模型,就是在3DMAX等一些3D建模工具里面建好的模型,我们导入到我们的程序里面来显示,这下我们的世界可就丰富多彩喽。先来看看代码吧:
//=============================================================================
// Desc: 文件网格模型的使用
//=============================================================================
#include <d3dx9.h>
//-----------------------------------------------------------------------------
// Desc: 全局变量
//-----------------------------------------------------------------------------
LPDIRECT3D9 g_pD3D = NULL; //Direct3D对象
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D设备对象
LPD3DXMESH g_pMesh = NULL; //网格模型对象
D3DMATERIAL9* g_pMeshMaterials = NULL; //网格模型材质
LPDIRECT3DTEXTURE9* g_pMeshTextures = NULL; //网格模型纹理
DWORD g_dwNumMaterials = 0L; //网格模型材质数量
//-----------------------------------------------------------------------------
// Desc: 设置世界矩阵
//-----------------------------------------------------------------------------
VOID SetWorldMatrix()
{
//创建并设置世界矩阵
D3DXMATRIXA16 matWorld;
D3DXMatrixRotationY( &matWorld, timeGetTime()/1000.0f );
g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
}
//-----------------------------------------------------------------------------
// Desc: 设置观察矩阵和投影矩阵
//-----------------------------------------------------------------------------
VOID SetViewAndProjMatrix()
{
//创建并设置观察矩阵
D3DXVECTOR3 vEyePt( 0.0f, 10.0f,-20.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 );
}
//-----------------------------------------------------------------------------
// Desc: 初始化Direct3D
//-----------------------------------------------------------------------------
HRESULT InitD3D( HWND hWnd )
{
//创建Direct3D对象, 该对象用于创建Direct3D设备对象
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
//设置D3DPRESENT_PARAMETERS结构, 准备创建Direct3D设备对象
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
//创建Direct3D设备对象
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
//设置环境光
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff );
//设置观察矩阵和投影矩阵
SetViewAndProjMatrix();
return S_OK;
}
//-----------------------------------------------------------------------------
// Desc: 从绝对路径中提取纹理文件名
//-----------------------------------------------------------------------------
void RemovePathFromFileName(LPSTR fullPath, LPWSTR fileName)
{
//先将fullPath的类型变换为LPWSTR
WCHAR wszBuf[MAX_PATH];
MultiByteToWideChar( CP_ACP, 0, fullPath, -1, wszBuf, MAX_PATH );
wszBuf[MAX_PATH-1] = L'/0';
WCHAR* wszFullPath = wszBuf;
//从绝对路径中提取文件名
LPWSTR pch=wcsrchr(wszFullPath,'//');
if (pch)
lstrcpy(fileName, ++pch);
else
lstrcpy(fileName, wszFullPath);
}
//-----------------------------------------------------------------------------
// Desc: 创建场景图形
//-----------------------------------------------------------------------------
HRESULT InitGeometry()
{
LPD3DXBUFFER pD3DXMtrlBuffer; //存储网格模型材质的缓冲区对象
//从磁盘文件加载网格模型
if( FAILED( D3DXLoadMeshFromX( L"airplane.x", D3DXMESH_MANAGED,
g_pd3dDevice, NULL,
&pD3DXMtrlBuffer, NULL, &g_dwNumMaterials,
&g_pMesh ) ) )
{
MessageBox(NULL, L"Could not find airplane.x", L"Mesh", MB_OK);
return E_FAIL;
}
//从网格模型中提取材质属性和纹理文件名
D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();
g_pMeshMaterials = new D3DMATERIAL9[g_dwNumMaterials];
if( g_pMeshMaterials == NULL )
return E_OUTOFMEMORY;
g_pMeshTextures = new LPDIRECT3DTEXTURE9[g_dwNumMaterials];
if( g_pMeshTextures == NULL )
return E_OUTOFMEMORY;
//逐块提取网格模型材质属性和纹理文件名
for( DWORD i=0; i<g_dwNumMaterials; i++ )
{
//材料属性
g_pMeshMaterials[i] = d3dxMaterials[i].MatD3D;
//设置模型材料的环境光反射系数, 因为模型材料本身没有设置环境光反射系数
g_pMeshMaterials[i].Ambient = g_pMeshMaterials[i].Diffuse;
g_pMeshTextures[i] = NULL;
if( d3dxMaterials[i].pTextureFilename != NULL &&
strlen(d3dxMaterials[i].pTextureFilename) > 0 )
{
//获取纹理文件名
WCHAR filename[256];
RemovePathFromFileName(d3dxMaterials[i].pTextureFilename, filename);
//创建纹理
if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice, filename,
&g_pMeshTextures[i] ) ) )
{
MessageBox(NULL, L"Could not find texture file", L"Mesh", MB_OK);
}
}
}
//释放在加载模型文件时创建的保存模型材质和纹理数据的缓冲区对象
pD3DXMtrlBuffer->Release();
return S_OK;
}
//-----------------------------------------------------------------------------
// Desc: 释放创建的对象
//-----------------------------------------------------------------------------
VOID Cleanup()
{
//释放网格模型材质
if( g_pMeshMaterials != NULL )
delete[] g_pMeshMaterials;
//释放网格模型纹理
if( g_pMeshTextures )
{
for( DWORD i = 0; i < g_dwNumMaterials; i++ )
{
if( g_pMeshTextures[i] )
g_pMeshTextures[i]->Release();
}
delete[] g_pMeshTextures;
}
//释放网格模型对象
if( g_pMesh != NULL )
g_pMesh->Release();
//释放Direct3D设备对象
if( g_pd3dDevice != NULL )
g_pd3dDevice->Release();
//释放Direct3D对象
if( g_pD3D != NULL )
g_pD3D->Release();
}
//-----------------------------------------------------------------------------
// Desc: 渲染场景
//-----------------------------------------------------------------------------
VOID Render()
{
// 清除缓冲区
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
//开始渲染场景
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
SetWorldMatrix(); //设置世界矩阵
//逐块渲染网格模型
for( DWORD i=0; i<g_dwNumMaterials; i++ )
{
//设置材料和纹理
g_pd3dDevice->SetMaterial( &g_pMeshMaterials[i] );
g_pd3dDevice->SetTexture( 0, g_pMeshTextures[i] );
//渲染模型
g_pMesh->DrawSubset( i );
}
//场景渲染结束
g_pd3dDevice->EndScene();
}
//在屏幕上显示场景
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
//-----------------------------------------------------------------------------
// Desc: 窗口过程, 处理消息
//-----------------------------------------------------------------------------
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 );
}
//-----------------------------------------------------------------------------
// Desc: 入口函数
//-----------------------------------------------------------------------------
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
//注册窗口类
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
L"ClassName", NULL };
RegisterClassEx( &wc );
//创建窗口
HWND hWnd = CreateWindow( L"ClassName", L"网格模型",
WS_OVERLAPPEDWINDOW, 200, 100, 500, 500,
GetDesktopWindow(), NULL, wc.hInstance, NULL );
//初始化Direct3D
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, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
Render(); //渲染场景
}
}
}
}
UnregisterClass( L"ClassName", wc.hInstance );
return 0;
}
上面的代码中用到了一个叫做airplane.x的文件,这个文件的扩展类型是.x的,是微软公司为D3D的学习专门开发的,在先阶段的学习,我们用这个类型的文件已经足够了,这个文件和上次用到图片一样都应该放在工程目录下,不过模型文件一般都会带有纹理图片,不要忘了把纹理图片和文件放到一起,关于模型的转换问题网上的教程多的是,大家可以到其他地方搜索一下,我这里就不多说了,如果你会了转换方法,完全可以用3DMAX做出一个简单的模型,然后把它导成X文件。
纵观这次的代码,不熟悉的地方还真多,呵呵,没关系,其实现在的这些都是以前的积累,没什么大不了的地方,让我们仔细的看看:
先看看多了哪些全局量:
//-----------------------------------------------------------------------------
// Desc: 全局变量
//-----------------------------------------------------------------------------
LPDIRECT3D9 g_pD3D = NULL; //Direct3D对象
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D设备对象
LPD3DXMESH g_pMesh = NULL; //网格模型对象
D3DMATERIAL9* g_pMeshMaterials = NULL; //网格模型材质
LPDIRECT3DTEXTURE9* g_pMeshTextures = NULL; //网格模型纹理
DWORD g_dwNumMaterials = 0L; //网格模型材质数量
具体的解释代码中都有,现在不明白?没关系,过会用到的时候我们再说一下。
我们看到里面多了一个这样的函数是我们以前所没见过的。
HRESULT InitGeometry()
{
LPD3DXBUFFER pD3DXMtrlBuffer; //存储网格模型材质的缓冲区对象
//从磁盘文件加载网格模型
if( FAILED( D3DXLoadMeshFromX( L"airplane.x", D3DXMESH_MANAGED,
g_pd3dDevice, NULL,
&pD3DXMtrlBuffer, NULL, &g_dwNumMaterials,
&g_pMesh ) ) )
{
MessageBox(NULL, L"Could not find airplane.x", L"Mesh", MB_OK);
return E_FAIL;
}
//从网格模型中提取材质属性和纹理文件名
D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();
g_pMeshMaterials = new D3DMATERIAL9[g_dwNumMaterials];
if( g_pMeshMaterials == NULL )
return E_OUTOFMEMORY;
g_pMeshTextures = new LPDIRECT3DTEXTURE9[g_dwNumMaterials];
if( g_pMeshTextures == NULL )
return E_OUTOFMEMORY;
//逐块提取网格模型材质属性和纹理文件名
for( DWORD i=0; i<g_dwNumMaterials; i++ )
{
//材料属性
g_pMeshMaterials[i] = d3dxMaterials[i].MatD3D;
//设置模型材料的环境光反射系数, 因为模型材料本身没有设置环境光反射系数
g_pMeshMaterials[i].Ambient = g_pMeshMaterials[i].Diffuse;
g_pMeshTextures[i] = NULL;
if( d3dxMaterials[i].pTextureFilename != NULL &&
strlen(d3dxMaterials[i].pTextureFilename) > 0 )
{
//获取纹理文件名
WCHAR filename[256];
RemovePathFromFileName(d3dxMaterials[i].pTextureFilename, filename);
//创建纹理
if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice, filename,
&g_pMeshTextures[i] ) ) )
{
MessageBox(NULL, L"Could not find texture file", L"Mesh", MB_OK);
}
}
}
//释放在加载模型文件时创建的保存模型材质和纹理数据的缓冲区对象
pD3DXMtrlBuffer->Release();
return S_OK;
}
其实这个函数的作用就是把硬盘上的X文件载入到内存里供我们使用,其中使用到得LPD3DXMESH 是D3D扩展实用库定义的多边形网络模型接口,来表示一个复杂的三维物体模型,主要包含顶点数据,材质数据,纹理数据等。
LPD3DXBUFFER pD3DXMtrlBuffer; //存储网格模型材质的缓冲区对象
这个数据结构是应为操作方便而产生的,它的好处是可以存储顶点位置坐标、材质、纹理等多种类型的数据。可以使用接口函数GetBufferPointer得到缓冲区中的数据,使用GetBufferSize得到数据的大小。
从网格模型中提取材质和纹理文件名的示例:
D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();这里机型了一个强制类型转换,就能直接转换过来,很方便。
里面还有一个函数:
D3DXLoadMeshFromX( L"airplane.x", D3DXMESH_MANAGED,
g_pd3dDevice, NULL,
&pD3DXMtrlBuffer, NULL, &g_dwNumMaterials,
&g_pMesh )
这个函数的作用就是从文件中读取我们的X文件。
我们仔细看一下:
HRESULT D3DXLoadMeshFromX(
LPCTSTR pFilename,
DWORD Options,
LPDIRECT3DDEVICE9 pD3DDevice,
LPD3DXBUFFER * ppAdjacency,
LPD3DXBUFFER * ppMaterials,
LPD3DXBUFFER * ppEffectInstances,
DWORD * pNumMaterials,
LPD3DXMESH * ppMesh
);
Parameters
pFilename
X文件名,字符串格式。
Options
通常设置成 D3DXMESH_MANAGED,表示使用显存
pD3DDevice
环境指针,我们常用到得
ppAdjacency
指向用来存储包含每个多边形周围多边形信息的缓冲区内存地址。我们用不到这个信息,所以我们设置成了NULL
ppMaterials
[out] 用来存储模型纹理和材质的缓冲区地址。
ppEffectInstances
[out]指向用来存储模型效果实例的缓冲区地址。
pNumMaterials
[out] 存储材质的数目。
ppMesh
[out] 生成的D3D网格模型的指针。
剩下的函数就是在得到的pD3DXMtrlBuffer中提取材质、纹理等信息,并把材质里的环境光设置了一下,代码比较简单,相信大家能看得懂。
这个载入函数就解释到这里,下面还有一个种要的函数中有了很大的改动,那就是渲染函数里面:
VOID Render()
{
// 清除缓冲区
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
//开始渲染场景
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
SetWorldMatrix(); //设置世界矩阵
//逐块渲染网格模型
for( DWORD i=0; i<g_dwNumMaterials; i++ )
{
//设置材料和纹理
g_pd3dDevice->SetMaterial( &g_pMeshMaterials[i] );
g_pd3dDevice->SetTexture( 0, g_pMeshTextures[i] );
//渲染模型
g_pMesh->DrawSubset( i );
}
//场景渲染结束
g_pd3dDevice->EndScene();
}
//在屏幕上显示场景
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
这个函数里面,前面的一部分我们还是比较熟悉的,就是在设置世界矩阵以后:
for( DWORD i=0; i<g_dwNumMaterials; i++ )
{
//设置材料和纹理
g_pd3dDevice->SetMaterial( &g_pMeshMaterials[i] );
g_pd3dDevice->SetTexture( 0, g_pMeshTextures[i] );
//渲染模型
g_pMesh->DrawSubset( i );
}
这些代码有点不太熟悉,仔细看看,g_pd3dDevice->SetMaterial( &g_pMeshMaterials[i] );
这句代码是设置相应的材质,以前我们用过的,g_pd3dDevice->SetTexture( 0, g_pMeshTextures[i] );
设置纹理图片,以前用过。
g_pMesh->DrawSubset( i );
一个三维网格模型通常是由几个子模型组合的,在制作模型时通常为每个子模型分别设置材质和纹理,所以这些子模型可能使用不同的材质和纹理,因此在渲染的时候就需要对不同的子模型设置材质和纹理,上面程序中的g_dwNumMaterials表示材质和纹理的数量,实际上就是三维模型子模型的数量。
到这里这次程序就介绍的差不多了,大家如果有不懂的地方我们可以一起讨论一下。
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/luoya263547560/archive/2009/04/05/4049563.aspx