Урок
1: Вывод простых фигур
В нашем первом уроке мы с помощью Direct3D нарисуем две простые фигуры: треугольник и квадрат. Для этого нам понадобятся последняя версия DirectX SDK, которую вы можете взять здесь, и любой компилятор языка С++. Сам я пользуюсь Visual C++ 6.0, потому что с ним не возникает никаких проблем при работе с DirectX SDK. Итак, начнем:
Во-первых , мы создадим главное окно программы, в котором будут нарисованы наши фигуры.
//
Функция CreateMainWindow() создает главное окно приложения
void CreateMainWindow(void)
{
WNDCLASSEX wc = {sizeof(WNDCLASSEX),CS_CLASSDC,WindowMsgProc,0,0,
GetModuleHandle(NULL),NULL,NULL,NULL,NULL,
"D3D
Samples",NULL};
// Регестрируем класс окна
RegisterClassEx(&wc);
// Создаем главное окно приложения
hWnd = CreateWindow("D3D Samples","D3D Lesson 1: Simple
Draw", WS_OVERLAPPEDWINDOW,100,100,600,300,
GetDesktopWindow(),NULL,GetModuleHandle(NULL),NULL);
// Показываем главное окно
ShowWindow(hWnd,SW_SHOWNORMAL);
}
Для тех, кто не знаком с программированием для Windows, поясню, что сперва необходимо зарегистрировать класс окна, затем мы создаем само окно и отображаем его на экране. Каждое окно имеет свой обработчик сообщений, поступающих от Windows. Я ограничился лишь обработкой сообщения о закрытиии окна WM_DESTROY, чтобы закрыть все приложение:
//
Функция обработки сообщений главного окна приложения
LRESULT WINAPI WindowMsgProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg) {
case WM_DESTROY:
PostQuitMessage(0);
return
0;
}
return DefWindowProc(hWnd,msg,wParam,lParam);
}
Теперь проинициализируем Direct3D:
//
Функция InitDirect3D() инициализирует Direct3D
int InitDirect3D(void)
{
D3DDISPLAYMODE d3d_dm;
D3DPRESENT_PARAMETERS d3d_pp;
// Создаем Direct3D
d3d = Direct3DCreate8(D3D_SDK_VERSION);
if(!d3d) return 0;
// Получаем текущий режим дисплея
if(FAILED(d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3d_dm)))
return 0;
// Создаем устройство Direct3D
ZeroMemory(&d3d_pp,sizeof(d3d_pp));
d3d_pp.Windowed = TRUE;
d3d_pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3d_pp.BackBufferFormat = d3d_dm.Format;
if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3d_pp,&d3d_device)))
return 0;
// Возращаем OK
return 1;
}
Функция инициализации InitDirect3D() разбита на два этапа:
1. Создание объекта Direct3D. Это первый объект, который должна получить любая ваша программа. Только через него мы сможем получить информацию о всех устройствах Direct3D и создать любой из них.
2.
Создание устройства Direct3D. Устройство Direct3D является основным компонентом,
который выполняет трансформацию, освещение и растеризацию наших графических
данных, проще говоря, именно оно и рисует нашу картинку. Устройство Direct3D
может быть нескольких типов, но остановимся на основных двух:
- D3DDEVTYPE_HAL - Аппаратное
устройство, в котором все операции выполняет с
помощью графического процессора видеокарты, что заметно ускоряет
работу.
- D3DDEVTYPE_REF - Программное
устройство, все операции в котором выполняются центральным
процессором, что крайне медленно, поэтому лучше его не использовать.
Перед тем, как мы продолжим, я бы хотел немного остановиться на основных принципах построения 3-х мерной графики, т.к. без понимания этих основ последующий материал будет просто не понятен.
Direct3D оперирует тремя основными пространственными координатими X,Y и Z. Иначе их можно назвать "ширина","высота" и "глубина". С помощью этих трех координат можно однозначно определить положение любого объекта в пространстве, будь-то это человек, дом или дерево. Любой объект состоит из конечного количества точек (для дальнейшего удобства будем называть точки - вершинами), которые образуют его каркас.
Рисунок 1. Куб
Каркас объекта состоит из треугольников, каждый из которых определен в пространстве тремя вершинами. Треугольник - это основной графический примитив, который используются в Direct3D. Например, модель куба, показанного на Рис.1, состоит из 12 треугольников и 8 вершин. Каждая вершина в объекте имеет свои уникальные параметры: положение в пространстве, цвет вершины и т.д.
Теперь вернемся обратно к нашим фигурам. Очевидно, что нам нужно всего три треугольника, два из которых будут образовывать квадрат. Т.к. все треугольники состоят из вершин, нам необходимо определит новый тип данных.
struct
CUSTOMVERTEX {
FLOAT x,y,z,rhw; // Позиция вершины
DWORD color; // Цвет вершины
};
Мы
создали структуру CUSTOMVERTEX, в которой содержится
необходимая нам информация о каждой вершине:
x,y,z
- координаты вершины
rhw
- обратная величина координаты w из гомогенной точки (x,y,z,w)
color
- цвет вершины
Мы используем величину rhw, для того, чтобы самим
определить позицию каждой вершины в окне без стандартных преобразований Direct3D.
Сформируем массив вершин Vertices с нашими данными:
//
Массив данных
CUSTOMVERTEX Vertices[] = {
{150.0f,50.0f,0.5f,1.0f,0x00ff0000},
{250.0f,250.0f,0.5f,1.0f,0x0000ff00},
{50.0f,250.0f,0.5f,1.0f,0x000000ff},
{500.0f,50.0f,0.5f,1.0f,0x00ff0000},
{500.0f,250.0f,0.5f,1.0f,0x0000ff00},
{300.0f,250.0f,0.5f,1.0f,0x000000ff},
{300.0f,250.0f,0.5f,1.0f,0x000000ff},
{300.0f,50.0f,0.5f,1.0f,0x0000ff00},
{500.0f,50.0f,0.5f,1.0f,0x00ff0000},
};
В нашем массиве 9 вершин, которые и составляют три треугольника. Чтобы устройство Direct3D нарисовало наши фигуры, необходимо данные записать в специальный буфер вершин.
//
Функция InitDirect3DData() загружает данные в буфер вершин
int InitDirect3DData(void)
{
void *pVertices;
// Создаем буфер вершин
if(FAILED(d3d_device->CreateVertexBuffer(9*sizeof(CUSTOMVERTEX),
0,D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT,&d3d_vb)))
return 0;
// Загружаем данные в буфер вершин
if(FAILED(d3d_vb->Lock(0,sizeof(Vertices),(BYTE**)&pVertices,0)))
return 0;
memcpy(pVertices,Vertices,sizeof(Vertices));
d3d_vb->Unlock();
// Возращаем OK
return 1;
}
При создании этого буфера мы должны указать формат наших вершин. Для этого существуют так называемые флаги формата вершины.
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE) // FVF Flags
Первый флаг D3DFVF_XYZRHW указывает на то, что передаваемая вершина содержит позицию вершины, а второй флаг D3DFVF_DIFFUSE - ее цвет. После инициализации Direct3D и подготовки данных, теперь мы можем нарисовать наши фигуры.
//
Функция RenderDirect3D() прорисовывает сцену
void RenderDirect3D(void)
{
// Очищение заднего буфера
d3d_device->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);
// Начало прорисовки сцены
d3d_device->BeginScene();
// Прорисовка сцены
d3d_device->SetStreamSource(0,d3d_vb,sizeof(CUSTOMVERTEX));
d3d_device->SetVertexShader(D3DFVF_CUSTOMVERTEX);
d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST,0,3);
// Конец прорисовки сцены
d3d_device->EndScene();
d3d_device->Present(NULL,NULL,NULL,NULL);
}
Прорисовка сцены выполняется функцией DrawPrimitive, аргументами которой являются: последовательность и тип графических примитивов, в нашем случае это список треугольников (D3DPT_TRIANGLELIST), индекс первого элемента в буфере вершин и количество выводимых треугольников. Чтобы выполнять все вышеперечисленные действия в правильном порядке, их нужно определить в главной функции программы.
//
Главная функция приложения
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int
nCmdShow)
{
MSG msg;
CreateMainWindow();
if(!InitDirect3D()||!InitDirect3DData()) return 0;
PeekMessage(&msg,NULL,0,0,PM_NOREMOVE);
while(msg.message!=WM_QUIT)
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
RenderDirect3D();
}
ReleaseDirect3D();
return 0;
}
Перед выходом из главной функции программы закроем все созданные нами объекты Direct3D.
//
Функция ReleaseDirect3D() деинициализирует DirectX Graphics
void ReleaseDirect3D(void)
{
if(d3d_vb) d3d_vb->Release();
if(d3d_device) d3d_device->Release();
if(d3d) d3d->Release();
}
Полный исходный текст программы и exe-файл находится тут. На этом пока все, но в дальнейшем я планирую продолжить тему уроков. Если возникли какие-то вопросы, то пишите мне на directxdesign@narod.ru. Может быть в дальнейшем появится раздел "Вопросы и Ответы".