다음을 통해 공유


화면 방향 지원(DirectX 및 C++)

유니버설 Windows 플랫폼(UWP) 앱은 DisplayInformation::OrientationChanged 이벤트를 처리할 때 여러 화면 방향을 지원할 수 있습니다. 여기서는 Windows 10 디바이스의 그래픽 하드웨어를 효율적이고 효과적으로 사용할 수 있도록 UWP DirectX 앱에서 화면 회전을 처리하는 모범 사례에 대해 논의합니다.

시작하기 전 그래픽 하드웨어는 디바이스의 방향에 관계없이 항상 동일한 방식으로 픽셀 데이터를 출력합니다. Windows 10 디바이스는 현재 디스플레이 방향(일종의 센서 또는 소프트웨어 토글 사용)을 결정하고 사용자가 디스플레이 설정을 변경할 수 있게 할 수 있습니다. 이로 인해 Windows 10 자체는 이미지 회전을 처리하여 디바이스의 방향에 따라 '수직' 유지되도록 합니다. 기본적으로 앱은 방향 변경 알림(예: 창 크기)을 받습니다. 이 경우 Windows 10은 최종 표시를 위해 이미지를 즉시 회전합니다. 4가지 특정 화면 방향 중 세 가지(나중에 논의)의 경우 Windows 10에서는 추가 그래픽 리소스와 계산을 사용하여 최종 이미지를 표시합니다.

UWP DirectX 앱의 경우 DisplayInformation 개체는 앱이 쿼리할 수 있는 기본 디스플레이 방향 데이터를 제공합니다. 기본 방향은 디스플레이의 픽셀 너비가 높이보다 큰 가로입니다. 대체 방향은 세로로 디스플레이가 어느 방향으로든 90도 회전되고 너비가 높이보다 작게 됩니다.

Windows 10은 다음과 같은 네 가지 특정 표시 방향 모드를 정의합니다.

  • 가로-Windows 10의 기본 표시 방향이며 회전의 기본 또는 ID 각도(0도)로 간주됩니다.
  • 세로-디스플레이가 시계 방향으로 90도(또는 시계 반대 방향으로 270도) 회전되었습니다.
  • 가로, 대칭 이동-디스플레이가 180도 회전되었습니다(거꾸로 설정됨).
  • 세로, 대칭 이동-디스플레이가 시계 방향으로 90도(또는 시계 반대 방향으로 270도) 회전되었습니다.

디스플레이가 한 방향에서 다른 방향으로 회전하면 Windows 10은 내부에서 회전 연산을 수행하여 그려진 이미지를 새 방향에 맞춥니다. 사용자는 화면에서 수직 이미지를 볼 수 있습니다.

또한 Windows 10은 자동 전환 애니메이션을 표시하여 다른 방향으로 이동할 때 원활한 사용자 환경을 생성합니다. 디스플레이 방향이 전환되면 사용자는 이러한 전환을 표시된 화면 이미지의 고정된 확대/축소 및 회전 애니메이션으로 봅니다. 시간은 Windows 10에서 새 방향의 레이아웃을 위해 앱에 할당됩니다.

전반적으로 화면 방향의 변경 내용을 처리하는 일반적인 프로세스입니다.

  1. 창 경계 값과 표시 방향 데이터의 조합을 사용하여 스왑 체인을 디바이스의 기본 디스플레이 방향과 정렬된 상태로 유지합니다.
  2. IDXGISwapChain1::SetRotation을 사용하여 Windows 10에 스왑 체인의 방향을 알립니다.
  3. 렌더링 코드를 변경하여 디바이스의 사용자 방향에 맞는 이미지를 생성합니다.

스왑 체인 크기 조정 및 콘텐츠 사전 회전

기본 디스플레이 크기를 조정하고 UWP DirectX 앱에서 콘텐츠를 사전 회전하려면 다음 단계를 구현합니다.

  1. DisplayInformation::OrientationChanged 이벤트를 처리합니다.
  2. 스왑 체인의 크기를 창의 새 차원으로 조정합니다.
  3. IDXGISwapChain1::SetRotation을 호출하여 스왑 체인의 방향을 설정합니다.
  4. 렌더링 대상 및 기타 픽셀 데이터 버퍼와 같은 창 크기 종속 리소스를 다시 생성합니다.

