< 변환의 중심 점은 어디인가? >

Direct2D의 변환과 관련된 API는 모두 변환을 수행할 중심점을 함수 인자로 요구를 합니다.
그렇다면, 이 중심점은 스크린 기준에서 정의되어지는 것일까요?

다음과 같은 다람쥐 그림이 있다고 가정해 보겠습니다.


이 그림은 ( 100, 100 ) 의 크기로 그려지기를 원한다고 가정해 보겠습니다.

 


그리고 우리의 모니터에 ( 300, 200 ) 위치에 그려지기 원하도록 설정하겠습니다.
그러면, 우리에게 변환을 위한 중점을 설정하기 위한 기준 좌표는 어떻게 설정되어야 할까요?
그림의 좌측 상단을 변환의 중점으로 원한다면 ( 300, 200 )으로 설정하면 될까요?
그림의 좌측 상단이 변환의 중점이 된다는 말의 의미를 잘 되새겨 보시기 바랍니다.

그림의 중심을 기준으로 중점을 설정해 두는 것이 훨씬 쉬운 개념으로 변환할 수 있습니다.
즉, 위의 그림에서 변환의 중심이 되는 좌측 상단은 좌표는 ( 0, 0 ) 이 되는 것입니다.
만약, 우측 하단이 변환이 중심이 되길 원한다면 ( 100, 100 )을 설정하면 됩니다.

앞서 살펴 보았던 샘플에는 이 의미를 그냥 넘겼지만
이번에 제공되는 샘플에서는 이를 고려해서 모두 작성했으니,
유심히 살펴보시기 바랍니다.^^

우리는 이 개념을 다음과 같이 지금까지 사용하고 있었습니다.


D2D1_RECT_F 정의의 변수가 보이시나요?
바로 이 dxArea 를 기준으로 변환이 되는 중점을 설정하는 것이 이해하기가 편리합니다.^^

이번 샘플은 지난 번 샘플의 연장에 있습니다.
지난 번 샘플에서 이번 내용과 관련된 부분을 추가 했습니다.

 




< Scale( 확대/축소 ) >

확대/축소 작업은 1.0을 기준으로 이루어집니다.
1.0 보다 작으면 축소가, 1.0보다 크다면 확대가 이루어 집니다.
이 작업은 D2D1::Matrix3x2F::Scale() API를 통해서 이루어 지는데,
역시나 변환의 중점을 요구합니다.^^



만약 좌측 상단을 기준으로 확대를 하면 다음과 같은 개념입니다.






< Skew( 찌그러뜨리기 ) >

마지막 변환은 찌그러뜨리기 작업입니다.
이는 Matrix3x2F::Skew() API를 통해서 설정할 수 있습니다.
함수 인자로 받는 것은 X축과 Y축의 찌그러질 각도와 역시나 기준이 되는 중점입니다.



역시나 변환이 되는 중점에 따라 결과가 변합니다.
아래의 그림은 30도씩 찌끄러뜨린 결과입니다.
첫번째는 X축만 적용한 것이고, 두번째는 Y축을,
세번째는 X와 Y축 모두를 30도씩 찌그러뜨린 결과입니다.



부족하지만, 제가 작성한 샘플을 첨부해 드립니다.^^


 

신고

 

첫 번째 Direct2D 프로그래밍~ 
 

지난 시간을 통해서 Direct2D의 필요성에 대해서, 제가 열심히(?) 언급해 드렸습니다.^^

이번 시간에는 Direct2D 프로그래밍의 세계에 대해서 들어가기 전에,

간단하게 프로그램을 작성해 볼 것입니다.

부끄럽지만, 저는 박식한 이론 내용 없이도 많은 프로그래밍 작업을 했었습니다.^^

그 만큼, 직접 프로그램을 작성하면 쉽게 이해할 수 있는 부분이 많이 있을 것입니다.

기본적으로 Direct2D는 2차원 그래픽을 만들기 위한 API입니다.

기존의 GDI를 이용한 프로그램의 일부분을 Direct2D로 대체를 하는 것만으로도 성능을 향상시킬 수 있습니다.

 

기본적으로 Direct2D로 작업하는 순서는 다음과 같습니다.

  1. Direct2D 팩토리를 생성한다.
  2. 팩토리에서 렌더타겟을 생성한다.
  3. 렌더타겟에서 리소스들을 생성한다.
  4. 생성 되어진 리소스들을 이용해서 그리기 작업을 수행한다.

 

