меню : 1229
Сидел я на днях, погруженный в унылую осеннюю тоску, и вспоминал прошлые дни. В прошлые дни все было лучше: свободное время не перерастало в скуку, хорошие идеи перерастали в хорошие программы. Короче говоря, в те дни я был молод и не работал. А еще я более-менее знал DirectX, и даже сделал симпатичную игру.

Ой, зря я начал так издалека. Попробуем снова.

Сидел я на днях, погруженный в унылую осеннюю скуку, и вспоминал прошлые дни. От тоски меня избавила идея разобраться в DirectX11. Загоревшись такой идеей, первым делом я попробовал найти уроки в интернете – безрезультатно. Тогда я открыл классический туториал MSDN. Вооружившись топором, я прорубил узкую тропу через его джунгли, снабжая код примеров густыми комментариями, а код статей – самодельными картинками и схемами. И вот. Дикая растительность повержена, длинный путь пройден, однако все эти сделанные мной штуковины могут кому-то пригодиться. Собственно, такая мысль и стала причиной рождения этого эпоса.

Все описанное ниже – очень вольный пересказ базового учебника Direct3D11 от Микрософт с сокращениями, пояснениями и дополнениями. Всего в MSDN насчитывается 7 уроков, которые я ужал до 6. Чтобы сравнять счет, пришлось составить собственный последний урок, который просто суммирует все описанное ранее (но в гораздо более интересной форме, поверьте).

Этот туториал не претендует на серьезность или, не дай бог, «остроумие» изложения. Я не пытаюсь тягаться с бессмертными творениями Antiloop’а. Цель одна – просто поделиться знаниями, которые накопились в моей голове, но мне самому вряд ли пригодятся.



Урок 1. Создание устройства DirectX

Давайте разберемся, что же нам пондобится. Кроме огромного желания сделать что-то крутое и обязательно трехмерное необходимо знание C++. Мы будем создавать окно при помощи WinAPI. Я использовал Visual C++ из Microsoft Visual Studio 2008, но в принципе версия C++ не играет роли. Вы можете взять любую другую среду. Кроме того, придется скачать с сайта Микрософт DirectX SDK. Ссылка найдется где-то здесь. После распаковки архива не забудьте через меню СервисaПараметры добавить каталоги с lib'ами и include'ами DirectX.

Итак, C++ выучен, SDK установлен, а идея будущей гениальной игры родилась. Что дальше? А дальше давайте разберемся, что такое вообще Direct3D. Как сообщает официальная справка, Direct3D – COM-библиотека, которая служит для облегчения и ускорения рисования в Windows. Direct3D напрямую работает с видеокартой, а это гораздо эффективнее, чем рисовать в стандартном окне при помощи стандартных API-функций. Кроме того, Direct3D сам проводит страшные трехмерные вычисления (ну или, во всяком случае, я так наивно думал, вспоминая DirectX8).

В этом уроке мы создадим минимально необходимые объекты Direct3D, привяжем их к нашему окну и научимся очищать его при помощи инструментов Direct3D. «Ха-ха», - скажете вы, - «очистить окно можно одной строчкой, и без всяких Иксов». Так оно и есть, но Микрософт довольно разумно сочла, что для начала осилить и такой объем информации – уже неплохо. Мы не будем идти против Стива Б. и Билла Г.

Прежде чем приступить к написанию собственно кода, давайте рассмотрим схему нашей программы.

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

// Главный цикл сообщений

MSG msg = {0};

while( WM_QUIT != msg.message ) {

if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )

{

TranslateMessage( &msg );

DispatchMessage( &msg );

}

else // Если сообщений нет

Render(); // Рисуем

}

CleanupDevice(); // Удалить объекты Direct3D

Тут все понятно. Теперь давайте разберемся с создаваемыми объектами.

HINSTANCE g_hInst = NULL;

HWND g_hWnd = NULL;

D3D_DRIVER_TYPE g_driverType = D3D_DRIVER_TYPE_NULL;

D3D_FEATURE_LEVEL g_featureLevel = D3D_FEATURE_LEVEL_11_0;

ID3D11Device* g_pd3dDevice = NULL;

ID3D11DeviceContext* g_pImmediateContext = NULL;

IDXGISwapChain* g_pSwapChain = NULL;

ID3D11RenderTargetView* g_pRenderTargetView = NULL;

