본문 바로가기

회사에서 살아남기

SDFFont 사용하기 (4)

드디어 SDFFont를 출력하기 위한 모든 준비가 끝났다.

우선 VS 프로젝트를 하나 생성해서 DirectX9을 사용할 세팅을 해주자 특별한 건 없고 그냥 SDK를 설치하면 같이 설치되는 샘플코드 중 하나를 가져와서 정말 간단하게 출력만 해줄 것이다.

출력 방법은 간단하다 

글자를 출력할 BaseLine만 잡아놓고 그 라인을 따라가면서 그려주기만 하면 된다.

void DrawFont(WCHAR* str, D3DXVECTOR2 drawPos)
{
    //출력할 문자열의 길이
    int strsize = wcslen(str);

    Glyph curGlyph;

    //현재 글자가 그려질 위치
    D3DXVECTOR2 curPenPos = startPos;

    //한글자 한글자씩 출력한다.
    for (int count = 0; count < strsize; count++)
    {
        long test = str[count];
        curGlyph = g_glyphs[test];

        DrawFont(curGlyph, curPenPos);
    }
}

우선 출력해 줄 문자열을 유니코드 문자열 형식으로 받아온 다음에 시작 위치(펜의 위치)만 정해주고 한 글자 한 글자씩 그려준다.

 

void CreateFontGeometry(Glyph glyph, D3DXVECTOR2 pos/*왼쪽 위의 좌표*/)
{

    CUSTOMVERTEX Vertices[4];
    //왼쪽 아래 Vertex
    float tu = (glyph.mTextureCoordX - g_margin);
    float tv = 1 - (glyph.mTextureCoordY - g_margin) - (glyph.mTextureHeight + g_margin * 2);
    Vertices[0] = CUSTOMVERTEX(D3DXVECTOR3(pos.x, pos.y - glyph.mHeight, 0.0f), 0xffffffff, tu, tv);
    
    //오른쪽 아래 Vertex
    tu = (Vertices[0].tu + (glyph.mTextureWidth + g_margin * 2));
    tv = Vertices[0].tv;
    Vertices[1] = CUSTOMVERTEX(D3DXVECTOR3(pos.x + glyph.mWidth, pos.y - glyph.mHeight, 0.0f), 0xffffffff, tu, tv);

	//오른쪽 위 Vertex
    tu = Vertices[1].tu;
    tv = 1 - (glyph.mTextureCoordY - g_margin);
    Vertices[2] = CUSTOMVERTEX(D3DXVECTOR3(pos.x + glyph.mWidth, pos.y, 0.0f), 0xffffffff, tu, tv);
	
    //왼쪽 위 Vertex
    tu = Vertices[0].tu;
    tv = 1 - (glyph.mTextureCoordY - g_margin);
    Vertices[3] = CUSTOMVERTEX(D3DXVECTOR3(pos.x, pos.y, 0.0f), 0xffffffff, tu, tv);
	
    //Vertex정보들을 정점 버퍼에 넣어준다.
    CUSTOMVERTEX* pVertices;
    if (FAILED(g_pVB->Lock(0, 0, (void**)&pVertices, 0)))
        return;

    memcpy(pVertices, Vertices, sizeof(CUSTOMVERTEX) * 4);

    g_pVB->Unlock();


    //인덱스 정보
    DWORD* pIndexes;

	//인덱스 정보들을 정점 버퍼에 넣어준다.
    if (FAILED(g_pIB->Lock(0, 0, (void**)&pIndexes, 0)))
        return;

    memcpy(pIndexes, g_Indexes, sizeof(DWORD) * 6);

    g_pIB->Unlock();
}



void DrawFont(Glyph glyph, D3DXVECTOR2& penPos)
{
    //펜의 위치를 기준으로 글자를 그려줄 시작점을 구해준다.
    D3DXVECTOR2 pos = D3DXVECTOR2(penPos.x + glyph.mHorizontalBearingX, penPos.y + glyph.mHorizontalBearingY);
    CreateFontGeometry(glyph, pos);

    //텍스쳐 설정
    g_pd3dDevice->SetTexture(0, g_SDFTexture);
    g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
    g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
    g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
    
    //RenderState 설정
    g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
    g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
    g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);

    //파이프라인 설정
    g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
    g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
    g_pd3dDevice->SetIndices(g_pIB);

    //출력
    g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 2);

    //글자 하나의 출력이 끝나고 나면 펜의 위치를 다음 글자 시작 위치로 옮겨준다.
    penPos.x += glyph.mHorizontalAdvance;
}

그리고 현재 펜의 위치에서 Bearing값만큼 이동한 위치에서 Glyph의 Width와 Height만큼 글자를 출력해 주고 펜의 위치를 Advance값만큼 이동해 주고 다음 글자를 그리는 것을 반복해 주면 끝나는 간단한 작업이다.

