Condividi tramite


Rendering in DirectX

Nota

Questo articolo si riferisce alle API native WinRT legacy. Per i nuovi progetti di app native, è consigliabile usare l'API OpenXR.

Windows Realtà mista è basato su DirectX per produrre esperienze grafiche avanzate e 3D per gli utenti. L'astrazione di rendering si trova appena sopra DirectX, che consente alle app di ragionarsi sulla posizione e l'orientamento degli osservatori della scena olografica stimati dal sistema. Lo sviluppatore può quindi individuare gli ologrammi in base a ogni fotocamera, consentendo all'app di eseguire il rendering di questi ologrammi in vari sistemi di coordinate spaziali man mano che l'utente si sposta intorno.

Nota: questa procedura dettagliata descrive il rendering olografico in Direct3D 11. Viene fornito anche un modello di app di Windows Realtà mista Direct3D 12 con l'estensione Realtà mista modelli di app.

Aggiornamento per il frame corrente

Per aggiornare lo stato dell'applicazione per gli ologrammi, una volta per ogni fotogramma l'app:

  • Ottenere un HolographicFrame dal sistema di gestione dello schermo.
  • Aggiornare la scena con la stima corrente della posizione in cui verrà completata la visualizzazione della fotocamera. Nota, può essere presente più di una fotocamera per la scena olografica.

Per eseguire il rendering nelle visualizzazioni della fotocamera olografica, una volta per fotogramma l'app:

  • Per ogni fotocamera, eseguire il rendering della scena per il fotogramma corrente, usando la visualizzazione della fotocamera e le matrici di proiezione dal sistema.

Creare un nuovo frame olografico e ottenere la stima

HolographicFrame contiene informazioni che l'app deve aggiornare ed eseguire il rendering del frame corrente. L'app inizia ogni nuovo frame chiamando il metodo CreateNextFrame . Quando questo metodo viene chiamato, le stime vengono eseguite usando i dati del sensore più recenti disponibili e incapsulate nell'oggetto CurrentPrediction .

È necessario utilizzare un nuovo oggetto frame per ogni frame di cui è stato eseguito il rendering perché è valido solo per un istante in tempo. La proprietà CurrentPrediction contiene informazioni quali la posizione della fotocamera. Le informazioni vengono estrapolate al momento esatto in cui si prevede che il fotogramma sia visibile all'utente.

Il codice seguente è estratto da AppMain::Update:

// The HolographicFrame has information that the app needs in order
// to update and render the current frame. The app begins each new
// frame by calling CreateNextFrame.
HolographicFrame holographicFrame = m_holographicSpace.CreateNextFrame();

// Get a prediction of where holographic cameras will be when this frame
// is presented.
HolographicFramePrediction prediction = holographicFrame.CurrentPrediction();

Elaborare gli aggiornamenti della fotocamera

I buffer back possono cambiare da frame a frame. L'app deve convalidare il buffer nascosto per ogni fotocamera e rilasciare e ricreare visualizzazioni delle risorse e buffer di profondità in base alle esigenze. Si noti che il set di pose nella stima è l'elenco autorevole delle fotocamere usate nel fotogramma corrente. In genere, si usa questo elenco per scorrere il set di fotocamere.

Da AppMain::Update:

m_deviceResources->EnsureCameraResources(holographicFrame, prediction);

Da DeviceResources::EnsureCameraResources:

for (HolographicCameraPose const& cameraPose : prediction.CameraPoses())
{
    HolographicCameraRenderingParameters renderingParameters = frame.GetRenderingParameters(cameraPose);
    CameraResources* pCameraResources = cameraResourceMap[cameraPose.HolographicCamera().Id()].get();
    pCameraResources->CreateResourcesForBackBuffer(this, renderingParameters);
}

Ottenere il sistema di coordinate da usare come base per il rendering

Windows Realtà mista consente alla tua app di creare vari sistemi di coordinate, ad esempio frame di riferimento collegati e fissi per tenere traccia delle posizioni nel mondo fisico. L'app può quindi usare questi sistemi di coordinate per determinare dove eseguire il rendering degli ologrammi ogni fotogramma. Quando si richiedono coordinate da un'API, si passerà sempre il SpatialCoordinateSystem all'interno del quale si desidera che tali coordinate vengano espresse.

Da AppMain::Update:

pose = SpatialPointerPose::TryGetAtTimestamp(
    m_stationaryReferenceFrame.CoordinateSystem(), prediction.Timestamp());

Questi sistemi di coordinate possono quindi essere usati per generare matrici di visualizzazione stereo durante il rendering del contenuto nella scena.

