dpi
dpi는 Dots per inch의 약자로, 인쇄물이나 영상물 등에서 1인치당 들어있는 데이터량을 말한다. 이게 포토샵을 주로 만지시는 아트 분들에게는 익숙한 개념인데, 나는 머리에서 이게 전환이 아직 잘 안됨.. 하지만 최근에는 디스플레이의 종류도 다양해지고, 여러 장치에서 호환되는 프로그램을 만들려면 좀 더 익숙해 지는 게 좋다. 요즘은 e-book에서 보는 pdf 자료의 해상도를 이야기할 때에도 자주 사용하니깐. 그리고 윈도우7 부터는 제어판 > 디스플레이 목록에서 사용자가 시스템의 dpi를 직접 설정할 수도 있다. 그리고 visual studio 2010의 프로젝트 부터(라고 알고 있는데 2005나 2008도 되는진 잘 모름)는 이렇게 사용자가 직접 지정한 dpi에도 화면이나 폰트가 뭉개지지 않도록 dpi 수치를 자동 인식하는 manifest 설정 플래그를 가지고 있다.
DIP
지금 Direct2D를 이용해 만들고 있는 게임에서 윈도우 창을 리사이즈 했을 때의 처리를 하고 있는데, 일단은 사용자가 창을 늘이거나 줄이거나 상관없이 게임 화면은 그에 맞게 알아서 스케일 되도록 처리하려고 한다. 하지만 내가 프로그램을 짤 때에는 이런 리사이즈 문제에 신경 쓰지 않고 작성할 수 있게, 스크립트에서 사용하는 좌표 수치는 윈도우의 리사이즈와 상관 없도록. 그렇게 해야 쓸데 없는데 고민 안하고 편하게 게임을 만들 수 있으니깐.
다시 말해서 윈도우의 화면 좌표와 내가 게임에서 사용하는 좌표가 분리 되어야 하는데, 작업하면서 찾아보니 이것을 DIP(Device Indipendent Pixel)라고 부른다. Direct3D에서는 어차피 world – view transform을 거칠 때 까지는 장치에 독립적인 별도의 3D 공간좌표를 이용해 프로그래밍 하고, 마지막 Projection 단계에서 실제 화면 영역과 매핑하기 때문에 이미 장치에 독립적인 좌표를 쓰고 있다고 볼 수 있다. D3D에서도 UI 시스템에서 2차원 좌표계를 처리할 땐 윈도우 시스템 좌표계에 휘둘리지 않게 신경 써 주어야 하겠지만 그다지 어려운 일은 아니다.
DIP 개념을 Direct2D에서 구현하려면 어떻게 해야 할까. 우선 맨 처음에는 d3d처럼 흉내를 내서 월드 변환 –> 프로젝션 변환 순으로 transform 단계를 나눠볼까 하고 생각을 했는데, 그러기에는 d2d가 허락한 1단계의 transform api만을 이용해야 하기에 깔끔하게 처리하기 곤란하다. 모든 행렬을 넣을 때마다 proj 행렬을 뒤에 곱해서 넣으면 되겠지만 그러기엔 나중에 파티클 시스템이라도 구현하게 되는 단계가 오면 행렬 곱셈을 미친듯이 하게 될 거잖아. 아무리 취미로 만드는 코드라도 그렇게 무식하게 만들 순 없다.
백버퍼 생성 – 더블 버퍼링
그래서 뜻하지 않게(?) 더블 버퍼링처럼 구현을 변경했다. 처음부터 화면에 바로 그리지 말고, 백버퍼에 우선 그렸다가 화면으로 옮겨 그리는 단계를 집어넣는다. GDI에서 메모리 DC를 얻어서 이곳에 먼저 그렸다가 한 번에 옮겨 그리는 기법이랑 로직은 똑같다. 하지만 GDI때처럼 flickering(화면 깜박임 현상)을 없애기 위해 넣는 것이 아니니 헷갈리지 말 것. 설명을 위해 비유를 한 것 뿐이다.
렌더 타겟을 ID2D1HwndRenderTarget으로 바로 사용하면 윈도우 리사이즈의 고난을 피해갈 수 없다. 호환되는 ID2D1BitmapRenderTarget을 만들어 실질적인 드로잉 처리는 모두 이곳에 한다.
// ID2D1HwndRenderTarget 생성. JIF( d2d::pD2DFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(m_hwnd, size), &d2d::pRenderTarget ) ); // ID2D1BitmapRenderTarget 생성. JIF( d2d::pRenderTarget->CreateCompatibleRenderTarget( d2d::SizeF( m_DISize ), // 게임 내에서 사용할 가상 좌표계. size, // 렌더 타겟의 실제 크기 &d2d::pBitmapTarget ) );
그리고 WM_PAINT를 받는 곳에선 비트맵을 얻어내서 hwnd 타겟에 한 방에 drawing.
static ID2D1Bitmap* pBitmap = NULL; d2d::pBitmapTarget->GetBitmap( &pBitmap ); d2d::pRenderTarget->BeginDraw(); d2d::pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); d2d::pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); D2D1_SIZE_F size = d2d::pRenderTarget->GetSize(); D2D1_RECT_F rect = D2D1::RectF( 0.f, 0.f, size.width, size.height ); d2d::pRenderTarget->DrawBitmap( pBitmap, &rect ); HRESULT hr = d2d::pRenderTarget->EndDraw(); SafeRelease( &pBitmap );
여기까지 처리하고 나면 이제 윈도우 사이즈를 사용자가 변경해도 게임 클라이언트의 내용은 변경된 사이즈에 맞게 스케일 된다.
화면 확대시 해상도 퀄리티 보장하기
여기까지만 하면 일단 장치에 독립적인 좌표계를 만드는 것은 성공이다. 스크린샷을 보면 가로세로 비율이 다르게 창을 줄였지만 게임 클라이언트는 적절하게 view 영역에 제대로 맞추어 줄어든다. 하지만 원본 사이즈보다 확대 될 때 문제가 있는데, 이미 백버퍼에서 가상 좌표계의 픽셀 크기 비트맵으로 래스터라이징 끝난 이미지를 늘리는 것이기 때문에, 크게 확대할 수록 화면이 흐리게 번지는 현상이 생긴다.
이걸 Direct2D에서는 간단히 해결할 수 있는데, 비트맵 타겟을 만들 때 좌표계로 사용할 비트맵 크기(size)와 실제 픽셀의 크기(pixel size)를 다르게 입력할 수 있기 때문. 두 크기의 값이 다르면 적당한 dpi가 자동으로 계산된다.
윈도우 사이즈가 변경되면 hwnd 타겟은 사이즈를 늘려주고, 비트맵 타겟은 변경된 윈도우 사이즈에 맞게 다시 생성해준다.
void BaseApp::OnResize(UINT width, UINT height) { if (d2d::pRenderTarget) { D2D1_SIZE_U size; size.width = width; size.height = height; // Note: This method can fail, but it's okay to ignore the // error here -- it will be repeated on the next call to // EndDraw. d2d::pRenderTarget->Resize(size); ::SafeRelease( &d2d::pBitmapTarget ); // bitmap target을 새로 생성. LIF( d2d::pRenderTarget->CreateCompatibleRenderTarget( d2d::SizeF( m_DISize ), size, &d2d::pBitmapTarget ) ); } }
그러면 화면이 확대 되어도 이미지가 뭉개지지 않고 선명하게 출력됨.
|
dpi 재계산 하기 전 |
|
dpi 재계산 후 |
출처>http://devnote.tistory.com/218
'메모 > C/C++' 카테고리의 다른 글
char -> int 변환 (0) | 2012.04.10 |
---|---|
Direct2D and Direct3D Interoperability Overview (0) | 2012.04.04 |
LPCWSTR <-> char 변환 (0) | 2012.04.04 |
C 파일 입출력 (0) | 2012.03.31 |
C 구조체 메모 (0) | 2012.03.31 |