Основы программирования графики Direct3D8

Глава 0 - Предисловие

В этой серии уроков я научу вас основам программирования графики Direct3D 8. Если вы хотите изучать Direct3D 9, то ничего страшного! Просто в соответствующих командах вместо 8 пишите 9, действовать будет нормально. Я создавал программу в Microsoft Visual C++ 6. Не исключено что и в прежних и в следующих версиях все будет нормально. Даже если постараться, можно и в BCB перенести.

Чтобы создавать программы с поддержкой DirectX8, вам нужны заголовочные и lib файлы. Они включены в состав заголовочных и lib файлов к DirectX9, которые вы можете скачать здесь:

Заголовки: http://www.cppguru.narod.ru/dxsdk_include.rar
Lib файлы: http://www.cppguru.narod.ru/dxsdk_lib.rar

Глава 1 - Инициализация окна

Добро пожаловать в серию уроков по основам программирования в Direct3D 8. Вам нужно достать заголовочные и библиотечные файлы DirectX8. Вы можете их достать из DirectX 8.0 SDK  или же скачать отсюда: Разместите содержимое в соответствующие папки

Теперь приступим к созданию приложения. Создайте новое Win32 приложение, выберете пустой проект. После этого создайте файл main.cpp. В этом файле у нас будут основные процедуры окна. Я сейчас буду размещать куски кода, а вы их копируйте. Я их буду пояснять.

 

#define APPNAME  "D3D8 Tutor from Gamedev.KZ by Drazd"#define APPTITLE  " D3D8 Tutor from Gamedev.KZ by Drazd"
#define _RELEASE_(p) { if(p) { (p)->Release(); (p)=NULL; };};
#define _DELETE_(p)  { if(p) { delete (p);     (p)=NULL; };};
#include <windows.h>
#include <time.h>
 
BOOL bActive = true;
char AppDir[1024];
HWND hWnd;

 

 

Здесь мы определяем некоторые постоянные и подключаем необходимые заголовочные файлы

 

LRESULT CALLBACK WindowProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
       
switch (message)
       
{
       
case WM_ACTIVATE:
              
bActive = LOWORD(wParam);
              
break; 
        case WM_DESTROY:
              
PostQuitMessage (0);
              
break;
         case WM_SETCURSOR:
             
SetCursor (NULL);

             
break;
       
};
        return DefWindowProc(hWnd, message, wParam, lParam);
};

 

Эта процедура принимает сообщения отосланные пользователем компьютеру. Так же здесь мы «скрываем» курсор

 

bool WindowInit (HINSTANCE hThisInst, int nCmdShow){
       
WNDCLASS                   wcl;
        wcl.hInstance          = hThisInst;
       
wcl.lpszClassName      = APPNAME;
       
wcl.lpfnWndProc        = WindowProc;
       
wcl.style              = 0;
        wcl.hIcon              = LoadIcon (hThisInst, IDC_ICON);
       
wcl.hCursor            = LoadCursor (hThisInst, IDC_ARROW);
       
wcl.lpszMenuName       = NULL;
        wcl.cbClsExtra         = 0;
       
wcl.cbWndExtra         = 0;
       
wcl.hbrBackground      = (HBRUSH) GetStockObject (BLACK_BRUSH);
        RegisterClass (&wcl); 
        hWnd = CreateWindowEx (
              
WS_EX_TOPMOST,
              
APPNAME,
              
APPTITLE,
              
WS_OVERLAPPEDWINDOW,
              
0, 0,
              
640,
              
480,
              
NULL,
              
NULL,
              
hThisInst,
              
NULL); 
        if(!hWnd) return false;
        return true;
};

 

Здесь мы создаем окно. В структуре wcl задаем основные параметры.

 

bool AppInit (HINSTANCE hThisInst, int nCmdShow){
       
srand (time(0)); // randomize timer
        if(!WindowInit (hThisInst, nCmdShow)) return false;
       
ShowWindow (hWnd, nCmdShow);
       
UpdateWindow (hWnd);
        return true;
};

 

