Урок DrectX 11 на C++ № 4. «3D-трансформации объектов»

Что, я опять обещал простой урок на этот раз? Эх, все не могу отучиться от вранья. Ну что мне с собой поделать? Казалось бы, надо только видоизменить предыдущий урок, чтобы продемонстрировать применение трансформаций к матрице мира, но вместе с этим набегает куча других дел, больших и мелких. Например, нагло проскочил в закрывающуюся дверь урока буфер глубин. С него-то мы и начнем.

1. Буфер глубин (Depth Stencil).

Давайте придумаем какой-нибудь пример, чтобы понять смысл этой штуки (я, конечно, о буфере глубин). Пусть у нас в игре есть космический корабль, мчащийся в космическом пространстве к неизведанным мирам сквозь звезды и даже галактики. Камера следит за Энтерпрайзом (назовем его так неоригинально) сбоку и немного спереди. Скорость выше световой! Мимо проносятся целые звездные системы! И в один момент, удачный для нашего примера, между камерой и космическим кораблем встала планета, которая загородила значительный кусок корабля.

Конечно, можно отрисовать сначала дальние звезды, потом корабль, и потом планету. В этом случае все будет нормально. Но кто знает, может в следующий раз еще одна планета окажется ЗА кораблем. Помните, как работает устройство рисования (Device Context): оно просто пиксель за пикселем отрисовывает вершины из буфера вершин в задний буфер, не заботясь о расстоянии до объекта. Поэтому если отрисовать сначала корабль, а потом дальние звезды, то они окажутся на переднем плане. То же касается и планеты. А вдруг сумасшедшему капитану Энтерпрайза понадобится разнести планету из импульсной пушки? Если начнем вычислять расстояние от камеры до каждого осколка, мы с ума сойдем.

В первом уроке немного говорилось о текстурах, а точнее, о трех областях их применения. Первый тип текстуры – это текстура, в которую происходит рисование (непосредственно задний буфер), второй тип – текстура буфера (или трафарета) глубин. Так что же это такое? Это некий аналог заднего буфера, тоже закрепленный в Device Context. Только каждый раз рисуя из буфера вершин, каждый раз просчитав цвет очередного пикселя и поместив его в задний буфер, устройство рисование еще и просчитает расстояние (глубину) до объекта в этом пикселе и поместит ее в буфер глубин. Каждый пиксель в буфере глубин может иметь значение от 1 (бесконечно далеко) до 0 (прямо под нашим носом, если не сказать «в носу»). Понятно, что еще перед установкой цвета пикселя в заднем буфере наш объект ID2D11DeviceContext теперь будет вычислять глубину и сравнивать ее с глубиной для этого же пикселя в трафарете глубин. И если в пиксель в трафарете глубин находится ближе, то новый его цвет установлен не будет.

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

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

Одна тонкость: при рисовании каждого кадра нам придется очищать не только задний буфер, но и буфер глубин (только устанавливать там будем не цвет, а 1.0f). С этим тоже нет никаких проблем:

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

    g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor );

    // Очистить буфер глубин до 1.0 (максимальное значение)

    g_pImmediateContext->ClearDepthStencilView( g_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0 );

Создается буфер глубин почти так же, как и задний.

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

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

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

Пусть у нас есть кубик, определенный в собственном пространстве, то есть с центром в точке (0, 0, 0) и с координатами углов от (-1, -1, -1) до (1, 1, 1). Так обычно и создают свои модели 3D художники. Что мы можем с ним сотворить?

Трансляция (перемещение). Ну да, просто смещение объекта от центра координат в определенную точку. Пример:

 XMMATRIX mTranslate = XMMatrixTranslation( -5.0f, 0.0f, 0.0f );

Если установить такую матрицу и отрендерить кубик, эффект будет как на этой картинке:

Слева, понятно, кубик до перемещения (в системе координат объекта), справа – уже перенесенный в точку в (-5, 0, 0) системы координат мира.

Вращение. С этим земноводным мы уже сталкивались. Вращать можно по любой оси и по всем сразу:

XMMATRIX mRotateX = XMMatrixRotationX( XM_PI / 4 );

    XMMATRIX mRotateY = XMMatrixRotationY( XM_PI / 4 );

    XMMATRIX mRotateZ = XMMatrixRotationZ( XM_PI / 4 );

    XMMATRIX mRotateAll = XMMatrixRotationAxis( XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f), XM_PI / 4 );

