Dela via


Renderingsramverk I: Introduktion till rendering

Anmärkning

Det här avsnittet är en del av Skapa ett enkelt UWP-spel (Universal Windows Platform) med DirectX självstudieserie. Ämnet på länken anger kontexten för serien.

Hittills har vi gått igenom hur du strukturerar ett UWP-spel (Universal Windows Platform) och hur du definierar en tillståndsdator för att hantera flödet i spelet. Nu är det dags att lära sig att utveckla renderingsramverket. Låt oss titta på hur exempelspelet renderar spelscenen med Direct3D 11.

Direct3D 11 innehåller en uppsättning API:er som ger åtkomst till avancerade funktioner i grafikmaskinvara med höga prestanda som kan användas för att skapa 3D-grafik för grafikintensiva program som spel.

Rendering av spelgrafik på skärmen innebär i princip återgivning av en sekvens med bildrutor på skärmen. I varje bildruta måste du återge objekt som är synliga i scenen, baserat på vyn.

För att kunna återge en ram måste du skicka den nödvändiga sceninformationen till maskinvaran så att den kan visas på skärmen. Om du vill att något ska visas på skärmen måste du påbörja rendering så fort spelet börjar köras.

Målsättningar

Konfigurera ett grundläggande renderingsramverk för att visa grafikutdata för ett UWP DirectX-spel. Du kan löst dela upp det i dessa tre steg.

  1. Upprätta en anslutning till grafikgränssnittet.
  2. Skapa de resurser som behövs för att rita grafiken.
  3. Visa grafiken genom att rendera ramen.

Det här avsnittet beskriver hur grafik återges och omfattar steg 1 och 3.

Renderingsramverk II: Spelrendering omfattar steg 2 – hur du konfigurerar renderingsramverket och hur data förbereds innan renderingen kan ske.

Kom igång

Det är en bra idé att bekanta dig med grundläggande grafik och renderingsbegrepp. Om du är nybörjare på Direct3D och rendering kan du läsa Termer och begrepp för en kort beskrivning av de grafik- och renderingsvillkor som används i det här avsnittet.

För det här spelet representerar klassen GameRenderer renderaren för det här exempelspelet. Den ansvarar för att skapa och underhålla alla Direct3D 11- och Direct2D-objekt som används för att generera de visuella spelobjekten. Den har också en referens till Simple3DGame--objektet som används för att hämta listan över objekt som ska renderas, samt spelets status för huvuddisplayen (HUD).

I den här delen av självstudien fokuserar vi på att återge 3D-objekt i spelet.

Upprätta en anslutning till grafikgränssnittet

Information om hur du kommer åt maskinvaran för rendering finns i avsnittet Definiera spelets UWP-appramverk .

Metoden App::Initialize

Funktionen std::make_shared, som visas nedan, används för att skapa en shared_ptr till DX::DeviceResources, som också ger åtkomst till enheten.

I Direct3D 11 används en enhet för att allokera och förstöra objekt, rendera primitiver och kommunicera med grafikkortet via grafikdrivrutinen.

void Initialize(CoreApplicationView const& applicationView)
{
    ...

    // At this point we have access to the device. 
    // We can create the device-dependent resources.
    m_deviceResources = std::make_shared<DX::DeviceResources>();
}

Visa grafiken genom att rendera ramen

Spelscenen måste återges när spelet lanseras. Instruktionerna för återgivningen börjar i metoden GameMain::Run enligt nedan.

Det enkla flödet är detta.

  1. Uppdatering
  2. Rendera
  3. Närvarande

GameMain::Run metod