Процедура инициализации. Пока тут только инициализация окна, но позже мы сюда добавим ссылки на другие инициализации.

 

void RenderScreen (void)
{
};

 

Эта процедура отрисовки окна. Небойтесь что она пуста, в следующем уроке она обрастет одной функцией.

 

int APIENTRY WinMain (HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow){
       
MSG msg;
       
if(!AppInit (hThisInst, nCmdShow)) return false;
        while (1)
       
{
              
if(PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
              
{
                      
if(!GetMessage (&msg, NULL, 0, 0)) break;
                      
TranslateMessage (&msg);
                      
DispatchMessage (&msg);
              
}
              
else
                      
if(bActive) { RenderScreen (); };
       
}; 
        return 0;
};

 

Это последняя процедура в этом уроке. Собственно сама программа. Использует все вышеописанные функции для инициализации.

Глава 2 - Инициализация Direct3D 8

 

Итак, теперь мы приступим к инициализации устройства. Создадим файл direct3d8.h и включим его в этом месте:

HWND hWnd;

#include "direct3d8.h"  //Ставить сюда, после hWnd

LRESULT CALLBACK WindowProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

 «Идите» в заголовочный файл. Здесь мы подключим заголовочный файл, библиотеку Direct3D8 и создадим пару переменных:

#include <d3d8.h>

#pragma comment (lib, "d3d8.lib")

LPDIRECT3D8             p_d3d        = NULL; LPDIRECT3DDEVICE8       p_d3d_Device = NULL;

 
Теперь нам нужно создать класс для упрощения дальнейшей работы. Так же создание классов может сделать удобнее создание мультиплатформенной программы(OpenGL, D3D8, D3D9 итд), так же я туда впишу одну процедуру:

class D3D8 {
public:
            void Init();

};

D3D8 d3d8; // Создаем класс d3d8 из образа D3D8

 А теперь нам нужно создать саму процедуру инициализации:

void D3D8::Init() {
            p_d3d = Direct3DCreate8 (D3D_SDK_VERSION);
            D3DDISPLAYMODE d3ddm;
            p_d3d->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &d3ddm);
           
            D3DPRESENT_PARAMETERS d3dpp;
            ZeroMemory (&d3dpp, sizeof(d3dpp));
            d3dpp.Windowed = true;
            d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
            d3dpp.BackBufferFormat = d3ddm.Format;

             p_d3d->CreateDevice (D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &p_d3d_Device);

}

 
После инициализации нам необходимо создать процедуру которая будет отрисоовать нашу сцену. Добавьте в наш класс: void Draw();

А ниже саму процедуру:

void D3D8::Draw() {
            p_d3d_Device->Clear (0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB (0, 0, 32101), 1.0f, 0);
            p_d3d_Device->BeginScene ();
            p_d3d_Device->EndScene ();
            p_d3d_Device->Present (NULL, NULL, NULL, NULL);
}

Поясню что значит, каждая команда и с какими параметрами к ней обращаются:

Clear

Очистка экрана. В скобках методы очистки экрана. Чтобы изменить цвет очистки отредактируйте вторые скобки, которые сейчас: 0,0, 32101

BeginScene

Начало приема примитивов

EndScene

Конец приема

Present

Вывод картинки

Мы создали наше устройство и оно будет занимать место даже после завершения работы компьютера что со временем да повлияет на работу компьютера. Создаем еще одну ссылку на процедуру в классе: void Destroy();

И саму процедуру:

void D3D8::Destroy() {
            _RELEASE_ (p_d3d_Device);
            _RELEASE_ (p_d3d);
}

С процедурами на этот урок закончили, но не спешите компилировать – ничего не получите. Ведь нам нужно эти процедуры еще объявить. Возвращайтесь в главный файл и по порядку вставляйте:

  • В AppInit() перед return true: d3d8.Init();
  • В RenderScreen() вставьте: d3d8.Draw();
  • В WinMain перед return 0 вставьте: d3d8.Destroy();

В подтверждении того, что все у нас получилось, вы увидите синее содержимое формы. Ну, если даже сейчас не верите, то запустите программу FRAPS и увидите  сколько у вас кадров в секунду.

