Урок 1. Простейшее приложение DirectX10

06.08.2012

Итак, мы начинаем цикл уроков по DirectX10. Оденьте наушники, стереоочки, пристегнитесь к креслу и покрепче держитесь — ведь настало время вдавить в пол акселератор и выжать полные обороты из вашего 3D ускорителя, так как он будет использоваться совместно с одной из самых захватывающих и передовых 3D систем — DirectX10. В этом первом уроке вы узнаете: как установить среду разработки для DirectX; как создавать окно; как инициализировать DirectXи получать доступ к интерфейсу Direct3Ddevice10; как отобразить наше первое окно DirectX.

Установка среды разработки DirectX10

Для того, чтобы работать с DirectX, необходимо иметь минимальные базовые навыки программирования под Windows. Более того, нужно быть неплохим программистом под Windows и хорошо знать C++, чтобы освоить DirectXи уже иметь определенный опыт написания программ под эту операционную систему. Для разработки подойдет компилятор MicrosoftVisualStudio2010. В наших примерах мы будем пользоваться MicrosoftDirectX10 SDK, этот SDK свободно распространяется Microsoft, так что вы легко сможете его найти в Интернете. После установки SDK в ProgramFilesвы найдете папку MicrosoftDirectX SDK. К каждому уроку нашего цикла статей по DirectX10 идет исходный текст примера а также скомпилированое приложение примера. Пример приложения для первого урока, расположен тут. Распакуйте папку с примером в любое место.  Откройте VisualC++  и загрузите в него проект. Вы также можете создать новый проект VisualC++ на основе одних файлов .cpp. О том как это сделать, будет сказано в конце этого урока. Сейчас же разберем наш пример простейшего приложения.

Архитектура нашей программы

Наше приложение Direct3D, как и последующие приложения, будет иметь следующую архитектуру: Для каждой отдельной операции мы будем использовать функцию. Большинство из этих функций будут вызываться из WinMain, некоторые из MsgProc. В наших примерах мы будем рассматривать отдельно каждую функцию и её содержимое.

Главная функция приложения: WinMain

Каждое приложение Windowsдолжно иметь функцию WinMain. Мы будем использовать эту функцию с той-же целью, что и всегда – как главную функцию программы. Рассмотрим исходный текст нашей функции WinMain:

int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
UNREFERENCED_PARAMETER( hPrevInstance );
UNREFERENCED_PARAMETER( lpCmdLine );
 
if( FAILED( InitWindow( hInstance, nCmdShow ) ) )
        return 0;
if( FAILED( InitDevice() ) )
{
        CleanupDevice();
        return 0;
}
 
// Цикл обработки сообщений
MSG msg = {0};
while( WM_QUIT != msg.message )
{
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            Render();
        }
}
CleanupDevice();
return ( int )msg.wParam;
}

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

Каждое приложение Windows имеет цикл обработки сообщений. Обработка сообщений нужна для того чтобы отслеживать поступающие в окно сообщения от системы – клавиши, сворачивание и разворачивание окна и т.п. На самом деле отслеживание сообщений будет выполнятся в функции WndProc, однако цикл обработки сообщений – это совершенно необходимая надстройка и часть любого приложения Windowsи DirectX. Из этого цикла выполняется функция Render, то есть периодически будет обновятся динамически меняющееся содержимое сцены. Вы можете взглянуть на содержимое Renderниже по тексту и получить уже на 75% представление о том, вообщем, как работает наше приложение, однако архитектура DirectX 10 изменена по сравнению с предыдущими версиями, так что нам нужно будет изучить все более детально. Итак, начнем.

Инициализация окна:InitWindow

Итак, откройте исходный текст нашего первого приложения и найдите функцию InitWindow. Эта функция просто создает окно для нашей программы. Не забываем, что так как мы пользуемся функциями Windows, то мы должны сделать в начале программы #include . Эта строка подключит необходимые функции из Windows API

Внутри функции InitWindow существует два основных вызова – это регистрация класса окна:

if( !RegisterClassEx( &wcex ) )        return E_FAIL;

И создание окна:

g_hInst = hInstance;
RECT rc = { 0, 0, 640, 480 };
AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
g_hWnd = CreateWindow( LWCaption,LWName , WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL );

Обратие внимание, что при создании окна мы можем указать различные параметры, в том числе его размер.

Глобальные переменные