Пока можно расслабиться. Я подскажу, когда надо будет начинать думать. Переменные g_hInst и g_hWnd – идентификаторы нашего приложения и окна, их не будем вообще трогать. g_driverType понадобится для создания устройств Direct3D. Этот параметр указывает, производить вычисления в видеокарте или в центральном процессоре. Можно смело ставить на видеокарту, но Микрософт, как всегда, немного поиздевается над нами. g_featureLevel – параметр, указывающий, какую версию DirectX поддерживает наша видеокарта. Он, как и предыдущий, понадобится только для создания устройств Direct3D. Внимание, начинаем думать! Следующие три объекта работают в связке, они даже создаются одной функцией. Честно говоря, все три раньше были одним объектом D3DDevice, но программисты из Микрософт решили, что с новым поколением DirectX объекты должны размножаться. Боюсь представить, сколько их будет в 12 версии.

Давайте всмотримся внимательнее в объект-указатель на интерфейс ID3D11Device. Раньше девайс (устройство) использовался как для создания всяческих ресурсов (текстур, буферов трехмерных объектов, шейдеров и т. д.), так и для собственно рисования. Теперь его просто разрезали: созданием ресурсов занялся интерфейс ID3D11Device, а выводом графической информации – интерфейс ID3D11DeviceContext (контекст устройства, который Микрософт обозвали СобственноКонтекстом, а мы будем называть устройством рисования). Создавать IDXGISwapChain понадобилось для работы с буферами рисования и выводом нарисованного на экран. В любой программе будет присутствовать объект IDXGISwapChain, содержащий как минимум два буфера – задний (back buffer) и передний (front buffer). Передний буфер – это экран, точнее его часть внутри нашего окна. На заднем буфере мы в DirectX отрисовываем сцену, и, когда она готова, мы вызываем функцию g_pSwapChain->Present(…), которая копирует задний буфер в передний, т. е. показывает на экране все тщательно и любовно нарисованное.

Конечно, тут выползает вопрос: а зачем эта муть с двумя буферами? Почему бы сразу не рисовать на экран? Если такой вопрос не возник, или вы уже догадались – можете пропустить абзац. Давайте пофантазируем. Пусть мы пишем крутую игру, в которой злобный монстр, сверкая кровожадными глазами, рыщет вдоль стены замка на фоне сумеречного неба. Сначала мы расправимся с небом: нарисуем какую-нибудь сферу, обтянутую текстурой облаков. Потом нарисуем травку, потом стену замка и, наконец, красноглазого красавца. Вполне очевидно, что трава закроет собой часть неба, стена – часть неба и травы и монстр – часть стены. Представили, что будет на экране, если рисовать прямо на нем? А ведь рисовать придется десятки раз в секунду! И каждый раз на экране будут мелькать облака, стена и травка, которых не должно быть видно. У любого игрока разболятся глаза, он в панике выключит игру, и никто так и не увидит, как красиво монстр разрывает врагов на куски.

Последний объект – g_pRenderTargetView. Не пугайтесь, тут нет ничего сложного. Это и есть объект нашего заднего буфера, в котором мы будем рисовать свой трехмерный мир.

Перейдем к важным местам из функции InitDevice(), которая займется созданием и инициализацией только что изученных объектов. Вот так мы можем создать связку трех основных объектов:

// Структура, описывающая цепь связи (Swap Chain)

DXGI_SWAP_CHAIN_DESC sd;

ZeroMemory( &sd, sizeof( sd ) ); // очищаем ее

sd.BufferCount = 1; // у нас один буфер

sd.BufferDesc.Width = 320; // ширина буфера

sd.BufferDesc.Height = 210; // высота буфера

sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // формат пикселя буфере

sd.BufferDesc.RefreshRate.Numerator = 75; // частота обновления экрана

sd.BufferDesc.RefreshRate.Denominator = 1;

sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // назначение буфера

sd.OutputWindow = hWnd // привязываем к нашему окну

sd.SampleDesc.Count = 1;

sd.SampleDesc.Quality = 0;

sd.Windowed = TRUE; // не полноэкранный режим



D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, NULL, &g_pImmediateContext);



