Freigeben über


Konvertieren des Renderingframeworks

Zusammenfassung

Zeigt, wie Sie ein einfaches Renderingframework von Direct3D 9 in Direct3D 11 konvertieren, einschließlich der Portierung von Geometriepuffern, zum Kompilieren und Laden von HLSL-Shaderprogrammen und zum Implementieren der Renderingkette in Direct3D 11. Teil 2 der exemplarischen Vorgehensweise zum Portieren einer einfachen Direct3D 9-App zu DirectX 11 und Universelle Windows-Plattform (UWP).

Konvertieren von Effekten in HLSL-Shader

Das folgende Beispiel ist eine einfache D3DX-Technik, die für die legacy Effects-API geschrieben wurde, für die Hardwarevertextransformation und Pass-Through-Farbdaten.

Direct3D 9-Shadercode

// 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(); 
    }
}

In Direct3D 11 können wir weiterhin unsere HLSL-Shader verwenden. Wir fügen jeden Shader in eine eigene HLSL-Datei ein, sodass Visual Studio sie in separate Dateien kompiliert und später als separate Direct3D-Ressourcen lädt. Wir legen die Zielebene auf Shadermodell 4 Ebene 9_1 (/4_0_level_9_1) fest, da diese Shader für DirectX 9.1-GPUs geschrieben werden.

Beim Definieren des Eingabelayouts haben wir sichergestellt, dass sie die gleiche Datenstruktur darstellt, die wir zum Speichern von Daten pro Vertex im Systemspeicher und im GPU-Speicher verwenden. Entsprechend sollte die Ausgabe eines Vertex-Shaders mit der Struktur übereinstimmen, die als Eingabe für den Pixelshader verwendet wird. Die Regeln entsprechen nicht dem Übergeben von Daten von einer Funktion an eine andere in C++; Sie können nicht verwendete Variablen am Ende der Struktur weglassen. Die Reihenfolge kann jedoch nicht neu angeordnet werden, und Sie können inhalte nicht in der Mitte der Datenstruktur überspringen.

Beachten Sie, dass die Regeln in Direct3D 9 zum Binden von Vertex-Shadern an Pixelshader entspannter waren als die Regeln in Direct3D 11. Die Direct3D 9-Anordnung war flexibel, aber ineffizient.

 

Es ist möglich, dass Ihre HLSL-Dateien eine ältere Syntax für die Shadersemantik verwenden , z. B. COLOR anstelle von SV_TARGET. Wenn ja, müssen Sie den HLSL-Kompatibilitätsmodus (/Gec-Compileroption) aktivieren oder die Shadersemantik auf die aktuelle Syntax aktualisieren. Der Vertex-Shader in diesem Beispiel wurde mit der aktuellen Syntax aktualisiert.

Hier ist unser Hardwaretransformations-Vertex-Shader, dieses Mal in einer eigenen Datei definiert.

Beachten Sie, dass Vertex-Shader erforderlich sind, um die SV_POSITION Systemwertsemantik auszugeben. Diese Semantik löst die Vertexpositionsdaten in Koordinatenwerte auf, wobei x zwischen -1 und 1 liegt, y zwischen -1 und 1 liegt, z wird durch den ursprünglichen homogenen Koordinatenwert w (z/w) dividiert, und w ist 1 dividiert durch den ursprünglichen w-Wert (1/w).

 

HLSL-Vertex-Shader (Featureebene 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;
}

Dies ist alles, was wir für unseren Pass-Through-Pixelshader benötigen. Obwohl wir es als Pass-Through bezeichnen, erhält es tatsächlich perspektivisch korrekte interpolierte Farbdaten für jedes Pixel. Beachten Sie, dass die SV_TARGET Systemwertsemantik auf die Farbwertausgabe von unserem Pixelshader angewendet wird, wie dies für die API erforderlich ist.

Hinweis : Shaderebene 9_x Pixelshader können nicht aus der SV_POSITION Systemwertsemantik gelesen werden. Modell 4.0 (und höher) Pixelshader können SV_POSITION verwenden, um die Pixelposition auf dem Bildschirm abzurufen, wobei x zwischen 0 und der Renderzielbreite und y zwischen 0 und der Höhe des Renderziels liegt (jeder Offset um 0,5).

 

Die meisten Pixelshader sind viel komplexer als ein Durchlauf; Beachten Sie, dass höhere Direct3D-Featureebenen eine wesentlich größere Anzahl von Berechnungen pro Shaderprogramm ermöglichen.

HLSL-Pixelshader (Featureebene 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;
}

Kompilieren und Laden von Shadern

Direct3D 9-Spiele verwendeten die Effektbibliothek häufig als bequeme Möglichkeit zum Implementieren programmierbarer Pipelines. Effekte können zur Laufzeit mithilfe der D3DXCreateEffectFromFile-Funktionsmethode kompiliert werden.

Laden eines Effekts in 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 funktioniert mit Shaderprogrammen als binäre Ressourcen. Shader werden kompiliert, wenn das Projekt erstellt und dann als Ressourcen behandelt wird. Daher lädt unser Beispiel das Shaderbytecode in den Systemspeicher, verwenden Sie die Direct3D-Geräteschnittstelle, um eine Direct3D-Ressource für jeden Shader zu erstellen, und zeigen Sie auf die Direct3D-Shaderressourcen, wenn wir jeden Frame einrichten.