이제 각 단계에 대해 좀 더 자세히 살펴보겠습니다.

첫 번째 단계는 DisplayInformation::OrientationChanged 이벤트에 대한 처리기를 등록하는 것입니다. 이 이벤트는 화면 방향이 변경될 때마다(예: 디스플레이가 회전되는 경우) 앱에서 발생합니다.

DisplayInformation::OrientationChanged 이벤트를 처리하려면 뷰 공급자가 구현해야 하는 IFrameworkView 인터페이스의 메서드 중 하나인 필수 SetWindow 메서드에서 DisplayInformation::OrientationChanged에 대한 처리기를 연결합니다.

이 코드 예제에서 DisplayInformation::OrientationChanged에 대한 이벤트 처리기는 OnOrientationChanged라는 메서드입니다. DisplayInformation::OrientationChanged가 발생하면 SetCurrentOrientation이라는 메서드를 호출한 다음 CreateWindowSizeDependentResources를 호출합니다.

void App::SetWindow(CoreWindow^ window)
{
  // ... Other UI event handlers assigned here ...
  
    currentDisplayInformation->OrientationChanged +=
        ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnOrientationChanged);

  // ...
}
}
void App::OnOrientationChanged(DisplayInformation^ sender, Object^ args)
{
    m_deviceResources->SetCurrentOrientation(sender->CurrentOrientation);
    m_main->CreateWindowSizeDependentResources();
}

// This method is called in the event handler for the OrientationChanged event.
void DX::DeviceResources::SetCurrentOrientation(DisplayOrientations currentOrientation)
{
    if (m_currentOrientation != currentOrientation)
    {
        m_currentOrientation = currentOrientation;
        CreateWindowSizeDependentResources();
    }
}

다음으로, 새 화면 방향에 맞게 스왑 체인의 크기를 조정하고 렌더링이 수행될 때 그래픽 파이프라인의 내용을 회전하도록 준비합니다. 이 예제에서 DirectXBase::CreateWindowSizeDependentResources는 IDXGISwapChain::ResizeBuffers 호출, 3D 및 2D 회전 행렬 설정, SetRotation 호출 및 리소스 다시 생성하기를 처리하는 메서드입니다.