void GameMain::Run()
{
    while (!m_windowClosed)
    {
        if (m_visible) // if the window is visible
        {
            switch (m_updateState)
            {
            ...
            default:
                CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
                Update();
                m_renderer->Render();
                m_deviceResources->Present();
                m_renderNeeded = false;
            }
        }
        else
        {
            CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }
    m_game->OnSuspending();  // Exiting due to window close, so save state.
}

Uppdatera

Mer information om hur speltillstånd uppdateras i metoden GameMain::Update finns i avsnittet Om hantering av spelflöden.

Rendera

Rendering implementeras genom att anropa metoden GameRenderer::Render från GameMain::Run.

Om stereorendering är aktiverat finns det två återgivningspass – en för vänster öga och en för höger. I varje återgivningspass binder vi återgivningsmålet och djupstencilvyn till enheten. Vi rensar också djupstencilvyn efteråt.

Anmärkning

Stereorendering kan uppnås med hjälp av andra metoder, till exempel enkelpass stereo med vertexinstansering eller geometri-shaders. Metoden med två återgivningspass är ett långsammare men bekvämare sätt att uppnå stereorendering.

När spelet körs och resurserna har lästs in uppdaterar vi projektionsmatrisen en gång per renderingspass. Objekt skiljer sig något från varje vy. Därefter konfigurerar vi pipelinen för grafikrendering.

Anmärkning

Mer information om hur resurser läses in finns i Skapa och läsa in DirectX-grafiska resurser .

I det här exempelspelet är renderaren utformad för att använda en standardhörnlayout över alla objekt. Detta förenklar skuggningsdesignen och möjliggör enkla ändringar mellan skuggningar, oberoende av objektens geometri.

GameRenderer::Render-metod

Vi ställer in Direct3D-kontexten så att den använder en vertex-ingångslayout. Indatalayoutobjekt representerar hur vertexbuffertdata strömmas till återgivningspipeline.

Därefter ställer vi in Direct3D-kontexten så att den använder de konstanta buffertar som definierades tidigare, som används av vertexshader pipeline-steg och pixelshader pipeline-steg.

Anmärkning

Mer information om definitionen av konstanta buffertar finns i Rendering framework II: Game rendering .

Eftersom samma indatalayout och uppsättning konstanta buffertar används för alla skuggningar som finns i pipelinen, konfigureras den en gång per bildruta.

void GameRenderer::Render()
{
    bool stereoEnabled{ m_deviceResources->GetStereoState() };

    auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
    auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };

    int renderingPasses = 1;
    if (stereoEnabled)
    {
        renderingPasses = 2;
    }

    for (int i = 0; i < renderingPasses; i++)
    {
        // Iterate through the number of rendering passes to be completed.
        // 2 rendering passes if stereo is enabled.
        if (i > 0)
        {
            // Doing the Right Eye View.
            ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetViewRight() };

            // Resets render targets to the screen.
            // OMSetRenderTargets binds 2 things to the device.
            // 1. Binds one render target atomically to the device.
            // 2. Binds the depth-stencil view, as returned by the GetDepthStencilView method, to the device.
            // For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets

            d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());

            // Clears the depth stencil view.
            // A depth stencil view contains the format and buffer to hold depth and stencil info.
            // For more info about depth stencil view, go to: 
            // https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-stencil-view--dsv-
            // A depth buffer is used to store depth information to control which areas of 
            // polygons are rendered rather than hidden from view. To learn more about a depth buffer,
            // go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-buffers
            // A stencil buffer is used to mask pixels in an image, to produce special effects. 
            // The mask determines whether a pixel is drawn or not,
            // by setting the bit to a 1 or 0. To learn more about a stencil buffer,
            // go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/stencil-buffers

            d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);

            // Direct2D -- discussed later
            d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmapRight());
        }
        else
        {
            // Doing the Mono or Left Eye View.
            // As compared to the right eye:
            // m_deviceResources->GetBackBufferRenderTargetView instead of GetBackBufferRenderTargetViewRight
            ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };

            // Same as the Right Eye View.
            d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
            d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);

            // d2d -- Discussed later under Adding UI
            d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmap());
        }

        const float clearColor[4] = { 0.5f, 0.5f, 0.8f, 1.0f };

        // Only need to clear the background when not rendering the full 3D scene since
        // the 3D world is a fully enclosed box and the dynamics prevents the camera from
        // moving outside this space.
        if (i > 0)
        {
            // Doing the Right Eye View.
            d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetViewRight(), clearColor);
        }
        else
        {
            // Doing the Mono or Left Eye View.
            d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), clearColor);
        }

        // Render the scene objects
        if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
        {
            // This section is only used after the game state has been initialized and all device
            // resources needed for the game have been created and associated with the game objects.
            if (stereoEnabled)
            {
                // When doing stereo, it is necessary to update the projection matrix once per rendering pass.

                auto orientation = m_deviceResources->GetOrientationTransform3D();

                ConstantBufferChangeOnResize changesOnResize;
                // Apply either a left or right eye projection, which is an offset from the middle
                XMStoreFloat4x4(
                    &changesOnResize.projection,
                    XMMatrixMultiply(
                        XMMatrixTranspose(
                            i == 0 ?
                            m_game->GameCamera().LeftEyeProjection() :
                            m_game->GameCamera().RightEyeProjection()
                            ),
                        XMMatrixTranspose(XMLoadFloat4x4(&orientation))
                        )
                    );

                d3dContext->UpdateSubresource(
                    m_constantBufferChangeOnResize.get(),
                    0,
                    nullptr,
                    &changesOnResize,
                    0,
                    0
                    );
            }

            // Update variables that change once per frame.
            ConstantBufferChangesEveryFrame constantBufferChangesEveryFrameValue;
            XMStoreFloat4x4(
                &constantBufferChangesEveryFrameValue.view,
                XMMatrixTranspose(m_game->GameCamera().View())
                );
            d3dContext->UpdateSubresource(
                m_constantBufferChangesEveryFrame.get(),
                0,
                nullptr,
                &constantBufferChangesEveryFrameValue,
                0,
                0
                );

            // Set up the graphics pipeline. This sample uses the same InputLayout and set of
            // constant buffers for all shaders, so they only need to be set once per frame.
            // For more info about the graphics or rendering pipeline, see
            // https://learn.microsoft.com/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline

            // IASetInputLayout binds an input-layout object to the input-assembler (IA) stage. 
            // Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
            // Set up the Direct3D context to use this vertex layout. For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
            d3dContext->IASetInputLayout(m_vertexLayout.get());

            // VSSetConstantBuffers sets the constant buffers used by the vertex shader pipeline stage.
            // Set up the Direct3D context to use these constant buffers. For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-vssetconstantbuffers

            ID3D11Buffer* constantBufferNeverChanges{ m_constantBufferNeverChanges.get() };
            d3dContext->VSSetConstantBuffers(0, 1, &constantBufferNeverChanges);
            ID3D11Buffer* constantBufferChangeOnResize{ m_constantBufferChangeOnResize.get() };
            d3dContext->VSSetConstantBuffers(1, 1, &constantBufferChangeOnResize);
            ID3D11Buffer* constantBufferChangesEveryFrame{ m_constantBufferChangesEveryFrame.get() };
            d3dContext->VSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
            ID3D11Buffer* constantBufferChangesEveryPrim{ m_constantBufferChangesEveryPrim.get() };
            d3dContext->VSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);

            // Sets the constant buffers used by the pixel shader pipeline stage. 
            // For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-pssetconstantbuffers

            d3dContext->PSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
            d3dContext->PSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
            ID3D11SamplerState* samplerLinear{ m_samplerLinear.get() };
            d3dContext->PSSetSamplers(0, 1, &samplerLinear);

            for (auto&& object : m_game->RenderObjects())
            {
                // The 3D object render method handles the rendering.
                // For more info, see Primitive rendering below.
                object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
            }
        }

        // Start of 2D rendering
        ...
    }
}