Если установить матрицу mRotateY, кубик станет вот таким:

Если забыли, XM_PI – это встроенная константа, равная числу π.

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

XMMATRIX mScale = XMMatrixScaling( 2.0f, 0.5f, 1.0f );

В этом примере мы в два раза вытянули куб по оси X и в два раза сжали по Y. Результат на картинке:

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

Хотя я опять нагло вру, сложность есть, и еще какая. Надо понять, как этот механизм умножения работает, иначе результат умножения сильно вас удивит. Это не математика за 3 класс, и при перестановке множителей-матриц произведение еще как меняется!

Все трансформации происходят относительно центра координат – точки (0, 0, 0). Давайте рассмотрим пример из последней микрософтовской картинки с зеленым кубиком. Чтобы получить такой результат, мы умножаем матрицу вращения на матрицу перемещения:

XMMATRIX mTranslate = XMMatrixTranslation( 3.0f, 0.0f, 0.0f );

    XMMATRIX mRotateY = XMMatrixRotationY( XM_PI / 4 );

    XMMATRIX mWorld = mRotateY * mTranslate;

Сначала происходит вращение, а затем перемещение. Что будет, если поменять множители местами. В этом случае мы сначала передвинем объект по оси Х, а потом повернем его относительно оси Y (которая уже не совпадает с осью Y куба!) на угол π/4. В результате куб будет находиться уже не на оси X, как ожидалось, а где-то в стороне, но на расстоянии 3.0 от центра.

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

3. Программа.

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

// Copyright (c) Microsoft Corporation. All rights reserved.

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

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

// Константные буферы

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

cbuffer ConstantBuffer : register( b0 )

{

       matrix World;

       matrix View;

       matrix Projection;

}

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

// Форматы данных

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

struct VS_INPUT

{

    float4 Pos : POSITION;

    float4 Color : COLOR;

};

struct PS_INPUT

{

    float4 Pos : SV_POSITION;

    float4 Color : COLOR;

};

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

// Вершинный шейдер

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

PS_INPUT VS( VS_INPUT input )

{

    PS_INPUT output = (PS_INPUT)0;

    output.Pos = mul( input.Pos, World );

    output.Pos = mul( output.Pos, View );

    output.Pos = mul( output.Pos, Projection );

    output.Color = input.Color;

    return output;

}

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

// Пиксельный шейдер

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

float4 PS( PS_INPUT input) : SV_Target

{

    return input.Color;

}

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

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

// Урок 4. 3D-трансформации объектов. Основан на примере из DX SDK (c) Microsoft Corp.

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

#include <windows.h>

#include <d3d11.h>

#include <d3dx11.h>

#include <d3dcompiler.h>

#include <xnamath.h>

#include "resource.h"

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

// Структуры

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

// Структура вершины

struct SimpleVertex

{

    XMFLOAT3 Pos;   // Координаты точки в пространстве

    XMFLOAT4 Color; // Теперь каждая вершина будет содержать информацию о цвете

};

// Структура константного буфера (совпадает со структурой в шейдере)

struct ConstantBuffer

{

    XMMATRIX mWorld;              // Матрица мира

    XMMATRIX mView;        // Матрица вида

    XMMATRIX mProjection;  // Матрица проекции

};

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

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

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

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;   // Объект вида, задний буфер

ID3D11Texture2D*        g_pDepthStencil = NULL;             // Текстура буфера глубин

ID3D11DepthStencilView* g_pDepthStencilView = NULL;          // Объект вида, буфер глубин

ID3D11VertexShader*     g_pVertexShader = NULL;             // Вершинный шейдер

ID3D11PixelShader*      g_pPixelShader = NULL;        // Пиксельный шейдер

ID3D11InputLayout*      g_pVertexLayout = NULL;             // Описание формата вершин

ID3D11Buffer*           g_pVertexBuffer = NULL;             // Буфер вершин

ID3D11Buffer*           g_pIndexBuffer = NULL;        // Буфер индексов вершин

ID3D11Buffer*           g_pConstantBuffer = NULL;           // Константный буфер

XMMATRIX                g_World;                      // Матрица мира

XMMATRIX                g_View;                       // Матрица вида

XMMATRIX                g_Projection;                 // Матрица проекции

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

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

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

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

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

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