void DX::DeviceResources::CreateWindowSizeDependentResources() 
{
    // Clear the previous window size specific context.
    ID3D11RenderTargetView* nullViews[] = {nullptr};
    m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr);
    m_d3dRenderTargetView = nullptr;
    m_d2dContext->SetTarget(nullptr);
    m_d2dTargetBitmap = nullptr;
    m_d3dDepthStencilView = nullptr;
    m_d3dContext->Flush();

    // Calculate the necessary render target size in pixels.
    m_outputSize.Width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_dpi);
    m_outputSize.Height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_dpi);
    
    // Prevent zero size DirectX content from being created.
    m_outputSize.Width = max(m_outputSize.Width, 1);
    m_outputSize.Height = max(m_outputSize.Height, 1);

    // The width and height of the swap chain must be based on the window's
    // natively-oriented width and height. If the window is not in the native
    // orientation, the dimensions must be reversed.
    DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation();

    bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270;
    m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width;
    m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height;

    if (m_swapChain != nullptr)
    {
        // If the swap chain already exists, resize it.
        HRESULT hr = m_swapChain->ResizeBuffers(
            2, // Double-buffered swap chain.
            lround(m_d3dRenderTargetSize.Width),
            lround(m_d3dRenderTargetSize.Height),
            DXGI_FORMAT_B8G8R8A8_UNORM,
            0
            );

        if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
        {
            // If the device was removed for any reason, a new device and swap chain will need to be created.
            HandleDeviceLost();

            // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method 
            // and correctly set up the new device.
            return;
        }
        else
        {
            DX::ThrowIfFailed(hr);
        }
    }
    else
    {
        // Otherwise, create a new one using the same adapter as the existing Direct3D device.
        DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};

        swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width); // Match the size of the window.
        swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height);
        swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
        swapChainDesc.Stereo = false;
        swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
        swapChainDesc.SampleDesc.Quality = 0;
        swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
        swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All UWP apps must use this SwapEffect.
        swapChainDesc.Flags = 0;    
        swapChainDesc.Scaling = DXGI_SCALING_NONE;
        swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

        // This sequence obtains the DXGI factory that was used to create the Direct3D device above.
        ComPtr<IDXGIDevice3> dxgiDevice;
        DX::ThrowIfFailed(
            m_d3dDevice.As(&dxgiDevice)
            );

        ComPtr<IDXGIAdapter> dxgiAdapter;
        DX::ThrowIfFailed(
            dxgiDevice->GetAdapter(&dxgiAdapter)
            );

        ComPtr<IDXGIFactory2> dxgiFactory;
        DX::ThrowIfFailed(
            dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory))
            );

        DX::ThrowIfFailed(
            dxgiFactory->CreateSwapChainForCoreWindow(
                m_d3dDevice.Get(),
                reinterpret_cast<IUnknown*>(m_window.Get()),
                &swapChainDesc,
                nullptr,
                &m_swapChain
                )
            );

        // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
        // ensures that the application will only render after each VSync, minimizing power consumption.
        DX::ThrowIfFailed(
            dxgiDevice->SetMaximumFrameLatency(1)
            );
    }

    // Set the proper orientation for the swap chain, and generate 2D and
    // 3D matrix transformations for rendering to the rotated swap chain.
    // Note the rotation angle for the 2D and 3D transforms are different.
    // This is due to the difference in coordinate spaces.  Additionally,
    // the 3D matrix is specified explicitly to avoid rounding errors.

    switch (displayRotation)
    {
    case DXGI_MODE_ROTATION_IDENTITY:
        m_orientationTransform2D = Matrix3x2F::Identity();
        m_orientationTransform3D = ScreenRotation::Rotation0;
        break;

    case DXGI_MODE_ROTATION_ROTATE90:
        m_orientationTransform2D = 
            Matrix3x2F::Rotation(90.0f) *
            Matrix3x2F::Translation(m_logicalSize.Height, 0.0f);
        m_orientationTransform3D = ScreenRotation::Rotation270;
        break;

    case DXGI_MODE_ROTATION_ROTATE180:
        m_orientationTransform2D = 
            Matrix3x2F::Rotation(180.0f) *
            Matrix3x2F::Translation(m_logicalSize.Width, m_logicalSize.Height);
        m_orientationTransform3D = ScreenRotation::Rotation180;
        break;

    case DXGI_MODE_ROTATION_ROTATE270:
        m_orientationTransform2D = 
            Matrix3x2F::Rotation(270.0f) *
            Matrix3x2F::Translation(0.0f, m_logicalSize.Width);
        m_orientationTransform3D = ScreenRotation::Rotation90;
        break;

    default:
        throw ref new FailureException();
    }


    //SDM: only instance of SetRotation
    DX::ThrowIfFailed(
        m_swapChain->SetRotation(displayRotation)
        );

    // Create a render target view of the swap chain back buffer.
    ComPtr<ID3D11Texture2D> backBuffer;
    DX::ThrowIfFailed(
        m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
        );

    DX::ThrowIfFailed(
        m_d3dDevice->CreateRenderTargetView(
            backBuffer.Get(),
            nullptr,
            &m_d3dRenderTargetView
            )
        );

    // Create a depth stencil view for use with 3D rendering if needed.
    CD3D11_TEXTURE2D_DESC depthStencilDesc(
        DXGI_FORMAT_D24_UNORM_S8_UINT, 
        lround(m_d3dRenderTargetSize.Width),
        lround(m_d3dRenderTargetSize.Height),
        1, // This depth stencil view has only one texture.
        1, // Use a single mipmap level.
        D3D11_BIND_DEPTH_STENCIL
        );

    ComPtr<ID3D11Texture2D> depthStencil;
    DX::ThrowIfFailed(
        m_d3dDevice->CreateTexture2D(
            &depthStencilDesc,
            nullptr,
            &depthStencil
            )
        );

    CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
    DX::ThrowIfFailed(
        m_d3dDevice->CreateDepthStencilView(
            depthStencil.Get(),
            &depthStencilViewDesc,
            &m_d3dDepthStencilView
            )
        );
    
    // Set the 3D rendering viewport to target the entire window.
    m_screenViewport = CD3D11_VIEWPORT(
        0.0f,
        0.0f,
        m_d3dRenderTargetSize.Width,
        m_d3dRenderTargetSize.Height
        );

    m_d3dContext->RSSetViewports(1, &m_screenViewport);

    // Create a Direct2D target bitmap associated with the
    // swap chain back buffer and set it as the current target.
    D2D1_BITMAP_PROPERTIES1 bitmapProperties = 
        D2D1::BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
            m_dpi,
            m_dpi
            );

    ComPtr<IDXGISurface2> dxgiBackBuffer;
    DX::ThrowIfFailed(
        m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer))
        );

    DX::ThrowIfFailed(
        m_d2dContext->CreateBitmapFromDxgiSurface(
            dxgiBackBuffer.Get(),
            &bitmapProperties,
            &m_d2dTargetBitmap
            )
        );

    m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());

    // Grayscale text anti-aliasing is recommended for all UWP apps.
    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

}