Da CameraResources::UpdateViewProjectionBuffer:

// Get a container object with the view and projection matrices for the given
// pose in the given coordinate system.
auto viewTransformContainer = cameraPose.TryGetViewTransform(coordinateSystem);

Elaborare l'input dello sguardo fisso e del movimento

Lo sguardo fisso e l'input della mano non sono basati sul tempo e non devono essere aggiornati nella funzione StepTimer . Tuttavia, questo input è un elemento che l'app deve esaminare ogni fotogramma.

Elaborare gli aggiornamenti basati sul tempo

Qualsiasi app di rendering in tempo reale richiederà un modo per elaborare gli aggiornamenti basati sul tempo: il modello di app Windows Holographic usa un'implementazione di StepTimer, simile a StepTimer fornita nel modello di app UWP DirectX 11. Questa classe helper di esempio StepTimer può fornire aggiornamenti a tempo fisso, aggiornamenti a tempo variabile e la modalità predefinita è passaggi temporali variabili.

Per il rendering olografico, è stato scelto di non inserire troppo nella funzione timer perché è possibile configurarlo come passaggio a tempo fisso. Potrebbe essere chiamato più volte per ogni fotogramma, o non affatto, per alcuni fotogrammi, e gli aggiornamenti dei dati olografici dovrebbero verificarsi una volta per ogni fotogramma.

Da AppMain::Update:

m_timer.Tick([this]()
{
    m_spinningCubeRenderer->Update(m_timer);
});

Posizionare e ruotare gli ologrammi nel sistema di coordinate

Se stai operando in un unico sistema di coordinate, come il modello fa con SpatialStationaryReferenceFrame, questo processo non è diverso da quello che stai altrimenti usato per nella grafica 3D. In questo caso, si ruota il cubo e si imposta la matrice del modello in base alla posizione nel sistema di coordinate stazionarie.

Da SpinningCubeRenderer::Update:

// Rotate the cube.
// Convert degrees to radians, then convert seconds to rotation angle.
const float    radiansPerSecond = XMConvertToRadians(m_degreesPerSecond);
const double   totalRotation = timer.GetTotalSeconds() * radiansPerSecond;
const float    radians = static_cast<float>(fmod(totalRotation, XM_2PI));
const XMMATRIX modelRotation = XMMatrixRotationY(-radians);

// Position the cube.
const XMMATRIX modelTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&m_position));

// Multiply to get the transform matrix.
// Note that this transform does not enforce a particular coordinate system. The calling
// class is responsible for rendering this content in a consistent manner.
const XMMATRIX modelTransform = XMMatrixMultiply(modelRotation, modelTranslation);

// The view and projection matrices are provided by the system; they are associated
// with holographic cameras, and updated on a per-camera basis.
// Here, we provide the model transform for the sample hologram. The model transform
// matrix is transposed to prepare it for the shader.
XMStoreFloat4x4(&m_modelConstantBufferData.model, XMMatrixTranspose(modelTransform));

Nota sugli scenari avanzati: il cubo rotante è un semplice esempio di come posizionare un ologramma all'interno di un singolo frame di riferimento. È anche possibile usare più SpatialCoordinateSystems nello stesso frame sottoposto a rendering contemporaneamente.

Aggiornare i dati del buffer costante

Le trasformazioni del modello per il contenuto vengono aggiornate come di consueto. A questo punto, si avranno calcolate trasformazioni valide per il sistema di coordinate in cui verrà eseguito il rendering.

Da SpinningCubeRenderer::Update:

// Update the model transform buffer for the hologram.
context->UpdateSubresource(
    m_modelConstantBuffer.Get(),
    0,
    nullptr,
    &m_modelConstantBufferData,
    0,
    0
);

Che ne dici delle trasformazioni di visualizzazione e proiezione? Per ottenere risultati ottimali, vogliamo aspettare fino a quando non siamo quasi pronti per le nostre chiamate di disegno prima di ottenere questi.

Eseguire il rendering del frame corrente

