DirectX의 렌더링

참고

이 문서는 레거시 WinRT 네이티브 API와 관련이 있습니다. 새 네이티브 앱 프로젝트의 경우 OpenXR API를 사용하는 것이 좋습니다.

Windows Mixed Reality 사용자를 위한 풍부한 3D 그래픽 환경을 생성하기 위해 DirectX를 기반으로 합니다. 렌더링 추상화는 DirectX 바로 위에 위치하므로 앱은 시스템에서 예측하는 홀로그램 장면 관찰자의 위치와 방향을 추론할 수 있습니다. 그런 다음 개발자는 각 카메라를 기반으로 홀로그램을 찾을 수 있으므로 사용자가 이동할 때 앱이 다양한 공간 좌표계에서 이러한 홀로그램을 렌더링할 수 있습니다.

참고: 이 연습에서는 Direct3D 11의 홀로그램 렌더링에 대해 설명합니다. Direct3D 12 Windows Mixed Reality 앱 템플릿도 Mixed Reality 앱 템플릿 확장과 함께 제공됩니다.

현재 프레임에 대한 업데이트

홀로그램에 대한 애플리케이션 상태를 업데이트하려면 프레임당 한 번씩 앱이 다음을 수행합니다.

  • 디스플레이 관리 시스템에서 HolographicFrame 을 가져옵니다.
  • 렌더링이 완료될 때 카메라 보기의 현재 예측으로 장면을 업데이트합니다. 홀로그램 장면에 대해 둘 이상의 카메라가 있을 수 있습니다.

홀로그램 카메라 보기로 렌더링하려면 프레임당 한 번씩 앱이 다음을 수행합니다.

  • 각 카메라에 대해 시스템의 카메라 보기 및 프로젝션 매트릭스를 사용하여 현재 프레임의 장면을 렌더링합니다.

새 홀로그램 프레임을 만들고 해당 예측을 가져옵니다.

HolographicFrame에는 앱이 현재 프레임을 업데이트하고 렌더링하는 데 필요한 정보가 있습니다. 앱은 CreateNextFrame 메서드를 호출하여 각 새 프레임을 시작합니다. 이 메서드가 호출되면 예측은 사용 가능한 최신 센서 데이터를 사용하여 만들어지고 CurrentPrediction 개체에 캡슐화됩니다.

새 프레임 개체는 특정 시간에만 유효하기 때문에 렌더링된 각 프레임에 사용해야 합니다. CurrentPrediction 속성에는 카메라 위치와 같은 정보가 포함됩니다. 이 정보는 프레임이 사용자에게 표시되어야 하는 정확한 시점으로 추정됩니다.

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

카메라 업데이트 처리

백 버퍼는 프레임에서 프레임으로 변경할 수 있습니다. 앱은 각 카메라에 대한 백 버퍼의 유효성을 검사하고 필요에 따라 리소스 뷰 및 깊이 버퍼를 해제하고 다시 만들어야 합니다. 예측의 포즈 집합은 현재 프레임에서 사용되는 카메라의 신뢰할 수 있는 목록입니다. 일반적으로 이 목록을 사용하여 카메라 집합을 반복합니다.

AppMain::Update에서:

m_deviceResources->EnsureCameraResources(holographicFrame, prediction);

DeviceResources::EnsureCameraResources에서:

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

렌더링의 기준으로 사용할 좌표계 가져오기

Windows Mixed Reality 통해 앱은 물리적 세계에서 위치를 추적하기 위한 연결 및 고정 참조 프레임과 같은 다양한 좌표계를 만들 수 있습니다. 그러면 앱에서 이러한 좌표계를 사용하여 각 프레임에서 홀로그램을 렌더링할 위치를 추론할 수 있습니다. API에서 좌표를 요청할 때는 항상 해당 좌표를 표현할 SpatialCoordinateSystem 을 전달합니다.

AppMain::Update에서:

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

그런 다음 이러한 좌표계를 사용하여 장면의 콘텐츠를 렌더링할 때 스테레오 뷰 행렬을 생성할 수 있습니다.

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

응시 및 제스처 입력 처리

응시 입력은 시간 기반이 아니며 StepTimer 함수에서 업데이트할 필요가 없습니다. 그러나 이 입력은 앱이 각 프레임을 확인해야 하는 내용입니다.

시간 기반 업데이트 처리