다음에 이 메서드를 호출할 때 창의 현재 높이 및 너비 값을 저장한 후 디스플레이 범위의 DIP(디바이스 독립형 픽셀) 값을 픽셀로 변환합니다. 샘플에서는 이 코드를 실행하는 간단한 함수인 ConvertDipsToPixels를 호출합니다.

floor((dips * dpi / 96.0f) + 0.5f);

0.5f를 추가하여 가장 가까운 정수 값으로 반올림합니다.

이외에도 CoreWindow 좌표는 항상 DIP에 정의됩니다. Windows 10 및 이전 Windows 버전의 경우 DIP는 1/96인치로 정의되고 OS의 최대 정의에 맞춰집니다. 디스플레이 방향이 세로 모드로 회전하면 앱이 CoreWindow의 너비와 높이를 대칭 이동하고 렌더링 대상 크기(범위)가 그에 따라 변경되어야 합니다. Direct3D의 좌표는 항상 실제 픽셀이므로 이러한 값을 Direct3D에 전달하여 스왑 체인을 설정하기 전에 CoreWindow의 DIP 값에서 정수 픽셀 값으로 변환해야 합니다.

프로세스 측면에서는 단순히 스왑 체인의 크기를 조정하는 경우보다 조금 더 많은 작업을 수행합니다. 즉, 프레젠테이션을 위해 합성하기 전 이미지의 Direct2D 및 Direct3D 구성 요소를 실제로 회전하고, 결과를 새 방향으로 렌더링했다고 스왑 체인에 지시합니다. DX::DeviceResources::CreateWindowSizeDependentResources에 대한 코드 예제와 같이 이 프로세스에 대한 자세한 내용은 다음과 같습니다.

  • 디스플레이의 새 방향을 결정합니다. 디스플레이가 가로에서 세로로 또는 그 반대로 대칭 이동된 경우, 디스플레이 경계에 대해 DIP 값에서 픽셀로 변경된 높이 및 너비 값을 바꿉니다.

  • 그런 다음 검사 스왑 체인이 생성되었는지 확인합니다. 생성되지 않은 경우 IDXGIFactory2::CreateSwapChainForCoreWindow를 호출하여 생성합니다. 그렇지 않으면 IDXGISwapchain:ResizeBuffers를 호출하여 기존 스왑 체인의 버퍼 크기를 새 디스플레이 차원으로 조정합니다. 회전 이벤트에 대한 스왑 체인의 크기를 조정할 필요는 없지만 렌더링 파이프라인에서 이미 회전한 콘텐츠를 출력하는 경우 크기 조정이 필요한 스냅 및 채우기 이벤트 등의 다른 크기 변경 이벤트가 있습니다.

  • 그런 다음 스왑 체인에 렌더링할 때 그래픽 파이프라인의 픽셀 또는 꼭짓점 각각에 적용할 적절한 2차원 또는 3차원 행렬 변환을 설정합니다. 다음과 같은 4가지 회전 행렬이 있습니다.

    • 가로(DXGI_MODE_ROTATION_IDENTITY)
    • 세로(DXGI_MODE_ROTATION_ROTATE270)
    • 가로, 대칭 이동(DXGI_MODE_ROTATION_ROTATE180)
    • 세로, 대칭 이동(DXGI_MODE_ROTATION_ROTATE90)

    올바른 행렬은 디스플레이 방향을 결정하기 위해 Windows 10에서 제공하는 데이터(예: DisplayInformation::OrientationChanged의 결과)에 따라 선택되며, 장면에서 각 픽셀(Direct2D) 또는 꼭짓점(Direct3D)의 좌표를 곱하여 화면 방향에 맞게 효과적으로 회전합니다. (Direct2D에서는 화면 원점이 좌측 상단 모서리로 정의되고 Direct3D에서는 원본이 창의 논리적 중심으로 정의됩니다.)

