Rendern in DirectX

Hinweis

Dieser Artikel bezieht sich auf die älteren nativen WinRT-APIs. Für neue native App-Projekte empfehlen wir die Verwendung der OpenXR-API.

Windows Mixed Reality basiert auf DirectX, um umfassende grafische 3D-Umgebungen für Benutzer zu erstellen. Die Renderingabstraktion befindet sich direkt über DirectX, wodurch Apps die Position und Ausrichtung holografischer Szenenbeobachter ermitteln können, die vom System vorhergesagt werden. Der Entwickler kann dann seine Hologramme basierend auf jeder Kamera lokalisieren, sodass die App diese Hologramme in verschiedenen räumlichen Koordinatensystemen rendern kann, während sich der Benutzer bewegt.

Hinweis: In dieser exemplarischen Vorgehensweise wird das holografische Rendering in Direct3D 11 beschrieben. Eine Direct3D 12-Windows Mixed Reality-App-Vorlage wird auch mit der Mixed Reality App-Vorlagenerweiterung bereitgestellt.

Aktualisieren für den aktuellen Frame

Um den Anwendungsstatus für Hologramme zu aktualisieren, führt die App einmal pro Frame folgendes aus:

  • Rufen Sie einen HolographicFrame aus dem Anzeigeverwaltungssystem ab.
  • Aktualisieren Sie die Szene mit der aktuellen Vorhersage, wo sich die Kameraansicht befindet, wenn das Rendern abgeschlossen ist. Beachten Sie, dass es mehrere Kameras für die holografische Szene geben kann.

Zum Rendern in holografische Kameraansichten führt die App einmal pro Frame folgendes aus:

  • Rendern Sie für jede Kamera die Szene für den aktuellen Frame, indem Sie die Kameraansicht und projektionsmatrizen aus dem System verwenden.

Erstellen eines neuen holografischen Frames und Abrufen seiner Vorhersage

Der HolographicFrame enthält Informationen, die die App zum Aktualisieren und Rendern des aktuellen Frames benötigt. Die App beginnt jeden neuen Frame, indem die CreateNextFrame-Methode aufgerufen wird. Wenn diese Methode aufgerufen wird, werden Vorhersagen unter Verwendung der neuesten verfügbaren Sensordaten erstellt und im CurrentPrediction-Objekt gekapselt.

Für jeden gerenderten Frame muss ein neues Frameobjekt verwendet werden, da es nur für einen Moment gültig ist. Die CurrentPrediction-Eigenschaft enthält Informationen wie die Kameraposition. Die Informationen werden auf den genauen Zeitpunkt extrapoliert, zu dem der Frame für den Benutzer sichtbar sein soll.

Der folgende Code ist aus AppMain::Update auszugsweise:

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

Kameraupdates verarbeiten

Rückpuffer können sich von Frame zu Frame ändern. Ihre App muss den Rückpuffer für jede Kamera überprüfen und bei Bedarf Ressourcenansichten und Tiefenpuffer freigeben und neu erstellen. Beachten Sie, dass der Satz von Posen in der Vorhersage die autoritative Liste der Kameras ist, die im aktuellen Frame verwendet werden. In der Regel verwenden Sie diese Liste, um den Kamerasatz zu durchlaufen.

Aus AppMain::Update:

m_deviceResources->EnsureCameraResources(holographicFrame, prediction);

Aus DeviceResources::EnsureCameraResources:

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

Abrufen des Koordinatensystems, das als Grundlage für das Rendering verwendet werden soll

Windows Mixed Reality können Ihre App verschiedene Koordinatensysteme erstellen, z. B. angefügte und stationäre Referenzrahmen zum Nachverfolgen von Standorten in der physischen Welt. Ihre App kann dann diese Koordinatensysteme verwenden, um zu ermitteln, wo Hologramme für jeden Frame gerendert werden sollen. Wenn Sie Koordinaten von einer API anfordern, übergeben Sie immer das SpatialCoordinateSystem , in dem diese Koordinaten ausgedrückt werden sollen.

Aus AppMain::Update:

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

Diese Koordinatensysteme können dann verwendet werden, um Stereoansichtsmatrizen beim Rendern des Inhalts in Ihrer Szene zu generieren.

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

Verarbeiten von Blick- und Gesteneingaben

Blick- und Handeingaben sind nicht zeitbasiert und müssen nicht in der StepTimer-Funktion aktualisiert werden. Diese Eingabe muss sich die App jedoch jeden Frame ansehen.

Verarbeiten zeitbasierter Updates