Grundläggande rendering

När du återger scenen loopar du igenom alla objekt som behöver renderas. Stegen nedan upprepas för varje objekt (primitivt).

  • Uppdatera konstantbufferten (m_constantBufferChangesEveryPrim) med modellens världen transformeringsmatris och materialinformation.
  • M_constantBufferChangesEveryPrim innehåller parametrar för varje objekt. Den innehåller transformeringsmatrisen från objekt till värld samt materialegenskaper som färg och spektulär exponent för belysningsberäkningar.
  • Ange Direct3D-kontexten för att använda indatahörnlayouten för mesh-objektdata som ska strömmas till IA-fasen (input-assembler) i renderingspipeline.
  • Ange Direct3D-kontexten för att använda en indexbuffert i IA-fasen. Ange den primitiva informationen: typ, dataordning.
  • Skicka ett anrop för att rita den indexerade, icke-instansbaserade primitiven. Metoden GameObject::Render uppdaterar den primitiva konstanta bufferten med data som är specifika för en viss primitiv. Detta resulterar i ett DrawIndexed-anrop på kontexten för att rita geometrin för varje primitiv. Mer specifikt köar det här anropet kommandon och data till grafikprocessorn (GPU), så som parametriseras av konstanta buffertdata. Varje anrop kör vertexshadern en gång per vertex och sedan pixelshadern en gång för varje pixel i varje triangel i primitiven. Texturerna är en del av det tillstånd som pixelskuggaren använder för att göra återgivningen.

Här är orsakerna till att du använder flera konstanta buffertar.

  • Spelet använder flera konstanta buffertar, men det behöver bara uppdatera dessa buffertar en gång per primitiv. Som tidigare nämnts är konstanta buffertar som indata till skuggningar som körs för varje primitiv. Vissa data är statiska (m_constantBufferNeverChanges); vissa data är konstanta över ramen (m_constantBufferChangesEveryFrame), till exempel kamerans position; och vissa data är specifika för primitiva, till exempel dess färg och texturer (m_constantBufferChangesEveryPrim).
  • Spelåtergivningen separerar dessa indata i olika konstanta buffertar för att optimera den minnesbandbredd som processorn och GPU:n använder. Den här metoden hjälper också till att minimera mängden data som GPU:n behöver hålla reda på. GPU:n har en stor kö med kommandon och varje gång spelet anropar Draw placeras kommandot i kö tillsammans med de data som är associerade med den. När spelet uppdaterar den primitiva konstantbufferten och utfärdar nästa kommando, Draw , så lägger grafikdrivrutinen till detta nästa kommando och de associerade data i kön. Om spelet drar 100 primitiver kan det potentiellt ha 100 kopior av konstanta buffertdata i kön. För att minimera mängden data som spelet skickar till GPU:n använder spelet en separat primitiv konstant buffert som endast innehåller uppdateringarna för varje primitiv.

GameObject::Render-metoden