참고 회전에 사용되는 2차원 변환 및 이를 정의하는 방법에 대한 자세한 내용은 화면 회전에 대한 행렬 정의(2-D)를 참조하세요. 회전에 사용되는 3차원 변환에 대한 자세한 내용은 화면 회전에 대한 행렬 정의(3차원)를 참조하세요.

 

이제 IDXGISwapChain1::SetRotation을 호출하고 다음과 같이 업데이트된 회전 행렬을 제공합니다.

m_swapChain->SetRotation(rotation);

또한 렌더링 메서드가 새 프로젝션을 계산할 때 가져올 수 있는 선택한 회전 행렬을 저장합니다. 최종 3차원 프로젝션을 렌더링하거나 최종 2차원 레이아웃을 합성할 때 이 행렬을 사용합니다. (자동 적용되지 않습니다.)

그런 다음 회전된 3차원 보기에 대한 새 렌더링 대상과 뷰에 대한 새 깊이 스텐실 버퍼를 만듭니다. ID3D11DeviceContext:RSSetViewports를 호출하여 회전된 장면에 대한 3차원 렌더링 뷰포트를 설정합니다.

마지막으로 회전하거나 배치할 2차원 이미지가 있는 경우, ID2D1DeviceContext::CreateBitmapFromDxgiSurface를 사용하여 크기 조정된 스왑 체인에 대한 쓰기 가능한 비트맵으로 2차원 렌더링 대상을 생성하고 업데이트된 방향에 대한 새 레이아웃을 합성합니다. 코드 예제에서 본 바와 같이 앤티앨리어싱 모드 등의 렌더링 대상에 필요한 속성을 설정합니다.

이제 스왑 체인을 표시합니다.

CoreWindowResizeManager를 사용하여 회전 지연 줄이기

기본적으로 Windows 10은 앱 모델이나 언어에 관계없이 모든 앱에 짧지만 눈에 띄는 시간 창을 제공하여 이미지 회전을 완료합니다. 그러나 앱이 여기에 설명된 기술 중 하나를 사용하여 회전 계산을 수행할 경우 이 기간이 종료되기 전에 잘 수행될 수 있습니다. 해당 시간을 되돌리고 회전 애니메이션을 완료하시겠습니까? 이 위치에서 CoreWindowResizeManager가 제공됩니다.

CoreWindowResizeManager를 사용하는 방법은 다음과 같습니다. DisplayInformation::OrientationChanged 이벤트가 발생하면 이벤트에 대한 처리기 내에서 CoreWindowResizeManager::GetForCurrentView를 호출하여 CoreWindowResizeManager의 인스턴스를 가져오고 새 방향의 레이아웃이 완료되고 표시되면 NotifyLayoutCompleted를 호출합니다. 회전 애니메이션을 완료하고 앱 화면을 표시할 수 있음을 Windows에 알릴 수 있습니다.

DisplayInformation::OrientationChanged에 대한 이벤트 처리기의 코드는 다음과 같습니다.

CoreWindowResizeManager^ resizeManager = Windows::UI::Core::CoreWindowResizeManager::GetForCurrentView();

// ... build the layout for the new display orientation ...

resizeManager->NotifyLayoutCompleted();

사용자가 디스플레이 방향을 회전하면 Windows 10은 앱과 독립적인 애니메이션을 사용자에게 피드백으로 표시합니다. 해당 애니메이션에는 다음 순서로 수행되는 세 가지 부분이 있습니다.

  • Windows 10은 원래 이미지를 축소합니다.
  • Windows 10은 새 레이아웃을 다시 빌드하는 데 걸리는 시간 동안 이미지를 유지합니다. 앱이 모두 필요하지 않기 때문에 이 기간은 줄이려는 시간 범위입니다.
  • 레이아웃 창이 만료되거나 레이아웃 완성 알림이 수신되면 Windows에서 이미지를 회전한 다음 교차 페이드 확대/축소를 새 방향으로 전환합니다.