Jede Echtzeit-Rendering-App benötigt eine Möglichkeit, zeitbasierte Updates zu verarbeiten. Die Windows Holographic-App-Vorlage verwendet eine StepTimer-Implementierung , ähnlich dem StepTimer, der in der DirectX 11-UWP-App-Vorlage bereitgestellt wird. Diese StepTimer-Beispielhilfsklasse kann feste Zeitschrittupdates, variable Zeitschrittaktualisierungen und der Standardmodus sind variable Zeitschrittschritte.

Für holografisches Rendering haben wir uns entschieden, nicht zu viel in die Timerfunktion zu stecken, da Sie sie als festen Zeitschritt konfigurieren können. Es kann mehr als einmal pro Frame aufgerufen werden – oder für einige Frames überhaupt nicht – und unsere holografischen Datenupdates sollten einmal pro Frame erfolgen.

Aus AppMain::Update:

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

Positionieren und Drehen von Hologrammen in Ihrem Koordinatensystem

Wenn Sie in einem einzelnen Koordinatensystem arbeiten, wie es die Vorlage mit dem SpatialStationaryReferenceFrame tut, unterscheidet sich dieser Prozess nicht von dem, was Sie sonst in 3D-Grafiken gewohnt sind. Hier drehen wir den Cube und legen die Modellmatrix basierend auf der Position im stationären Koordinatensystem fest.

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

Hinweis zu erweiterten Szenarien: Der sich drehende Würfel ist ein einfaches Beispiel für die Positionierung eines Hologramms innerhalb eines einzelnen Referenzrahmens. Es ist auch möglich, mehrere SpatialCoordinateSystems gleichzeitig im selben gerenderten Frame zu verwenden.

Aktualisieren von Konstantenpufferdaten

Modelltransformationen für Inhalte werden wie gewohnt aktualisiert. Inzwischen verfügen Sie über gültige Transformationen für das Koordinatensystem, in dem Sie rendern möchten.

Von SpinningCubeRenderer::Update:

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

Was ist mit Sicht- und Projektionstransformationen? Um beste Ergebnisse zu erzielen, möchten wir warten, bis wir fast bereit für unsere Ziehungsaufrufe sind, bevor wir diese erhalten.

Rendern des aktuellen Frames

Das Rendern auf Windows Mixed Reality unterscheidet sich nicht wesentlich vom Rendern auf einem 2D-Mono-Display, aber es gibt einige Unterschiede:

  • Holografische Framevorhersagen sind wichtig. Je näher die Vorhersage ist, wenn Ihr Frame angezeigt wird, desto besser sehen Ihre Hologramme aus.
  • Windows Mixed Reality steuert die Kameraansichten. Rendern Sie für jeden, da der holografische Frame sie später für Sie darstellt.
  • Es wird empfohlen, Stereorendering mit instanzierter Zeichnung für ein Renderzielarray durchzuführen. Die holografische App-Vorlage verwendet den empfohlenen Ansatz der Instanzzeichnung für ein Renderzielarray, das eine Renderzielansicht für eine Textur2DArray verwendet.
  • Wenn Sie ohne Stereo-Instancing rendern möchten, müssen Sie zwei Nicht-Array-RenderTargetViews erstellen, eine für jedes Auge. Jede RenderTargetViews verweist auf einen der beiden Slices im Texture2DArray , der der App vom System bereitgestellt wird. Dies wird nicht empfohlen, da dies in der Regel langsamer ist als die Verwendung von Instancing.

Abrufen einer aktualisierten HolographicFrame-Vorhersage

Das Aktualisieren der Framevorhersage erhöht die Effektivität der Bildstabilisierung. Sie erhalten eine genauere Positionierung von Hologrammen aufgrund der kürzeren Zeit zwischen der Vorhersage und dem Zeitpunkt, an dem der Frame für den Benutzer sichtbar ist. Aktualisieren Sie idealerweise Ihre Framevorhersage direkt vor dem Rendern.

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

Rendern für jede Kamera

Schleife für den Satz der Kameraposen in der Vorhersage, und rendern Sie für jede Kamera in diesem Satz.

Einrichten des Renderingdurchlaufs

Windows Mixed Reality verwendet stereoskopisches Rendering, um die Illusion der Tiefe zu verbessern und stereoskopisch zu rendern, sodass sowohl das linke als auch das rechte Display aktiv sind. Beim stereoskopischen Rendering gibt es einen Offset zwischen den beiden Displays, den das Gehirn als tatsächliche Tiefe ausgleichen kann. In diesem Abschnitt wird das stereoskopische Rendering mithilfe von Instancing mithilfe von Code aus der Vorlage für die Holographic-App von Windows behandelt.