void GameObject::Render(
    _In_ ID3D11DeviceContext* context,
    _In_ ID3D11Buffer* primitiveConstantBuffer
    )
{
    if (!m_active || (m_mesh == nullptr) || (m_normalMaterial == nullptr))
    {
        return;
    }

    ConstantBufferChangesEveryPrim constantBuffer;

    // Put the model matrix info into a constant buffer, in world matrix.
    XMStoreFloat4x4(
        &constantBuffer.worldMatrix,
        XMMatrixTranspose(ModelMatrix())
        );

    // Check to see which material to use on the object.
    // If a collision (a hit) is detected, GameObject::Render checks the current context, which 
    // indicates whether the target has been hit by an ammo sphere. If the target has been hit, 
    // this method applies a hit material, which reverses the colors of the rings of the target to 
    // indicate a successful hit to the player. Otherwise, it applies the default material 
    // with the same method. In both cases, it sets the material by calling Material::RenderSetup, 
    // which sets the appropriate constants into the constant buffer. Then, it calls 
    // ID3D11DeviceContext::PSSetShaderResources to set the corresponding texture resource for the 
    // pixel shader, and ID3D11DeviceContext::VSSetShader and ID3D11DeviceContext::PSSetShader 
    // to set the vertex shader and pixel shader objects themselves, respectively.

    if (m_hit && m_hitMaterial != nullptr)
    {
        m_hitMaterial->RenderSetup(context, &constantBuffer);
    }
    else
    {
        m_normalMaterial->RenderSetup(context, &constantBuffer);
    }

    // Update the primitive constant buffer with the object model's info.
    context->UpdateSubresource(primitiveConstantBuffer, 0, nullptr, &constantBuffer, 0, 0);

    // Render the mesh.
    // See MeshObject::Render method below.
    m_mesh->Render(context);
}

MeshObject::Render-metod

void MeshObject::Render(_In_ ID3D11DeviceContext* context)
{
    // PNTVertex is a struct. stride provides us the size required for all the mesh data
    // struct PNTVertex
    //{
    //  DirectX::XMFLOAT3 position;
    //  DirectX::XMFLOAT3 normal;
    //  DirectX::XMFLOAT2 textureCoordinate;
    //};
    uint32_t stride{ sizeof(PNTVertex) };
    uint32_t offset{ 0 };

    // Similar to the main render loop.
    // Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
    ID3D11Buffer* vertexBuffer{ m_vertexBuffer.get() };
    context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

    // IASetIndexBuffer binds an index buffer to the input-assembler stage.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetindexbuffer.
    context->IASetIndexBuffer(m_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);

    // Binds information about the primitive type, and data order that describes input data for the input assembler stage.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetprimitivetopology.
    context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Draw indexed, non-instanced primitives. A draw API submits work to the rendering pipeline.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed.
    context->DrawIndexed(m_indexCount, 0, 0);
}

DeviceResources::Present-metod

Vi anropar metoden DeviceResources::P resent för att visa innehållet som vi har placerat i buffertarna.

Vi använder termen växlingskedja för en samling buffertar som används för att visa bildrutor för användaren. Varje gång ett program visar en ny bildruta för visning, tar den första bufferten i växlingskedjan platsen för den visade bufferten. Den här processen kallas växling eller vändning. Mer information finns i Växla kedjor.

  • Metoden IDXGISwapChain1-gränssnittets Present instruerar DXGI- att blockera tills vertikal synkronisering (VSync) äger rum, vilket försätter programmet i viloläge till nästa VSync. Detta säkerställer att du inte slösar bort några cykler på att rendera bilder som aldrig visas på skärmen.
  • Metoden ID3D11DeviceContext3-gränssnittets DiscardView tar bort innehållet i rendera mål. Detta är bara en giltig åtgärd när det befintliga innehållet skrivs över helt. Om smutsiga rektanglar eller rullningsrektanglar används bör det här anropet tas bort.
  • Med samma metod DiscardView tar du bort innehållet i djupstencilen.
  • Metoden HandleDeviceLost används för att hantera scenariot där enheten tas bort. Om enheten har tagits bort antingen genom en frånkoppling eller en drivrutinsuppgradering måste du återskapa alla enhetsresurser. Mer information finns i Hantera enhets borttagna scenarier i Direct3D 11.

Tips/Råd

För att uppnå en jämn bildfrekvens måste du se till att mängden arbete som krävs för att återge en ram passar i tiden mellan VSyncs.

// Present the contents of the swap chain to the screen.
void DX::DeviceResources::Present()
{
    // The first argument instructs DXGI to block until VSync, putting the application
    // to sleep until the next VSync. This ensures we don't waste any cycles rendering
    // frames that will never be displayed to the screen.
    HRESULT hr = m_swapChain->Present(1, 0);

    // Discard the contents of the render target.
    // This is a valid operation only when the existing contents will be entirely
    // overwritten. If dirty or scroll rects are used, this call should be removed.
    m_d3dContext->DiscardView(m_d3dRenderTargetView.get());

    // Discard the contents of the depth stencil.
    m_d3dContext->DiscardView(m_d3dDepthStencilView.get());

    // If the device was removed either by a disconnection or a driver upgrade, we 
    // must recreate all device resources.
    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
    {
        HandleDeviceLost();
    }
    else
    {
        winrt::check_hresult(hr);
    }
}

Nästa steg

I det här avsnittet beskrivs hur grafik återges på skärmen och innehåller en kort beskrivning av några av de återgivningsvillkor som används (nedan). Läs mer om rendering i avsnittet Rendering framework II: Game rendering och lär dig hur du förbereder den data som behövs innan rendering.

Termer och begrepp

Enkel spelscen