Il rendering in Windows Realtà mista non è molto diverso dal rendering in uno schermo mono 2D, ma esistono alcune differenze:

  • Le stime dei fotogrammi olografici sono importanti. Più la stima è più vicina a quando viene presentato il fotogramma, migliore sarà l'aspetto degli ologrammi.
  • Windows Realtà mista controlla le visualizzazioni della fotocamera. Eseguire il rendering in ognuno di essi perché il fotogramma olografico li presenterà in un secondo momento.
  • È consigliabile eseguire il rendering stereo usando il disegno a istanza in una matrice di destinazione di rendering. Il modello di app olografica usa l'approccio consigliato per il disegno a istanza in una matrice di destinazione di rendering, che usa una visualizzazione di destinazione di rendering in un oggetto Texture2DArray.
  • Se si vuole eseguire il rendering senza usare la creazione di istanze stereo, è necessario creare due oggetti RenderTargetView non matrice, uno per ogni occhio. Ogni RenderTargetViews fa riferimento a una delle due sezioni di Texture2DArray fornite all'app dal sistema. Non è consigliabile, perché in genere è più lento rispetto all'uso di istanze.

Ottenere una stima HolographicFrame aggiornata

L'aggiornamento della stima dei fotogrammi migliora l'efficacia della stabilizzazione delle immagini. Si ottiene un posizionamento più accurato degli ologrammi a causa del tempo più breve tra la stima e quando il fotogramma è visibile all'utente. Idealmente, aggiornare la stima dei fotogrammi subito prima del rendering.

holographicFrame.UpdateCurrentPrediction();
HolographicFramePrediction prediction = holographicFrame.CurrentPrediction();

Eseguire il rendering in ogni fotocamera

Eseguire il ciclo sul set di pose della fotocamera nella stima ed eseguire il rendering in ogni fotocamera in questo set.

Configurare il passaggio di rendering

Windows Realtà mista usa il rendering stereoscopico per migliorare l'illusione della profondità e per eseguire il rendering stereoscopico, quindi sia a sinistra che a destra sono attivi. Con il rendering stereoscopico, c'è un offset tra i due schermi, che il cervello può riconciliare come profondità effettiva. Questa sezione illustra il rendering stereoscopico usando la creazione di istanze, usando il codice del modello di app Windows Holographic.

Ogni fotocamera ha una propria destinazione di rendering (buffer nascosto) e matrici di visualizzazione e proiezione nello spazio olografico. L'app dovrà creare qualsiasi altra risorsa basata su fotocamera, ad esempio il buffer di profondità, in base alla fotocamera. Nel modello di app Windows Holographic forniamo una classe helper per raggruppare queste risorse in DX::CameraResources. Per iniziare, configurare le visualizzazioni di destinazione di rendering:

Da AppMain::Render:

// This represents the device-based resources for a HolographicCamera.
DX::CameraResources* pCameraResources = cameraResourceMap[cameraPose.HolographicCamera().Id()].get();

// Get the device context.
const auto context = m_deviceResources->GetD3DDeviceContext();
const auto depthStencilView = pCameraResources->GetDepthStencilView();

// Set render targets to the current holographic camera.
ID3D11RenderTargetView *const targets[1] =
    { pCameraResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, depthStencilView);