Jede Kamera verfügt über ein eigenes Renderziel (Rückpuffer) sowie Übersichts- und Projektionsmatrizen in den holografischen Raum. Ihre App muss alle anderen kamerabasierten Ressourcen – z. B. den Tiefenpuffer – auf Kamerabasis erstellen. In der Windows Holographic-App-Vorlage stellen wir eine Hilfsklasse bereit, um diese Ressourcen in DX::CameraResources zu bündeln. Richten Sie zunächst die Renderzielansichten ein:

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

Verwenden der Vorhersage zum Abrufen der Ansichts- und Projektionsmatrizen für die Kamera

Die Ansichts- und Projektionsmatrizen für jede holografische Kamera ändern sich mit jedem Frame. Aktualisieren Sie die Daten im Konstantenpuffer für jede holografische Kamera. Tun Sie dies, nachdem Sie die Vorhersage aktualisiert haben und bevor Sie Für diese Kamera Zeichnen aufrufen.

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

Hier zeigen wir, wie die Matrizen aus der Kamerapose erfasst werden. Während dieses Vorgangs erhalten wir auch den aktuellen Viewport für die Kamera. Beachten Sie, wie wir ein Koordinatensystem bereitstellen: Dies ist dasselbe Koordinatensystem, das wir zum Verstehen des Blicks verwendet haben, und es ist dasselbe, das wir zum Positionieren des sich drehenden Würfels verwendet haben.

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

Der Viewport sollte für jeden Frame festgelegt werden. Ihr Vertex-Shader (mindestens) benötigt in der Regel Zugriff auf die Ansichts-/Projektionsdaten.

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

Rendern Sie in den Kamerarückpuffer, und committen Sie den Tiefenpuffer:

Es empfiehlt sich, zu überprüfen, ob TryGetViewTransform erfolgreich war, bevor Sie versuchen, die Ansichts-/Projektionsdaten zu verwenden, denn wenn das Koordinatensystem nicht erreichbar ist (z. B. die Nachverfolgung wurde unterbrochen), kann Ihre App nicht damit für diesen Frame rendern. Die Vorlage ruft rendern nur auf dem sich drehenden Cube auf, wenn die CameraResources-Klasse eine erfolgreiche Aktualisierung anzeigt.

Windows Mixed Reality enthält Funktionen zur Bildstabilisierung, um Hologramme an der Stelle zu halten, an der ein Entwickler oder Benutzer sie in der Welt platziert. Die Bildstabilisierung hilft dabei, die Latenz einer Renderingpipeline zu verbergen, um die beste holografische Benutzeroberfläche für Benutzer zu gewährleisten. Es kann ein Fokuspunkt angegeben werden, um die Bildstabilisierung noch weiter zu verbessern, oder es kann ein Tiefenpuffer bereitgestellt werden, um eine optimierte Bildstabilisierung in Echtzeit zu berechnen.

Um optimale Ergebnisse zu erzielen, sollte Ihre App mithilfe der CommitDirect3D11DepthBuffer-API einen Tiefenpuffer bereitstellen. Windows Mixed Reality können dann Geometrieinformationen aus dem Tiefenpuffer verwenden, um die Bildstabilisierung in Echtzeit zu optimieren. Die Windows Holographic-App-Vorlage committ standardmäßig den Tiefenpuffer der App, um die Stabilität des Hologramms zu optimieren.

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

Hinweis

Windows verarbeitet Ihre Tiefentextur auf der GPU. Daher muss es möglich sein, Ihren Tiefenpuffer als Shaderressource zu verwenden. Die ID3D11Texture2D, die Sie erstellen, sollte in einem typlosen Format vorliegen und als Shaderressourcenansicht gebunden sein. Hier sehen Sie ein Beispiel für das Erstellen einer Tiefentextur, die für die Bildstabilisierung festgelegt werden kann.

Code für die Erstellung von Tiefenpufferressourcen für 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
    ));

Zeichnen holografischer Inhalte

Die Windows Holographic-App-Vorlage rendert Inhalte in Stereo, indem sie die empfohlene Technik zum Zeichnen von Instanzgeometrie auf ein Texture2DArray der Größe 2 verwendet. Sehen wir uns den Teil der Finanzierung an und wie es auf Windows Mixed Reality funktioniert.

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

Jede instance greift auf eine andere Ansichts-/Projektionsmatrix aus dem Konstantenpuffer zu. Hier ist die konstante Pufferstruktur, bei der es sich nur um ein Array aus zwei Matrizen handelt.

Von VertexShaderShared.hlsl, enthalten von 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];
};