실시간 렌더링 앱은 시간 기반 업데이트를 처리하는 방법이 필요합니다. Windows Holographic 앱 템플릿은 DirectX 11 UWP 앱 템플릿에 제공된 StepTimer와 유사한 StepTimer 구현을 사용합니다. 이 StepTimer 샘플 도우미 클래스는 고정된 시간 단계 업데이트, 가변 시간 단계 업데이트를 제공할 수 있으며 기본 모드는 가변 시간 단계입니다.

홀로그램 렌더링의 경우 고정 시간 단계로 구성할 수 있으므로 타이머 함수에 너무 많이 넣지 않도록 선택했습니다. 일부 프레임의 경우 프레임당 두 번 이상 호출되거나 전혀 호출되지 않을 수 있으며, 홀로그램 데이터 업데이트는 프레임당 한 번씩 수행되어야 합니다.

AppMain::Update에서:

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

좌표계에서 홀로그램 위치 및 회전

템플릿이 SpatialStationaryReferenceFrame과 마찬가지로 단일 좌표계에서 작동하는 경우 이 프로세스는 3D 그래픽에서 익숙한 프로세스와 다르지 않습니다. 여기서는 큐브를 회전하고 고정 좌표계의 위치에 따라 모델 행렬을 설정합니다.

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

고급 시나리오에 대한 참고 사항: 회전 큐브는 홀로그램을 단일 참조 프레임 내에 배치하는 방법의 간단한 예입니다. 동일한 렌더링된 프레임에서 동시에 여러 SpatialCoordinateSystemstems를 사용할 수도 있습니다.

상수 버퍼 데이터 업데이트

콘텐츠에 대한 모델 변환은 평소와 같이 업데이트됩니다. 지금까지 렌더링할 좌표계에 대한 유효한 변환을 계산했습니다.

SpinningCubeRenderer::Update에서:

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

보기 및 프로젝션 변환은 어떻습니까? 최상의 결과를 얻으려면 그리기 호출을 받기 전에 거의 준비가 될 때까지 기다려야 합니다.

현재 프레임 렌더링

Windows Mixed Reality 렌더링은 2D 모노 디스플레이의 렌더링과 크게 다르지 않지만 몇 가지 차이점이 있습니다.

  • 홀로그램 프레임 예측이 중요합니다. 프레임이 표시될 때 예측이 가까울수록 홀로그램이 더 잘 보입니다.
  • Windows Mixed Reality 카메라 보기를 제어합니다. 홀로그램 프레임이 나중에 표시되므로 각 프레임에 렌더링합니다.
  • 렌더링 대상 배열에 인스턴스화된 그리기를 사용하여 스테레오 렌더링을 수행하는 것이 좋습니다. 홀로그램 앱 템플릿은 Texture2DArray에 렌더링 대상 뷰를 사용하는 렌더링 대상 배열에 인스턴스화된 그리기의 권장 접근 방식을 사용합니다.
  • 스테레오 인스턴싱을 사용하지 않고 렌더링하려면 각 눈마다 하나씩 배열이 아닌 두 개의 RenderTargetView를 만들어야 합니다. 각 RenderTargetViews는 시스템에서 앱에 제공된 Texture2DArray 의 두 조각 중 하나를 참조합니다. 일반적으로 인스턴싱을 사용하는 것보다 느리기 때문에 권장되지 않습니다.

업데이트된 HolographicFrame 예측 가져오기

프레임 예측을 업데이트하면 이미지 안정화의 효과가 향상됩니다. 예측 사이의 짧은 시간과 프레임이 사용자에게 표시되는 시간 때문에 홀로그램의 더 정확한 위치를 얻을 수 있습니다. 렌더링 직전에 프레임 예측을 업데이트하는 것이 가장 좋습니다.

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

각 카메라에 렌더링

예측에서 포즈를 취하는 카메라 세트의 Loop 이 세트의 각 카메라에 렌더링됩니다.

렌더링 패스 설정

Windows Mixed Reality 입체 렌더링을 사용하여 깊이의 환상을 높이고 입체적으로 렌더링하므로 왼쪽 및 오른쪽 디스플레이가 모두 활성화됩니다. 스테레오스코픽 렌더링을 사용하면 두 디스플레이 사이에 오프셋이 있으며, 이 오프셋은 뇌가 실제 깊이로 조정됩니다. 이 섹션에서는 Windows Holographic 앱 템플릿의 코드를 사용하여 인스턴싱을 사용하는 스테레오스코픽 렌더링에 대해 설명합니다.