Для того, чтобы приложение функционировало, нужно объявить типы объектов и переменные для их хранения, так что в начале программы (сразу после #include) вы видите глобальные переменные:

HINSTANCE               g_hInst = NULL;
HWND                    g_hWnd = NULL;
ID3D10Device*           g_pd3dDevice = NULL;
IDXGISwapChain*         g_pSwapChain = NULL;
ID3D10RenderTargetView* g_pRenderTargetView = NULL;

Первые две строки нужны для хранения указателя на экземпляр окна. Дальнейшие переменные, которые мы заполним уже в следующем разделе относятся к Direct3D. Это – самый главный объект, через который мы будем обращаться к устройству Direct3D — g_pd3dDevice. Также мы видим здесь объект для цепочки-обмена
и рендер–таргет, то есть поверхности для рендеринга. В следующем разделе мы познакомимся поближе с этими объектами и их назначением.

Инициализация DirectX:InitDevice

Итак, продолжаем наполнять наше приложение новым кодом. Теперь нужно инициализировать собственно DirectX, так чтобы в нашем окне вообще что-то отображалось. Сечас нам предстоит сделать InitDevice. Хорошая  новость заключается в том, что ранее мы просто сделали окно, а вот дальнейший код приведет к тому, что включится ваш видео ускоритель.

Начнем рассмотрение функции InitDevice. Прежде чем получить доступ к корневому объекту Direct3Ddevice разберемся слега вообще архитектуре Direct3Dверсии 10. В этой версии рисование при вызове функций Direct3Ddevice::DrawPrimitive осуществляется не куда-то, что задано по умолчанию – то есть на экран или в окно, — в версии DirectX10 нужно прежде всего задать куда именно
выполнять рисование. Вы можете догадаться, что рендер осуществляться может на различные поверхности, не обязательно на экран. Так что, нам нужно указать Direct3D на рендер-таргетRenderTarget – то есть куда рисовать – а также указать как это потом «перелистывать» — то есть осуществлять замену старого кадра новым при помощи цепочки-обмена SwapChain. На этом рисунке показывается то какие объекты используются Direct3D 10 для вывода на экран:

Итак, при инициализации устройства Direct3D 10 мы должны сделать несколько операций: создать объект g_pd3dDevice, создать рендер-таргетg_pRenderTargetView, указать 3d устройству что именно этот рендер-таргет используется для рисования в него любых примитив, а также создать объект «копировалку» g_pSwapChainс рендер-таргета на окно, то есть на экран. В последующем в программе мы уже не будем для рисования обращаться к рендер-таргету, для рисования будем использвать только g_pd3dDevice::DrawPrimitive и затем отображать на экране при помощи g_pSwapChain::Present. Итак, создадим главный объект — Direct3Ddevice.

DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory( &sd, sizeof( sd ) );
sd.BufferCount = 1;
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
 
for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
{
g_driverType = driverTypes[driverTypeIndex];
hr = D3D10CreateDeviceAndSwapChain( NULL, g_driverType, NULL, createDeviceFlags,D3D10_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice );
}

Обратите внимание, что при создании объекта функцией D3D10CreateDeviceAndSwapChain также автоматически создается и цепочка-обмена , а также обратите внимание на обширное количество параметров структуры sd. Эти параметры, как нетрудно догадаться, и есть параметры инициализации адаптера и Direct3D – то есть, форамт пикселей, геометрические размеры окна, частота кадров и т.п. Вообще, в структуре DXGI_SWAP_CHAIN_DESC еще больше параметров, и возможно, какие-то вам пригодяться для того чтобы устанавливать различные режимы отображения и работы DirectX. В более ранних версиях DirectXтажке были многочисленные параметры, используемые при инициализации Direct3DDevice. Итак, мы создали главный объект и цепочку обмена, осталось только создать рендер-тергет и указать DirectX, что он предназначен для рисования. Создаем рендер-таргет:

// Создание рендер-таргет
ID3D10Texture2D* pBackBuffer;
hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), ( LPVOID* )&pBackBuffer );
if( FAILED( hr ) ) return hr;
 
hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
pBackBuffer->Release();

Указываем D3Dчто этот рендер-таргет надо использовать по умолчанию и что именно в него и надо всё рисовать:

g_pd3dDevice->OMSetRenderTargets( 1, &g_pRenderTargetView, NULL );

Теперь настало время узнать о самом интересном, как собственно рисовать, то есть разберем функцию Render. А потом посмотрим на то, как перехватывать сообщения от клавиатуры, чтобы мы имели возможность управления нашим приложением, и главное, монстрами.

Отображение сцены:Render