Direct2D의 모든 작업은 위의 순서를 따릅니다.

이제 이들 순서를 어떻게 API로 표현하는지 살펴보겠습니다.

 

화면 작업을 위해 준비하기

 

첫 번째로 작성해볼 프로그램은 특정 색상으로 화면을 채우는 작업을 하는 것입니다.

먼저 마법사로 프로젝트를 생성하고, "stdafx.h" 헤더파일에 Direct2D와 관련된 선언을 추가시켜주기 바랍니다.

위의 내용은 Direct2D와 관련된 라이브러리와 헤더파일을 선언해 준 것입니다.

 

그리고 작업을 수행할 .cpp 파일에 전역 변수를 두 개 선언합니다.

 

Direct2D 프로그래밍을 위해서 가장 먼저 해야 하는 일은 ID2D1Factory 를 생성하는 일입니다.

 

D2D1CreateFactory()의 첫 번째 인자는 멀티 스레드 지원 여부를 설정합니다.

이번 내용에서는 싱글 스레드만을 사용합니다.( 멀티스레드 어려워요~~)

두 번째 인자는 팩토리가 생성되어서 결과를 반환 받을 수 있는 팩토리 포인터를 넘겨줍니다.

이것이 성공하면, Direct2D 와 관련된 작업을 할 수 있게 됩니다.( 참~~ 쉽죠잉!! )

 

우리가 만들려고 하는 프로그램이 화면에 어떤 내용을 그리는 것입니다.

화면에 무엇인가를 그린다는 개념은 하드웨어 입장에서 봤을 때는 메모리에 값을 쓰는 것입니다.

여러분들이 보고 있는 모니터 화면은 거대한 메모리에 색상 값이 기록되어 있는 것입니다.

 

이번에 할 일은 바로 이 메모리 영역을 생성하는 일입니다.

Direct2D의 가장 큰 장점이 바로 이 메모리 영역에 값을 기록하는 작업( 이하 렌더링 )이

GDI를 이용하는 것보다 훨씬 빠르다는 것입니다.

왜냐하면, 바로 이 메모리 영역이 그래픽 카드에 있기 때문입니다.

 

그리기 명령을 수행할 메모리 영역을 생성하기 위해서 다음과 같이 코딩을 합니다.

바로 이 메모리 영역을 렌더타겟( RenderTarget ) 이라 합니다.

 

 

CreateHwndRenderTarget() 의 첫번째 인자는 화면에 대한 정보를 설정합니다.

픽셀 포맷이나 DPI 등의 많은 플래그와 옵션이 있지만, 현재는 디폴트 정보로 넘겨주었습니다.

두 번째 인자는 하드웨어 가속을 받는 렌더링에 대한 옵션을 설정합니다.

간단하게 크기 정보만 넘겨주는 것으로 마무리했습니다.

마지막으로는 이 API 호출이 성공했을 때 리턴되어지는 렌더타겟의 포인터를 저장할 변수를 넣어주었습니다.

이렇게 함으로써 간단하게 우리는 하드웨어 가속을 받을 수 있는 렌더타겟을 생성할 수 있습니다.

 

옵션이나 인자에 대한 설명을 충분히 드리면 좋겠지만, 너무 많습니다.^^

중요한 인자나 옵션에 대해서만 설명 드리는 점 양해 부탁 드립니다.

 

이제 우리는 렌더타겟을 가지고 있으니 이 메모리 영역에 값을 쓰면, 모니터로 결과를 확인할 수 있습니다.

윈도우가 화면에 그리기 위해서 발생하는 메시지가 WM_PAINT 입니다.

저는 이 메시지를 처리해서, 원하는 색상으로 렌더타겟의 색상을 채울 것입니다.

다음과 같이 코딩을 합니다.

 

이번 코드를 실행시키면, 파란색으로 칠해진 윈도우 프로그램을 만나게 될 것입니다.

더 정확하게 얘기하면, 메모리 영역( 렌더타겟 )이 파란색 색상데이터로 채워진 것입니다.

샘플을 올려둡니다.( SimpleDraw.zip )

도움이 되셨으면 좋겠습니다.^^

 


GDI vs Direct2D 비교해 보기.  