세 번째 글머리 기호에 제안된 대로 앱이 NotifyLayoutCompleted를 호출하면 Windows 10은 시간 제한 창을 중지하고 회전 애니메이션을 완료하고 컨트롤을 앱에 반환합니다. 그러면 이제 새 디스플레이 방향으로 그려집니다. 전반적인 효과는 이제 앱이 좀 더 유동적이고 반응성이 뛰어나며 좀 더 효율적으로 작동한다는 것입니다.

부록 A: 화면 회전에 행렬 적용(2차원)

스왑 체인의 크기 조정 및 내용 미리 회전(및 DXGI 스왑 체인 회전 샘플)의 예제 코드를 통해 Direct2D 출력과 Direct3D 출력에 대해 다른 회전 행렬을 적용했다는 사실을 알게 되었을 것입니다. 먼저 2차원 행렬을 살펴보겠습니다.

Direct2D 및 Direct3D 콘텐츠에 동일한 회전 행렬을 적용할 수 없는 두 가지 이유가 있습니다.

  • 하나는 다른 카티전 좌표 모델을 사용합니다. Direct2D는 y 좌표가 원점에서 위로 이동하는 양수 값이 증가하는 오른손 규칙을 사용합니다. 그러나 Direct3D는 왼손 규칙을 사용합니다. 여기서 y 좌표는 원점에서 오른쪽으로 양수 값으로 증가합니다. 결과는 Direct2D의 경우 화면 좌표의 원점이 왼쪽 위에 있는 반면, 화면의 원점(프로젝션 평면)은 Direct3D의 왼쪽 아래에 있습니다. (자세한 내용은 3차원 좌표계를 참조하세요.)

    direct3d 좌표계입니다.direct2d 좌표계입니다.

  • 둘째, 반올림 오류를 방지하려면 3차원 회전 행렬을 명시적으로 지정해야 합니다.

스왑 체인은 원점이 왼쪽 아래에 있다고 가정하므로 오른손 Direct2D 좌표계를 스왑 체인에서 사용하는 왼손 좌표계와 정렬하기 위해 회전해야 합니다. 특히 회전 행렬에 회전 좌표계 원점의 변환 행렬을 곱하여 새 왼손 방향 아래 이미지의 위치를 변경하고 CoreWindow의 좌표 공간에서 스왑 체인의 좌표 공간으로 이미지를 변환합니다. 또한 Direct2D 렌더링 대상이 스왑 체인과 연결된 경우에도 앱이 이 변환을 일관되게 적용해야 합니다. 그러나 앱이 스왑 체인과 직접 연결되지 않은 중간 표면에 그리는 경우, 이 좌표 공간 변환을 적용하지 마세요.

4개의 가능한 회전에서 올바른 행렬을 선택하는 코드는 다음과 같을 수 있습니다(새 좌표계 원점으로의 변환에 유의하세요).

   
// Set the proper orientation for the swap chain, and generate 2D and
// 3D matrix transformations for rendering to the rotated swap chain.
// Note the rotation angle for the 2D and 3D transforms are different.
// This is due to the difference in coordinate spaces.  Additionally,
// the 3D matrix is specified explicitly to avoid rounding errors.

switch (displayRotation)
{
case DXGI_MODE_ROTATION_IDENTITY:
    m_orientationTransform2D = Matrix3x2F::Identity();
    m_orientationTransform3D = ScreenRotation::Rotation0;
    break;

case DXGI_MODE_ROTATION_ROTATE90:
    m_orientationTransform2D = 
        Matrix3x2F::Rotation(90.0f) *
        Matrix3x2F::Translation(m_logicalSize.Height, 0.0f);
    m_orientationTransform3D = ScreenRotation::Rotation270;
    break;

case DXGI_MODE_ROTATION_ROTATE180:
    m_orientationTransform2D = 
        Matrix3x2F::Rotation(180.0f) *
        Matrix3x2F::Translation(m_logicalSize.Width, m_logicalSize.Height);
    m_orientationTransform3D = ScreenRotation::Rotation180;
    break;

case DXGI_MODE_ROTATION_ROTATE270:
    m_orientationTransform2D = 
        Matrix3x2F::Rotation(270.0f) *
        Matrix3x2F::Translation(0.0f, m_logicalSize.Width);
    m_orientationTransform3D = ScreenRotation::Rotation90;
    break;

default:
    throw ref new FailureException();
}
    