각 카메라에는 자체 렌더링 대상(백 버퍼)과 보기 및 프로젝션 행렬이 홀로그램 공간에 있습니다. 앱은 카메라별로 깊이 버퍼와 같은 다른 카메라 기반 리소스를 만들어야 합니다. Windows Holographic 앱 템플릿에서는 DX::CameraResources에서 이러한 리소스를 함께 번들로 묶는 도우미 클래스를 제공합니다. 먼저 렌더링 대상 뷰를 설정합니다.

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

예측을 사용하여 카메라의 보기 및 프로젝션 행렬을 가져옵니다.

각 홀로그램 카메라의 보기 및 프로젝션 매트릭스는 프레임마다 변경됩니다. 각 홀로그램 카메라에 대한 상수 버퍼의 데이터를 새로 고칩니다. 예측을 업데이트한 후 해당 카메라에 대한 그리기 호출을 하기 전에 이 작업을 수행합니다.

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

여기서는 카메라 포즈에서 매트릭스를 획득하는 방법을 보여 줍니다. 이 과정에서 카메라의 현재 뷰포트도 가져옵니다. 좌표계를 제공하는 방법을 확인합니다. 응시를 이해하는 데 사용한 것과 동일한 좌표계이며 회전 큐브를 배치하는 데 사용한 것과 동일합니다.

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

뷰포트는 각 프레임을 설정해야 합니다. 꼭짓점 셰이더(적어도)는 일반적으로 뷰/프로젝션 데이터에 액세스해야 합니다.

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

카메라 백 버퍼에 렌더링하고 깊이 버퍼를 커밋합니다.

보기/프로젝션 데이터를 사용하기 전에 TryGetViewTransform 이 성공했는지 확인하는 것이 좋습니다. 좌표계를 찾을 수 없는 경우(예: 추적이 중단된 경우) 앱이 해당 프레임에 대해 렌더링할 수 없기 때문입니다. 이 템플릿은 CameraResources 클래스가 성공적인 업데이트를 나타내는 경우에만 회전하는 큐브에서 Render를 호출합니다.

Windows Mixed Reality 개발자 또는 사용자가 전 세계에 배치하는 위치에 홀로그램을 유지하기 위한 이미지 안정화 기능을 포함합니다. 이미지 안정화는 사용자에게 최상의 홀로그램 환경을 보장하기 위해 렌더링 파이프라인에 내재된 대기 시간을 숨기는 데 도움이 됩니다. 이미지 안정화를 더욱 향상시키기 위해 포커스 지점을 지정하거나, 실시간에 최적화된 이미지 안정화를 컴퓨팅하기 위해 깊이 버퍼를 제공할 수 있습니다.

최상의 결과를 얻으려면 앱이 CommitDirect3D11DepthBuffer API를 사용하여 깊이 버퍼를 제공해야 합니다. 그런 다음 Windows Mixed Reality 깊이 버퍼의 기하 도형 정보를 사용하여 이미지 안정화를 실시간으로 최적화할 수 있습니다. Windows Holographic 앱 템플릿은 기본적으로 앱의 깊이 버퍼를 커밋하여 홀로그램 안정성을 최적화합니다.

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

참고

Windows GPU에서 깊이 텍스처를 처리하므로 깊이 버퍼를 셰이더 리소스로 사용할 수 있어야 합니다. 만든 ID3D11Texture2D는 무형식 형식이어야 하며 셰이더 리소스 뷰로 바인딩되어야 합니다. 다음은 이미지 안정화를 위해 커밋할 수 있는 깊이 텍스처를 만드는 방법의 예입니다.

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

홀로그램 콘텐츠 그리기

Windows Holographic 앱 템플릿은 인스턴스화된 기하 도형을 크기 2의 Texture2DArray에 그리는 권장 기술을 사용하여 스테레오로 콘텐츠를 렌더링합니다. 이 부분과 Windows Mixed Reality 작동 방식을 살펴보겠습니다.

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

각 인스턴스는 상수 버퍼에서 다른 뷰/프로젝션 매트릭스에 액세스합니다. 다음은 두 행렬의 배열인 상수 버퍼 구조입니다.

VPRTVertexShader.hlsl에 포함된 VertexShaderShared.hlsl에서:

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