Глава 3 - Примитивы

Нужная и нудная часть уроков закончилась. Теперь давайте что-нибудь нарисуем. А именно мы нарисуем два треугольника таким образом, что получится квадрат.

Чтобы выводить вершины, нам нужно инициализовать вершинный буфер. Вставьте так:

LPDIRECT3D8             p_d3d        = NULL; LPDIRECT3DDEVICE8       p_d3d_Device = NULL; 
LPDIRECT3DVERTEXBUFFER8 p_VertexBuffer = NULL; //
инициализация буфера здесь

 
Чтобы создавать треугольники (полигоны) нам нужно описать структуру и определить постоянную. Вставьте ниже:

struct CUSTOMVERTEX { FLOAT x, y, z, rhw; DWORD color; };

#define D3DFVF_CUSTOMVERTEX ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE )

Здесь нужно кое – что пояснить. Мы определяем тип примитива D3DFVF_CUSTOMVERTEX с определенными параметрами. При создании вершин нужно вводить эти параметры именно в такой последовательности, какую мы задали здесь. Поэтому в нашем случае в структуре CUSTOMVERTEX мы задаем: Четыре точки для XYZRHW, одно «слово» для параметра DIFFUSE – цвет. Если в #define мы поменяем их местами, то и в структуре мы должны поменять их местами.

Теперь опишите в нашем классе процедуру загрузки полигонов. Сейчас мы вводим полигоны через сам код, но в последующих уроках мы сделаем загрузку из файла. В классе задайте void Init_primit();

В процедуре мы создаем структуру по образу ранее созданной CUSTOMVERTEX и, следовательно, задавать параметры мы должны в том же порядке.

Немного о структуре DWORD. Это число, которое пишется как 0xLLRRGGBBLL – концентрация яркости, RR – Концентрация красного, GG – концентрация зеленого, BB – концентрация голубого. Концентрация записывается двумя числами из 0 и f: 00 – нету, 0f или f0 – средне, ff – максимум. Можно смешивать цвета таким образом. Если все цвета выставить на ff, то получите белый. Ну а теперь сама процедура:  

void D3D8::Init_primit() {

            CUSTOMVERTEX g_Vertices[] =
            {
                        {  60.0f,  60.0f, 0.5f, 1.0f, 0xffff0000, },
                        { 200.0f,  60.0f, 0.5f, 1.0f, 0xff00ff00, },
                        {  60.0f, 200.0f, 0.5f, 1.0f, 0xff0000ff, },
                        {  60.0f, 200.0f, 0.5f, 1.0f, 0xff0000ff, },
                        { 200.0f,  60.0f, 0.5f, 1.0f, 0xff00ff00, },
                        { 200.0f, 200.0f, 0.5f, 1.0f, 0xffffff00, },
            };

            p_d3d_Device->CreateVertexBuffer (6*sizeof(CUSTOMVERTEX), 0,
                                               D3DFVF_CUSTOMVERTEX,
                                               D3DPOOL_DEFAULT,
                                               &p_VertexBuffer);
          

            VOID* pVertices;
            p_VertexBuffer->Lock (0, sizeof(g_Vertices), (BYTE**)&pVertices, 0);
            memcpy (pVertices, g_Vertices, sizeof(g_Vertices));
            p_VertexBuffer->Unlock();

}

Здесь происходит следующее: Создаем безразмерный массив, который содержит вершины. С каждой третьей вершиной мы получаем треугольник и начинаем заново. После построения всех вершин переводим их в наше устройство. Заметьте, что там есть цифра 6 – когда вы будете добавлять\убавлять вершины, вы должны вписать их количество вместо шестерки. После создания этой процедуры объявите ее в конце D3D8::Init(): Init_primit();

Так как мы создали Вершинный буфер, то нам нужно его удалить. Аналогично прошлому уроку помещаем его в D3D8::Destroy():  _RELEASE_ (p_VertexBuffer); 