HRESULT InitGeometry();    // Инициализация шаблона ввода и буфера вершин

HRESULT InitMatrixes();    // Инициализация матриц

void SetMatrixes(float fAngle); // Обновление матрицы мира

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

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

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

В этом уроке функция установки матриц будет вызываться не перед каждым кадром, а прямо из функции Render() перед рисованием каждой из шести пирамид. В качестве параметра ей передается начальный угол поворота по оси Y, под которым находится пирамида (то есть будет каждый кадр по шесть вызовов с параметрами 0, 1*2π/6, 2*2π/6, 3*2π/6, 4*2π/6, 5*2π/6). 2π, как вы помните из школьного курса алгебры, составляет полный круг.

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

// Точка входа в программу. Инициализация всех объектов и вход в цикл сообщений.

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

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

int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )

{

    UNREFERENCED_PARAMETER( hPrevInstance );

    UNREFERENCED_PARAMETER( lpCmdLine );

    // Создание окна приложения

    if( FAILED( InitWindow( hInstance, nCmdShow ) ) )

        return 0;

    // Создание объектов DirectX

    if( FAILED( InitDevice() ) )

    {

        CleanupDevice();

        return 0;

    }

    // Создание шейдеров и буфера вершин

    if( FAILED( InitGeometry() ) )

    {

        CleanupDevice();

        return 0;

    }

    // Инициализация матриц

    if( FAILED( InitMatrixes() ) )

    {

        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(); // Рисуем сцену

        }

    }

       // Освобождаем объекты DirectX

    CleanupDevice();

    return ( int )msg.wParam;

}

Не забудьте удалить вызов SetMatrixes() перед вызовом Render(), если решили изменить старый проект.

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

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

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 = LoadIcon( hInstance, ( LPCTSTR )IDI_ICON1 );

    wcex.hCursor = LoadCursor( NULL, IDC_ARROW );

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

    wcex.lpszMenuName = NULL;

    wcex.lpszClassName = L"Urok4WindowClass";

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

    if( !RegisterClassEx( &wcex ) )

        return E_FAIL;

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

    g_hInst = hInstance;

    RECT rc = { 0, 0, 400, 300 };

    AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );

    g_hWnd = CreateWindow( L"Urok4WindowClass", L"Урок 4. Трехмерные преобразования", 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;

}

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

// Вспомогательная функция для компиляции шейдеров в D3DX11

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

HRESULT CompileShaderFromFile( WCHAR* szFileName, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut )

{

    HRESULT hr = S_OK;

    DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;

    ID3DBlob* pErrorBlob;

    hr = D3DX11CompileFromFile( szFileName, NULL, NULL, szEntryPoint, szShaderModel,

        dwShaderFlags, 0, NULL, ppBlobOut, &pErrorBlob, NULL );

    if( FAILED(hr) )

    {

        if( pErrorBlob != NULL )

            OutputDebugStringA( (char*)pErrorBlob->GetBufferPointer() );

        if( pErrorBlob ) pErrorBlob->Release();

        return hr;

    }

    if( pErrorBlob ) pErrorBlob->Release();

    return S_OK;

}

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

// Создание устройства Direct3D (D3D Device), связующей цепи (Swap Chain) и

// контекста устройства (Immediate Context).

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

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;

#ifdef _DEBUG

    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;

#endif

    D3D_DRIVER_TYPE driverTypes[] =

    {

        D3D_DRIVER_TYPE_HARDWARE,

        D3D_DRIVER_TYPE_WARP,

        D3D_DRIVER_TYPE_REFERENCE,

    };

    UINT numDriverTypes = ARRAYSIZE( driverTypes );

    // Тут мы создаем список поддерживаемых версий 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;                               // не полноэкранный режим

    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;

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

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

    // Извлекаем описание заднего буфера

    ID3D11Texture2D* pBackBuffer = NULL;

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

    if (FAILED(hr)) return hr;

    // По полученному описанию создаем поверхность рисования

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

    pBackBuffer->Release();

    if (FAILED(hr)) return hr;

Мы проскочили до того момента, когда был создан объект буфера рисования. Далее мы подключали его к контексту устройства, а затем настраивали вьюпорт. Теперь необходимо создать буфер глубин.