렌더링 대상 배열 인덱스가 각 픽셀에 대해 설정되어야 합니다. 다음 코드 조각에서 output.viewId는 SV_RenderTargetArrayIndex 의미 체계에 매핑됩니다. 이를 위해서는 셰이더 단계에서 렌더링 대상 배열 인덱스 의미 체계를 설정할 수 있는 선택적 Direct3D 11.3 기능을 지원해야 합니다.

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

VPRTVertexShader.hlsl에 포함된 VertexShaderShared.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;
}

기존 인스턴스 그리기 기술을 스테레오 렌더링 대상 배열에 그리는 이 메서드와 함께 사용하려는 경우 일반적으로 사용하는 인스턴스 수의 두 배 수를 그립니다. 셰이더에서 input.instId 를 2로 분할하여 개체별 데이터의 버퍼로 인덱싱할 수 있는 원래 인스턴스 ID를 가져옵니다. int actualIdx = input.instId / 2;

HoloLens 스테레오 콘텐츠 렌더링에 대한 중요 참고 사항

Windows Mixed Reality 셰이더 단계에서 렌더링 대상 배열 인덱스를 설정하는 기능을 지원합니다. 일반적으로 이 작업은 Direct3D 11에 대한 의미 체계가 정의된 방식 때문에 기하 도형 셰이더 단계에서만 수행할 수 있는 작업입니다. 여기서는 꼭짓점 및 픽셀 셰이더 단계만 설정된 렌더링 파이프라인을 설정하는 방법에 대한 전체 예제를 보여 드립니다. 셰이더 코드는 위에서 설명한 대로 제공됩니다.

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

비 HoloLens 디바이스의 렌더링에 대한 중요 참고 사항

꼭짓점 셰이더에서 렌더링 대상 배열 인덱스를 설정하려면 그래픽 드라이버에서 HoloLens 지원하는 선택적 Direct3D 11.3 기능을 지원해야 합니다. 앱은 렌더링을 위해 해당 기술만 안전하게 구현할 수 있으며, Microsoft HoloLens 실행하기 위한 모든 요구 사항이 충족됩니다.

홀로그램 앱에 대한 강력한 개발 도구가 될 수 있는 HoloLens 에뮬레이터를 사용하고 Windows 10 PC에 연결된 Windows Mixed Reality 몰입형 헤드셋 디바이스를 지원하려는 경우가 있을 수 있습니다. 모든 Windows Mixed Reality HoloLens 없는 렌더링 경로에 대한 지원도 Windows Holographic 앱 템플릿에 기본 제공되어 있습니다. 템플릿 코드에서는 개발 PC의 GPU에서 홀로그램 앱을 실행할 수 있도록 하는 코드를 찾을 수 있습니다. DeviceResources 클래스에서 이 선택적 기능 지원을 확인하는 방법은 다음과 같습니다.

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

이 선택적 기능 없이 렌더링을 지원하려면 앱에서 기하 도형 셰이더를 사용하여 렌더링 대상 배열 인덱스 설정해야 합니다. 이 코드 조각은VSSetConstantBuffers와 이전 섹션에 표시된 코드 예제의PSSetShader 앞에 추가되어 HoloLens 스테레오를 렌더링하는 방법을 설명합니다.

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 참고: 이 경우 TEXCOORD0과 같이 항상 허용되는 셰이더 의미 체계를 사용하여 렌더링 대상 배열 인덱스를 기하 도형 셰이더에 전달하는 약간 수정된 꼭짓점 셰이더도 로드해야 합니다. 기하 도형 셰이더는 작업을 수행할 필요가 없습니다. 템플릿 기하 도형 셰이더는 SV_RenderTargetArrayIndex 의미 체계를 설정하는 데 사용되는 렌더링 대상 배열 인덱스를 제외하고 모든 데이터를 전달합니다.

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

표시

홀로그램 프레임이 스왑 체인을 표시하도록 설정

Windows Mixed Reality 통해 시스템은 스왑 체인을 제어합니다. 그런 다음 시스템은 고품질 사용자 환경을 보장하기 위해 각 홀로그램 카메라에 프레임을 표시하는 것을 관리합니다. 또한 각 카메라에 대한 뷰포트 업데이트를 제공하여 이미지 안정화 또는 Mixed Reality 캡처와 같은 시스템의 측면을 최적화합니다. 따라서 DirectX를 사용하는 홀로그램 앱은 DXGI 스왑 체인에서 Present 를 호출하지 않습니다. 대신 HolographicFrame 클래스를 사용하여 프레임 그리기를 완료하면 프레임에 대한 모든 스왑 체인을 표시합니다.

DeviceResources::P에서:

HolographicFramePresentResult presentResult = frame.PresentUsingCurrentPrediction();

기본적으로 이 API는 프레임이 반환되기 전에 완료되기를 기다립니다. 홀로그램 앱은 대기 시간을 줄이고 홀로그램 프레임 예측에서 더 나은 결과를 허용하기 때문에 새 프레임에서 작업을 시작하기 전에 이전 프레임이 완료되기를 기다려야 합니다. 이는 어려운 규칙이 아니며 렌더링하는 데 화면 새로 고침 시간이 두 개 이상 걸리는 프레임이 있는 경우 HolographicFramePresentWaitBehavior 매개 변수를 PresentUsingCurrentPrediction에 전달하여 이 대기를 사용하지 않도록 설정할 수 있습니다. 이 경우 비동기 렌더링 스레드를 사용하여 GPU에서 연속 부하를 유지할 수 있습니다. HoloLens 디바이스의 새로 고침 속도는 60hz이며, 한 프레임의 지속 시간은 약 16ms입니다. 몰입형 헤드셋 장치는 60hz에서 90hz까지 다양할 수 있습니다. 디스플레이를 90hz로 새로 고치면 각 프레임의 지속 시간은 약 11ms입니다.

HolographicFrame과 협력하여 DeviceLost 시나리오 처리

DirectX 11 앱은 일반적으로 DXGI 스왑 체인의 Present 함수에서 반환된 HRESULT를 확인하여 DeviceLost 오류가 있는지 확인하려고 합니다. HolographicFrame 클래스는 이를 처리합니다. 반환된 HolographicFramePresentResult 를 검사하여 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();
}

Direct3D 디바이스를 분실하고 다시 생성한 경우 HolographicSpace 에 새 디바이스 사용을 시작하도록 지시해야 합니다. 이 디바이스에 대해 스왑 체인이 다시 만들어집니다.

DeviceResources::InitializeUsingHolographicSpace에서:

m_holographicSpace.SetDirect3D11Device(m_d3dInteropDevice);

프레임이 표시되면 기본 프로그램 루프로 돌아가서 다음 프레임으로 계속 진행할 수 있습니다.

하이브리드 그래픽 PC 및 혼합 현실 애플리케이션

Windows 10 크리에이터스 업데이트 PC는 불연속 GPU와 통합 GPU를 모두 사용하여 구성할 수 있습니다. 이러한 유형의 컴퓨터를 사용하면 Windows 헤드셋이 연결된 어댑터를 선택합니다. 애플리케이션은 만든 DirectX 디바이스가 동일한 어댑터를 사용하는지 확인해야 합니다.

대부분의 일반적인 Direct3D 샘플 코드는 하이브리드 시스템에서 헤드셋에 사용되는 것과 동일하지 않을 수 있는 기본 하드웨어 어댑터를 사용하여 DirectX 디바이스를 만드는 방법을 보여 줍니다.

문제를 해결하려면 HolographicSpace에서 HolographicAdapterID를 사용합니다. PrimaryAdapterId() 또는 HolographicDisplay. AdapterId(). 그런 다음 이 adapterId를 사용하여 IDXGIFactory4.EnumAdapterByLuid를 사용하여 올바른 DXGIAdapter를 선택할 수 있습니다.

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

IDXGIAdapter를 사용하도록 DeviceResources::CreateDeviceResources를 업데이트하는 코드

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

하이브리드 그래픽 및 미디어 파운데이션

하이브리드 시스템에서 Media Foundation을 사용하면 Media Foundation이 시스템 동작을 기본값으로 설정하기 때문에 비디오가 렌더링되지 않거나 비디오 텍스처가 손상되는 문제가 발생할 수 있습니다. 일부 시나리오에서는 다중 스레딩을 지원하기 위해 별도의 ID3D11Device를 만들어야 하며 올바른 만들기 플래그가 설정됩니다.

ID3D11Device를 초기화할 때 D3D11_CREATE_DEVICE_VIDEO_SUPPORT 플래그는 D3D11_CREATE_DEVICE_FLAG 일부로 정의해야 합니다. 디바이스 및 컨텍스트가 만들어지면 SetMultithreadProtected 를 호출하여 다중 스레딩을 사용하도록 설정합니다. 디바이스를 IMFDXGIDeviceManager와 연결하려면 IMFDXGIDeviceManager::ResetDevice 함수를 사용합니다.

ID3D11Device를 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;

참고 항목