Мы провели инициализацию треугольников, но не объявили их создание. Чтобы нарисовать наши треугольники, вставьте в процедуре Draw() между BeginScene и EndScene:

p_d3d_Device->BeginScene ();

p_d3d_Device->SetVertexShader (D3DFVF_CUSTOMVERTEX); p_d3d_Device->SetStreamSource (0, p_VertexBuffer, sizeof(CUSTOMVERTEX));
p_d3d_Device->DrawPrimitive (D3DPT_TRIANGLELIST, 0, 2);

 p_d3d_Device->EndScene ();

Немного о том, что же там происходит. В первой команде мы выбираем тип вывода, который мы задавали в начале. В следующей команде мы указываем источник откуда мы берем информацию. И, в конце концов, выбираем тип примитивов – треугольники, с каково начинать рисовать и на каком остановится. Наша код готов! Для удобства может подправить цвета очистки на нули, чтобы экран был черный.

Глава 4 - Текстурирование

Обычно Текстурирование дается после изучения перехода в 3D, но я решил дать текстуры еще в двух измерениях. Ведь наверно кое-кто не желает писать 3хмерную игру, а хочет лишь 2D аркаду.

В стандартной библиотеке d3d8, которую мы подключили во втором уроке не входит процедура загрузки текстуры, для этого нужно подключить еще один заголовочный файл и библиотеку:

#include <d3dx8.h>

#pragma comment (lib, "d3dx8.lib")

 Так же нам нужно добавить переменную для текстур. Пусть это будет массив, чтобы позже мы туда могли вместить больше текстур и меньше мучатся с кодом. Вставьте в соответствующее место:

LPDIRECT3D8             p_d3d        = NULL; LPDIRECT3DDEVICE8       p_d3d_Device = NULL;
LPDIRECT3DVERTEXBUFFER8 p_VertexBuffer = NULL;
LPDIRECT3DTEXTURE8 texture[128]; //
ставить сюда

struct CUSTOMVERTEX { FLOAT x, y, z, rhw; DWORD color;};

Чтобы использовать текстуры при выводе примитивов, нам нужно задать новые параметры. Измените определение и структуру следующим образом:

struct CUSTOMVERTEX { FLOAT x, y, z, rhw; DWORD color; FLOAT tx, ty;};

#define D3DFVF_CUSTOMVERTEX ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1

 Мы изменили структуру, теперь нам нужно соответственно изменить наши данные, где мы задаем вершины:

      {  60.0f,  60.0f, 0.5f, 1.0f, 0xffffffff, 0.0f, 0.0f },       { 200.0f,  60.0f, 0.5f, 1.0f, 0xffffffff, 1.0f, 0.0f },
      {  60.0f, 200.0f, 0.5f, 1.0f, 0xffffffff, 0.0f, 1.0f },

      {  60.0f, 200.0f, 0.5f, 1.0f, 0xffffffff, 0.0f, 1.0f  },
      { 200.0f,  60.0f, 0.5f, 1.0f, 0xffffffff, 1.0f, 0.0f  },

      { 200.0f, 200.0f, 0.5f, 1.0f, 0xffffffff, 1.0f, 1.0f  },

Заметьте, что я изменил цвет на белый. Так у нас будет квадрат полностью соответствующий текстуре. Но при желании вы можете побаловаться с цветами и получить разукрашенную текстуру.

 Теперь нам нужно загрузить саму текстуру. Позаботьтесь, чтобы графический файл лежал в папке с вашим проектом и exe файлом (папке Debug или Release). Теперь в процедуру инициализации в конце запишите:

D3DXCreateTextureFromFile (p_d3d_Device, "tex0.jpg", &texture[0]);

Тут важны два параметра: Второй – имя файла с текстурой, третий – переменная, куда будет вынесена структура. Процедура D3DXCreateTextureFromFile знакома с несколькими форматами, так что можете не беспокоится, что формат не пойдет.

Ну и необходимо выбрать текстуру перед рисованием полигона. Вот как выглядит процедура вывода изображения:

p_d3d_Device->Clear (0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB (0, 0, 0), 1.0f, 0); p_d3d_Device->BeginScene ();