Сначала мы описываем передний буфер. sd.BufferDesc.Format установлен как DXGI_FORMAT_R8G8B8A8_UNORM. Это означает, что для каждого пикселя в буфере будут храниться 4 значения: красный, зеленый, синий компоненты цвета и альфа-канал, все по 8 бит. В DirectX вообще много всяких буферов, поэтому буферы, используемые для рисования, в Микрософте решили назвать Видами. Понятия не имею, почему именно видами. А вот передний буфер назвали буфером вывода. Поэтому флаг DXGI_USAGE_RENDER_TARGET_OUTPUT указывает, что буфер является целевым буфером для вывода графической информации, т. е. передним буфером. Название флага так и переводится: «использовать как целевой буфер для вывода». Наконец, мы вызываем функцию D3D11CreateDeviceAndSwapChain(...), которая по нашему описанию создает все устройства.

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

Для начала создайте пустой проект C++ Win32 под названием Urok1, добавьте в него файл Urok1.cpp и какую-нибудь иконку в ресурсы. У меня это значок DirectX'а, но можно что угодно впихнуть, хоть значок OpenGL – никто не обидится.

Теперь код:

//--------------------------------------------------------------------------------------

// Урок 1. Создание устройств Direct3D11. Основан на примере из SDK (c) Microsoft Corp.

//--------------------------------------------------------------------------------------

#include

#include

#include

#include "resource.h"



//--------------------------------------------------------------------------------------

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

//--------------------------------------------------------------------------------------

HINSTANCE g_hInst = NULL;

HWND g_hWnd = NULL;

D3D_DRIVER_TYPE g_driverType = D3D_DRIVER_TYPE_NULL;

D3D_FEATURE_LEVEL g_featureLevel = D3D_FEATURE_LEVEL_11_0;

ID3D11Device* g_pd3dDevice = NULL; // Устройство (для создания объектов)

ID3D11DeviceContext* g_pImmediateContext = NULL; // Контекст устройства (рисование)

IDXGISwapChain* g_pSwapChain = NULL; // Цепь связи (буфера с экраном)

ID3D11RenderTargetView* g_pRenderTargetView = NULL; // Объект заднего буфера





//--------------------------------------------------------------------------------------

// Предварительные объявления функций

//--------------------------------------------------------------------------------------

HRESULT InitWindow( HINSTANCE hInstance, int nCmdShow ); // Создание окна

HRESULT InitDevice(); // Инициализация устройств DirectX

void CleanupDevice(); // Удаление созданнных устройств DirectX

LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ); // Функция окна

void Render(); // Функция рисования





//--------------------------------------------------------------------------------------

// Точка входа в программу. Здесь мы все инициализируем и входим в цикл сообщений.

// Время простоя используем для вызова функции рисования.

//--------------------------------------------------------------------------------------

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;

}



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

//--------------------------------------------------------------------------------------

// Регистрация класса и создание окна

//--------------------------------------------------------------------------------------

HRESULT InitWindow( HINSTANCE hInstance, int nCmdShow )

{

// Регистрация класса

WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW;

wcex.lpfnWndProc = WndProc;

wcex.cbClsExtra = 0;

wcex.cbWndExtra = 0;

wcex.hInstance = hInstance;

wcex.hIcon = NULL;

wcex.hCursor = LoadCursor (NULL, IDC_ARROW);

wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

wcex.lpszMenuName = NULL;

wcex.lpszClassName = L"Urok01WindowClass";

wcex.hIconSm = LoadIcon (wcex.hInstance, (LPCTSTR)IDI_ICON1);

if (!RegisterClassEx(&wcex))

return E_FAIL;



// Создание окна

g_hInst = hInstance;

RECT rc = { 0, 0, 640, 480 };

AdjustWindowRect (&rc, WS_OVERLAPPEDWINDOW, FALSE);

g_hWnd = CreateWindow (L"Urok01WindowClass", L"Урок 1: Создание устройств Direct3D", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL );

if (!g_hWnd)

return E_FAIL;



ShowWindow (g_hWnd, nCmdShow);

return S_OK;

}





//--------------------------------------------------------------------------------------

// Вызывается каждый раз, когда приложение получает сообщение

//--------------------------------------------------------------------------------------

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;

}

Создавть API-шное окно на самом деле вовсе не обязательно. Можно и в проект MFC засунуть код DirectX, но зачем нам тащить с собой горы лишних скучных файлов? Рассмотрим долгожданную функцию создания Direct3D. На ней мы немного замедлимся, чтобы мозги не вскипели.

HRESULT InitDevice()