En enkel spelscen består av några objekt med flera ljuskällor.

Ett objekts form definieras av en uppsättning X-, Y-, Z-koordinater i rymden. Den faktiska återgivningsplatsen i spelvärlden kan fastställas genom att en transformeringsmatris tillämpas på positionella X-, Y-, Z-koordinater. Det kan också ha en uppsättning strukturkoordinater – du och V – som anger hur ett material ska tillämpas på objektet. Detta definierar objektets ytegenskaper och ger dig möjlighet att se om ett objekt har en grov yta (som en tennisboll) eller en slät glansig yta (som en bowlingboll).

Scen- och objektinformation används av renderingsramverket för att återskapa scenramen efter ram, vilket gör att den blir levande på bildskärmen.

Återgivningspipeline

Återgivningspipelinen är den process genom vilken 3D-sceninformation översätts till en bild som visas på skärmen. I Direct3D 11 är den här pipelinen programmerbar. Du kan anpassa stegen för att stödja dina renderingsbehov. Steg som har vanliga skuggningskärnor kan programmeras med hjälp av programmeringsspråket HLSL. Det kallas även grafikrenderingspipeline, eller helt enkelt pipeline.

För att hjälpa dig att skapa den här pipelinen måste du känna till den här informationen.

Mer information finns i Förstå Direct3D 11-renderingspipelinen och Graphics-pipelinen.

HLSL

HLSL är det övergripande skuggningsspråket för DirectX. Med HLSL kan du skapa C-liknande programmerbara skuggor för Direct3D-pipelinen. Mer information finns i HLSL.

skuggare