// Clear the back buffer and depth stencil view.
if (m_canGetHolographicDisplayForCamera &&
    cameraPose.HolographicCamera().Display().IsOpaque())
{
    context->ClearRenderTargetView(targets[0], DirectX::Colors::CornflowerBlue);
}
else
{
    context->ClearRenderTargetView(targets[0], DirectX::Colors::Transparent);
}
context->ClearDepthStencilView(
    depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

Usare la stima per ottenere le matrici di visualizzazione e proiezione per la fotocamera

Le matrici di visualizzazione e proiezione per ogni fotocamera olografica cambieranno con ogni fotogramma. Aggiornare i dati nel buffer costante per ogni fotocamera olografica. Eseguire questa operazione dopo aver aggiornato la stima e prima di effettuare chiamate di disegno per la fotocamera.

Da AppMain::Render:

// The view and projection matrices for each holographic camera will change
// every frame. This function refreshes the data in the constant buffer for
// the holographic camera indicated by cameraPose.
if (m_stationaryReferenceFrame)
{
    pCameraResources->UpdateViewProjectionBuffer(
        m_deviceResources, cameraPose, m_stationaryReferenceFrame.CoordinateSystem());
}

// Attach the view/projection constant buffer for this camera to the graphics pipeline.
bool cameraActive = pCameraResources->AttachViewProjectionBuffer(m_deviceResources);

Qui mostriamo come le matrici vengono acquisite dalla posizione della fotocamera. Durante questo processo, otteniamo anche il viewport corrente per la fotocamera. Si noti come si fornisce un sistema di coordinate: si tratta dello stesso sistema di coordinate usato per comprendere lo sguardo fisso ed è lo stesso usato per posizionare il cubo rotante.

Da CameraResources::UpdateViewProjectionBuffer:

// The system changes the viewport on a per-frame basis for system optimizations.
auto viewport = cameraPose.Viewport();
m_d3dViewport = CD3D11_VIEWPORT(
    viewport.X,
    viewport.Y,
    viewport.Width,
    viewport.Height
);

// The projection transform for each frame is provided by the HolographicCameraPose.
HolographicStereoTransform cameraProjectionTransform = cameraPose.ProjectionTransform();

// Get a container object with the view and projection matrices for the given
// pose in the given coordinate system.
auto viewTransformContainer = cameraPose.TryGetViewTransform(coordinateSystem);

// If TryGetViewTransform returns a null pointer, that means the pose and coordinate
// system cannot be understood relative to one another; content cannot be rendered
// in this coordinate system for the duration of the current frame.
// This usually means that positional tracking is not active for the current frame, in
// which case it is possible to use a SpatialLocatorAttachedFrameOfReference to render
// content that is not world-locked instead.
DX::ViewProjectionConstantBuffer viewProjectionConstantBufferData;
bool viewTransformAcquired = viewTransformContainer != nullptr;
if (viewTransformAcquired)
{
    // Otherwise, the set of view transforms can be retrieved.
    HolographicStereoTransform viewCoordinateSystemTransform = viewTransformContainer.Value();

    // Update the view matrices. Holographic cameras (such as Microsoft HoloLens) are
    // constantly moving relative to the world. The view matrices need to be updated
    // every frame.
    XMStoreFloat4x4(
        &viewProjectionConstantBufferData.viewProjection[0],
        XMMatrixTranspose(XMLoadFloat4x4(&viewCoordinateSystemTransform.Left) *
            XMLoadFloat4x4(&cameraProjectionTransform.Left))
    );
    XMStoreFloat4x4(
        &viewProjectionConstantBufferData.viewProjection[1],
        XMMatrixTranspose(XMLoadFloat4x4(&viewCoordinateSystemTransform.Right) *
            XMLoadFloat4x4(&cameraProjectionTransform.Right))
    );
}

Il riquadro di visualizzazione deve essere impostato per ogni fotogramma. Il vertex shader (almeno) dovrà in genere accedere ai dati di visualizzazione/proiezione.

Da CameraResources::AttachViewProjectionBuffer:

// Set the viewport for this camera.
context->RSSetViewports(1, &m_d3dViewport);

// Send the constant buffer to the vertex shader.
context->VSSetConstantBuffers(
    1,
    1,
    m_viewProjectionConstantBuffer.GetAddressOf()
);

Eseguire il rendering nel buffer nascosto della fotocamera ed eseguire il commit del buffer di profondità:

È consigliabile verificare che TryGetViewTransform abbia avuto esito positivo prima di provare a usare i dati di visualizzazione/proiezione, perché se il sistema di coordinate non è locabile (ad esempio, il rilevamento è stato interrotto) l'app non può eseguire il rendering con tale frame. Il modello chiama Render solo sul cubo rotante se la classe CameraResources indica un aggiornamento riuscito.

Windows Realtà mista include funzionalità per la stabilizzazione delle immagini per mantenere gli ologrammi posizionati in cui uno sviluppatore o un utente li inserisce nel mondo. La stabilizzazione delle immagini consente di nascondere la latenza intrinseca in una pipeline di rendering per garantire le migliori esperienze olografiche per gli utenti. È possibile specificare un punto di messa a fuoco per migliorare ulteriormente la stabilizzazione delle immagini oppure è possibile fornire un buffer di profondità per calcolare la stabilizzazione delle immagini ottimizzata in tempo reale.

Per ottenere risultati ottimali, l'app deve fornire un buffer di profondità usando l'API CommitDirect3D11DepthBuffer . Windows Realtà mista può quindi usare informazioni geometriche dal buffer di profondità per ottimizzare la stabilizzazione delle immagini in tempo reale. Il modello di app Windows Holographic esegue il commit del buffer di profondità dell'app per impostazione predefinita, consentendo di ottimizzare la stabilità dell'ologramma.

Da AppMain::Render:

// Only render world-locked content when positional tracking is active.
if (cameraActive)
{
    // Draw the sample hologram.
    m_spinningCubeRenderer->Render();
    if (m_canCommitDirect3D11DepthBuffer)
    {
        // On versions of the platform that support the CommitDirect3D11DepthBuffer API, we can 
        // provide the depth buffer to the system, and it will use depth information to stabilize 
        // the image at a per-pixel level.
        HolographicCameraRenderingParameters renderingParameters =
            holographicFrame.GetRenderingParameters(cameraPose);
        
        IDirect3DSurface interopSurface =
            DX::CreateDepthTextureInteropObject(pCameraResources->GetDepthStencilTexture2D());

        // Calling CommitDirect3D11DepthBuffer causes the system to queue Direct3D commands to 
        // read the depth buffer. It will then use that information to stabilize the image as
        // the HolographicFrame is presented.
        renderingParameters.CommitDirect3D11DepthBuffer(interopSurface);
    }
}

Nota

Windows elabora la trama di profondità sulla GPU, quindi deve essere possibile usare il buffer di profondità come risorsa shader. L'ID3D11Texture2D creato deve essere in un formato senza tipi e deve essere associato come visualizzazione risorse shader. Di seguito è riportato un esempio di come creare una trama di profondità di cui è possibile eseguire il commit per la stabilizzazione delle immagini.

Codice per la creazione della risorsa buffer depth per CommitDirect3D11DepthBuffer:

// Create a depth stencil view for use with 3D rendering if needed.
CD3D11_TEXTURE2D_DESC depthStencilDesc(
    DXGI_FORMAT_R16_TYPELESS,
    static_cast<UINT>(m_d3dRenderTargetSize.Width),
    static_cast<UINT>(m_d3dRenderTargetSize.Height),
    m_isStereo ? 2 : 1, // Create two textures when rendering in stereo.
    1, // Use a single mipmap level.
    D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE
);

winrt::check_hresult(
    device->CreateTexture2D(
        &depthStencilDesc,
        nullptr,
        &m_d3dDepthStencil
    ));

CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(
    m_isStereo ? D3D11_DSV_DIMENSION_TEXTURE2DARRAY : D3D11_DSV_DIMENSION_TEXTURE2D,
    DXGI_FORMAT_D16_UNORM
);
winrt::check_hresult(
    device->CreateDepthStencilView(
        m_d3dDepthStencil.Get(),
        &depthStencilViewDesc,
        &m_d3dDepthStencilView
    ));

Disegnare contenuto olografico

Il modello di app Windows Holographic esegue il rendering del contenuto in stereo usando la tecnica consigliata di disegno della geometria istanzata in texture2DArray di dimensioni 2. Esaminiamo la parte che fa parte di questo e come funziona in Windows Realtà mista.

Da SpinningCubeRenderer::Render:

// Draw the objects.
context->DrawIndexedInstanced(
    m_indexCount,   // Index count per instance.
    2,              // Instance count.
    0,              // Start index location.
    0,              // Base vertex location.
    0               // Start instance location.
);

Ogni istanza accede a una matrice di visualizzazione/proiezione diversa dal buffer costante. Ecco la struttura del buffer costante, che è solo una matrice di due matrici.

Da VertexShaderShared.hlsl, incluso da VPRTVertexShader.hlsl:

// A constant buffer that stores each set of view and projection matrices in column-major format.
cbuffer ViewProjectionConstantBuffer : register(b1)
{
    float4x4 viewProjection[2];
};

L'indice della matrice di destinazione di rendering deve essere impostato per ogni pixel. Nel frammento di codice seguente, output.viewId viene mappato alla semantica SV_RenderTargetArrayIndex . Questo richiede il supporto per una funzionalità facoltativa direct3D 11.3, che consente di impostare la semantica dell'indice della matrice di destinazione di rendering da qualsiasi fase dello shader.

Da VPRTVertexShader.hlsl:

// Per-vertex data passed to the geometry shader.
struct VertexShaderOutput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;

    // The render target array index is set here in the vertex shader.
    uint        viewId  : SV_RenderTargetArrayIndex;
};