2차원 이미지에 대한 올바른 회전 행렬과 원점이 있으면 ID2D1DeviceContext::SetTransformID2D1DeviceContext::BeginDrawID2D1DeviceContext::EndDraw 호출 간에 호출하여 설정합니다.

경고 Direct2D에는 변환 스택이 없습니다. 앱이 그리기 코드의 일부로 ID2D1DeviceContext::SetTransform을 사용하는 경우 이 행렬을 적용한 다른 변환에 곱해야 합니다.

 

    ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext();
    Windows::Foundation::Size logicalSize = m_deviceResources->GetLogicalSize();

    context->SaveDrawingState(m_stateBlock.Get());
    context->BeginDraw();

    // Position on the bottom right corner.
    D2D1::Matrix3x2F screenTranslation = D2D1::Matrix3x2F::Translation(
        logicalSize.Width - m_textMetrics.layoutWidth,
        logicalSize.Height - m_textMetrics.height
        );

    context->SetTransform(screenTranslation * m_deviceResources->GetOrientationTransform2D());

    DX::ThrowIfFailed(
        m_textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING)
        );

    context->DrawTextLayout(
        D2D1::Point2F(0.f, 0.f),
        m_textLayout.Get(),
        m_whiteBrush.Get()
        );

    // Ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = context->EndDraw();

다음에 스왑 체인을 표시할 때 2차원 이미지가 새 디스플레이 방향과 일치하도록 회전됩니다.

부록 B: 화면 회전에 행렬 적용(3차원)

스왑 체인의 크기 조정 및 내용 미리 회전(및 DXGI 스왑 체인 회전 샘플)의 예제 코드에서 가능한 각 화면 방향에 대한 특정 변환 행렬을 정의했습니다. 이제 3차원 장면을 회전하기 위한 행렬을 살펴보겠습니다. 이전과 마찬가지로 가능한 4개의 방향 각각에 대한 행렬 집합을 생성합니다. 반올림 오류 및 경미한 시각적 아티팩트를 방지하려면 코드에서 행렬을 명시적으로 선언합니다.

다음과 같이 이러한 3차원 회전 행렬을 설정합니다. 다음 코드 예제에 표시된 행렬은 카메라의 3차원 장면 공간에서 점을 정의하는 꼭짓점의 0, 90, 180, 270도 회전에 대한 표준 회전 행렬입니다. 화면의 2차원 프로젝션이 컴퓨팅될 때 화면의 각 꼭짓점 [x, y, z] 좌표 값에 이 회전 행렬을 곱합니다.

   
// 0-degree Z-rotation
static const XMFLOAT4X4 Rotation0( 
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
    );

// 90-degree Z-rotation
static const XMFLOAT4X4 Rotation90(
    0.0f, 1.0f, 0.0f, 0.0f,
    -1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
    );

// 180-degree Z-rotation
static const XMFLOAT4X4 Rotation180(
    -1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, -1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
    );

// 270-degree Z-rotation
static const XMFLOAT4X4 Rotation270( 
    0.0f, -1.0f, 0.0f, 0.0f,
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
    );            
    }

다음과 같이 IDXGISwapChain1::SetRotation을 호출하여 스왑 체인에서 회전 유형을 설정합니다.

m_swapChain->SetRotation(rotation);

이제 렌더링 메서드에서 다음과 유사한 코드를 구현합니다.

struct ConstantBuffer // This struct is provided for illustration.
{
    // Other constant buffer matrices and data are defined here.

    float4x4 projection; // Current matrix for projection
} ;
ConstantBuffer  m_constantBufferData;          // Constant buffer resource data

// ...

// Rotate the projection matrix as it will be used to render to the rotated swap chain.
m_constantBufferData.projection = mul(m_constantBufferData.projection, m_rotationTransform3D);

이제 render 메서드를 호출하면 현재 회전 행렬(클래스 변수 m_orientationTransform3D로 지정)에 현재 프로젝션 행렬을 곱한 다음, 작업 결과를 렌더러의 새 프로젝션 행렬로 할당합니다. 업데이트된 디스플레이 방향에서 장면을 보려면 스왑 체인을 표시합니다.