转换呈现框架

总结

介绍如何将简单的呈现框架从 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 会根据硬件功能检查此属性。

 

hlsl 着色器属性hlsl 着色器类型

这是创建输入布局的好位置,它对应于 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 部分显示 :视区和游戏循环