Da VertexShaderShared.hlsl, incluso da VPRTVertexShader.hlsl:

// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
    min16float3 pos     : POSITION;
    min16float3 color   : COLOR0;
    uint        instId  : SV_InstanceID;
};

// Simple shader to do vertex processing on the GPU.
VertexShaderOutput main(VertexShaderInput input)
{
    VertexShaderOutput output;
    float4 pos = float4(input.pos, 1.0f);

    // Note which view this vertex has been sent to. Used for matrix lookup.
    // Taking the modulo of the instance ID allows geometry instancing to be used
    // along with stereo instanced drawing; in that case, two copies of each 
    // instance would be drawn, one for left and one for right.
    int idx = input.instId % 2;

    // Transform the vertex position into world space.
    pos = mul(pos, model);

    // Correct for perspective and project the vertex position onto the screen.
    pos = mul(pos, viewProjection[idx]);
    output.pos = (min16float4)pos;

    // Pass the color through without modification.
    output.color = input.color;

    // Set the render target array index.
    output.viewId = idx;

    return output;
}

Se si desidera utilizzare le tecniche di disegno con istanze esistenti con questo metodo di disegno in una matrice di destinazione di rendering stereo, disegnare due volte il numero di istanze normalmente disponibili. Nello shader dividere input.instId per 2 per ottenere l'ID istanza originale, che può essere indicizzato in (ad esempio) un buffer di dati per oggetto: int actualIdx = input.instId / 2;

