DirectX Graphics - начнем снова
Часть 6 - Загружаем объекты
[Предыдущая часть]
[Следующая часть] [Приложения
к этой части]
В прошлой части мы познакомились с текстурами, теперь же мы будем знакомиться
с объектами.
Во всех предыдущих учебниках сцены состояли из одного треугольника или цилиндра,
созданного с помощью тех же треугольников. В принципе, применяя специально разработанные
формулы, из треугольников можно слепить гладкие абстрактные объекты, очень часто
появляющиеся во всевозможных интро и демках, но что делать если ваша цель совершенно
конкретна: нарисовать некий геометрический объект, например куб.
Конечно можно прямо в программе сделать массив вершин для 12 треугольников
и создать при помощи кучи строчек куб. А если надо построить более сложный объект,
я уже не говорю про каких-либо персонажей, то количество строчек становится
просто устрашающим. И каждый раз компилировать в принципе не несущие никакой
полезной нагрузки строки совершенно неблагоразумно.
Очевидно, что надо придумать собственный формат файла объекта, чтобы в любое
время мы могли загрузить заранее рассчитанные координаты в буфер вершин. Самый
простой формат может выглядет так:
идентификатор;
количество вершин;
Вершина_x_y_z;
Используя стандартные методы работы с текстовым файлом, мы сможем с легкостью
заполнить соответствующий буфер вершин, да еще и создать класс, в который помимо
этого буфера можно поместить переменную под идентификатор, а если копнуть глубже,
то и про текстуру и вообще про все, что душа пожелает. Таким образом и создаются
всевозможные форматы, в которых потом храняся модели игроков для Quake и иже
с ними.
Однако в этом учебнике мы не будет рассматривать создание собственного формата
(по крайней мере пока), а вместо этого, мы будем учиться использовать уже существующий.
Я говорю о формате для 3D-моделей, разработанном в Microsoft специально для
использования в Direct3D. Я говорю о формате .X
Подробное описание формата вы найдете в SDK Documentations. Кстати, он довольно
не плох и оптимально подходит для первоначальных целей обучения. В формате содержатся
координаты вершин, информация о материалах, используемых текстурах и даже анимации.
Создавать .x-файлы можно хоть в блокноте, но обычно это все же делают в каком-либо
редакторе моеделей с экспортом в .3ds и последующей конвертацией в .x при помощи
этой утилиты.
Учебник по всему этому процессу выйдет на La
Vision скорее всего отдельной статьей.
В этой части мы загрузим объект из уже подготовленного .x-файла, натянем на
него текстуру и заставим вращаться. Заодно, кстати, сделаем так, чтобы наша
программа работала в полноэкранном режиме. И объект и текстуру можно скачать
тут вместе
с готовым проектом.
Ну что ж, приступим. Создайте новый Win32-проект и добавьте в него файл dgtut6.cpp.
Позаботьтесь о том, чтобы во всех конфигурациях были указаны библиотеки d3dx8.lib
d3d8.lib winmm.lib (winmm.lib нужна, потому что мы опять будем использовать
системный таймер для привязки вращения ко времени)
Вы можете так же модифицировать код из предыдущих уроков, так как в этом уроке
изменится буквально пара процедур. Я же приведу полный код программы. В начале
как всегда стандартные заголовочные файлы и объявления.
#include <d3dx8.h>
#include <mmsystem.h>
LPDIRECT3D8 g_pD3D = NULL;
LPDIRECT3DDEVICE8 g_pd3dDevice = NULL;
LPD3DXMESH g_pMesh = NULL;
D3DMATERIAL8* g_pMeshMaterials = NULL;
LPDIRECT3DTEXTURE8* g_pMeshTextures = NULL;
DWORD g_dwNumMaterials = 0L;
Ну с LPD3DXMESH все ясно, должен ведь загруженный объект где-то в памяти храниться.
А вот следующие объявления требуют немного более подробных объяснений. D3DMATERIAL8*
и LPDIRECT3DTEXTURE8* - это указатели на хранилища для материалов и текстур,
которые будет использовать загруженный объект. Дело в том, что D3DX сам позаботится
о создании необходимых буферов вершин и даже распихает в них координаты из файла,
но вот материалы и текстуры, объявленные в файле, остаются на нашей совести.
И это даже хорошо, так как мы имеем возможность использовать несколько различных
текстур и материалов для одного и того же объекта!
VOID Cleanup();
HRESULT InitD3D( HWND hWnd );
HRESULT InitGeometry();
VOID SetupMatrices();
VOID Render();
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam
)
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
"D3D Tutorial", NULL };
RegisterClassEx( &wc );
HWND hWnd = CreateWindow( "D3D Tutorial", "DirectX Graphics
6 - Объекты",
WS_OVERLAPPEDWINDOW, 0, 0, 640, 480,
GetDesktopWindow(), NULL, wc.hInstance, NULL );
Мы изменили размеры создаваемого окна на 640x480. Такой у нас будет полноэкранный
режим.
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();
}
}
}
Cleanup();
UnregisterClass( "D3D Tutorial", wc.hInstance );
return 0;
}
HRESULT InitD3D( HWND hWnd )
{
if( NULL == ( g_pD3D = Direct3DCreate8( D3D_SDK_VERSION ) ) )
return E_FAIL;
D3DDISPLAYMODE d3ddm;
if( FAILED( g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm
) ) )
return E_FAIL;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
Следующие строчки изменятся для того, чтобы программа работала в полном экране.
d3dpp.Windowed = FALSE;
d3dpp.BackBufferFormat = d3ddm.Format;
d3dpp.BackBufferCount = 1;
d3dpp.BackBufferWidth = 640;
d3dpp.BackBufferHeight = 480;
Далее все стандартно. Обратите внимание, что мы используем Z-буфер и вещественный
цвет.
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff );
return S_OK;
}
Далее идет процедура, которая будет загружать объект и нужные ему текстуры.
В это месте обычно появляется надпись "Loading..." :)
HRESULT InitGeometry()
{
LPD3DXBUFFER pD3DXMtrlBuffer;
Буфер материалов - это такая временная структура, куда загружается вся информация
о материалах и текстурах прямо из файла. Далее вы и сами все поймете - читайте
комментарии!
if( FAILED( D3DXLoadMeshFromX( "Tiger.x", D3DXMESH_SYSTEMMEM,
g_pd3dDevice, NULL,
&pD3DXMtrlBuffer, &g_dwNumMaterials,
&g_pMesh ) ) )
{
return E_FAIL;
}
D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();
g_pMeshMaterials = new D3DMATERIAL8[g_dwNumMaterials];
g_pMeshTextures = new LPDIRECT3DTEXTURE8[g_dwNumMaterials];
for( DWORD i=0; i<g_dwNumMaterials; i++ )
{
g_pMeshMaterials[i] = d3dxMaterials[i].MatD3D;
g_pMeshMaterials[i].Ambient = g_pMeshMaterials[i].Diffuse;
if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice,
d3dxMaterials[i].pTextureFilename,
&g_pMeshTextures[i] ) ) )
{
g_pMeshTextures[i] = NULL;
}
}
pD3DXMtrlBuffer->Release();
У нас получились массивы материалов и текстур, которые мы сможем потом использовать
для рендеринга.
return S_OK;
}
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();
if( g_pd3dDevice != NULL )
g_pd3dDevice->Release();
if( g_pD3D != NULL )
g_pD3D->Release();
}
В процедуре задания матриц, зададим вращение всго мира вокруг своей оси.
VOID SetupMatrices()
{
D3DXMATRIX matWorld;
D3DXMatrixRotationY( &matWorld, timeGetTime()/1000.0f );
g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
D3DXMATRIX matView;
D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3( 0.0f, 3.0f,-5.0f ),
&D3DXVECTOR3( 0.0f, 0.0f, 0.0f ),
&D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );
g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
D3DXMATRIX matProj;
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f );
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
}
Наконец, последняя процедура - рендеринг.
VOID Render()
{
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
g_pd3dDevice->BeginScene();
SetupMatrices();
Объект может состоять из нескольких составляющих, у каждой из которых может
быть своя текстура и свой материал. Для этого мы и маялись с созданием массивов
текстур, это нам и поможет легко вывести объект на экран:
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 );
}
Все! Компилируем и смотрим на тигренка. Когда надоест жмем Alt-F4.
Приложение: Готовый
проект (170k)
Приятного программирования: Anti
[Вверх]
|