En skuggning kan ses som en uppsättning instruktioner som avgör hur ytan på ett objekt visas när det återges. De som programmeras med HLSL kallas HLSL-skuggningar. Källkodsfiler för [HLSL](#hlsl) shaders har filändelsen .hlsl. Dessa skuggningar kan kompileras vid byggtid eller vid körning och ställas in vid körning till lämplig pipelinefas. Ett kompilerat skuggningsobjekt har ett .cso filnamnstillägg.

Direct3D 9-skuggningar kan utformas med skuggningsmodell 1, skuggningsmodell 2 och skuggningsmodell 3. Direct3D 10-skuggningar kan endast utformas på skuggningsmodell 4. Direct3D 11-skuggningar kan utformas på skuggningsmodell 5. Direct3D 11.3 och Direct3D 12 kan utformas på skuggningsmodell 5.1, och Direct3D 12 kan också utformas på skuggningsmodell 6.

Vertex shaders och Pixel shaders

Data går in i grafikpipelinen som en ström av grundelement och bearbetas av olika shaders, till exempel vertex-shaders och pixel-shaders.

Hörnskuggor bearbetar hörn och utför vanligtvis åtgärder som transformeringar, skalning och belysning. Pixelskuggare möjliggör omfattande skuggningstekniker som belysning per bildpunkt och efterbearbetning. Den kombinerar konstanta variabler, texturdata, interpolerade värden per hörn och andra data för att producera utdata per bildpunkt.

Skuggningssteg

En sekvens av dessa olika skuggningar som definierats för att bearbeta den här strömmen av primitiver kallas skuggningssteg i en återgivningspipeline. De faktiska stegen beror på versionen av Direct3D, men omfattar vanligtvis hörn-, geometri- och pixelstegen. Det finns också andra steg, till exempel hull- och domänshader för tessellering och beräkningsshader. Alla dessa steg är helt programmerbara med HLSL. Mer information finns i Grafikpipeline.

Olika skuggningsfilformat

Här är filnamnstilläggen för skuggningskoden.

  • En fil med .hlsl tillägget innehåller [HLSL])(#hlsl) källkod.
  • En fil med .cso tillägget innehåller ett kompilerat skuggningsobjekt.
  • En fil med .h tillägget är en rubrikfil, men i en skuggningskodkontext definierar den här huvudfilen en bytematris som innehåller skuggningsdata.
  • En fil med .hlsli tillägget innehåller formatet för de konstanta buffertarna. I exempelspelet är filen Shaders>ConstantBuffers.hlsli.

Anmärkning

Du bäddar in en shader antingen genom att läsa in en .cso-fil under körning eller genom att lägga till en .h-fil i din körbara kod. Men du skulle inte använda båda för samma skuggning.

Djupare förståelse för DirectX

Direct3D 11 är en uppsättning API:er som kan hjälpa oss att skapa grafik för grafikintensiva program som spel, där vi vill ha ett bra grafikkort för att bearbeta intensiv beräkning. I det här avsnittet beskrivs kortfattat begreppen för direct3D 11-grafikprogrammering: resurs, underresurs, enhet och enhetskontext.

Resurs

Du kan se resurser (även kallade enhetsresurser) som information om hur du renderar ett objekt, till exempel struktur, position eller färg. Resurser tillhandahåller data till pipelinen och definierar vad som återges i din scen. Resurser kan läsas in från spelmedia eller skapas dynamiskt under körning.

En resurs är i själva verket ett område i minnet som kan nås av Direct3D-pipelinen. För att pipelinen ska kunna komma åt minnet effektivt måste data som tillhandahålls till pipelinen (till exempel indatageometri, skuggningsresurser och texturer) lagras i en resurs. Det finns två typer av resurser som alla Direct3D-resurser härleds från: en buffert eller en struktur. Upp till 128 resurser kan vara aktiva för varje pipelinesteg. Mer information finns i Resurser.

Underresurs

Termen underresurs refererar till en delmängd av en resurs. Direct3D kan referera till en hel resurs eller referera till delmängder av en resurs. Mer information finns i Underresurs.

Djupstencil

En djupstencilresurs innehåller formatet och bufferten för att lagra djup- och stencilinformation. Den skapas med hjälp av en strukturresurs. Mer information om hur du skapar en djupstencilresurs finns i Konfigurera Depth-Stencil Funktionalitet. Vi kommer åt resursen depth-stencil via djupstencilvyn som implementeras med hjälp av gränssnittet ID3D11DepthStencilView.

Djupinformation anger vilka områden av polygoner som ligger bakom andra, så att vi kan avgöra vilka som är dolda. Stencilinformation anger vilka pixlar som är maskerade. Den kan användas för att ge specialeffekter eftersom den avgör om en pixel ritas eller inte. anger biten till 1 eller 0.

Mer information finns i djupstencilvy, djupbuffertoch stencilbuffert.

Rendera mål

Ett återgivningsmål är en resurs som vi kan skriva till i slutet av ett renderingspass. Den skapas ofta genom att använda metoden ID3D11Device::CreateRenderTargetView med swapkedjans back buffer (som också är en resurs) som indataparameter.

Varje återgivningsmål bör också ha en motsvarande djupstencilvy eftersom när vi använder OMSetRenderTargets för att ange återgivningsmålet innan vi använder det, krävs även en djupstencilvy. Vi kommer åt renderingsmålresursen via återgivningsmålvyn som implementeras genom gränssnittet ID3D11RenderTargetView.

Apparat

Du kan föreställa dig en enhet som ett sätt att allokera och förstöra objekt, rendera primitiver och kommunicera med grafikkortet via grafikdrivrutinen.

För en mer exakt förklaring är en Direct3D-enhet återgivningskomponenten i Direct3D. En enhet kapslar in och lagrar återgivningstillståndet, utför transformeringar och belysningsåtgärder och rastrerar en bild till en yta. Mer information finns i Enheter

En enhet representeras av ID3D11Enhetsgränssnittet . Med andra ord representerar gränssnittet ID3D11Enhet ett virtuellt bildskärmskort och används för att skapa resurser som ägs av en enhet.

Det finns olika versioner av ID3D11Enhet. ID3D11Device5 är den senaste versionen och lägger till nya metoder i ID3D11Enhet4. Mer information om hur Direct3D kommunicerar med den underliggande maskinvaran finns i Arkitekturen för Windows Device Driver Model (WDDM).

Varje program måste ha minst en enhet. de flesta program skapar bara en. Skapa en enhet för en av maskinvarudrivrutinerna som är installerade på datorn genom att anropa D3D11CreateDevice eller D3D11CreateDeviceAndSwapChain och ange drivrutinstypen med flaggan D3D_DRIVER_TYPE . Varje enhet kan använda en eller flera enhetskontexter, beroende på vilka funktioner som önskas. Mer information finns i D3D11CreateDevice-funktionen.

Enhetssammanhang

En enhetskontext används för att ange pipeline- tillstånd och generera återgivningskommandon med hjälp av de resurser som ägs av en enhet.

Direct3D 11 implementerar två typer av enhetskontexter, en för omedelbar återgivning och den andra för uppskjuten återgivning. båda kontexterna representeras med ett ID3D11DeviceContext-gränssnitt .

Gränssnitten ID3D11DeviceContext har olika versioner. ID3D11DeviceContext4 lägger till nya metoder i ID3D11DeviceContext3.

ID3D11DeviceContext4 introduceras i Windows 10 Creators Update och är den senaste versionen av gränssnittet ID3D11DeviceContext . Program som riktar sig till Windows 10 Creators Update och senare bör använda det här gränssnittet i stället för tidigare versioner. Mer information finns i ID3D11EnhetContext4.

DX::D eviceResources

Klassen DX::DeviceResources finns i DeviceResources.cpp/ filerna och styr alla DirectX-enhetsresurser.

Buffert

En buffertresurs är en samling fullständigt inskrivna data grupperade i element. Du kan använda buffertar för att lagra en mängd olika data, inklusive positionsvektorer, normala vektorer, strukturkoordinater i en brytpunktsbuffert, index i en indexbuffert eller enhetstillstånd. Buffertelement kan innehålla paketerade datavärden (till exempel R8G8B8A8 ytvärden), enkla 8-bitars heltal eller fyra 32-bitars flyttalsvärden.

Det finns tre typer av buffertar: brytpunktsbuffert, indexbuffert och konstant buffert.

Brytpunktsbuffert

Innehåller hörndata som används för att definiera geometrin. Hörndata innehåller positionskoordinater, färgdata, strukturkoordinatdata, normala data och så vidare.

Indexbuffert

Innehåller heltalsförskjutningar i vertexbuffertar och används för att rendera primitiver effektivare. En indexbuffert innehåller en sekventiell uppsättning 16-bitars- eller 32-bitarsindex. varje index används för att identifiera ett hörn i en brytpunktsbuffert.

Konstant buffert eller skuggningskonstant buffert

Gör att du effektivt kan leverera skuggningsdata till pipelinen. Du kan använda konstanta buffertar som indata till shaderprogrammen som körs för varje primitiv och lagra resultat från strömflödesutmatningsfasen i återgivningspipelinen. Konceptuellt ser en konstant buffert ut precis som en hörnbuffert med ett element.

Design och implementering av buffertar

Du kan utforma buffertar baserat på datatypen, till exempel i vårt exempelspel, skapas en buffert för statiska data, en annan för data som är konstanta över ramen och en annan för data som är specifika för en primitiv.

Alla bufferttyper kapslas in av gränssnittet ID3D11Buffer och du kan skapa en buffertresurs genom att anropa ID3D11Enhet::CreateBuffer. Men en buffert måste vara bunden till pipelinen innan den kan nås. Buffertar kan bindas till flera steg i bearbetningskedjan samtidigt för läsning. En buffert kan också bindas till ett enda pipelinesteg för skrivning. Samma buffert kan dock inte bindas till både läsning och skrivning samtidigt.

Du kan binda buffertar på dessa sätt.

  • Till input-assembler-steget genom att anropa ID3D11DeviceContext metoder som ID3D11DeviceContext::IASetVertexBuffers och ID3D11DeviceContext::IASetIndexBuffer.
  • Till stream-output-fasen genom att anropa ID3D11DeviceContext::SOSetTargets.
  • I skuggningsfasen genom att anropa shader-metoder, till exempel ID3D11DeviceContext::VSSetConstantBuffers.

Mer information finns i Introduktion till buffertar i Direct3D 11.

DXGI

Microsoft DirectX Graphics Infrastructure (DXGI) är ett undersystem som kapslar in några av de lågnivåuppgifter som krävs av Direct3D. Särskild försiktighet måste iakttas när du använder DXGI i ett program med flera flöden för att säkerställa att dödlägen inte inträffar. Mer information finns i Multithreading och DXGI

Funktionsnivå

Funktionsnivå är ett begrepp som introduceras i Direct3D 11 för att hantera mångfalden av grafikkort i nya och befintliga datorer. En funktionsnivå är en väldefinierad uppsättning GPU-funktioner (graphics processing unit).

Varje grafikkort implementerar en viss nivå av DirectX-funktioner beroende på vilka GPU:er som är installerade. I tidigare versioner av Microsoft Direct3D kan du ta reda på vilken version av Direct3D grafikkortet implementerade och sedan programmera programmet i enlighet med detta.

När du skapar en enhet med funktionsnivå kan du försöka skapa en enhet för den funktionsnivå som du vill begära. Om skapandet av enheten lyckas, finns den funktionsnivån; om inte, stöder maskinvaran inte den funktionsnivån. Du kan antingen försöka återskapa en enhet på en lägre funktionsnivå eller så kan du välja att avsluta programmet. Funktionsnivån 12_0 kräver till exempel Direct3D 11.3 eller Direct3D 12 och skuggningsmodell 5.1. Mer information finns i Direct3D-funktionsnivåer: Översikt för varje funktionsnivå.

Med hjälp av funktionsnivåer kan du utveckla ett program för Direct3D 9, Microsoft Direct3D 10 eller Direct3D 11 och sedan köra det på maskinvaran 9, 10 eller 11 (med vissa undantag). Mer information finns i Direct3D-funktionsnivåer.

Stereoåtergivning

Stereorendering används för att förbättra illusionen av djup. Den använder två bilder, en från vänster öga och den andra från höger öga för att visa en scen på skärmen.

Matematiskt använder vi en stereoprojektionsmatris, som är en liten vågrät förskjutning till höger och till vänster, av den vanliga monoprojektionsmatrisen för att uppnå detta.

Vi gjorde två renderingspass för att uppnå stereorendering i det här exempelspelet.

  • Bind till rätt återgivningsmål, använd rätt projektion och rita sedan det primitiva objektet.
  • Bind till vänster återgivningsmål, använd vänster projektion och rita sedan det primitiva objektet.

Kamera- och koordinatutrymme

Spelet har koden på plats för att uppdatera världen i sitt eget koordinatsystem (kallas ibland världsrymden eller scenutrymmet). Alla objekt, inklusive kameran, är placerade och orienterade i det här utrymmet. Mer information finns i Koordinatsystem.

En hörnskuggning gör det tunga arbetet med att konvertera från modellkoordinaterna till enhetskoordinater med följande algoritm (där V är en vektor och M är en matris).

V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)

  • M(model-to-world) är en transformeringsmatris för modellkoordinater till världskoordinater, även kallat world transform matrix. Detta tillhandahålls av den grundläggande funktionen.
  • M(world-to-view) är en transformeringsmatris för världskoordinater för att visa koordinater, även kallat View-transformeringsmatrisen.
    • Detta tillhandahålls av kamerans visningsmatris. Det definieras av kamerans position tillsammans med siktvektorerna (blicka mot vektor som pekar direkt in i scenen från kameran och blicka upp vektor som är vinkelrät mot den).
    • I exempelspelet är m_viewMatrix visningstransformeringsmatrisen och beräknas med kamera::SetViewParams.
  • M(view-to-device) är en transformeringsmatris för visningskoordinater till enhetskoordinater, även kallat projektionens transformeringsmatris.
    • Detta tillhandahålls av kamerans projektion. Den innehåller information om hur mycket av det utrymmet som faktiskt visas i slutscenen. Bildfältet (FoV), proportioner och urklippsplan definierar projektionens transformeringsmatris.
    • I exempelspelet definierar m_projectionMatrix transformering till projektionskoordinaterna som beräknas med kamera::SetProjParams (För stereoprojektion använder du två projektionsmatriser – en för varje ögas vy).