p_d3d_Device->SetTexture (0, texture[0]);
p_d3d_Device->SetTextureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
p_d3d_Device->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
p_d3d_Device->SetTextureStageState (0, D3DTSS_COLOROP, D3DTOP_MODULATE);
p_d3d_Device->SetVertexShader (D3DFVF_CUSTOMVERTEX);
p_d3d_Device->SetStreamSource (0, p_VertexBuffer, sizeof(CUSTOMVERTEX));
p_d3d_Device->DrawPrimitive (D3DPT_TRIANGLELIST, 0, 2);

p_d3d_Device->EndScene ();

p_d3d_Device->Present (NULL, NULL, NULL, NULL);

Были добавлены команды, начинающиеся с SetTexture. Интересует нас пока только первая – в ней вторым параметром задается переменной с текстурой.

Ну и в конце нам нужно уничтожить переменную в процедуру D3D8::Destroy добавьте:

for (int tmp = 0; tmp < 128; tmp++)    _RELEASE_ (texture[tmp]);

Так мы уничтожим весь массив.

Теперь вы можете смело компилировать наш проект и получать квадрат с картинкой.

Все исходные файлы приложены к этотй статье.

ВложенияРазмер
dx8_gd_kz_02.zip3.45 Кбайт
dx8_gd_kz_03.zip3.79 Кбайт
dx8_gd_kz_04.zip10.63 Кбайт

Комментарии

Прочитал по диагонали.
Честно говоря, плохо. Очень плохо.
Где структура? Где обзор? Где последовательность? Зачем, что и куда мы "вставляем"?
И не говорите мне, что, мол, за остальным - "читай доку". Там-то как раз находится все, что нужно.
Форматирование тоже сильно хромает. Мне кажется, что отсутствие оного - дурной тон.

Вот еще бросилось в глаза: в классе D3D8 объявлена всего одна функция void Init() - откуда дальше взялись остальные?

Linker
Ну зачем же так категорично? ))

Drazd
Макросы -- зло. Убери нафиг.
У меня возникли вопросы. Как окно создавать? Зачем нужен обработчик событий? Какие события обрабатывает? При каких условиях ОС отправляет сообщения с этими событиями? Как D3D device создавать? Нужно понимание что и зачем делается, иначе прочитавший ничему не научится.
А вообще, keep up the good work.

Dima-san
>Ну зачем же так категорично? ))
Вредный характер. ;)

Drazd
Макросы в топку. Константы и шаблоны для чего придумали?

Присоединяюсь к мнению Dima-san: keep it up.

Drazd, спасибо за статью! :) DirectX SDK 7, 8 можно достать ещё здесь: http://vertexland.narod.ru. Личное мнение о статье напишу позже. =)

Linker, Dima-san эээ. Немного не пойму - какие макросы?
На счет структуры и построения вообще.... Ругайте меня, давайте, указывайте побольше на ошибки. Это ведь моя первая статья в таком роде, мне нужно узнать - что есть гуд, а что не есть гуд.
resu_t, ну вот у тебя DirectX SDK 7, 8, а я дал ссылки на DirectXSDK 8,9 :)

Еще заметил ошибочку(у меня на компе все нормально, щас пожалуйсь тому, кто выкладывал).
Сайт оказывается не поддерживает кавычки больше-меньше, поэтому знайте, что там, где пустый #include, нужно вставить:
1 глава) windows.h и time.h
2 глава) d3d8.h
4 глава) d3dx8.h

Исправил найденные баги, + сделал "Авторское" формотирование статьи. Ради всего этого прикрутил к сайцту визуальный редактор :)

>Ради всего этого прикрутил к сайцту визуальный редактор :)

Ну вот еще одно полезное дело для сайта с моей стороны - вспоминается фраза "для развития России нужен был толчок" :)

Ну еще на счет макросов, тут стоит признатся, что код первого урока(создание окна) не мой, просто взял его за основу.

Scervak, боян. Видели такое на gamedev.ru/flame/forum. :)

The play online poker is the spot a good luck works for you. |