{

HRESULT hr = S_OK;



RECT rc;

GetClientRect( g_hWnd, &rc );

UINT width = rc.right - rc.left; // получаем ширину

UINT height = rc.bottom - rc.top; // и высоту окна

UINT createDeviceFlags = 0;



D3D_DRIVER_TYPE driverTypes[] =

{

D3D_DRIVER_TYPE_HARDWARE,

D3D_DRIVER_TYPE_WARP,

D3D_DRIVER_TYPE_REFERENCE,

};

UINT numDriverTypes = ARRAYSIZE( driverTypes );

С началом все понятно: мы при помощи функции GetClientRect(…) получаем координаты нашего окна и вычисляем его ширину и высоту. Они понадобятся позднее. Потом создается массив D3D_DRIVER_TYPE. Такое значение требуется указать при создании устройств Direct3D. Но мы не знаем заранее, поддерживается ли на компьютере хардварная обработка 3D (хотя на самом деле знаем: поддерживается). Поэтому Микрософт запихала возможные значения в массив в порядке убывания их желательности для нас и возрастания вероятности того, что система их поддерживает (ага, вероятность возрастает с 99,9% до 100). Вообще, Микрософт создает учебные примеры таким образом, что код, который можно написать одной строчкой, превращается в десять. Ниже мы пройдемся по этому массиву и будем пытаться создать устройство. Если пройдет ошибка, пробуем следующий тип. Всё примитивно, но поскольку сейчас все компьютеры поддерживают хардварную обработку, спрашивается, зачем такая перестраховка в учебном примере?

// Тут мы создаем список поддерживаемых версий DirectX

D3D_FEATURE_LEVEL featureLevels[] =

{

D3D_FEATURE_LEVEL_11_0,

D3D_FEATURE_LEVEL_10_1,

D3D_FEATURE_LEVEL_10_0,

};

UINT numFeatureLevels = ARRAYSIZE( featureLevels );



// Сейчас мы создадим устройства DirectX. Для начала заполним структуру,

// которая описывает свойства переднего буфера и привязывает его к нашему окну.

DXGI_SWAP_CHAIN_DESC sd; // Структура, описывающая цепь связи (Swap Chain)

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 = 75; // частота обновления экрана

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; // не полноэкранный режим

А вот список поддерживаемых версий действительно необходим. Кому нужна игра, которая перестанет запускаться при выходе обновленной версии DirectX? Если она запускается в 10 версии, то в 11-ой точно должна работать. Структуру с описанием переднего буфера мы уже рассматривали. Только теперь мы указываем в ней размеры нашего окна.

for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )

{

g_driverType = driverTypes[driverTypeIndex];

hr = D3D11CreateDeviceAndSwapChain ( NULL, g_driverType, NULL, createDeviceFlags, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &g_featureLevel, &g_pImmediateContext );

if (SUCCEEDED(hr)) // Если устройства созданы успешно, то выходим из цикла

break;

}

if (FAILED(hr)) return hr;

Как и обещано, в цикле проходимся по массиву driverTypes. Вуаля! Все три устройства создаются одной строчкой. Но это не все. Нам ведь надо куда-то рисовать. А куда? Правильно, в задний буфер.

// Теперь создаем задний буфер. Обратите внимание, в SDK

// RenderTargetOutput - это передний буфер, а RenderTargetView - задний.

ID3D11Texture2D* pBackBuffer = NULL;

hr = g_pSwapChain->GetBuffer( 0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer );

if (FAILED(hr)) return hr;



// Я уже упоминал, что интерфейс g_pd3dDevice будет

// использоваться для создания остальных объектов

hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );

pBackBuffer->Release();

if (FAILED(hr)) return hr;



// Подключаем объект заднего буфера к контексту устройства

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

Не пугайтесь! Все кажется непонятным, но это только благодаря злодейскому замыслу Микрософта. В первой строчке создается объект текстуры. «Серьезно, текстуры?» - скажете вы. – «Что мы собрались покрывать текстурой, если еще даже рисовать негде?» На самом деле все очень умно. В Direct3D текстура – это не какое-то изображение, а просто область памяти. Память эту можно использовать в разных целях. Точнее, в трех. Как буфер для рисования, как буфер глубин и как собственно текстуру. Второй и третий случаи мы рассмотрим немного позже. Поэтому первой строчкой мы просто создали указатель на объект буфера. Вторая строчка загружает из объекта g_pSwapChain характеристики буфера. Он ведь должен точь-в-точь соответствовать переднему, да? Теперь давайте вспомним об объекте g_pRenderTargetView. Не зря же мы объявили его в самом начале. Он создается при помощи девайса по характеристикам, загруженным, как уже сказано, из g_pSwapChain. Напоминаю, через контекст мы будем рисовать. Поэтому в конце необходимо подключить созданный задний буфер к нему. Фффу, с этим разделались.