// Переходим к созданию буфера глубин

    // Создаем текстуру-описание буфера глубин

    D3D11_TEXTURE2D_DESC descDepth;     // Структура с параметрами

    ZeroMemory( &descDepth, sizeof(descDepth) );

    descDepth.Width = width;            // ширина и

    descDepth.Height = height;    // высота текстуры

    descDepth.MipLevels = 1;            // уровень интерполяции

    descDepth.ArraySize = 1;

    descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; // формат (размер пикселя)

    descDepth.SampleDesc.Count = 1;

    descDepth.SampleDesc.Quality = 0;

    descDepth.Usage = D3D11_USAGE_DEFAULT;

    descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;         // вид - буфер глубин

    descDepth.CPUAccessFlags = 0;

    descDepth.MiscFlags = 0;

    // При помощи заполненной структуры-описания создаем объект текстуры

    hr = g_pd3dDevice->CreateTexture2D( &descDepth, NULL, &g_pDepthStencil );

    if (FAILED(hr)) return hr;

Сначала необходимо создать текстуру. Текстура для заднего буфера создается автоматически при создании цепи обмена (Swap Chain), поэтому нам было достаточно лишь извлечь ее описание. Здесь же приходится задавать все параметры. Впрочем, они стандартные, и ничего сложного в этом нет. Создание вида трафарета глубин (Depth Stencil View) похоже на создание вида заднего буфера (Render Target View), но нам придется заполнить еще одну структуру-описание.

// Теперь надо создать сам объект буфера глубин

    D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;            // Структура с параметрами

    ZeroMemory( &descDSV, sizeof(descDSV) );

    descDSV.Format = descDepth.Format;         // формат как в текстуре

    descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;

    descDSV.Texture2D.MipSlice = 0;

    // При помощи заполненной структуры-описания и текстуры создаем объект буфера глубин

    hr = g_pd3dDevice->CreateDepthStencilView( g_pDepthStencil, &descDSV, &g_pDepthStencilView );

    if (FAILED(hr)) return hr;

Теперь подключим к устройству рисования одновременно оба вида:

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

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

Раньше вместо буфера глубин в этой функции мы выставляли NULL. В э то функции осталось установить вьюпорт. А код функции InitGeometry() вообще не изменился.

// Установки вьюпорта (масштаб и система координат). В предыдущих версиях он создавался

    // автоматически, если не был задан явно.

    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 );

    return S_OK;

}

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

// Создание буфера вершин, шейдеров (shaders) и описания формата вершин (input layout)

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

HRESULT InitGeometry()

