캐릭터 이름 표시

캐릭터 머리위에 따라 다니는 이름을 표시하는 방법에는  2가지가 있다. 빌보드 판을 머리 위에 붙이는 방법이 있고, 또 하나는 머리 위에 2D 방식의 UI를 붙이는 방법이 있다.

빌보드 판은 3D로 처리하기 때문에 폰트가 멀리있으면 작게 보이고 가까이 가면 크게 보인다.
2D로 처리하는 방식은 거리와 관계없이 폰트가 일정한 크기로 보이지만 벽뒤에 갔을 때도 캐릭터 이름이 보여 보기에 좋지 않다.

이 장에서 처리하는 것은 폰트에 깊이값을 넣어서 캐릭터가 벽뒤에 가면 캐릭터 이름이 숨도도록 처리한다.

"Hello World!"라는 문자열을 2D로 그리고 있지만  깊이값을 가지고 있기 때문에 주변 배경에 의해 가려진다.
이것을 처리하기 위한 기본 처리는 다음 순서로 진행한다.

 

< D3DXSprite, D3DXFONT를 통한 폰트 출력 방식 >

D3DXFONT와 D3DXSprite를 조합하여 폰트를 출력 한다. D3DXFONT로 DrawText시 스프라이트 핸들을 지정해준다. 스프라이트를 통하여 폰트를 그리면 D3DXSprite::SetTransform() 명령을 통해 좌표를 이동 할 수 있다.
D3DXMatrixTranslation( &world, 100, 100, 0 )에서 보면 스크린 좌표 x=100, y=100으로 이동 시킴을 알수 있다.

코드는 다음의 순서로 만들면 된다.
1. 스프라이트 생성
2. 스프라이트 렌더링
3. 스프라이트 소멸

//1. 스프라이트 생성

LPD3DXFONT          g_pNameFont = NULL;  //폰트용

ID3DXSprite*        g_pSprite = NULL;    //스프라이트

 

void CreateSprite( IDirect3DDevice9* pd3dDevice )

{

    D3DXCreateFont(pd3dDevice, 20, 0, FW_BOLD, 0,
                  FALSE, DEFAULT_CHARSET,

                  OUT_DEFAULT_PRECIS, DEFAULT_QUALITY,
                  DEFAULT_PITCH | FF_DONTCARE,TEXT("궁서"),
                  &g_pNameFont);

    D3DXCreateSprite( pd3dDevice, &g_pSprite );

}

 

//2. 스프라이트 렌더링

void RenderSprite( IDirect3DDevice9* pd3dDevice, D3DXMATRIX* pMat )

{

    if( g_pSprite == NULL )

        CreateSprite( pd3dDevice );

 

    D3DXMATRIX world;

    D3DXMatrixTranslation( &world, 100, 100, 0 );

    g_pSprite->SetTransform( &world );

    g_pSprite->Begin( D3DXSPRITE_ALPHABLEND | D3DXSPRITE_SORT_TEXTURE );//2D

    RECT rectTemp = { 0, 0, 200, 30};

    g_pNameFont->DrawText(g_pSprite, L"Hello World!", -1, &rectTemp, 0,
                               D3DCOLOR_COLORVALUE(1, 0, 0, 1) );//0xFFFF0000);

    g_pSprite->End();

}

 

//3. 스프라이트 소멸

SAFE_RELEASE(g_pNameFont);

SAFE_RELEASE(g_pSprite);

 

< D3DXSprite::SetTransform()을 이용한 깊이값 표시 >

2D에 투영된 좌표를 D3DXMatrixTranslation()에 넣는다. 즉,

D3DXMatrixTranslation( &world, scrPos.x, scrPos.y, scrPos.z )에서 scrPos.x, scrPos.y는 스크린 좌표를 scrPos.z에는 0에서 1사이의 깊이 값을 넣고 D3DXSprite::SetTransform() 하면 스크린 좌표와 깊이값이 적용되서 원하는 결과를 얻을수 있다.

 

GetScreenPos()와 WorldToScreen()는 같은 결과값을 반환하는 함수이다.

 

void RenderSprite( IDirect3DDevice9* pd3dDevice, D3DXMATRIX* pMat )

{

    if( g_pSprite == NULL )

        CreateSprite( pd3dDevice );

 

    D3DXMATRIX world;

    D3DXVECTOR3 scrPos;

 

    D3DXMATRIX* pLightMat = g_LCamera.GetWorldMatrix();

    D3DXVECTOR3 pos = D3DXVECTOR3( pLightMat->_41, pLightMat->_42, pLightMat->_43 );

 

#if  0

    //3D 표시

    scrPos = GetScreenPos( pd3dDevice, pos );

#else

    //3D 표시

    WorldToScreen( pd3dDevice, scrPos, pos );

#endif

    D3DXMatrixTranslation( &world, scrPos.x, scrPos.y, scrPos.z );

    g_pSprite->SetTransform( &world );

    g_pSprite->Begin( D3DXSPRITE_ALPHABLEND | D3DXSPRITE_SORT_TEXTURE );

    RECT rectTemp = { 0, 0, 200, 30};

    g_pNameFont->DrawText(g_pSprite, L"Hello World!", -1, &rectTemp, 0,
                                  D3DCOLOR_COLORVALUE(1, 0, 0, 1) );

    g_pSprite->End();

}

 