// Настройка вьюпорта

D3D11_VIEWPORT vp;

vp.Width = (FLOAT)width;

vp.Height = (FLOAT)height;

vp.MinDepth = 0.0f;

vp.MaxDepth = 1.0f;

vp.TopLeftX = 0;

vp.TopLeftY = 0;

// Подключаем вьюпорт к контексту устройства

g_pImmediateContext->RSSetViewports (1, &vp);

Еще один подарочек от Микрософта. Раньше вьюпорт всегда устанавливался по умолчанию при создании девайса, и не возникало необходимости инициализировать его самостоятельно. Здесь мы просто указываем, что верхний левый угол окна у нас имеет координаты (0, 0), а ширина и высота соответствуют ширине и высоте окна. Еще мы настраиваем масштаб буфера глубины. Просто пока не обращайте на это внимания. Вот теперь точно все.

return S_OK;

}

Осталось совсем немножко. Так выглядит функция очистки памяти:

//--------------------------------------------------------------------------------------

// Удалить все созданные объекты

//--------------------------------------------------------------------------------------

void CleanupDevice()

{

// Сначала отключим контекст устройства, потом отпустим объекты.

if( g_pImmediateContext ) g_pImmediateContext->ClearState();

// Порядок удаления имеет значение. Обратите внимание, мы удалеям

// эти объекты порядке, обратном тому, в котором создавали.

if( g_pRenderTargetView ) g_pRenderTargetView->Release();

if( g_pSwapChain ) g_pSwapChain->Release();

if( g_pImmediateContext ) g_pImmediateContext->Release();

if( g_pd3dDevice ) g_pd3dDevice->Release();

}

Пояснять просто нечего. И то, ради чего мы столько возились: функция рендеринга.

//--------------------------------------------------------------------------------------

// Рисование кадра

//--------------------------------------------------------------------------------------

void Render()

{

// Просто очищаем задний буфер

float ClearColor[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; // красный, зеленый, синий, альфа-канал

g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor );

// Выбросить задний буфер на экран

g_pSwapChain->Present( 0, 0 );

}

Ну очень просто. 1. Создаем цвет, которым будем очищать задний буфер. 2. Очищаем задний буфер (напоминаю: мы всегда рисуем в буфере при помощи контекста, для этого он и создавался). 3. Показываем задний буфер зрителям (еще напоминание: SwapChain у нас как раз и занимается связью буфера с экраном)!

Готово! Компилируем… Ошибка. И правильно. Заходим в свойства проекта, Свойства конфигурацииaКомпоновщикaВвод. В «Дополнительные зависимости» вставляем «d3d11.lib d3dcompiler.lib d3dx11d.lib d3dx9d.lib dxerr.lib dxguid.lib winmm.lib comctl32.lib» (без кавычек). Теперь должно скомпоноваться нормально.

Быстрее запускаем и наслаждаемся восхитительным темно-синим цветом – пожалуй, лучшей находкой Микрософта со времен DX8… упс! А вот нет! Из тоски по старым временам я заменил цвет на тот самый классический жутко-синий, который так манил меня много лет назад. В моих уроках вам придется смириться с этим цветом. Хотите насладиться микрософтовским – просто откройте любой пример из их туториала.
меню : 1228
1
Часть 1.1 — OpenGL


Вступление

Прежде чем мы начнем наше путешествие нам стоило бы разобраться что такое OpenGL. В основном под OpenGL понимают API (Интерфейс Программирования Приложений), который предоставляет большой набор функций, которые мы можем использовать для управления графикой и изображениями. Но на самом деле OpenGL это скорее спецификация, разработанная и поддерживаемая Khronos Group.

Спецификация OpenGL описывает каким будет результат выполнения каждой конкретной функции и что она должна делать. А уже реализация этих спецификаций лежит на плечах разработчиков. И поскольку спецификация не описывает детали реализации, соответственно имеют право на существование различные реализации OpenGL, по крайней мере пока они соответствуют спецификациям.