Nota importante sul rendering del contenuto stereo in HoloLens

Windows Realtà mista supporta la possibilità di impostare l'indice della matrice di destinazione di rendering da qualsiasi fase dello shader. In genere, si tratta di un'attività che può essere eseguita solo nella fase geometry shader a causa della modalità di definizione della semantica per Direct3D 11. Di seguito viene illustrato un esempio completo di come configurare una pipeline di rendering con solo le fasi vertex e pixel shader impostate. Il codice dello shader è come descritto in precedenza.

Da SpinningCubeRenderer::Render:

const auto context = m_deviceResources->GetD3DDeviceContext();

// Each vertex is one instance of the VertexPositionColor struct.
const UINT stride = sizeof(VertexPositionColor);
const UINT offset = 0;
context->IASetVertexBuffers(
    0,
    1,
    m_vertexBuffer.GetAddressOf(),
    &stride,
    &offset
);
context->IASetIndexBuffer(
    m_indexBuffer.Get(),
    DXGI_FORMAT_R16_UINT, // Each index is one 16-bit unsigned integer (short).
    0
);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->IASetInputLayout(m_inputLayout.Get());

// Attach the vertex shader.
context->VSSetShader(
    m_vertexShader.Get(),
    nullptr,
    0
);
// Apply the model constant buffer to the vertex shader.
context->VSSetConstantBuffers(
    0,
    1,
    m_modelConstantBuffer.GetAddressOf()
);

// Attach the pixel shader.
context->PSSetShader(
    m_pixelShader.Get(),
    nullptr,
    0
);

// Draw the objects.
context->DrawIndexedInstanced(
    m_indexCount,   // Index count per instance.
    2,              // Instance count.
    0,              // Start index location.
    0,              // Base vertex location.
    0               // Start instance location.
);

Nota importante sul rendering nei dispositivi non HoloLens

L'impostazione dell'indice della matrice di destinazione di rendering nel vertex shader richiede che il driver di grafica supporti una funzionalità facoltativa direct3D 11.3 supportata da HoloLens. L'app può implementare in modo sicuro solo questa tecnica per il rendering e tutti i requisiti verranno soddisfatti per l'esecuzione in Microsoft HoloLens.

Può essere il caso in cui si vuole usare anche l'emulatore HoloLens, che può essere uno strumento di sviluppo potente per l'app olografica e supportare Windows Realtà mista dispositivi vr immersive collegati ai PC Windows 10. Il supporto per il percorso di rendering non HoloLens, per tutti i Realtà mista di Windows, è integrato anche nel modello di app Windows Holographic. Nel codice del modello troverai il codice per abilitare l'esecuzione dell'app olografica nella GPU nel PC di sviluppo. Ecco come la classe DeviceResources controlla questa funzionalità facoltativa.

Da DeviceResources::CreateDeviceResources:

// Check for device support for the optional feature that allows setting the render target array index from the vertex shader stage.
D3D11_FEATURE_DATA_D3D11_OPTIONS3 options;
m_d3dDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS3, &options, sizeof(options));
if (options.VPAndRTArrayIndexFromAnyShaderFeedingRasterizer)
{
    m_supportsVprt = true;
}

Per supportare il rendering senza questa funzionalità facoltativa, l'app deve usare uno shader geometry per impostare l'indice della matrice di destinazione di rendering. Questo frammento di codice verrà aggiunto dopo VSSetConstantBuffers e prima di PSSetShader nell'esempio di codice illustrato nella sezione precedente che illustra come eseguire il rendering dello stereo in HoloLens.

Da SpinningCubeRenderer::Render:

if (!m_usingVprtShaders)
{
    // On devices that do not support the D3D11_FEATURE_D3D11_OPTIONS3::
    // VPAndRTArrayIndexFromAnyShaderFeedingRasterizer optional feature,
    // a pass-through geometry shader is used to set the render target 
    // array index.
    context->GSSetShader(
        m_geometryShader.Get(),
        nullptr,
        0
    );
}

