转换呈现框架
总结
- 第 1 部分:初始化 Direct3D 11
- 第 2 部分:转换呈现框架
- 第 3 部分:移植游戏循环
介绍如何将简单的呈现框架从 Direct3D 9 转换到 Direct3D 11,包括如何移植几何图形缓冲区、如何编译和加载 HLSL 着色器程序以及如何在 Direct3D 11 中实现呈现链。 将简单的 Direct3D 9 应用移植到 DirectX 11 和 通用 Windows 平台 (UWP) 演练的第 2 部分。
将效果转换为 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),因为这些着色器是为 DirectX 9.1 GPU 编写的。
定义输入布局时,我们确保它表示用于在系统内存和 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;
}
这就是传递像素着色器所需的一切。 尽管我们称之为直通,但它实际上也会为每个像素获取透视正确的内插颜色数据。 请注意,我们的像素着色器会根据 API 的需要将 SV_TARGET 系统值语义应用于颜色值输出。
注意 着色器级别 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 编译器设置正确的目标功能级别:右键单击 Visual Studio 中的 HLSL 源文件,选择“属性”,然后在 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 设备上创建缓冲区、锁定内存并将数据从 CPU 内存复制到 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 呈现链仍将执行相同的任务,但呈现传递需要以不同的方式实现。 我们将设置C++中的所有呈现,而不是将细节放在 FX 文件中,让呈现技术对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 部分显示 :视区和游戏循环。