Люди, разрабатывающие OpenGL библиотеки, зачастую, являются производителями видеокарт. Каждая видеокарта, которую вы покупаете, поддерживает конкретные версии OpenGL из набора библиотек, разработанных для данной серии видеокарт. При использовании Apple системы, OpenGL библиотеки поддерживаются Apple, под Linux существуют комбинации версий от поставщиков и пользовательских адаптаций этих библиотек. Это также означает, что если используемая вами версия OpenGL показывает странное поведение, значит, с большой вероятностью — это ошибка производителей видеокарт.

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

Khronos выложила в публичный доступ все спецификации для всех версий OpenGL. Заинтересовавшийся читатель может найти спецификации OpenGL 3.3 (именно эту версию OpenGL мы будем использовать) здесь. Спецификации отлично показывают правила работы всех функций.

Core-profile и Immediate mode (Мгновенный режим)

Раньше, использование OpenGL предполагало разработку в Immediate mode (также известен как фиксированный конвейер (fixed function pipeline)), которая была проста в использовании для рисования графики. Большинство функционала OpenGL было скрыто в библиотеках и у разработчиков не было свободы в понимании вычислений, производимых OpenGL.

Разработчики требовали большей гибкости в разработке и позже спецификация стала более гибкой, а разработчики получили больше контроля над процессом отрисовки их графики. Immediate mode был прост в использовании и понимании, но он был крайне неэффективным. По этой причине спецификация указала Immediate mode как устаревший, и начиная с версии 3.2 начала мотивировать программистов использовать Core-profile режим, который исключал весь устаревший функционал.

При использовании core-profile, OpenGL заставляет нас пользоваться современными практиками. Когда мы пытаемся использовать устаревшие функции, OpenGL выбрасывает ошибку и прекращает отрисовку. Преимущества использования современных практик — это гибкость и эффективность, но к сожалению бОльшая сложность в изучении. Immediate mode является бОльшей абстракцией и он скрывает большое количество реальной работы, выполняемой OpenGL и поэтому его было легко изучать, но трудно разобраться, как OpenGL на самом деле работает. Современный подход требует от разработчика полного понимания OpenGL и графического программирования в целом и хоть это немного сложнее, такая схема позволяет добиться большей гибкости, эффективности.

Это причина, почему наши уроки основаны на Core-Profile OpenGL версии 3.3.
Хоть он немного и сложнее, но это того стоит.

Сейчас уже вышли гораздо более новые версии OpenGL (на момент написания 4.5) и вы можете спросить: зачем мы должны изучать OpenGL 3.3, когда уже вышел 4.5? Ответ довольно прост. Все старшие версии OpenGL, начиная от версии 3.3 не добавляют различные полезные возможности без изменения основной механики. Новые версии просто предоставляют немного более эффективные или более удобные способы выполнения одних и тех же операций. В результате все концепты и техники, применимые к OpenGL 3.3 можно применить к новым версиям OpenGL.

Использование новейших версий OpenGL сопряжено с одной проблемой. Исполнять новейшие API смогут только современные видеокарты.

Расширения

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

Разработчику надо лишь проверить доступность расширения (или использовать библиотеку расширения). Такой подход позволяет программисту выполнять действия более эффективно, основываясь на имеющихся у него расширениях:

if(GL_ARB_extension_name)
{
// Можно использовать новый функционал. Он поддерживается железом
}
else
{
// Расширение не поддерживается: делаем по-старинке.
}

C OpenGL 3.3 нам редко будут нужны расширения, но когда будут нужны, необходимые инструкции будут предоставлены.

Конечный автомат

OpenGL по своей сути — это большой конечный автомат: набор переменных, определяющий поведение OpenGL. Под состоянием OpenGL в основном имеется ввиду контекст OpenGL. В процессе использования OpenGL, мы часто изменяем состояния, устанавливая некоторых опции, управляем буферами, а затем отрисовываем, используя текущий контекст.

Когда мы говорим OpenGL, что мы хотим начать отрисовывать, к примеру, линии, вместо треугольников, то мы меняем состояние OpenGL, изменяя опцию, отвечающую за то как OpenGL должен рисовать. После изменения состояния OpenGL, на отрисовку линий, все последующие функции отрисовки будут отрисовывать линии вместо треугольников.

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

Объекты

Библиотеки OpenGL написаны на C и имеют множественные ответвления, но в основном это C библиотека. Поскольку большинство конструкций из языка C не транслируется в высокоуровневые языки OpenGL был разработан с использованием большого количества абстракций. Одной из таких абстракций является система объектов в OpenGL.

