Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Tidigare har du tittat på hur du skapar ett fönster som du kan använda för att rita i Arbeta med DirectX-enhetsresurser. Nu får du lära dig hur du skapar grafikpipelinen och var du kan ansluta till den.
Du kommer ihåg att det finns två Direct3D-gränssnitt som definierar grafikpipelinen: ID3D11Enhet, som ger en virtuell representation av GPU:n och dess resurser. och ID3D11DeviceContext, som representerar grafikbearbetningen för pipelinen. Vanligtvis använder du en instans av ID3D11Enhet för att konfigurera och hämta de GPU-resurser som du behöver för att börja bearbeta grafiken i en scen, och du använder ID3D11DeviceContext för att bearbeta dessa resurser i varje lämplig skuggningsfas i grafikpipelinen. Du anropar vanligtvis ID3D11Enhet metoder sällan– det vill säga bara när du konfigurerar en scen eller när enheten ändras. Å andra sidan anropar du ID3D11DeviceContext varje gång du bearbetar en bildruta för visning.
Det här exemplet skapar och konfigurerar en minimal grafikpipeline som lämpar sig för att visa en enkel snurrande, hörnskuggad kub. Den visar ungefär den minsta uppsättning resurser som krävs för visning. När du läser informationen här bör du notera begränsningarna i det angivna exemplet där du kan behöva utöka den för att stödja scenen som du vill återge.
Det här exemplet omfattar två C++-klasser för grafik: en enhetsresurshanterare-klass och 3D-scenåtergivningsklass. Det här avsnittet fokuserar specifikt på 3D-scenåtergivningen.
Vad gör kubrenderaren?
Grafikpipelinen definieras av 3D-scenåtergivningsklassen. Scenrenderaren kan:
- Definiera konstanta buffertar för att lagra dina enhetliga data.
 - Definiera vertexbuffertar för att lagra din objekts vertexdata och motsvarande indexbuffertar för att göra det möjligt för vertexshadern att hantera trianglarna korrekt.
 - Skapa strukturresurser och resursvyer.
 - Läs in skuggningsobjekten.
 - Uppdatera grafikdata så att de visar varje bildruta.
 - Rendera (rita) grafiken till swap-kedjan.
 
De första fyra processerna använder vanligtvis ID3D11Enhet gränssnittsmetoder för att initiera och hantera grafikresurser, och de två sista använder ID3D11DeviceContext gränssnittsmetoder för att hantera och köra grafikpipelinen.
En instans av klassen Renderer skapas och hanteras som en medlemsvariabel i huvudprojektklassen. Instansen DeviceResources hanteras som en delad pekare i flera klasser, inklusive huvudklass för projektet, App vyleverantörsklassen och Renderer. Om du ersätter Renderer- med din egen klass kan du överväga att deklarera och tilldela DeviceResources instans som delad pekarmedlem också:
std::shared_ptr<DX::DeviceResources> m_deviceResources;
Skicka bara pekaren till klasskonstruktorn (eller annan initieringsmetod) när DeviceResources-instansen har skapats i Initiera-metoden för klassen App. Du kan också skicka en weak_ptr referens om du i stället vill att huvudklassen ska helt äga DeviceResources-instansen.
Skapa kubåtergivningen
I det här exemplet organiserar vi scenåtergivningsklassen med följande metoder:
- CreateDeviceDependentResources: Anropas när scenen måste initieras eller startas om. Den här metoden läser in dina inledande hörndata, texturer, skuggningar och andra resurser och konstruerar de inledande konstant- och hörnbuffertarna. Vanligtvis utförs det mesta av arbetet här med ID3D11Enhet metoder, inte ID3D11DeviceContext metoder.
 - CreateWindowSizeDependentResources: Anropas när fönstertillståndet ändras, till exempel när storleksändringen sker eller när orienteringen ändras. Den här metoden återskapar transformeringsmatriser, till exempel de för kameran.
 - Update: Anropas vanligtvis från den del av programmet som hanterar omedelbart speltillstånd. I det här exemplet anropar vi det bara från klassen Main. Låt den här metoden läsas från all speltillståndsinformation som påverkar renderingen, till exempel uppdateringar av objektposition eller animeringsramar, plus alla globala speldata som ljusnivåer eller ändringar i spelfysiken. Dessa indata används för att uppdatera konstantbuffertar per bildruta och objektdata.
 - Rendera: Anropas vanligtvis från den del av programmet som hanterar spelloopen. I det här fallet anropas den från klassen Main. Den här metoden konstruerar grafikpipelinen: den binder skuggningar, binder buffertar och resurser till skuggningssteg och anropar ritning för den aktuella ramen.
 
