Visual Basic - мастерская разработчика
Библиотеки

DirectX

Обзоры
DirectDraw
Direct3D
DirectX Audio
DirectPlay
DirectInput
Fido Topics
SourceCode
Tools&Libs

OpenGL

Статьи и учебники
Fido Topics
SourceCode
Tools&Libs

Архив по Glide

Движки

Обзоры
Учебники
SourceCode
Downloads

Создание игр

Ваши игры

Обзорные статьи
Учебники
Fido Topics
SourceCode
Download

Stuff

Программер-Чат

Псевдо-FTP
Disclaimer
Оффтопик

 

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; // Объект Direct3D
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();


//Обработка сообщений Windows
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. Такой у нас будет полноэкранный режим.

// Инициализировать 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, 0U, 0U, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
Render();
}
}
}

// Убить все и выйти
Cleanup();
UnregisterClass( "D3D Tutorial", wc.hInstance );
return 0;
}


//Инициализация Direct3D
HRESULT InitD3D( HWND hWnd )
{
// Создать объект D3D, который потребуется для создания устройства рендеринга
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;
}


// Включить Z-буфер
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;
}

// Теперь надо извлечь свойства материалов и структур, которые оказались
// в pD3DXMtrlBuffer


//Получаем ссылку на буфер
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;

// Установить вещественный цвет для материала, так как D3DX этого не делает
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()
{
// Очистить задний буфер и z-буфер в синий цвет
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

[Вверх]

 

 


Проект
Создание Народного Учебника по OpenGL

Участвовать!
Поиск
Найдите статью или файл:


Рассылка
Новости сайта
La Vision в вашем почтовом ящике








Программирование на С++ Delphi и Паскаль
Центр демо-искусства в России