Skuggningskoden i VertexShader.hlsl läses in med dessa vektorer och matriser från de konstanta buffertarna och utför den här omvandlingen för varje hörn.

Koordinattransformeringen

Direct3D använder tre transformeringar för att ändra dina 3D-modellkoordinater till pixelkoordinater (skärmutrymme). Dessa transformeringar är världstransformering, vytransformering och projektionstransformering. För mer info, se Transform översikt.

Världstransformationsmatris

En värld transformerar koordinater från modellutrymmet, där hörn definieras i förhållande till en modells lokala ursprung, till världsrymden, där hörn definieras i förhållande till ett ursprung som är gemensamt för alla objekt i en scen. I grund och botten innebär världstransformation att placera en modell i världen; därav namnet. Mer information finns i World transform.

Visa transformeringsmatris

Visningstransformeringen lokaliserar betraktaren i världsrymden och omvandlar vertiklar till kamerarymd. I kamerautrymmet är kameran, eller tittaren, vid ursprunget och tittar i den positiva z-riktningen. Mer information finns i Visa transformering.

Projektionstransformationsmatris

Projektionstransformen konverterar visningsfrustumet till en kubformad form. En visningsfrustum är en 3D-volym i en scen som är placerad i förhållande till viewportens kamera. En vyport är en 2D-rektangel där en 3D-scen projiceras. Mer information finns i Viewports and Clipping