De här metoderna utgör uppsättningen av rutiner för att rendera en scen med Direct3D med hjälp av dina resurser. Om du utökar det här exemplet med en ny renderingsklass deklarerar du det i huvudprojektklassen. Så här:
std::unique_ptr<Sample3DSceneRenderer> m_sceneRenderer;
blir detta:
std::unique_ptr<MyAwesomeNewSceneRenderer> m_sceneRenderer;
Observera återigen att det här exemplet förutsätter att metoderna har samma signaturer i implementeringen. Om signaturerna har ändrats granskar du loopen Main och gör ändringarna därefter.
Låt oss ta en titt på scenrenderingsmetoder i detalj.
Skapa enhetsberoende resurser
CreateDeviceDependentResources konsoliderar alla åtgärder för att initiera scenen och dess resurser med hjälp av ID3D11Device-anrop. Den här metoden förutsätter att Direct3D-enheten just har initierats (eller har återskapats) för en scen. Den återskapar eller läser in alla scenspecifika grafikresurser, till exempel hörn och pixelskuggar, hörn- och indexbuffertar för objekt och andra resurser (till exempel texturer och motsvarande vyer).
Här är exempelkoden för CreateDeviceDependentResources:
void Renderer::CreateDeviceDependentResources()
{
    // Compile shaders using the Effects library.
    auto CreateShadersTask = Concurrency::create_task(
            [this]( )
            {
                CreateShaders();
            }
        );
    // Load the geometry for the spinning cube.
    auto CreateCubeTask = CreateShadersTask.then(
            [this]()
            {
                CreateCube();
            }
        );
}
void Renderer::CreateWindowSizeDependentResources()
{
    // Create the view matrix and the perspective matrix.
    CreateViewAndPerspective();
}
Varje gång du läser in resurser från disk – resurser som CSO-filer (kompilerade skuggningsobjekt eller .cso)-filer eller texturer – gör det asynkront. På så sätt kan du hålla igång annat arbete samtidigt (som andra installationsuppgifter), och eftersom huvudslingan inte blockeras kan du fortsätta att visa något visuellt intressant för användaren (som en inläsningsanimation för ditt spel). I det här exemplet används api:et Concurrency::Tasks som är tillgängligt från och med Windows 8. Observera lambda-syntaxen som används för att kapsla in asynkrona inläsningsuppgifter. Dessa lambdas representerar funktionerna som anropas i en separat tråd, så en pekare till det aktuella klassobjektet (this) avbildas uttryckligen.
Här är ett exempel på hur du kan läsa in shaderbytekod:
HRESULT hr = S_OK;
// Use the Direct3D device to load resources into graphics memory.
ID3D11Device* device = m_deviceResources->GetDevice();
// You'll need to use a file loader to load the shader bytecode. In this
// example, we just use the standard library.
FILE* vShader, * pShader;
BYTE* bytes;
size_t destSize = 4096;
size_t bytesRead = 0;
bytes = new BYTE[destSize];
fopen_s(&vShader, "CubeVertexShader.cso", "rb");
bytesRead = fread_s(bytes, destSize, 1, 4096, vShader);
hr = device->CreateVertexShader(
    bytes,
    bytesRead,
    nullptr,
    &m_pVertexShader
    );
