Rendering in DirectX

Nota

Questo articolo riguarda le API native WinRT legacy. Per nuovi progetti di app native, è consigliabile usare l'API OpenXR.

Windows Mixed Reality è basato su DirectX per produrre esperienze grafiche avanzate e 3D per gli utenti. L'astrazione del rendering si trova appena sopra DirectX, che consente alle app di stabilire la 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 mentre l'utente si sposta intorno.

Nota: questa procedura dettagliata descrive il rendering olografico in Direct3D 11. Viene fornito anche un modello di app Direct3D 12 Windows Mixed Reality 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 frame 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 delle 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 e eseguire il rendering del frame corrente. L'app inizia ogni nuovo frame chiamando il metodo CreateNextFrame . Quando questo metodo viene chiamato, le stime vengono effettuate usando i dati più recenti del sensore disponibili e incapsulati nell'oggetto CurrentPrediction .

Un nuovo oggetto frame deve essere usato per ogni frame di rendering perché è valido solo per un'ora istantanea. La proprietà CurrentPrediction contiene informazioni quali la posizione della fotocamera. Le informazioni vengono estrapolate al momento esatto in cui si prevede che il frame 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 indietro possono cambiare da frame a frame. L'app deve convalidare il buffer indietro per ogni fotocamera e rilasciare e ricreare le visualizzazioni delle risorse e i buffer di profondità in base alle esigenze. Si noti che il set di pose nella stima è l'elenco autorevole delle fotocamere usate nel frame 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 Mixed Reality consente all'app di creare vari sistemi di coordinate, ad esempio frame di riferimento collegati e stazioni per il rilevamento delle posizioni nel mondo fisico. L'app può quindi usare questi sistemi di coordinate per motivi su dove eseguire il rendering di ologrammi ogni fotogramma. Quando si richiedono coordinate da un'API, si passerà sempre in 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 di sguardo e movimento

L'input dello sguardo e della mano non è basato sul tempo e non è necessario aggiornare nella funzione StepTimer . Tuttavia, questo input è qualcosa che l'app deve esaminare ogni frame.

Elaborare gli aggiornamenti basati sul tempo

Qualsiasi app di rendering in tempo reale richiederà alcuni modi per elaborare gli aggiornamenti basati sul tempo: il modello di app Windows Holographic usa un'implementazione di StepTimer , simile al passaggio fornito nel modello di app UWP DirectX 11. Questa classe helper di esempio StepTimer può fornire aggiornamenti temporali fissi, aggiornamenti temporizzato variabile e la modalità predefinita è passaggi di tempo variabile.

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

Da AppMain::Update:

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

Posizionare e ruotare gli ologrammi nel sistema di coordinate

Se si opera in un singolo sistema di coordinate, come il modello fa con SpatialStationaryReferenceFrame, questo processo non è diverso da quello che si usa in caso contrario 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 stazioni.

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

Si notino scenari avanzati: Il cubo di rotazione è un semplice esempio di come posizionare un ologramma all'interno di un singolo frame di riferimento. È anche possibile usare più SpatialCoordinateSystems nello stesso frame di rendering, contemporaneamente.

Aggiornare i dati del buffer costante

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

Da SpinningCubeRenderer::Update:

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

Quali sono le trasformazioni di visualizzazione e proiezione? Per ottenere risultati ottimali, vogliamo attendere fino a quando non siamo quasi pronti per le chiamate di disegno prima di ottenere questi risultati.

Eseguire il rendering del frame corrente

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

  • Le stime dei fotogrammi olografici sono importanti. La stima più vicina consiste nel momento in cui viene presentata la cornice, più i tuoi ologrammi saranno più vicini.
  • Windows Mixed Reality controlla le visualizzazioni della fotocamera. Eseguire il rendering in ognuno di essi perché la cornice olografica li presenterà in un secondo momento.
  • È consigliabile eseguire il rendering stereo usando il disegno istanzato in una matrice di destinazione di rendering. Il modello di app olografica usa l'approccio consigliato di disegno istanza in una matrice di destinazione di rendering, che usa una visualizzazione di destinazione di rendering in una trama2DArray.
  • Se si vuole eseguire il rendering senza usare l'instancing stereo, è necessario creare due rendertargetView non di matrice, uno per ogni occhio. Ogni RenderingTargetViews fa riferimento a una delle due sezioni della Trama2DArray fornita all'app dal sistema. Non è consigliabile, perché in genere è più lento rispetto all'uso di instancing.

Ottenere una stima HolographicFrame aggiornata

L'aggiornamento della stima del frame migliora l'efficacia della stabilizzazione delle immagini. Si ottiene una posizione più accurata degli ologrammi a causa del tempo più breve tra la stima e quando il frame è visibile all'utente. Aggiornare idealmente la stima del frame appena prima del rendering.

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

Eseguire il rendering in ogni fotocamera

Ciclo nel set di posizioni della fotocamera nella stima e rendering in ogni fotocamera in questo set.

Configurare il passaggio di rendering

Windows Mixed Reality 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 display, che il cervello può riconciliare come profondità effettiva. Questa sezione illustra il rendering stereoscopico usando instancing, usando il codice dal modello di app Windows Holographic.

Ogni fotocamera ha una propria destinazione di rendering (buffer indietro) 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à, per ogni fotocamera. Nel modello di app Windows Holographic è disponibile una classe helper per raggruppare queste risorse in DX::CameraResources. Iniziare configurando 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 tale 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);