제가 아무리 Direct2D가 GDI보다 좋다고 혼자 말하는 것 보다, 여러분들이 직접 결과를 확인하는 것이 좋습니다.

그래서 이번에는 GDI와 Direct2D를 이용해서 타원을 렌더링 할 것입니다.

그리고 결과로 나오는 것을 보고, 여러분들이 직접 확인해 보기 바랍니다.

 

GDI 로 작업하기.

 

GDI를 이용하는 것은 전통적인 윈도우 프로그래밍에서 사용되던 방식입니다.

주변에서 이와 관련한 많은 내용들을 접할 수 있어서, 내용에 대한 자세한 설명은 생략하겠습니다.

Windows 운영체제에서 모든 드로잉(Drawing) 작업은

디바이스 컨텍스트 오브젝트( device-context object )를 통해서 실행이 됩니다.

이를 줄여서 'DC' 라고 줄여서 얘기합니다.( 다 아시죠? ^^ )

DC란, 윈도우즈 운영체제에서 컴퓨터 모니터나 프린터에 그리기 명령을 수행하기 위한

여러 속성 정보들을 구조화한 데이터입니다.

우리는 Windows API를 이용해서 DC를 이용한 그리기 작업을 수행할 수 있습니다.

DC를 이용하면, 우리는 어떠한 하드웨어 장치와는 관련이 없이 공통된 형식으로 화면에 그릴 수 있습니다.

이것이 가능한 이유는 DC는 CPU가 그리기 작업을 처리해 주기 때문입니다.

 

<코드>

HDC hDC;

HBRUSH hBrush,

hOldBrush;

 

if (!(hDC = GetDC (hwnd)))

return;

 

hBrush = CreateSolidBrush (RGB(0, 255, 255));

hOldBrush = SelectObject (hDC, hBrush);

 

Rectangle (hDC, 0, 0, 100, 200);

SelectObject (hDC, hOldBrush);

DeleteObject (hBrush);

ReleaseDC (hwnd, hDC);

</코드>

 

위의 코드는 간단히 DC를 이용해서 사각형을 그리는 코드입니다.

위에서 보는 것과 같이,

GetDC() 라는 API 를 통해서 현재 윈도우 애플리케이션에 대한 DC를 얻어서 작업을 수행합니다.

DC와 관련된 코드를 살펴보면, SelectXXX() 형식을 자주 볼 수 있습니다.

이는 DC가 일종의 상태 정보를 유지하고 있기 때문입니다.

예를 들면, 빨간 펜과 파란 펜으로 두 개 동시에 작업을 할 수는 없습니다.

하나의 펜으로 먼저 작업을 한 후에, 작업한 펜을 제거하고 다음 펜을 선택한 후에 작업을 해야 한다는 얘기입니다.

이점을 잘 고려해서 작업을 해야 하는 것이죠.

 

이것은 DC를 이용하는 하나의 방법일 뿐입니다.

만약 WM_PAINT 메시지 내부에서 처리하고자 한다면, 아래와 같은 구조를 취할 수 있습니다.

<코드>

PAINTSTRUCT ps;

 

case WM_PAINT :

hdc = BeginPaint(hWnd, &ps);

{

DoSomething()

}

EndPaint(hWnd, &ps);

break;

</코드>

 

PAINTSTRUCT 는 내부에 DC 관련 멤버 변수를 가지고 있습니다.

BeginPaint()를 통해서 DC 정보가 채워지게 되는 것입니다.

이러한 DC를 활용하는 것이 GDI 를 이용하는 것의 핵심입니다.

이와 관련된 내용을 더 언급하고 싶지만, 이 정도에서 정리하겠습니다.

이미 많은 분들이 저 보다는 훨씬 많은 지식을 가지고 있을 것이며,

관련 자료들도 이미 훌륭히 찾아 볼 수 있기 때문입니다.^^

 

우리가 지금 하려는 것은 GDI와 Direct2D의 비교입니다.

프로젝트를 만들고, 시스템을 셋팅하시기 바랍니다.( Direct2D 오브젝트도 생성해 주어야 합니다. )

WM_PAINT 메시지에 이 두 가지 방법을 사용해서 렌더링 하는 함수를 호출했습니다.

( 제가 정의한 함수들입니다. )

 

 

먼저, Direct2D를 이용해서 타원을 렌더링 하는 코드를 살펴보겠습니다.

 