NOTA HLSL: in questo caso, è necessario caricare anche un vertex shader leggermente modificato che passa l'indice della matrice di destinazione di rendering al geometry shader usando una semantica dello shader sempre consentita, ad esempio TEXCOORD0. Il geometry shader non deve eseguire alcuna operazione; lo shader geometry del modello passa attraverso tutti i dati, ad eccezione dell'indice della matrice di destinazione di rendering, usato per impostare la semantica SV_RenderTargetArrayIndex.

Codice del modello di app per GeometryShader.hlsl:

// Per-vertex data from the vertex shader.
struct GeometryShaderInput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;
    uint instId         : TEXCOORD0;
};

// Per-vertex data passed to the rasterizer.
struct GeometryShaderOutput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;
    uint rtvId          : SV_RenderTargetArrayIndex;
};

// This geometry shader is a pass-through that leaves the geometry unmodified 
// and sets the render target array index.
[maxvertexcount(3)]
void main(triangle GeometryShaderInput input[3], inout TriangleStream<GeometryShaderOutput> outStream)
{
    GeometryShaderOutput output;
    [unroll(3)]
    for (int i = 0; i < 3; ++i)
    {
        output.pos   = input[i].pos;
        output.color = input[i].color;
        output.rtvId = input[i].instId;
        outStream.Append(output);
    }
}

Present

Abilitare il frame olografico per presentare la catena di scambio

Con Windows Realtà mista, il sistema controlla la catena di scambio. Il sistema gestisce quindi la presentazione dei fotogrammi a ogni fotocamera olografica per garantire un'esperienza utente di alta qualità. Fornisce anche un aggiornamento del viewport per ogni fotogramma, per ogni fotocamera, per ottimizzare gli aspetti del sistema, ad esempio la stabilizzazione delle immagini o l'acquisizione di Realtà mista. Quindi, un'app olografica che usa DirectX non chiama Present in una catena di scambio DXGI. Al contrario, si usa la classe HolographicFrame per presentare tutti i portachiavi per un fotogramma dopo averlo disegnato.

Da DeviceResources::P resent:

HolographicFramePresentResult presentResult = frame.PresentUsingCurrentPrediction();

Per impostazione predefinita, questa API attende il completamento del frame prima che venga restituito. Le app olografiche devono attendere il completamento del frame precedente prima di iniziare a lavorare su un nuovo frame, perché ciò riduce la latenza e consente risultati migliori dalle stime dei fotogrammi olografici. Questa non è una regola rigida e, se si dispone di fotogrammi che richiedono più di un aggiornamento dello schermo per eseguire il rendering, è possibile disabilitare questa attesa passando il parametro HolographicFramePresentWaitBehavior a PresentUsingCurrentPrediction. In questo caso, è probabile che si usi un thread di rendering asincrono per mantenere un carico continuo sulla GPU. La frequenza di aggiornamento del dispositivo HoloLens è di 60 hz, dove un frame ha una durata di circa 16 ms. I dispositivi vr immersive possono variare da 60 hz a 90 hz; quando si aggiorna lo schermo a 90 hz, ogni fotogramma avrà una durata di circa 11 ms.

Gestire gli scenari DeviceLost in collaborazione con HolographicFrame

Le app DirectX 11 in genere vogliono controllare l'HRESULT restituito dalla funzione Present della catena di scambio DXGI per scoprire se si è verificato un errore DeviceLost. La classe HolographicFrame gestisce automaticamente questa operazione. Esaminare l'oggetto HolographicFramePresentResult restituito per scoprire se è necessario rilasciare e ricreare le risorse basate su dispositivo e dispositivo Direct3D.

// The PresentUsingCurrentPrediction API will detect when the graphics device
// changes or becomes invalid. When this happens, it is considered a Direct3D
// device lost scenario.
if (presentResult == HolographicFramePresentResult::DeviceRemoved)
{
    // The Direct3D device, context, and resources should be recreated.
    HandleDeviceLost();
}

Se il dispositivo Direct3D è andato perso ed è stato ricreato, è necessario indicare a HolographicSpace di iniziare a usare il nuovo dispositivo. La catena di scambio verrà ricreata per questo dispositivo.

Da DeviceResources::InitializeUsingHolographicSpace:

m_holographicSpace.SetDirect3D11Device(m_d3dInteropDevice);

Una volta presentata la cornice, è possibile tornare al ciclo principale del programma e consentire di continuare con il fotogramma successivo.

PC grafici ibridi e applicazioni di realtà mista

I PC Windows 10 Creators Update possono essere configurati con GPU discrete e integrate. Con questi tipi di computer, Windows sceglierà l'adattatore a cui è connesso il visore VR. Le applicazioni devono assicurarsi che il dispositivo DirectX creato usi la stessa scheda.