Ecco come vengono acquisite le matrici dalla posa della fotocamera. Durante questo processo, si ottiene anche il riquadro di visualizzazione 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, ed è lo stesso che abbiamo usato per posizionare il cubo di rotazione.

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 ogni frame. 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 indietro della fotocamera e eseguire il commit del buffer di profondità:

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

Windows Mixed Reality 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. Un punto di messa a fuoco può essere specificato per migliorare ulteriormente la stabilizzazione delle immagini o un buffer di profondità può essere fornito per la stabilizzazione dell'immagine ottimizzata in tempo reale.

Per ottenere risultati ottimali, l'app deve fornire un buffer di profondità usando l'API CommitDirect3D11DepthBuffer . Windows Mixed Reality può quindi usare le informazioni geometry 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. ID3D11Texture2D creato deve essere in formato senza tipi e deve essere associato come visualizzazione delle risorse shader. Ecco un esempio di come creare una trama di profondità che può essere eseguita per la stabilizzazione delle immagini.

Codice per la creazione della risorsa buffer di profondità 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
    ));

Disegna 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 una trama 2DArray di dimensioni 2. Esaminiamo la parte instancing di questo e come funziona su Windows Mixed Reality.

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 eseguito il mapping alla semantica SV_RenderTargetArrayIndex . Ciò richiede il supporto per una funzionalità direct3D 11.3 facoltativa, che consente di impostare la semantica dell'indice della matrice di destinazione di rendering da qualsiasi fase 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 usare le tecniche di disegno 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 dell'istanza originale, che può essere indicizzato in (ad esempio) un buffer di dati per oggetto: int actualIdx = input.instId / 2;

Nota importante sul rendering di contenuti stereo in HoloLens

Windows Mixed Reality supporta la possibilità di impostare l'indice della matrice di destinazione di rendering da qualsiasi fase shader. In genere, si tratta di un'attività che può essere eseguita solo nella fase geometry shader a causa del modo in cui la semantica è definita 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 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 grafico supporti una funzionalità Direct3D 11.3 facoltativa, che HoloLens supporta. L'app può implementare in modo sicuro solo questa tecnica per il rendering e tutti i requisiti verranno soddisfatti per l'esecuzione nel 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 Mixed Reality dispositivi visori immersivi collegati ai PC Windows 10. Il supporto per il percorso di rendering non HoloLens, per tutti i Windows Mixed Reality, è integrato anche nel modello di app Holographic di Windows. Nel codice modello si troverà il codice per abilitare l'app olografica da eseguire 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 dopoVSSetConstantBuffers e primadi PSSetShader nell'esempio di codice illustrato nella sezione precedente che spiega 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. Lo shader geometry non deve eseguire alcun lavoro; 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 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);
    }
}

Presente

Abilitare il frame olografico per presentare la catena di scambio

Con Windows Mixed Reality, il sistema controlla la catena di scambio. Il sistema gestisce quindi la presentazione di fotogrammi a ogni fotocamera olografica per garantire un'esperienza utente di alta qualità. Fornisce anche un aggiornamento del riquadro di visualizzazione 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. Usare invece la classe HolographicFrame per presentare tutte le swapchain per una cornice dopo averla creata.

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é riduce la latenza e consente di ottenere risultati migliori dalle stime dei fotogrammi olografici. Questa non è una regola complessa e, se si dispone di fotogrammi che richiedono più tempo di un aggiornamento dello schermo, è possibile disabilitare questa attesa passando il parametro HolographicFramePresentWaitBehavior a PresentUsingCurrentPrediction. In questo caso, è probabile che usi un thread di rendering asincrono per mantenere un carico continuo sulla GPU. La frequenza di aggiornamento del dispositivo HoloLens è 60 hz, dove un frame ha una durata di circa 16 ms. I dispositivi visori immersivi possono variare da 60 hz a 90 hz; quando si aggiorna il display 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 il valore HRESULT restituito dalla funzione Present della catena di scambio DXGI per scoprire se si è verificato un errore DeviceLost . La classe HolographicFrame gestisce questa operazione. Esaminare holographicFramePresentResult restituito per scoprire se è necessario rilasciare e ricreare le risorse basate su 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 è stato perso e lo hai ricreato, devi 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);

Dopo aver presentato la cornice, è possibile tornare al ciclo principale del programma e consentire la continuazione al frame successivo.

Pc grafici ibridi e applicazioni di realtà mista

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

La maggior parte del codice di esempio Direct3D generale illustra la creazione di un dispositivo DirectX usando l'adattatore hardware predefinito, che in un sistema ibrido potrebbe non essere uguale a quello usato per il visore visore.

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 in cui il video non verrà eseguito il rendering o la trama video sono danneggiati perché Media Foundation è predefinito in un comportamento di sistema. In alcuni scenari è necessario creare un ID3D11Device separato per supportare il multi-threading e i flag di creazione corretti sono impostati.

Quando si inizializza l'ID3D11Device, D3D11_CREATE_DEVICE_VIDEO_SUPPORT flag deve essere definito come parte della D3D11_CREATE_DEVICE_FLAG. Dopo aver creato il dispositivo e il contesto, chiamare SetMultithreadProtected per abilitare il multithreading. Per associare il dispositivo a FMDXGIDeviceManager, 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