Урок 4. Установка матриц трансформаций и камеры в DirectX11

22.09.2012

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

Типы данных для хранения параметров камеры

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

Существует два вида представления камеры: ненаправленная,

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

class CameraDirectional
{
        XMFLOAT3 pos;
        XMFLOAT3 direction;
        float FOV;
};

Такая камера будет называться ненаправленной, так как не существует определенной точки в которую она смотрит. В качестве направления вы можете определить любой нормализованный вектор. Умножив вектор direction на одну или несколько матриц поворота вы сможете поворачивать камеру. Преимущество ненаправленной камеры в том, что для неё не обязательно иметь точку, в которую она смотрит – а постоянно следить за тем, какую конкретно точку направить камеру и к чему эту точку привязать – весьма проблематично.

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

class CameraTarget
{
        XMFLOAT3 pos;
        XMFLOAT3 target;
        float FOV;
}

Типы данных Direct3D

В предыдущем уроке мы рассмотрели различные математические операции и представления чисел. Давайте немного отвлечемся от установки камеры и рассмотрим, какие типы данных понадобятся в DirectX11:

  • Для хранения скалярных значений — чисел с плавающей точкой используется тип данных float
  • Для хранения трехкомпонентных векторов используется тип данных XMFLOAT3
  • Для хранения информации о цвете используется XMFLOAT4
  • Матрицы в DirectX описываются типом данных XMMATRIX

Также существуют и другие типы данных DirectX, однако они гораздо менее популярны.

Типы данных для хранения параметров камеры

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

struct Vertex
{
        XMFLOAT3 coord;
} Vertices[];

Чтобы осуществить нужные преобразования для камеры, достаточно лишь задать две матрицы:

XMMATRIX g_View;
XMMATRIX g_Projection;

И затем умножить координату каждой из вершин на обе матрицы:

FromViewVertices[].coord=Vertices[].coord*g_View*g_Projection;

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

class CameraTarget
{
        XMFLOAT3 pos;
        XMFLOAT3 target;
        float FOV;
}

Однако, перед тем как использовать камеру, вы должны преобразовать её в соответствующие матрицы камеры и проекций.

Установка матрицы камеры и проективной матрицы в Direct3D

Итак, продолжим. Перед тем как установить камеру типа CameraTarget вам потребуется преобразовать её координаты в матрицы DirectX, для этого в Direct3D11 существует следующая функция:

XMMATRIX XMMatrixLookAtLH(
        __in     const XMVECTOR pEye,
        __in     const XMVECTOR pAt,
        __in     const XMVECTOR pUp
);

Выходящими данными функции будет являться матрица. Входящие параметры определяют положение и направление камеры, что подходит для нашего объекта типа CameraTarget.

Таким образом, для того, чтобы преобразовать объект из CameraTarget в нужные нам матрицы нужно сделать следующее. Сначала заполним CameraTarget какими-либо данными:

XMVECTOR Eye = XMVectorSet( MyCamera.pos );
XMVECTOR At =  XMVectorSet( MyCamera.target );
XMVECTOR Up =  XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );

Обратите внимание, что вместе с заданием координат камеры мы также задали параметр FOV. Это fieldofview, то есть угол обзора. Просто камера может узкоугольной и широкоугольной – по вашему выбору. Оказывается, в DirectX тоже можно менять линзы! Зададим в виде глобальных переменных матрицы

XMMATRIX                  g_World;
XMMATRIX                  g_View;
XMMATRIX                  g_Projection;

Теперь зададим нужные матрицы трансформаций, камеры и проекций:

g_View     = XMMatrixLookAtLH( Eye, At, Up );
g_World    = XMMATRIXIdentity();
g_Projection=XMMATRIXPerspectiveFovLH( MyCamera.FOV,1.6f.0f,0.1f,100.0f);

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

Установка матриц камеры, трансформаций и проективной матрицы в качестве констант шейдера

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

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

//------------------------------------------------------------------------------------
// Структуры
//------------------------------------------------------------------------------------
struct ConstantBuffer
{
        XMMATRIX mWorld;
        XMMATRIX mView;
        XMMATRIX mProjection;
};
//------------------------------------------------------------------------------------
// Глобальные переменные
//------------------------------------------------------------------------------------
ID3D11Buffer*           g_pConstantBuffer = NULL;

Обратите внимание что пока у нас объект имеет значение NULL, то есть объект еще не инициализирован. Также обратите внимание, что существует отдельно структура для хранения значений буфера ConstantBuffer и собственно объект константного буфера g_pConstantBuffer. В функции инициализации геометрии мы должны создать константный буфер, точно также, как мы создавали вершинный и индексный буферы:

// Создание буфера констант шейдера
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof(ConstantBuffer);
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
bd.CPUAccessFlags = 0;
hr = g_pd3dDevice->CreateBuffer( &bd, NULL, &g_pConstantBuffer );

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

Итак, в процедуре Render установим глобальные переменные шейдера для наших матриц (вы помните, что раньше мы получили нужные матрицы, преобразовав их из объекта CameraTarget).

// Установка констант шейдера
ConstantBuffer cb;
сb.mWorld = XMMatrixTranspose( g_World);
cb.mView  = XMMatrixTranspose( g_View );
cb.mProjection = XMMatrixTranspose( g_Projection );
g_pImmediateContext->UpdateSubresource( g_pConstantBuffer, 0, NULL, &cb, 0, 0 ); 
g_pImmediateContext->VSSetShader( g_pVertexShader, NULL, 0 );
g_pImmediateContext->PSSetShader( g_pPixelShader, NULL, 0 );
g_pImmediateContext->VSSetConstantBuffers( 0, 1, &g_pConstantBuffer );

Выможетеспросить, зачемдваразаустанавливатьматрицы? Сначала мы установили матрицы при помощи кода

g_View     = XMMatrixLookAtLH( Eye, At, Up );
g_World    = XMMATRIXIdentity();
g_Projection=XMMATRIXPerspectiveFovLH( MyCamera.FOV,1.6f.0f,0.1f,100.0f);

А потом установили еще раз, при помощи установки переменных для шейдера (см. выше).

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

Полезные мелочи: матрица трансформаций

Итак, мы установили матрицы для камеры, однако невыясненным остался вопрос, зачем нужна переменная g_World, заполнявшееся единичной матрицей. Это матрица трансформаций. На самом деле этой переменной можно присваивать значение, отличное от единицы. И это полезная матрица, так как перед тем как отобразить объект мы его можем правильно расположить в пространстве.

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

При помощи матриц трансформаций g_World мы можем переместить объекты куда угодно перед тем как сделать их Render. При этом предварительно мы можем повернуть объект вокруг своей оси на нужный угол а также увеличить или уменьшить объект. Зачастую объекты «приходят» из 3d редактора в таком виде, что их надо подгонять по размеру. В редких случаях в редакторе можно точно угадать и подобрать нужную величину создаваемого объекта, так как системы измерений всегда могут отличаться.

Пример приложения

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

В качестве целевой точки для камеры будет выберем центр координат. Значение FOV для камеры установим по умолчанию.

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

Теперь остается скомпилировать и запустить приложение.

Исходный текст приложения к данному уроку вы можете найти тут. Данное приложение устанавливает камеру в соответствии с одной из точек эллиптической траектории и динамически изменяет положение камеры.

Заключение

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

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

avatar

Об Авторе ()

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

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

Наверх