Der Renderzielarrayindex muss für jedes Pixel festgelegt werden. Im folgenden Codeausschnitt wird output.viewId der SV_RenderTargetArrayIndex Semantik zugeordnet. Dies erfordert Unterstützung für ein optionales Direct3D 11.3-Feature, mit dem die Semantik des Renderzielarrayindexes aus jeder Shaderstufe festgelegt werden kann.

Von 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;
};

Von VertexShaderShared.hlsl, enthalten von 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;
}

Wenn Sie Ihre vorhandenen Instanzzeichnungstechniken mit dieser Methode zum Zeichnen auf ein Stereorenderzielarray verwenden möchten, zeichnen Sie die doppelte Anzahl von Instanzen, die Sie normalerweise haben. Teilen Sie im Shader input.instId durch 2, um die ursprüngliche instance-ID abzurufen, die (z. B.) in einen Puffer mit Objektdaten indiziert werden kann:int actualIdx = input.instId / 2;

Wichtiger Hinweis zum Rendern von Stereoinhalten auf HoloLens

Windows Mixed Reality unterstützt die Möglichkeit, den Renderzielarrayindex aus jeder Shaderstufe festzulegen. Normalerweise ist dies eine Aufgabe, die nur in der Geometrie-Shaderphase ausgeführt werden konnte, da die Semantik für Direct3D 11 definiert wird. Hier zeigen wir ein vollständiges Beispiel für das Einrichten einer Renderingpipeline, bei der nur die Scheitelpunkt- und Pixel-Shaderstufen festgelegt sind. Der Shadercode ist wie oben beschrieben.

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

Wichtiger Hinweis zum Rendern auf Nicht-HoloLens-Geräten

Das Festlegen des Renderzielarrayindex im Vertex-Shader erfordert, dass der Grafiktreiber ein optionales Direct3D 11.3-Feature unterstützt, das HoloLens unterstützt. Möglicherweise kann Ihre App genau diese Technik zum Rendern sicher implementieren, und alle Anforderungen für die Ausführung auf dem Microsoft HoloLens werden erfüllt.

Möglicherweise möchten Sie auch den HoloLens-Emulator verwenden, der ein leistungsfähiges Entwicklungstool für Ihre holografische App sein kann und Windows Mixed Reality immersiven Headset-Geräte unterstützt, die an Windows 10 PCs angeschlossen sind. Die Unterstützung für den Nicht-HoloLens-Renderingpfad – für alle Windows Mixed Reality – ist auch in die Windows Holographic-App-Vorlage integriert. Im Vorlagencode finden Sie Code, mit dem Ihre holografische App auf der GPU auf Ihrem Entwicklungs-PC ausgeführt werden kann. Hier erfahren Sie, wie die DeviceResources-Klasse auf diese optionale Featureunterstützung überprüft.

Aus 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;
}

Um das Rendering ohne dieses optionale Feature zu unterstützen, muss Ihre App einen Geometrie-Shader verwenden, um den Index des Renderzielarrays festzulegen. Dieser Codeausschnitt wird nachVSSetConstantBuffers und vorPSSetShader im Codebeispiel im vorherigen Abschnitt hinzugefügt, in dem erläutert wird, wie Stereo in HoloLens gerendert wird.

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

HLSL-HINWEIS: In diesem Fall müssen Sie auch einen leicht geänderten Vertex-Shader laden, der den Renderzielarrayindex mithilfe einer immer zulässigen Shadersemantik wie TEXCOORD0 an den Geometrie-Shader übergibt. Der Geometrie-Shader muss keine Arbeit erledigen. Der Vorlagengeometrie-Shader durchläuft alle Daten mit Ausnahme des Renderzielarrayindex, der zum Festlegen der SV_RenderTargetArrayIndex Semantik verwendet wird.

App-Vorlagencode für 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);
    }
}

Anzahl

Aktivieren des holografischen Rahmens, um die Swap chain darzustellen

Mit Windows Mixed Reality steuert das System die Swapchain. Das System verwaltet dann die Darstellung von Frames für jede holografische Kamera, um eine hochwertige Benutzererfahrung zu gewährleisten. Es bietet auch ein Viewport-Update für jeden Frame für jede Kamera, um Aspekte des Systems wie Bildstabilisierung oder Mixed Reality Capture zu optimieren. Daher ruft eine holografische App, die DirectX verwendet, keine Present in einer DXGI-Swapchain auf. Stattdessen verwenden Sie die HolographicFrame-Klasse , um alle Swapchains für einen Frame anzuzeigen, sobald Sie mit dem Zeichnen fertig sind.

Von DeviceResources::P resent:

HolographicFramePresentResult presentResult = frame.PresentUsingCurrentPrediction();

Standardmäßig wartet diese API, bis der Frame abgeschlossen ist, bevor er zurückgegeben wird. Holografische Apps sollten warten, bis der vorherige Frame abgeschlossen ist, bevor sie mit der Arbeit an einem neuen Frame beginnen, da dies die Latenz verringert und bessere Ergebnisse aus holografischen Framevorhersagen ermöglicht. Dies ist keine harte Regel, und wenn Sie Frames haben, die länger als eine Bildschirmaktualisierung benötigen, können Sie diese Wartezeit deaktivieren, indem Sie den HolographicFramePresentWaitBehavior-Parameter an PresentUsingCurrentPrediction übergeben. In diesem Fall würden Sie wahrscheinlich einen asynchronen Renderingthread verwenden, um eine kontinuierliche Auslastung der GPU aufrechtzuerhalten. Die Aktualisierungsrate des HoloLens-Geräts beträgt 60 Hz, wobei ein Frame eine Dauer von ca. 16 ms hat. Immersive Headset-Geräte können von 60 Hz bis 90 Hz reichen; beim Aktualisieren der Anzeige auf 90 Hz hat jeder Frame eine Dauer von ca. 11 ms.

Behandeln von DeviceLost-Szenarien in Zusammenarbeit mit dem HolographicFrame

DirectX 11-Apps möchten in der Regel das von der DXGI-Swapchain-Funktion Present zurückgegebene HRESULT überprüfen, um herauszufinden, ob ein DeviceLost-Fehler aufgetreten ist. Die HolographicFrame-Klasse übernimmt dies für Sie. Überprüfen Sie das zurückgegebene HolographicFramePresentResult , um herauszufinden, ob Sie das Direct3D-Gerät und gerätebasierte Ressourcen freigeben und neu erstellen müssen.

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

Wenn das Direct3D-Gerät verloren gegangen ist und Sie es neu erstellt haben, müssen Sie holographicSpace anweisen, das neue Gerät zu verwenden. Die Swapchain wird für dieses Gerät neu erstellt.

Von DeviceResources::InitializeUsingHolographicSpace:

m_holographicSpace.SetDirect3D11Device(m_d3dInteropDevice);

Sobald Ihr Frame angezeigt wurde, können Sie zur Standard Programmschleife zurückkehren und zulassen, dass er mit dem nächsten Frame fortfahren kann.

Hybridgrafik-PCs und Mixed Reality-Anwendungen

Windows 10 Creators Update PCs können sowohl mit diskreten als auch mit integrierten GPUs konfiguriert werden. Bei diesen Computertypen wählt Windows den Adapter aus, mit dem das Headset verbunden ist. Anwendungen müssen sicherstellen, dass das directX-Gerät, das erstellt wird, denselben Adapter verwendet.

Der meiste allgemeine Direct3D-Beispielcode veranschaulicht das Erstellen eines DirectX-Geräts mit dem Standardhardwareadapter, der in einem Hybridsystem möglicherweise nicht mit dem für das Headset verwendeten gerät.

Um Probleme zu umgehen, verwenden Sie holographicAdapterID von HolographicSpace. PrimaryAdapterId() oder HolographicDisplay. AdapterId(). Diese adapterId kann dann verwendet werden, um den richtigen DXGIAdapter mit IDXGIFactory4.EnumAdapterByLuid auszuwählen.

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

Code zum Aktualisieren von DeviceResources::CreateDeviceResources für die Verwendung von 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.
);

Hybridgrafik und Media Foundation

Die Verwendung von Media Foundation auf Hybridsystemen kann Zu Problemen führen, bei denen Video nicht gerendert wird oder die Videotextur beschädigt ist, da Media Foundation standardmäßig ein Systemverhalten verwendet. In einigen Szenarien ist das Erstellen eines separaten ID3D11Device erforderlich, um Multithreading zu unterstützen, und die richtigen Erstellungsflags werden festgelegt.

Beim Initialisieren des ID3D11Device muss D3D11_CREATE_DEVICE_VIDEO_SUPPORT Flag als Teil der D3D11_CREATE_DEVICE_FLAG definiert werden. Nachdem das Gerät und der Kontext erstellt wurden, rufen Sie SetMultithreadProtected auf, um Multithreading zu aktivieren. Um das Gerät dem IMFDXGIDeviceManager zuzuordnen, verwenden Sie die Funktion IMFDXGIDeviceManager::ResetDevice .

Code zum Zuordnen eines ID3D11Device zu 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;

Weitere Informationen