Eftersom den nära änden av visningsfrustumet är mindre än den fjärran änden, har detta effekten att förstora objekt som är nära kameran; det är så perspektiv tillämpas på scenen. Så objekt som är närmare spelaren verkar större; objekt som ligger längre bort verkar mindre.

Matematiskt sett är projektionstransformionen en matris som vanligtvis är både en skalning och en perspektivprojektion. Den fungerar som linsen på en kamera. Mer information finns i Projection transform.

Samplertillstånd

Sampler-tillståndet avgör hur texturdata samplas med hjälp av strukturhanteringslägen, filtrering och detaljnivå. Sampling utförs varje gång en texturpixel (eller texel) läses från en textur.

En textur innehåller en matris med texlar. Positionen för varje texel anges av (u,v), där u är bredden och v är höjden och mappas mellan 0 och 1 baserat på texturbredden och höjden. De resulterande strukturkoordinaterna används för att hantera en texel vid sampling av en textur.

När strukturkoordinaterna är under 0 eller högre än 1 definierar texturadressläget hur strukturkoordinaten adresserar en texelplats. När du till exempel använder TextureAddressMode.Clamp kläms alla koordinater utanför 0-1-intervallet till ett maximalt värde på 1 och minimivärdet 0 före sampling.

Om strukturen är för stor eller för liten för polygonen filtreras strukturen så att den passar utrymmet. Ett förstoringsfilter förstorar en struktur, ett minifieringsfilter minskar strukturen så att den får plats i ett mindre område. Texturförstoring upprepar exempeltexel för en eller flera adresser som ger en suddigare bild. Texturminimering är mer komplicerat eftersom det kräver att fler än ett texelvärde kombineras till ett enda värde. Detta kan orsaka aliasing eller ojämna kanter beroende på texturens data. Den mest populära metoden för minifiering är att använda en mipmap. En mipmap är en struktur på flera nivåer. Storleken på varje nivå är en tvåpotens mindre än den tidigare nivån ned till en 1x1-textur. När minifiering används väljer ett spel den mipmap-nivå som är närmast den storlek som behövs vid återgivningen.

Klassen "BasicLoader"

BasicLoader är en enkel inläsningsklass som ger stöd för inläsning av skuggningar, texturer och nät från filer på disk. Den innehåller både synkrona och asynkrona metoder. I det här exempelspelet BasicLoader.h/.cpp finns filerna i mappen Verktyg .

Mer information finns i Grundläggande inläsare.