Laden einer Shaderressource in 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
    );

Um den Shaderbytecode in Das kompilierte App-Paket einzuschließen, fügen Sie einfach die HLSL-Datei zum Visual Studio-Projekt hinzu. Visual Studio verwendet das Effektcompilertool (FXC), um HLSL-Dateien in kompilierte Shaderobjekte zu kompilieren (. CSO-Dateien) und fügen sie in das App-Paket ein.

Hinweis: Achten Sie darauf, die richtige Zielfeatureebene für den HLSL-Compiler festzulegen: Klicken Sie mit der rechten Maustaste in Visual Studio auf die HLSL-Quelldatei, wählen Sie "Eigenschaften" aus, und ändern Sie die Shadermodelleinstellung unter HLSL-Compiler –> Allgemein. Direct3D überprüft diese Eigenschaft anhand der Hardwarefunktionen, wenn Ihre App die Direct3D-Shaderressource erstellt.

 

hlsl-Shadereigenschaftenhlsl-Shadertyp

Dies ist ein guter Ort zum Erstellen des Eingabelayouts, das der Vertexstreamdeklaration in Direct3D 9 entspricht. Die Datenstruktur pro Scheitelpunkt muss dem entsprechen, was der Vertex-Shader verwendet; in Direct3D 11 haben wir mehr Kontrolle über das Eingabelayout; wir können die Arraygröße und Bitlänge von Gleitkommavektoren definieren und Semantik für den Vertex-Shader angeben. Wir erstellen eine D3D11_INPUT_ELEMENT_DESC Struktur und verwenden sie, um Direct3D darüber zu informieren, wie die Daten pro Vertex aussehen. Wir haben gewartet, bis der Vertex-Shader geladen wurde, um das Eingabelayout zu definieren, da die API das Eingabelayout für die Vertex-Shaderressource überprüft. Wenn das Eingabelayout nicht kompatibel ist, löst Direct3D eine Ausnahme aus.

Daten pro Scheitelpunkt müssen in kompatiblen Typen im Systemspeicher gespeichert werden. DirectXMath-Datentypen können ihnen helfen; beispielsweise entspricht DXGI_FORMAT_R32G32B32_FLOAT XMFLOAT3.

Note Constant buffers use a fixed input layout that aligns to four floating-point numbers at a time. XMFLOAT4 (und deren Ableitungen) werden für Konstantenpufferdaten empfohlen.

 

Festlegen des Eingabelayouts in 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 },
};

Erstellen von Geometrieressourcen

In Direct3D 9 wurden Geometrieressourcen gespeichert, indem Puffer auf dem Direct3D-Gerät erstellt werden, der Speicher gesperrt und Daten aus dem CPU-Speicher in den GPU-Speicher kopiert werden.

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 folgt einem einfacheren Prozess. Die API kopiert die Daten automatisch aus dem Systemspeicher in die GPU. Wir können intelligente COM-Zeiger verwenden, um die Programmierung zu vereinfachen.

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

Implementieren der Renderingkette

Direct3D 9-Spiele verwendeten häufig eine effektbasierte Renderingkette. Diese Art von Renderingkette richtet das Effektobjekt ein, stellt es die benötigten Ressourcen bereit und ermöglicht es, jeden Durchlauf zu rendern.

Direct3D 9-Renderingkette

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

Die DirectX 11-Renderingkette führt weiterhin dieselben Aufgaben aus, aber die Renderingdurchläufe müssen anders implementiert werden. Anstatt die Besonderheiten in FX-Dateien einzufügen und die Renderingtechniken für unseren C++-Code mehr oder weniger undurchsichtig zu machen, richten wir das gesamte Rendering in C++ ein.

Hier erfahren Sie, wie unsere Renderingkette aussehen wird. Wir müssen das Eingabelayout angeben, das wir nach dem Laden des Vertex-Shaders erstellt haben, die einzelnen Shaderobjekte bereitstellen und die Konstantenpuffer für jeden zu verwendenden Shader angeben. In diesem Beispiel werden nicht mehrere Renderingdurchläufe eingeschlossen. Wenn es jedoch eine ähnliche Renderingkette für jeden Durchlauf durchgeführt hätte, ändern Sie das Setup bei Bedarf.

Direct3D 11-Renderingkette

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

Die Swapchain ist Teil der Grafikinfrastruktur, daher verwenden wir unsere DXGI-Swapchain, um den fertigen Frame darzustellen. DXGI blockiert den Aufruf bis zur nächsten vsync; dann wird sie zurückgegeben, und unsere Spielschleife kann mit der nächsten Iteration fortfahren.

Darstellen eines Frames auf dem Bildschirm mit DirectX 11

m_swapChain->Present(1, 0);

Die gerade erstellte Renderingkette wird aus einer Spielschleife aufgerufen, die in der IFrameworkView::Run-Methode implementiert ist. Dies wird in Teil 3 gezeigt: Viewport und Spielschleife.