D3D11_INPUT_ELEMENT_DESC iaDesc [] =
{
    { "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 },
};
hr = device->CreateInputLayout(
    iaDesc,
    ARRAYSIZE(iaDesc),
    bytes,
    bytesRead,
    &m_pInputLayout
    );
delete bytes;
bytes = new BYTE[destSize];
bytesRead = 0;
fopen_s(&pShader, "CubePixelShader.cso", "rb");
bytesRead = fread_s(bytes, destSize, 1, 4096, pShader);
hr = device->CreatePixelShader(
    bytes,
    bytesRead,
    nullptr,
    m_pPixelShader.GetAddressOf()
    );
delete bytes;
CD3D11_BUFFER_DESC cbDesc(
    sizeof(ConstantBufferStruct),
    D3D11_BIND_CONSTANT_BUFFER
    );
hr = device->CreateBuffer(
    &cbDesc,
    nullptr,
    m_pConstantBuffer.GetAddressOf()
    );
fclose(vShader);
fclose(pShader);
Här är ett exempel på hur du skapar hörn- och indexbuffertar:
HRESULT Renderer::CreateCube()
{
    HRESULT hr = S_OK;
    // Use the Direct3D device to load resources into graphics memory.
    ID3D11Device* device = m_deviceResources->GetDevice();
    // Create cube geometry.
    VertexPositionColor CubeVertices[] =
    {
        {DirectX::XMFLOAT3(-0.5f,-0.5f,-0.5f), DirectX::XMFLOAT3(  0,   0,   0),},
        {DirectX::XMFLOAT3(-0.5f,-0.5f, 0.5f), DirectX::XMFLOAT3(  0,   0,   1),},
        {DirectX::XMFLOAT3(-0.5f, 0.5f,-0.5f), DirectX::XMFLOAT3(  0,   1,   0),},
        {DirectX::XMFLOAT3(-0.5f, 0.5f, 0.5f), DirectX::XMFLOAT3(  0,   1,   1),},
        {DirectX::XMFLOAT3( 0.5f,-0.5f,-0.5f), DirectX::XMFLOAT3(  1,   0,   0),},
        {DirectX::XMFLOAT3( 0.5f,-0.5f, 0.5f), DirectX::XMFLOAT3(  1,   0,   1),},
        {DirectX::XMFLOAT3( 0.5f, 0.5f,-0.5f), DirectX::XMFLOAT3(  1,   1,   0),},
        {DirectX::XMFLOAT3( 0.5f, 0.5f, 0.5f), DirectX::XMFLOAT3(  1,   1,   1),},
    };
    
    // Create vertex buffer:
    
    CD3D11_BUFFER_DESC vDesc(
        sizeof(CubeVertices),
        D3D11_BIND_VERTEX_BUFFER
        );
    D3D11_SUBRESOURCE_DATA vData;
    ZeroMemory(&vData, sizeof(D3D11_SUBRESOURCE_DATA));
    vData.pSysMem = CubeVertices;
    vData.SysMemPitch = 0;
    vData.SysMemSlicePitch = 0;
    hr = device->CreateBuffer(
        &vDesc,
        &vData,
        &m_pVertexBuffer
        );
    // Create index buffer:
    unsigned short CubeIndices [] = 
    {
        0,2,1, // -x
        1,2,3,
        4,5,6, // +x
        5,7,6,
        0,1,5, // -y
        0,5,4,
        2,6,7, // +y
        2,7,3,
        0,4,6, // -z
        0,6,2,
        1,3,7, // +z
        1,7,5,
    };
    m_indexCount = ARRAYSIZE(CubeIndices);
    CD3D11_BUFFER_DESC iDesc(
        sizeof(CubeIndices),
        D3D11_BIND_INDEX_BUFFER
        );
    D3D11_SUBRESOURCE_DATA iData;
    ZeroMemory(&iData, sizeof(D3D11_SUBRESOURCE_DATA));
    iData.pSysMem = CubeIndices;
    iData.SysMemPitch = 0;
    iData.SysMemSlicePitch = 0;
    
    hr = device->CreateBuffer(
        &iDesc,
        &iData,
        &m_pIndexBuffer
        );
    return hr;
}
Det här exemplet läser inte in några nät eller texturer. Du måste skapa metoder för att läsa in de nät- och strukturtyper som är specifika för ditt spel och anropa dem asynkront.
Fyll i initiala värden för dina konstanta buffertar per scen även här. Exempel på konstant buffert per scen är fasta lampor eller andra statiska scenelement och data.
Implementera metoden CreateWindowSizeDependentResources
CreateWindowSizeDependentResources metoder anropas varje gång fönstrets storlek, orientering eller upplösning ändras.
Resurser för fönsterstorlek uppdateras enligt följande: Den statiska meddelandehanteraren får en av flera möjliga händelser som indikerar en ändring av fönstrets tillstånd. Huvudloopen informeras sedan om händelsen och anropar CreateWindowSizeDependentResources på huvudklassinstansen, som i sin tur anropar CreateWindowSizeDependentResources implementationen på scenrendreringsklassen.
Det primära jobbet för den här metoden är att se till att de visuella objekten inte blir förvirrade eller ogiltiga på grund av en ändring i fönsteregenskaperna. I det här exemplet uppdaterar vi projektmatriserna med ett nytt vyfält (FOV) för det storleksanpassade eller omorienterade fönstret.
Vi har redan sett koden för att skapa fönsterresurser i DeviceResources – nämligen swapkedjan (med bakbuffer) och rendermålvy. Så här skapar renderaren proportionsberoende transformeringar:
void Renderer::CreateViewAndPerspective()
{
    // Use DirectXMath to create view and perspective matrices.
    DirectX::XMVECTOR eye = DirectX::XMVectorSet(0.0f, 0.7f, 1.5f, 0.f);
    DirectX::XMVECTOR at  = DirectX::XMVectorSet(0.0f,-0.1f, 0.0f, 0.f);
    DirectX::XMVECTOR up  = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.f);
    DirectX::XMStoreFloat4x4(
        &m_constantBufferData.view,
        DirectX::XMMatrixTranspose(
            DirectX::XMMatrixLookAtRH(
                eye,
                at,
                up
                )
            )
        );
    float aspectRatioX = m_deviceResources->GetAspectRatio();
    float aspectRatioY = aspectRatioX < (16.0f / 9.0f) ? aspectRatioX / (16.0f / 9.0f) : 1.0f;
    DirectX::XMStoreFloat4x4(
        &m_constantBufferData.projection,
        DirectX::XMMatrixTranspose(
            DirectX::XMMatrixPerspectiveFovRH(
                2.0f * std::atan(std::tan(DirectX::XMConvertToRadians(70) * 0.5f) / aspectRatioY),
                aspectRatioX,
                0.01f,
                100.0f
                )
            )
        );
}
Om din scen har en specifik layout av komponenter som är beroende av proportionerna, är det här platsen för att ordna om dem så att de matchar det proportionerna. Du kanske också vill ändra konfigurationen av efterbearbetningsbeteendet här.
Implementera uppdateringsmetoden
Metoden Update anropas en gång per spelloop . I det här exemplet anropas den av huvudklassens metod med samma namn. Den har ett enkelt syfte: uppdatera scengeometri och speltillstånd baserat på hur lång tid som gått (eller förflutit tidssteg) sedan föregående bildruta. I det här exemplet roterar vi bara kuben en gång per bildruta. I en riktig spelscen innehåller den här metoden mycket mer kod för att kontrollera speltillstånd, uppdatera konstanta buffertar per bildruta (eller andra dynamiska) buffertar, geometribuffertar och andra minnesinterna tillgångar i enlighet därmed. Eftersom kommunikationen mellan processorn och GPU:n medför kostnader måste du bara uppdatera buffertar som faktiskt har ändrats sedan den senaste ramen – dina konstanta buffertar kan grupperas eller delas upp efter behov för att göra detta mer effektivt.
void Renderer::Update()
{
    // Rotate the cube 1 degree per frame.
    DirectX::XMStoreFloat4x4(
        &m_constantBufferData.world,
        DirectX::XMMatrixTranspose(
            DirectX::XMMatrixRotationY(
                DirectX::XMConvertToRadians(
                    (float) m_frameCount++
                    )
                )
            )
        );
    if (m_frameCount == MAXUINT)  m_frameCount = 0;
}
I det här fallet uppdaterar Rotera konstantbufferten med en ny transformeringsmatris för kuben. Matrisen multipliceras per hörn under hörnskuggningssteget. Eftersom den här metoden anropas med varje bildruta är det här ett bra ställe att aggregera alla metoder som uppdaterar dina dynamiska konstant- och hörnbuffertar, eller att utföra andra åtgärder som förbereder objekten i scenen för transformering av grafikpipelinen.
Implementera återgivningsmetoden
Den här metoden anropas en gång per spel-loop efter att Updatehar anropats. Precis som Updateanropas även metoden Render från huvudklassen. Det här är metoden där grafikpipelinen konstrueras och bearbetas för bildrutan genom att använda metoder på ID3D11DeviceContext-instansen. Detta kulminerar i ett sista anrop till ID3D11DeviceContext::DrawIndexed. Det är viktigt att förstå att det här anropet (eller andra liknande Draw*-anrop som definierats på ID3D11DeviceContext) faktiskt kör pipelinen. Närmare bestämt är det när Direct3D kommunicerar med GPU:n för att ange ritningstilstånd, kör varje pipelinesteg och skriver pixelresultatet till render-target-buffertresursen för visning genom växlingskedjan. Eftersom kommunikationen mellan CPU och GPU medför kostnader kombinerar du flera anrop till ett enda om du kan, särskilt om din scen har många renderade objekt.
void Renderer::Render()
{
    // Use the Direct3D device context to draw.
    ID3D11DeviceContext* context = m_deviceResources->GetDeviceContext();
    ID3D11RenderTargetView* renderTarget = m_deviceResources->GetRenderTarget();
    ID3D11DepthStencilView* depthStencil = m_deviceResources->GetDepthStencil();
    context->UpdateSubresource(
        m_pConstantBuffer.Get(),
        0,
        nullptr,
        &m_constantBufferData,
        0,
        0
        );
    // Clear the render target and the z-buffer.
    const float teal [] = { 0.098f, 0.439f, 0.439f, 1.000f };
    context->ClearRenderTargetView(
        renderTarget,
        teal
        );
    context->ClearDepthStencilView(
        depthStencil,
        D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,
        1.0f,
        0);
    // Set the render target.
    context->OMSetRenderTargets(
        1,
        &renderTarget,
        depthStencil
        );
    // Set up the IA stage by setting the input topology and layout.
    UINT stride = sizeof(VertexPositionColor);
    UINT offset = 0;
    context->IASetVertexBuffers(
        0,
        1,
        m_pVertexBuffer.GetAddressOf(),
        &stride,
        &offset
        );
    context->IASetIndexBuffer(
        m_pIndexBuffer.Get(),
        DXGI_FORMAT_R16_UINT,
        0
        );
    
    context->IASetPrimitiveTopology(
        D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
        );
    context->IASetInputLayout(m_pInputLayout.Get());
    // Set up the vertex shader stage.
    context->VSSetShader(
        m_pVertexShader.Get(),
        nullptr,
        0
        );
    context->VSSetConstantBuffers(
        0,
        1,
        m_pConstantBuffer.GetAddressOf()
        );
    // Set up the pixel shader stage.
    context->PSSetShader(
        m_pPixelShader.Get(),
        nullptr,
        0
        );
    // Calling Draw tells Direct3D to start sending commands to the graphics device.
    context->DrawIndexed(
        m_indexCount,
        0,
        0
        );
}
Det är bra att ange de olika grafikpipeline-stegen i rätt ordning. Vanligtvis är ordningen:
- Uppdatera konstanta buffertresurser med nya data efter behov (med hjälp av data från Update).
 - Indatasammansättning (IA): Här ansluter vi vertex- och indexbuffertar som definierar scengeometrin. Du måste bifoga varje hörn- och indexbuffert för varje objekt i scenen. Eftersom det här exemplet bara har kuben är det ganska enkelt.
 - Hörnskuggning (VS): Koppla alla hörnskuggor som transformerar data i hörnbuffertarna och koppla konstanta buffertar för hörnskuggningen.
 - Pixelskuggning (PS): Koppla alla pixelskuggare som utför åtgärder per pixel i den rastrerade scenen och koppla enhetsresurser för pixelskuggningen (konstanta buffertar, texturer och så vidare).
 - Utdatasammanslagning (OM): Det här är fasen där bildpunkter blandas, när skuggningarna har slutförts. Det här är ett undantag från regeln eftersom du bifogar dina djupstenciler och renderar mål innan du anger något av de andra stegen. Du kan ha flera stenciler och mål om du har ytterligare hörn- och pixelskuggor som genererar texturer som skuggkartor, höjdkartor eller andra samplingstekniker. I det här fallet behöver varje ritningspass rätt måluppsättning innan du anropar en ritningsfunktion.
 
I det sista avsnittet (Arbeta med skuggnings- och skuggningsresurser) ska vi titta på skuggningarna och diskutera hur Direct3D kör dem.
Relaterade ämnen