Теперь нужно что-нибудь отобразить в нашем окне. Функция Renderобычно вызывается в играх несколько десятков раз в секунду. Но нам не нужно заботится о том, чтобы устанавливать конкретные цифры. Функция g_pSwapChain::Present

сама вносит необходимую задержку, в соотствии с тем, на какой частоте работает видеоадаптер. Так что мы будем просто вызывать Renderтак часто, как получиться, а задержка выставиться автамотически. Рассмотримфункцию:

void Render()
{
// Очистка рендер-таргета
float ClearColor[4] = { 0.0f, 0.9f, 0.5f, 1.0f }; //red,green,blue,alpha
g_pd3dDevice->ClearRenderTargetView( g_pRenderTargetView, ClearColor );
// Отображение геометрии на рендер-таргете
// Вывод содержимого рендер-таргета на экран
g_pSwapChain->Present( 0, 0 );
}

В нашей функции Renderмы сначала очищаем рендер-таргет, затем рисуем на нем, — об том как это делать – в дальнеших уроках, — а затем выведем изображение на экран. В данном примере мы просто заполняем рендер-таргет зеленым цветом и сразу-же выводим изобажение на экран. Обратите внимение, что функции g_pSwapChain::Present не передаются никакие параметры о том, какой рендер-таргет нужно выводить на экран. В функции InitDevice мы уже дали знать Direct3D какой рендер-таргет мы используем для рисования. Так что все операции рисования, сколько бы их не было Direct3D будет осуществлять на один раз установленном в начале RenderTarget — е.

Цикл обработки сообщений

Итак, возрвратимся на некоторое время к функции WinMain. Каждое приложение Windows имеет так называемый цикл обработки сообщений. В этом цикле мы должны получить сообщения от системы Windows. В идеале конечно мы могли бы просто запускать в цикле функцию Renderдля отрисовки всего что будет происходить в окне несколько десятков раз в секунду, не заботясь об остальном. Однако нужно еще и перехватывать сообщения от мыши и клавиатуры, да и собственно если нажата кнопка закрытия окна – это нужно тоже как-то обрабатывать. Так что для этих целей нужен цикл обработки сообщений.

// Цикл обработки сообщений
MSG msg = {0};
while( WM_QUIT != msg.message )
{
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            Render();
        }
}
CleanupDevice();
return ( int )msg.wParam;
}

На этом функция WinMainзавершена. Обратите внимание, что внутри цикла выполняется функция Render, что не удивительно, так как периодически нужно перерисовывать динамически изменяющуюся в играх сцену. Цикл обработки сообщений завершается тогда когда поступает сообщение WM_QUIT, прерывающий цикл. Функция WinMain завершается и также завершается программа и закрывается окно.

Очистка объектов:CleanupDevice

Когда окно закрывается, нужно очистить все ранее инициализированные объекты Direct3D. Для этого предназначена небольшая функция очистки, вызываемая из MsgProc. То есть, когда приходит сообщение о закрытии окна (а также когда приходят и другие особщения) Windowsвызывает MsgProc, и если окно закрывается, то мы делаем из MsgProc очистку объектов и завершение приложения.

void CleanupDevice()
{
if( g_pd3dDevice ) g_pd3dDevice->ClearState();
 
if( g_pRenderTargetView ) g_pRenderTargetView->Release();
if( g_pSwapChain ) g_pSwapChain->Release();
if( g_pd3dDevice ) g_pd3dDevice->Release();
}

Цикл обработки сообщений

Перед нашей главной и глобальной функцией WinMainдолжна быть еще одна функция, MsgProc. Это так называемся CallBack функция, и Windows вызывает её даже самостоятельно, когда ей это потребуется. Что же касается наполнения функции MsgProcто в неё приходят различные сообщения Windows, и нужно на это как то реагировать. В дальнейшем внутрь неё вы конечно же вставите обработку клавиш «вперед, назад, влево вправо» а также обязательно «пробел» или «ctrl» для того, чтобы эффективно и быстро нажав на них уничтожать ваших ненавистных 3d– монстров из плазмомета.

LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC hdc;
 
    switch( message )
    {
        case WM_PAINT:
            hdc = BeginPaint( hWnd, &ps );
            EndPaint( hWnd, &ps );
            break;
 
        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;
 
        default:
            return DefWindowProc( hWnd, message, wParam, lParam );
    }
 
    return 0;
}

Кстати, как бонус, можно рассказать собственно как обрабатывать указанные выше клавиши, для этого нужно немного изменить пример. Скачать изменный пример можно тут.

На этом наше первое приложение завершено, с чем мы можем поздравить себя а также будущих пользователей наших Direct3D программ.