Объект в OpenGL — это набор опций, которые представляют подмножество состояний OpenGL. К примеру мы можем создать объект, описывающий конфигурацию отрисовки окна; мы можем задать размер, количество цветов и так далее. Такой объект можно представить C-подобной структурой:


struct object_name {
GLfloat option1;
GLuint option2;
GLchar[] name;
};

Примитивные типы
Заметьте, что при использовании OpenGL рекомендуется использовать примитивы, заданные OpenGL. Вместо использования float записывать его с приставной GL. Тоже самое для int, uint char, bool и так далее. OpenGL определяет разметку памяти для его GL примитивов для обеспечения кроссплатформенности, поскольку некоторые операционные системы могут иметь иную разметку. Использования OpenGL примитивов позволяет добиться полной кроссплатформенности вашего приложения.

Каждый раз, когда мы хотим использовать объекты в основном мы запишем это как-то так:

// OpenGL состояние
struct OpenGL_Context {
...
object* object_Window_Target;
...
};

// Создание объекта
GLuint objectId = 0;
glGenObject(1, &objectId);
// Привязка объекта к контексту
glBindObject(GL_WINDOW_TARGET, objectId);
// Установка значений опции объекта, привязанного к GL_WINDOW_TARGET
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// Установка цели контекста в значение по умолчанию
glBindObject(GL_WINDOW_TARGET, 0);

Этот небольшой участок кода — то, что вы будете часто встречать во время работы с OpenGL. В начале мы создаем объект и сохраняем ссылку на него в виде идентификационного номера (id). (Реальные данные объекта спрятаны в реализации). Затем мы привязываем объект к требуемой части контекста (Расположение целевого объекта окна из примера задано, как `GL_WINDOW_TARGET`). Затем мы устанавливаем значения опций окна и, в конце концов, отвязываем объект, установив id в 0. Значения, установленные нами продолжают храниться в объекте, доступ к которому мы можем получить через objectId и восстановить их снова привязав объект к GL_WINDOW_TARGET.
Данный код лишь показывает пример того, как работает OpenGL. В последствии будут представлены реальные примеры.

Основная фишка этих объектов состоит в том, что мы можем объявлять множество объектов в нашем приложении, задавать их опции и когда бы мы не запускали операции с использованием состояния OpenGL мы можем просто привязать объект с нашими предпочитаемыми настройками. К примеру этом могут быть объекты с данными 3D модели или нечто, что мы хотим на этой модели отрисовать. Владение несколькими объектами позволяет просто переключаться между ними в процессе отрисовки.

Давайте начнем

Теперь вы немного узнали про OpenGL как о спецификации, так и о библиотеке. Узнали примерный алгоритм работы и несколько особенностей, используемых OpenGL. Не расстраивайтесь, если что-то недопоняли, далее мы пошагово пройдемся по всем этапам и вы увидите достаточно примеров, чтобы разобраться во всех хитросплетениях OpenGL. Если вы уже готовы начать — то мы можем начать создавать OpenGL контекст и наше первое окно прямо тут.

меню : 801

C# OpenGL Setup Perspective ViewPort

меню : 800

c# Cad program openGL для кадастровых инженеров created by me cad program.

меню : 442

C# OpenGL

Warning: file_get_contents(rs/js/script.js): failed to open stream: Нет такого файла или каталога in /var/www/rt16147/data/www/setom.ru/demo/rs/realestate.php on line 324

Notice: Undefined variable: path_engine_version in /var/www/rt16147/data/www/setom.ru/demo/rs/realestate.php on line 325

Notice: Use of undefined constant API_ENGENE - assumed 'API_ENGENE' in /var/www/rt16147/data/www/setom.ru/demo/rs/image_view/imgview.php on line 19

Warning: include_once(API_ENGENEInputControltemV.php): failed to open stream: Нет такого файла или каталога in /var/www/rt16147/data/www/setom.ru/demo/rs/image_view/imgview.php on line 19

Warning: include_once(): Failed opening 'API_ENGENEInputControltemV.php' for inclusion (include_path='.:/opt/php56/share/pear:/opt/php56/share/php/php') in /var/www/rt16147/data/www/setom.ru/demo/rs/image_view/imgview.php on line 19

Fatal error: Class 'Control' not found in /var/www/rt16147/data/www/setom.ru/demo/rs/image_view/imgview.php on line 21