다만 주의할 점이 해당 SDF Generator에서 뽑혀 나온 TextureCoord 좌표는 오른쪽 아래가 0,0 인 좌표로 되어있지만

DirectX에서의 UV좌표는 오른쪽 위가 0,0인 좌표계이기 때문에 그 부분만 주의해서 UV좌표를 계산해 주면 정상적으로 출력이 된다.

그 결과물이 이것이다.

 

이제 이 흐리멍텅한 글자에 Shader라는 마법을 부려줄 것이다.

일단 셰이더 코드를 작성해 보자

//--------------------------------------------------------------------------------------
// SDFFont 셰이더
//--------------------------------------------------------------------------------------

float4 	    _baseColor;		// BaseColor
float4x4 	_matWorldViewProjection;	// World * View * Projection matrix

//--------------------------------------------------------------------------------------
// 메인 텍스쳐 셈플러
//--------------------------------------------------------------------------------------
sampler _FontSampler : register(s0);

//--------------------------------------------------------------------------------------
// 정점 셰이더 입력값
//--------------------------------------------------------------------------------------
struct VS_INPUT 
{
   float4 Position : POSITION0;
   float2 TexCoord0 : TEXCOORD0;
   
};

//--------------------------------------------------------------------------------------
// 픽셀 셰이더에 넘겨줄 정점 셰이더 출력값
//--------------------------------------------------------------------------------------
struct VS_OUTPUT
{
   float4 Position : POSITION;
   float2 TexCoord0 : TEXCOORD0;
};

//--------------------------------------------------------------------------------------
// 정점 셰이더
//--------------------------------------------------------------------------------------
VS_OUTPUT Vert(VS_INPUT In)
{
    VS_OUTPUT Output;
   Output.Position = mul(In.Position, _matWorldViewProjection);
   Output.TexCoord0 = In.TexCoord0;
   return Output;
}

//--------------------------------------------------------------------------------------
// 픽셀 셰이더 출력값
//--------------------------------------------------------------------------------------
struct PS_OUTPUT
{
   float4 Color : COLOR0;
};

//--------------------------------------------------------------------------------------
// 픽셀 셰이더
//--------------------------------------------------------------------------------------
PS_OUTPUT Frag(VS_OUTPUT In)
{
   PS_OUTPUT Out;
   float4 color = float4(0, 0, 0, 0);
   float alpha = tex2D(_FontSampler, In.TexCoord0).a;
   
   if(alpha >= _lowThreshold)
   {
	color.rgb = _baseColor;
	color.a = 1.0;
   }

   Out.Color = color;
   return Out;
}

technique BasicDraw
{
    pass P0
    {          
        VertexShader = compile vs_2_a Vert();
        PixelShader  = compile ps_2_a Frag();
    }
}

정말 간단한 코드이다 특별한 건 없고 그저 해당 픽셀의 알파값이  _lowThreshold값 이상이면 문자로 인식하고 출력해 준다는 뜻이다. 여기서 _lowThreshold는 적당히 0.5로 설정해 주었다.

여기서 문제가 발생한다. 바로 문자가 자글자글하게 계단현상이 생기는 것이다!

 

 

그 해결 방법이 바로 smoothstep 함수를 사용해서 글자를 출력하는 것이다.

PS_OUTPUT SoftEdgeFrag(VS_OUTPUT In)
{
    PS_OUTPUT Out;
   float4 color = _baseColor;

   color.a = smoothstep(0.5 - _smoothing, 0.5 + _smoothing, tex2D(_FontSampler, In.TexCoord0).a);

   Out.Color = color;
   return Out;
}

해당 픽셀 셰이더는 텍스쳐의 알파값이 0.5를 기준으로 -smoothing +smoothing 안쪽의 범위일 경우에 부드럽게 보간이 되도록 해줌으로써 글자가 마치 안티 앨리어싱이 된 것처럼 부드럽게 보이도록 해주는 역할을 한다.

위의 사진이 그 결과이다. 색상이 조금 연해졌지만 확연하게 글자가 부드러워진 것이 보인다.

 

다음에는 이 SDFFont를 셰이더를 통해서 더욱 다양하게 이쁘게 출력해 보도록 할 것이다.

'회사에서 살아남기' 카테고리의 다른 글

SDFFont 최적화  (0) 2025.07.23
SDFFont 사용하기 (5)  (0) 2024.03.18
SDFFont 사용하기 (3)  (0) 2023.10.31
SDFFont 사용하기 (2)  (0) 2023.10.31
SDFFont 사용하기 (1)  (0) 2023.10.31