{

    HRESULT hr = S_OK;

    // Компиляция вершинного шейдера из файла

    ID3DBlob* pVSBlob = NULL; // Вспомогательный объект - просто место в оперативной памяти

    hr = CompileShaderFromFile( L"urok4.fx", "VS", "vs_4_0", &pVSBlob );

    if (FAILED(hr))

    {

        MessageBox( NULL, L"Невозможно скомпилировать файл FX. Пожалуйста, запустите данную программу из папки, содержащей файл FX.", L"Ошибка", MB_OK );

        return hr;

    }

    // Создание вершинного шейдера

    hr = g_pd3dDevice->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &g_pVertexShader );

    if (FAILED(hr))

    {

        pVSBlob->Release();

        return hr;

    }

    // Определение шаблона вершин

    D3D11_INPUT_ELEMENT_DESC layout[] =

    {

        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },

        { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },

    };

    UINT numElements = ARRAYSIZE( layout );

    // Создание шаблона вершин

    hr = g_pd3dDevice->CreateInputLayout( layout, numElements, pVSBlob->GetBufferPointer(),

                                          pVSBlob->GetBufferSize(), &g_pVertexLayout );

    pVSBlob->Release();

    if (FAILED(hr)) return hr;

    // Подключение шаблона вершин

    g_pImmediateContext->IASetInputLayout( g_pVertexLayout );

    // Компиляция пиксельного шейдера из файла

    ID3DBlob* pPSBlob = NULL;

    hr = CompileShaderFromFile( L"urok4.fx", "PS", "ps_4_0", &pPSBlob );

    if( FAILED( hr ) )

    {

        MessageBox( NULL, L"Невозможно скомпилировать файл FX. Пожалуйста, запустите данную программу из папки, содержащей файл FX.", L"Ошибка", MB_OK );

        return hr;

    }

    // Создание пиксельного шейдера

    hr = g_pd3dDevice->CreatePixelShader( pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), NULL, &g_pPixelShader );

    pPSBlob->Release();

    if (FAILED(hr)) return hr;

    // Создание буфера вершин (пять углов пирамиды)

    SimpleVertex vertices[] =

    {  /* координаты X, Y, Z                          цвет R, G, B, A     */

        { XMFLOAT3(  0.0f,  1.5f,  0.0f ), XMFLOAT4( 1.0f, 1.0f, 0.0f, 1.0f ) },

        { XMFLOAT3( -1.0f,  0.0f, -1.0f ), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },

        { XMFLOAT3(  1.0f,  0.0f, -1.0f ), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },

        { XMFLOAT3( -1.0f,  0.0f,  1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f ) },

        { XMFLOAT3(  1.0f,  0.0f,  1.0f ), XMFLOAT4( 1.0f, 0.0f, 1.0f, 1.0f ) }

    };

    D3D11_BUFFER_DESC bd;  // Структура, описывающая создаваемый буфер

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

    bd.Usage = D3D11_USAGE_DEFAULT;

    bd.ByteWidth = sizeof( SimpleVertex ) * 5; // размер буфера

    bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;          // тип буфера - буфер вершин

    bd.CPUAccessFlags = 0;

    D3D11_SUBRESOURCE_DATA InitData;                  // Структура, содержащая данные буфера

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

    InitData.pSysMem = vertices;               // указатель на наши 8 вершин

    // Вызов метода g_pd3dDevice создаст объект буфера вершин

    hr = g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pVertexBuffer );

    if (FAILED(hr)) return hr;

    // Создание буфера индексов:

    // Создание массива с данными

    WORD indices[] =

    {  // индексы массива vertices[], по которым строятся треугольники

        0,2,1,      /* Треугольник 1 = vertices[0], vertices[2], vertices[3] */

        0,3,4,      /* Треугольник 2 = vertices[0], vertices[3], vertices[4] */

        0,1,3,      /* и т. д. */

        0,4,2,

        1,2,3,

        2,4,3,

    };

    bd.Usage = D3D11_USAGE_DEFAULT;            // Структура, описывающая создаваемый буфер

    bd.ByteWidth = sizeof( WORD ) * 18; // для 6 треугольников необходимо 18 вершин

    bd.BindFlags = D3D11_BIND_INDEX_BUFFER; // тип - буфер индексов

    bd.CPUAccessFlags = 0;

    InitData.pSysMem = indices;         // указатель на наш массив индексов

    // Вызов метода g_pd3dDevice создаст объект буфера индексов

    hr = g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pIndexBuffer );

    if (FAILED(hr)) return hr;

    // Установка буфера вершин

    UINT stride = sizeof( SimpleVertex );

    UINT offset = 0;

    g_pImmediateContext->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );

    // Установка буфера индексов

    g_pImmediateContext->IASetIndexBuffer( g_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );

    // Установка способа отрисовки вершин в буфере

    g_pImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

    // Создание константного буфера

    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 );

    if (FAILED(hr)) return hr;

    return S_OK;

}

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

// Инициализация матриц

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

HRESULT InitMatrixes()

{

    RECT rc;

    GetClientRect( g_hWnd, &rc );

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

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

    // Инициализация матрицы мира

    g_World = XMMatrixIdentity();

    // Инициализация матрицы вида

    XMVECTOR Eye = XMVectorSet( 0.0f, 2.0f, -8.0f, 0.0f );  // Откуда смотрим

    XMVECTOR At = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );    // Куда смотрим

    XMVECTOR Up = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );    // Направление верха

    g_View = XMMatrixLookAtLH( Eye, At, Up );

    // Инициализация матрицы проекции

    g_Projection = XMMatrixPerspectiveFovLH( XM_PIDIV4, width / (FLOAT)height, 0.01f, 100.0f );

    return S_OK;

}

Мы немного сдвинули назад по оси Z нашу камеру, чтобы в объектив попадала вся сцена с пирамидками.

// Обновление матриц

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

void SetMatrixes(float fAngle)