Обзор приложения

Теперь остается только скомпилировать и запустить приложение. Не забываем, что в системе должен быть установлен DirectX версии не ниже 10.0. В текущих версиях Windows, например Windows 7, а также Windows 8 всё уже установлено, так что заботиться об установке DirectX не надо, и наше приложение успешно запуститься на них и будет прекрасно функционировать.

Данное приложение всего-лишь отображает поверхность, заполненную зеленым цветом. Однако, если это произошло, то это значит что все ок, и это не мало, так как мы создали окно, инициализировали DirectX, очистили поверхность окна зеленым цветом, вообщем, сделали достаточно для того, чтобы приступать к отображению чего-то трехмерного, чем и займемся в дальнейших уроках.

Дополнение к уроку. Настройка среды разработки

Помните, что для того чтобы работать с DirectX10 для начала нужно установить MicrosoftDirectXSDK версии10. Для того, чтобы работать с файлами .cppв MicrosoftVisualC++ нужно либо открыть существующий проект, либо создать новый. Если вы решите создать новый проект DirectX10 на основе существующих файлов C++, то необходимо воспользоваться следующей процедурой.

  1. Открыть MicrosoftVisualC++ 2010
  2. Выбрать пункт меню «Файл->Создать->Проект из существующего кода»

Далее нужно выбрать папку проекта, в этой папке уже должны быть будут .cppфайлы, назначить имя проекта, например, MyProject1и далее нажать копку «Создать».

VisualC++ создаст проект, при этом в обозревателе решений будет ваш проект MyProject1. В нем будут следующие папки:

  1. Header Files
  2. Resource Files
  3. Source Files
  4. Внешние зависимости

Прежде всего, убедитесь что в папке «SourceFiles» расположен ваш код .cpp. После этого нужно настроить собственно проект. Для этого нужно нажать в обозревателе решений на MyProject1, то есть перейти на проект, и затем выбрать в меню «Проект->Свойства».

После этого нужно нажать на «Свойства конфигурации».

Обратите внимание, что существует несколько конфигураций: Debug, Releaseи Все конфигурации. Перед любым дальнейшим изменеием выберите в выпадающем списке конфигурации «все конфигурации». Это нужно сделать обязательно, иначе опции, которые вы будете устанавливать применятся не ко всему, а к чему-то отдельному, что будет мешать. Итак, настроим наш проект.

  1. Во вкладке «Общее» выберите «Набор символов->Использовать набор символов Юникода»
  2. Во вкладке «Каталоги VC++» выберите «Каталоги включения», далее добавьте следующий элемент (нужно впечатать текст, который идет далее): $(DXSDK_DIR)Include
  3. Во вкладке «Каталоги VC++» выберите «Каталоги библиотек», далее добавьте следующий элемент (нужно впечатать текст, который идет далее): $(DXSDK_DIR)Lib\x86

Во вкладке «Компоновщик» выберите пункт «Ввод», затем добавьте следующий список:

d3d10.lib
d3dx10d.lib
d3dx9d.lib
dxerr.lib
dxguid.lib
winmm.lib
comctl32.lib

Итак, на этом конфигурация MiscrosoftVisualC++ завершена и можно редактировать код C++ вашего проекта. Чтобы вы поняли, для чего нужны были настройки, описанные выше, можно пару слов сказать о них. Самое главное, что мы сделали это прописали пути к файлам Include а также пути к библиотекам Lib. Однако все те библиотеки, которые есть в Lib, либо часть из них, еще нужно подключить к проекту. Для этого и нужно было прописывать список файлов с расширением lib – тем самым, мы подключили библиотеки .Lib к проекту.

Заключение

В этом уроке мы научились создавать окно и инициализировать устройство Direct3D10. Также мы знаем, как обрабатывать сообщения Windows и в каком месте программы расположена функция Render. В следующем уроке мы научимся рисовать 3dпримитиву на подготовленной поверхности.

Заполнен: DirectX10
Присвоен тэг:

avatar

Об Авторе ()

Планета назначения, JO - 8703 - IV, обозначается светлой точкой на объемном экране корабля. К тому времени, как Робоид нанялся на юпитерианский круговой рейс, ему следовало бы стать главным инженером, но после Суховодного пионерского его вышибли, внеся в черный список, и высадили в Луна-Сити за то, что вместо слежения за приборами он провел время, программируя синтезатор пищи корабля строжайше запрещенной корабельными инструкциями последовательностью синтеза пива.

Комментирование закрыто.

Наверх