이 방법은 뒤에도 설명을 하겠지만, Direct2D에서 DC를 활용하는 렌더링 방법입니다.

몇몇 생소한 개념들이 보이지만, 이들에 대해서는 차후에 설명을 할 것입니다.

 

GDI를 활용해서 타원을 렌더링 하는 코드는 다음과 같습니다.

자, 이제 이 둘의 결과는 예제 코드를 실행시키면, 다음과 같이 확인할 수 있습니다.

 

 

 

이는 타원의 일부를 캡쳐한 화면입니다.

좌측은 Direct2D의 결과이고, 우측은 GDI를 이용한 결과입니다.

겉으로 보기에는 차이가 없어 보이지만,

자세히 보면 우측의 경우는 울퉁불퉁한 계단 현상이 심한 것을 알 수 있습니다.

( 잘 보이지 않는다면, 샘플을 실행시켜보시기 바랍니다.^^ )

분명히 같은 기능을 하는 두 함수이지만, 결과에서는 이렇게 차이가 납니다.

샘플 파일( BasicRender.zip )을 같이 첨부했으니, 도움이 되셨으면 좋겠습니다.^^


신고


이제 테셀레이션 작업의 마지막 단계입니다.
테셀레이터를 통한 결과와 Hull Shader 단계에서의 결과인 패치 데이터가
Domain Shader 의 입력으로 전달
이 되게 됩니다.

Domain Shader 에서는
우리가 DX9 세대에서 주로 수행했던 Vertex 변환 작업을 수행하게 됩니다.
즉, 실제적으로 Projection 변환까지 Domain Shader 단계에서 이루어지게 됩니다.


테셀레이션 작업만으로는 폴리곤의 퀄리티를 향상시키는데에 효과적이지 못합니다.
그래서 실제적으로 높이 정보를 포함하고 있는 텍스쳐를 사용하게 되는데,
이 텍스쳐를 사용하는 것을 'Displacement mapping' 이라고 합니다.

아래의 그림을 살펴보도록 하겠습니다.




첫번째 모델은 로우 폴리곤으로 제작된 것입니다.
두번째가 바로 테셀레이션 작업이 종료된 폴리곤 모델입니다.
( 아주 미끄러운 캐러멜 같은 느낌이지요? ^^ )

세번째는 테셀레이션 작업을 마치고, Displacement mapping 까지 마친 모델입니다.
Displacement mapping 의 특징은 실제 Vertex 데이터를 조작하는 것에 있습니다.
위의 작업을 단순하게 표현해 보면 아래의 그림과 같습니다.



수년 전부터 가장 많이 쓰이고 있는 Bump mapping 기법은 우리의 눈을 속이기 위한 방법이라면,
Displacement mapping 은 눈속임이 아니라, 실제로 데이터를 조작하는 텍스쳐 기법입니다.
아래의 그림은 이들에 대한 차이점을 질감적으로 잘 표현해 주고 있습니다. ^^
Bump mapping의 경우에는 실제로 평면이지만,
텍스쳐링 효과를 이용해서 우리에게 질감의 느낌을 전달해 주고 있습니다.
반면에 Displacement mapping은 실제 Vertex 데이터를 조작해서 질감의 느낌을 전달합니다.



Displacement mapping 의 설명에 제가 열을 올리는 이유는
바로 이 작업 Domain Shader 단계에서 계산되어서 적용되기 때문입니다.
앞서 설명했듯이, Displacement mapping 은 실제 Vertex 를 조작하는 기법이기 때문에
최종 Vertex 를 생성하는 Domain Shader 에서 적용되는 것이 당연한 일입니다.^^
이 작업 자체가 어려운 일은 당연히 아닙니다.
하지만, Displacement mapping을 고려하게 됨으로써,
Vertex 변환 단계가 조금 복잡해 진 것은 사실입니다.
텍스쳐링의 단계를 수행하고, 그 텍셀의 수치만큼 실제 Vetex를 조정해 주어야 하기 때문입니다.
거기다, 쉐이더 단계에서 LOD 레벨을 고려해야하기 때문에
쉐이더의 Texture 멤버함수로 SampleLevel() 를 이용하는 상황이 생기기도 할 것입니다.
또 하나의 더 큰 효과를 위한 하나의 방법이 늘었다고 생각하시면 더 좋을 것 같습니다.^^

신고