{

    // Обновление переменной-времени

    static float t = 0.0f;

    if( g_driverType == D3D_DRIVER_TYPE_REFERENCE )

    {

        t += ( float )XM_PI * 0.0125f;

    }

    else

    {

        static DWORD dwTimeStart = 0;

        DWORD dwTimeCur = GetTickCount();

        if( dwTimeStart == 0 )

            dwTimeStart = dwTimeCur;

        t = ( dwTimeCur - dwTimeStart ) / 1000.0f;

    }

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

// Матрица-орбита: позиция объекта

    XMMATRIX mOrbit = XMMatrixRotationY( -t + fAngle );

    // Матрица-спин: вращение объекта вокруг своей оси

    XMMATRIX mSpin = XMMatrixRotationY( t*2 );

    // Матрица-позиция: перемещение на три единицы влево от начала координат

    XMMATRIX mTranslate = XMMatrixTranslation( -3.0f, 0.0f, 0.0f );

    // Матрица-масштаб: сжатие объекта в 2 раза

    XMMATRIX mScale = XMMatrixScaling( 0.5f, 0.5f, 0.5f );

    // Результирующая матрица

    //  --Сначала мы в центре, в масштабе 1:1:1, повернуты по всем осям на 0.0f.

    //  --Сжимаем -> поворачиваем вокруг Y (пока мы еще в центре) -> переносим влево ->

    //  --снова поворачиваем вокруг Y.

    g_World = mScale * mSpin * mTranslate * mOrbit;

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

// Обновить константный буфер

    // --создаем временную структуру и загружаем в нее матрицы

    ConstantBuffer cb;

    cb.mWorld = XMMatrixTranspose( g_World );

    cb.mView = XMMatrixTranspose( g_View );

    cb.mProjection = XMMatrixTranspose( g_Projection );

    // --загружаем временную структуру в объект константного буфера

    g_pImmediateContext->UpdateSubresource( g_pConstantBuffer, 0, NULL, &cb, 0, 0 );

}
// Освобождение всех созданных объектов

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

void CleanupDevice()

{

    // Сначала отключим контекст устройства

    if( g_pImmediateContext ) g_pImmediateContext->ClearState();

    // Потом удалим объекты

    if( g_pConstantBuffer ) g_pConstantBuffer->Release();

    if( g_pVertexBuffer ) g_pVertexBuffer->Release();

    if( g_pIndexBuffer ) g_pIndexBuffer->Release();

    if( g_pVertexLayout ) g_pVertexLayout->Release();

    if( g_pVertexShader ) g_pVertexShader->Release();

    if( g_pPixelShader ) g_pPixelShader->Release();

    if( g_pDepthStencil ) g_pDepthStencil->Release();

    if( g_pDepthStencilView ) g_pDepthStencilView->Release();

    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.0, 1.0f, 1.0f }; // красный, зеленый, синий, альфа

    g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor );

    // Очистить буфер глубин до 1.0 (максимальное значение)

    g_pImmediateContext->ClearDepthStencilView( g_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0 );

Вы видите аналогию между задним буфером и буфером глубин? Оба содержат информацию об одном и том же изображении, только в первом буфере хранится цвет пикселей, а во втором – их глубина.

    // Для шести пирамидок

    for (int i=0; i<6; i++) {

        // Устанавливаем матрицу, параметр - положение относительно оси Y в радианах

        SetMatrixes ( i * (XM_PI * 2) / 6 );

        // Рисуем i-тую пирамидку

        g_pImmediateContext->VSSetShader( g_pVertexShader, NULL, 0 );

        g_pImmediateContext->VSSetConstantBuffers( 0, 1, &g_pConstantBuffer );

        g_pImmediateContext->PSSetShader( g_pPixelShader, NULL, 0 );

        g_pImmediateContext->DrawIndexed( 18, 0, 0 );

    }
// Показываем задний буфер на экране

    g_pSwapChain->Present( 0, 0 );

}

Чтобы убедиться, что все это работает, компилируем программу и запускаем.

Ну вот, я опять вас обманул – вышло не так уж и сложно! Чтобы лучше понять, как это работает, попробуйте в функции SetMatrixes(…)  при вращении не использовать переменную t (замените ее на ноль). Заодно попробуйте переставить местами матрицы при умножении. Удачи в экспериментах. В следующий раз возьмемся за освещение наших пирамид.

До встречи!

<<Предыдущий урок                                                         Следующий урок>>

Добавить комментарий

Внимание! Не будут добавляться комментарии в виде откровенного спама или прямого анкора на свои сайты. Все спамеры будут передаваться в базу Akismet

Подтвердите, что Вы не бот — выберите человечка с поднятой рукой: