Преобразование платформы отрисовки
Сводка
- Часть 1. Инициализация Direct3D 11
- Часть 2. Преобразование платформы отрисовки
- Часть 3. Перенос цикла игры
Показывает, как преобразовать простую платформу отрисовки из Direct3D 9 в Direct3D 11, включая перенос геометрических буферов, компиляцию и загрузку программ шейдеров HLSL и реализацию цепочки отрисовки в Direct3D 11. Часть 2 из простого приложения Direct3D 9 в DirectX 11 и универсальная платформа Windows (UWP).
Преобразование эффектов в шейдеры HLSL
В следующем примере представлен простой метод D3DX, написанный для API устаревших эффектов, для преобразования вершин оборудования и сквозных данных цвета.
Код шейдера Direct3D 9
// Global variables
matrix g_mWorld; // world matrix for object
matrix g_View; // view matrix
matrix g_Projection; // projection matrix
// Shader pipeline structures
struct VS_OUTPUT
{
float4 Position : POSITION; // vertex position
float4 Color : COLOR0; // vertex diffuse color
};
struct PS_OUTPUT
{
float4 RGBColor : COLOR0; // Pixel color
};
// Vertex shader
VS_OUTPUT RenderSceneVS(float3 vPos : POSITION,
float3 vColor : COLOR0)
{
VS_OUTPUT Output;
float4 pos = float4(vPos, 1.0f);
// Transform the position from object space to homogeneous projection space
pos = mul(pos, g_mWorld);
pos = mul(pos, g_View);
pos = mul(pos, g_Projection);
Output.Position = pos;
// Just pass through the color data
Output.Color = float4(vColor, 1.0f);
return Output;
}
// Pixel shader
PS_OUTPUT RenderScenePS(VS_OUTPUT In)
{
PS_OUTPUT Output;
Output.RGBColor = In.Color;
return Output;
}
// Technique
technique RenderSceneSimple
{
pass P0
{
VertexShader = compile vs_2_0 RenderSceneVS();
PixelShader = compile ps_2_0 RenderScenePS();
}
}
В Direct3D 11 мы по-прежнему можем использовать наши шейдеры HLSL. Мы помещаем каждый шейдер в собственный HLSL-файл, чтобы Visual Studio компилировал их в отдельные файлы и более поздние версии, мы загрузим их в виде отдельных ресурсов Direct3D. Мы устанавливаем целевой уровень на модель 4 уровня 9_1 (/4_0_level_9_1), так как эти шейдеры записываются для GPU DirectX 9.1.
При определении макета входных данных мы убедились, что они представляют ту же структуру данных, которую мы используем для хранения данных на вершинах в системной памяти и в памяти GPU. Аналогичным образом выходные данные вершинного шейдера должны соответствовать структуре, используемой в качестве входных данных для шейдера пикселей. Правила не совпадают с передачей данных из одной функции в другую в C++; В конце структуры можно опустить неиспользуемые переменные. Но порядок не может быть перестроен, и вы не можете пропустить содержимое в середине структуры данных.
Обратите внимание , что правила в Direct3D 9 для привязки шейдеров вершин к шейдерам пикселей были более расслабленными, чем правила в Direct3D 11. Соглашение Direct3D 9 было гибким, но неэффективным.
Возможно, файлы HLSL используют старый синтаксис для семантики шейдера, например COLOR вместо SV_TARGET. Если это так, необходимо включить режим совместимости HLSL (/Gec компилятор) или обновить семантику шейдера до текущего синтаксиса. Шейдер вершин в этом примере обновлен с использованием текущего синтаксиса.
Вот наш аппаратный шейдер преобразования вершин, на этот раз определенный в собственном файле.
Обратите внимание , что шейдеры вершин необходимы для вывода семантики системного значения SV_POSITION. Эта семантика разрешает данные положения вершины для координат значений, где x составляет от -1 до 1, y составляет от -1 до 1, z делится на исходное однородное значение координаты w (z/w), а w — 1, разделенное на исходное значение w (1/w).
Шейдер вершин HLSL (уровень компонента 9.1)
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mWorld; // world matrix for object
matrix View; // view matrix
matrix Projection; // projection matrix
};
struct VS_INPUT
{
float3 vPos : POSITION;
float3 vColor : COLOR0;
};
struct VS_OUTPUT
{
float4 Position : SV_POSITION; // Vertex shaders must output SV_POSITION
float4 Color : COLOR0;
};
VS_OUTPUT main(VS_INPUT input) // main is the default function name
{
VS_OUTPUT Output;
float4 pos = float4(input.vPos, 1.0f);
// Transform the position from object space to homogeneous projection space
pos = mul(pos, mWorld);
pos = mul(pos, View);
pos = mul(pos, Projection);
Output.Position = pos;
// Just pass through the color data
Output.Color = float4(input.vColor, 1.0f);
return Output;
}
Это все, что нам нужно для нашего сквозного шейдера пикселей. Несмотря на то, что мы называем его сквозным, он фактически получает правильные интерполированные данные цвета для каждого пикселя. Обратите внимание, что семантика системного значения SV_TARGET применяется к выходным данным значения цвета нашим шейдером пикселей в соответствии с требованиями API.
Обратите внимание , что уровень шейдера 9_x пиксельных шейдеров нельзя считывать из семантики системного значения SV_POSITION. Шейдеры пикселей модели 4.0 (и выше) могут использовать SV_POSITION для получения расположения пикселей на экране, где x составляет от 0 до ширины целевой отрисовки y от 0 до высоты целевого объекта отрисовки (каждое смещение на 0,5).
Большинство шейдеров пикселей гораздо сложнее, чем сквозь; обратите внимание, что более высокие уровни функций Direct3D позволяют гораздо больше вычислений на программу шейдера.
Шейдер пикселей HLSL (уровень компонентов 9.1)
struct PS_INPUT
{
float4 Position : SV_POSITION; // interpolated vertex position (system value)
float4 Color : COLOR0; // interpolated diffuse color
};
struct PS_OUTPUT
{
float4 RGBColor : SV_TARGET; // pixel color (your PS computes this system value)
};
PS_OUTPUT main(PS_INPUT In)
{
PS_OUTPUT Output;
Output.RGBColor = In.Color;
return Output;
}
Компиляция и загрузка шейдеров
Игры Direct3D 9 часто используют библиотеку эффектов как удобный способ реализации программируемых конвейеров. Эффекты можно скомпилировать во время выполнения с помощью метода функции D3DXCreateEffectFromFile.
Загрузка эффекта в Direct3D 9
// Turn off preshader optimization to keep calculations on the GPU
DWORD dwShaderFlags = D3DXSHADER_NO_PRESHADER;
// Only enable debug info when compiling for a debug target
#if defined (DEBUG) || defined (_DEBUG)
dwShaderFlags |= D3DXSHADER_DEBUG;
#endif
D3DXCreateEffectFromFile(
m_pd3dDevice,
L"CubeShaders.fx",
NULL,
NULL,
dwShaderFlags,
NULL,
&m_pEffect,
NULL
);
Direct3D 11 работает с программами шейдера в виде двоичных ресурсов. Шейдеры компилируются при построении проекта, а затем обрабатываются как ресурсы. Поэтому наш пример загружает байт-код шейдера в системную память, использует интерфейс устройства Direct3D для создания ресурса Direct3D для каждого шейдера и указывает на ресурсы шейдера Direct3D при настройке каждого кадра.
Загрузка ресурса шейдера в Direct3D 11
// BasicReaderWriter is a tested file loader used in SDK samples.
BasicReaderWriter^ readerWriter = ref new BasicReaderWriter();
// Load vertex shader:
Platform::Array<byte>^ vertexShaderData =
readerWriter->ReadData("CubeVertexShader.cso");
// This call allocates a device resource, validates the vertex shader
// with the device feature level, and stores the vertex shader bits in
// graphics memory.
m_d3dDevice->CreateVertexShader(
vertexShaderData->Data,
vertexShaderData->Length,
nullptr,
&m_vertexShader
);
Чтобы включить байт-код шейдера в скомпилированный пакет приложения, просто добавьте файл HLSL в проект Visual Studio. Visual Studio будет использовать средство компилятора эффектов (FXC) для компиляции файлов HLSL в скомпилированные объекты шейдера (). Файлы CSO) и включите их в пакет приложения.
Обратите внимание , что необходимо задать правильный целевой уровень компонентов компилятора HLSL: щелкните правой кнопкой мыши исходный файл HLSL в Visual Studio, выберите "Свойства" и измените параметр модели шейдера в разделе компилятор HLSL —> Общие. Direct3D проверяет это свойство на соответствие аппаратным возможностям, когда приложение создает ресурс шейдера Direct3D.
Это хорошее место для создания входного макета, соответствующего объявлению потока вершин в Direct3D 9. Структура данных для каждой вершины должна соответствовать тому, что использует шейдер вершин; в Direct3D 11 у нас есть больше контроля над макетом входных данных; Можно определить размер массива и длину векторов с плавающей запятой и указать семантику для шейдера вершин. Мы создадим структуру D3D11_INPUT_ELEMENT_DESC и используем ее для информирования Direct3D о том, как будут выглядеть данные по вершинам. Мы ждали, пока не загрузили шейдер вершин, чтобы определить входной макет, так как API проверяет входной макет в ресурсе шейдера вершин. Если входной макет не совместим, Direct3D создает исключение.
Данные для каждой вершины должны храниться в совместимых типах в системной памяти. Типы данных DirectXMath могут помочь; например, DXGI_FORMAT_R32G32B32_FLOAT соответствует XMFLOAT3.
Буферы констант используют фиксированный входной макет, который соответствует четыре числам с плавающей запятой за раз. XMFLOAT4 (и его производных) рекомендуется использовать для данных буфера констант.
Настройка макета ввода в Direct3D 11
// Create input layout:
const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT,
0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT,
0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
Создание геометрических ресурсов
В Direct3D 9 мы храним геометрические ресурсы путем создания буферов на устройстве Direct3D, блокировки памяти и копирования данных из памяти ЦП в память GPU.
Direct3D 9
// Create vertex buffer:
VOID* pVertices;
// In Direct3D 9 we create the buffer, lock it, and copy the data from
// system memory to graphics memory.
m_pd3dDevice->CreateVertexBuffer(
sizeof(CubeVertices),
0,
D3DFVF_XYZ | D3DFVF_DIFFUSE,
D3DPOOL_MANAGED,
&pVertexBuffer,
NULL);
pVertexBuffer->Lock(
0,
sizeof(CubeVertices),
&pVertices,
0);
memcpy(pVertices, CubeVertices, sizeof(CubeVertices));
pVertexBuffer->Unlock();
DirectX 11 следует более простому процессу. API автоматически копирует данные из системной памяти в GPU. Мы можем использовать смарт-указатели COM, чтобы упростить программирование.
DirectX 11
D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
vertexBufferData.pSysMem = CubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC vertexBufferDesc(
sizeof(CubeVertices),
D3D11_BIND_VERTEX_BUFFER);
// This call allocates a device resource for the vertex buffer and copies
// in the data.
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&m_vertexBuffer
);
Реализация цепочки отрисовки
Игры Direct3D 9 часто используют цепочку отрисовки на основе эффектов. Этот тип цепочки отрисовки настраивает объект эффекта, предоставляет ему необходимые ресурсы и позволяет отображать каждый проход.
Цепочка отрисовки Direct3D 9
// Clear the render target and the z-buffer.
m_pd3dDevice->Clear(
0, NULL,
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_ARGB(0, 45, 50, 170),
1.0f, 0
);
// Set the effect technique
m_pEffect->SetTechnique("RenderSceneSimple");
// Rotate the cube 1 degree per frame.
D3DXMATRIX world;
D3DXMatrixRotationY(&world, D3DXToRadian(m_frameCount++));
// Set the matrices up using traditional functions.
m_pEffect->SetMatrix("g_mWorld", &world);
m_pEffect->SetMatrix("g_View", &m_view);
m_pEffect->SetMatrix("g_Projection", &m_projection);
// Render the scene using the Effects library.
if(SUCCEEDED(m_pd3dDevice->BeginScene()))
{
// Begin rendering effect passes.
UINT passes = 0;
m_pEffect->Begin(&passes, 0);
for (UINT i = 0; i < passes; i++)
{
m_pEffect->BeginPass(i);
// Send vertex data to the pipeline.
m_pd3dDevice->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
m_pd3dDevice->SetStreamSource(
0, pVertexBuffer,
0, sizeof(VertexPositionColor)
);
m_pd3dDevice->SetIndices(pIndexBuffer);
// Draw the cube.
m_pd3dDevice->DrawIndexedPrimitive(
D3DPT_TRIANGLELIST,
0, 0, 8, 0, 12
);
m_pEffect->EndPass();
}
m_pEffect->End();
// End drawing.
m_pd3dDevice->EndScene();
}
// Present frame:
// Show the frame on the primary surface.
m_pd3dDevice->Present(NULL, NULL, NULL, NULL);
Цепочка отрисовки DirectX 11 по-прежнему будет выполнять те же задачи, но отрисовка передается по-другому. Вместо того чтобы поместить особенности в FX-файлы и позволить методам отрисовки быть более или менее непрозрачными в коде C++, мы настроим все наши отрисовки в C++.
Вот как будет выглядеть наша цепочка отрисовки. Нам необходимо предоставить входной макет, созданный после загрузки шейдера вершин, указать каждый из объектов шейдера и указать буферы констант для каждого шейдера. В этом примере не содержится несколько проходов отрисовки, но если бы мы сделали аналогичную цепочку отрисовки для каждого прохода, изменив настройку по мере необходимости.
Цепочка отрисовки Direct3D 11
// Clear the back buffer.
const float midnightBlue[] = { 0.098f, 0.098f, 0.439f, 1.000f };
m_d3dContext->ClearRenderTargetView(
m_renderTargetView.Get(),
midnightBlue
);
// Set the render target. This starts the drawing operation.
m_d3dContext->OMSetRenderTargets(
1, // number of render target views for this drawing operation.
m_renderTargetView.GetAddressOf(),
nullptr
);
// Rotate the cube 1 degree per frame.
XMStoreFloat4x4(
&m_constantBufferData.model,
XMMatrixTranspose(XMMatrixRotationY(m_frameCount++ * XM_PI / 180.f))
);
// Copy the updated constant buffer from system memory to video memory.
m_d3dContext->UpdateSubresource(
m_constantBuffer.Get(),
0, // update the 0th subresource
NULL, // use the whole destination
&m_constantBufferData,
0, // default pitch
0 // default pitch
);
// Send vertex data to the Input Assembler stage.
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
m_d3dContext->IASetVertexBuffers(
0, // start with the first vertex buffer
1, // one vertex buffer
m_vertexBuffer.GetAddressOf(),
&stride,
&offset
);
m_d3dContext->IASetIndexBuffer(
m_indexBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0 // no offset
);
m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());
// Set the vertex shader.
m_d3dContext->VSSetShader(
m_vertexShader.Get(),
nullptr,
0
);
// Set the vertex shader constant buffer data.
m_d3dContext->VSSetConstantBuffers(
0, // register 0
1, // one constant buffer
m_constantBuffer.GetAddressOf()
);
// Set the pixel shader.
m_d3dContext->PSSetShader(
m_pixelShader.Get(),
nullptr,
0
);
// Draw the cube.
m_d3dContext->DrawIndexed(
m_indexCount,
0, // start with index 0
0 // start with vertex 0
);
Цепочка буферов является частью графической инфраструктуры, поэтому мы используем нашу цепочку буферов DXGI для представления завершенного кадра. DXGI блокирует вызов до следующей vsync; затем он возвращается, и наш игровой цикл может продолжить следующую итерацию.
Отображение кадра на экране с помощью DirectX 11
m_swapChain->Present(1, 0);
Только что созданная цепочка отрисовки будет вызвана из цикла игры, реализованного в методе IFrameworkView::Run . Это показано в разделе 3. Просмотр и игровой цикл.