Il codice di esempio Direct3D più generale illustra la creazione di un dispositivo DirectX usando la scheda hardware predefinita, che in un sistema ibrido potrebbe non corrispondere a quella usata per il visore VR.

Per risolvere eventuali problemi, usare HolographicAdapterID da HolographicSpace. PrimaryAdapterId() o HolographicDisplay. AdapterId(). Questo adapterId può quindi essere usato per selezionare il DXGIAdapter destro usando IDXGIFactory4.EnumAdapterByLuid.

Da DeviceResources::InitializeUsingHolographicSpace:

// The holographic space might need to determine which adapter supports
// holograms, in which case it will specify a non-zero PrimaryAdapterId.
LUID id =
{
    m_holographicSpace.PrimaryAdapterId().LowPart,
    m_holographicSpace.PrimaryAdapterId().HighPart
};

// When a primary adapter ID is given to the app, the app should find
// the corresponding DXGI adapter and use it to create Direct3D devices
// and device contexts. Otherwise, there is no restriction on the DXGI
// adapter the app can use.
if ((id.HighPart != 0) || (id.LowPart != 0))
{
    UINT createFlags = 0;

    // Create the DXGI factory.
    ComPtr<IDXGIFactory1> dxgiFactory;
    winrt::check_hresult(
        CreateDXGIFactory2(
            createFlags,
            IID_PPV_ARGS(&dxgiFactory)
        ));
    ComPtr<IDXGIFactory4> dxgiFactory4;
    winrt::check_hresult(dxgiFactory.As(&dxgiFactory4));

    // Retrieve the adapter specified by the holographic space.
    winrt::check_hresult(
        dxgiFactory4->EnumAdapterByLuid(
            id,
            IID_PPV_ARGS(&m_dxgiAdapter)
        ));
}
else
{
    m_dxgiAdapter.Reset();
}

Codice per aggiornare DeviceResources::CreateDeviceResources per usare IDXGIAdapter

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

const D3D_DRIVER_TYPE driverType = m_dxgiAdapter == nullptr ? D3D_DRIVER_TYPE_HARDWARE : D3D_DRIVER_TYPE_UNKNOWN;
const HRESULT hr = D3D11CreateDevice(
    m_dxgiAdapter.Get(),        // Either nullptr, or the primary adapter determined by Windows Holographic.
    driverType,                 // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for Windows Runtime apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
);

Grafica ibrida e Media Foundation

L'uso di Media Foundation nei sistemi ibridi può causare problemi per cui il video non eseguirà il rendering o la trama video è danneggiata perché Media Foundation usa per impostazione predefinita un comportamento di sistema. In alcuni scenari, la creazione di un ID3D11Device separato è necessaria per supportare il multithreading e vengono impostati i flag di creazione corretti.

Quando si inizializza ID3D11Device, D3D11_CREATE_DEVICE_VIDEO_SUPPORT flag deve essere definito come parte del D3D11_CREATE_DEVICE_FLAG. Dopo aver creato il dispositivo e il contesto, chiamare SetMultithreadProtected per abilitare il multithreading. Per associare il dispositivo a IMFDXGIDeviceManager, usare la funzione IMFDXGIDeviceManager::ResetDevice.

Codice per associare un ID3D11Device a IMFDXGIDeviceManager:

// create dx device for media pipeline
winrt::com_ptr<ID3D11Device> spMediaDevice;

// See above. Also make sure to enable the following flags on the D3D11 device:
//   * D3D11_CREATE_DEVICE_VIDEO_SUPPORT
//   * D3D11_CREATE_DEVICE_BGRA_SUPPORT
if (FAILED(CreateMediaDevice(spAdapter.get(), &spMediaDevice)))
    return;                                                     

// Turn multithreading on 
winrt::com_ptr<ID3D10Multithread> spMultithread;
if (spContext.try_as(spMultithread))
{
    spMultithread->SetMultithreadProtected(TRUE);
}

// lock the shared dxgi device manager
// call MFUnlockDXGIDeviceManager when no longer needed
UINT uiResetToken;
winrt::com_ptr<IMFDXGIDeviceManager> spDeviceManager;
hr = MFLockDXGIDeviceManager(&uiResetToken, spDeviceManager.put());
if (FAILED(hr))
    return hr;
    
// associate the device with the manager
hr = spDeviceManager->ResetDevice(spMediaDevice.get(), uiResetToken);
if (FAILED(hr))
    return hr;

Vedi anche