< 월드 좌표를 스크린 좌표로 변환 1>

월드 좌표를 스크린 좌표로 변환하는 첫 번째 방법이다.
월드 좌표를 뷰 행렬 X 프로젝션 행렬에 의해서 변환하면 가로, 세로, 깊이는 0~1사이의 값을 가지게 된다.  
pooint.x와 point.y는 스크린 좌표로 변환시키고, z값은 그대로 반환한다.

D3DXVECTOR3 GetScreenPos( IDirect3DDevice9* pd3dDevice, D3DXVECTOR3& pos )

{

    D3DXMATRIX matView = *g_VCamera.GetViewMatrix();

    D3DXMATRIX matProj = *g_VCamera.GetProjMatrix();

    D3DXMATRIX mat = matView * matProj;

 

    D3DXVECTOR3 point;

    D3DXVec3TransformCoord( &point, &pos, &mat );

 

    const D3DSURFACE_DESC* pBackBufferSurfaceDesc = DXUTGetD3D9BackBufferSurfaceDesc();

    float width = (float)pBackBufferSurfaceDesc->Width;

    float height = (float)pBackBufferSurfaceDesc->Height;


    //x, y를 스크린 좌표로 변환

    point.x = (  point.x + 1 ) * width  / 2;

    point.y = ( -point.y + 1 ) * height / 2;

 

    return point;

}

 

< 월드  좌표를 깊이 버퍼 값으로 변환 >

D3DXVec3Project()를 이용해서 월드 좌표를 2D 좌표로 투영하고 있다.

D3DXVECTOR3* WorldToScreen( IDirect3DDevice9* pDevice, D3DXVECTOR3 &vScreenCoord, D3DXVECTOR3 vWorldLocation )

{

    D3DVIEWPORT9 viewPort;

    D3DXMATRIX matProj, matView, identity;

    pDevice->GetViewport( &viewPort );

    D3DXMatrixIdentity( &identity );

 

    //뷰, 프로젝트, 뷰포트 설정

    matView = *g_VCamera.GetViewMatrix();

    matProj = *g_VCamera.GetProjMatrix();

    const D3DSURFACE_DESC* pBackBufferSurfaceDesc = DXUTGetD3D9BackBufferSurfaceDesc();

    viewPort.Width = (float)pBackBufferSurfaceDesc->Width;

    viewPort.Height = (float)pBackBufferSurfaceDesc->Height;

 

#if   0

    D3DXVec3Project( &vScreenCoord, &vWorldLocation, &viewPort, &matProj, &matView, &identity );

#else   

    //D3DXVec3Project() 같은 값이 나온다. 소수점 아래 3자리 차이의 오차가 난다.

    Vec3Project( &vScreenCoord, &vWorldLocation, &viewPort, &matProj, &matView, &identity );

#endif

 

    if(vScreenCoord.z >= 0 && vScreenCoord.z <= 1)

        return &vScreenCoord;

    return NULL;

}

 

< D3DXVec3Project 구현해보기 >

D3DXVec3Project() API에 대해서 자세하게 알아보자.

// Project vector from object space into screen space

D3DXVECTOR3* WINAPI D3DXVec3Project(
             D3DXVECTOR3 *pOut,
             CONST D3DXVECTOR3 *pV,
             CONST D3DVIEWPORT9 *pViewport,

             CONST D3DXMATRIX *pProjection,
             CONST D3DXMATRIX *pView,
             CONST D3DXMATRIX *pWorld);

오브젝트 좌표를 2D로 투영하는 D3D  API이다.
카메라 뷰포트와 프로젝션 행렬, 뷰 행렬, 월드 행렬을 넘겨주면 pv 좌표를 2D에 투영해서 pOut로 변환한다.
리턴값 pOut.z 값이 0 ~ 1 사이에 있으면 화면안에 있는 경우이다.

Vec3Project는 D3DXVec3Project()를 직접 구현한것이다.

/*

D3DXVec3Project(Out, V, Viewport, Proj, View, World)

a = (Vx, Vy, Vz, 1)

b = a×World×View×Proj

c = b⁄bw

Outx = ViewportX + ViewportWidth*(1+cx)/2

Outy = ViewportY + ViewportHeight*(1-cy)/2

Outz = ViewportMinZ + cz*(ViewportMaxZ-ViewportMinZ)

*/

D3DXVECTOR3* Vec3Project( D3DXVECTOR3 *pOut, CONST D3DXVECTOR3 *pV, CONST D3DVIEWPORT9 *pViewport,

      CONST D3DXMATRIX *pProjection, CONST D3DXMATRIX *pView, CONST D3DXMATRIX *pWorld)

{

    D3DXVECTOR4 a = D3DXVECTOR4( pV->x, pV->y, pV->z, 1 );

    D3DXMATRIX  mat = (*pWorld) * (*pView) * (*pProjection);

    D3DXVECTOR4 b;   D3DXVec4Transform( &b, &a, &mat );

    D3DXVECTOR3 c = b / b.w;

 

    pOut->x = pViewport->X + pViewport->Width * (1+c.x)/2.0f;

    pOut->y = pViewport->Y + pViewport->Height * (1-c.y)/2.0f;

    pOut->z = pViewport->MinZ + c.z * (pViewport->MaxZ - pViewport->MinZ);

    return pOut;

}

다운로드: font_test.zip