DirectX11/Eternal Return 모작

[DirectX 11 Eternal Return 모작] 16. Direct2D 텍스트 렌더링

Vfly 2025. 9. 3. 04:44

전체 코드 : https://github.com/HyangRim/DirectX11-Engine-Client

 

GitHub - HyangRim/DirectX11-Engine-Client

Contribute to HyangRim/DirectX11-Engine-Client development by creating an account on GitHub.

github.com


Direct2D 텍스트 렌더링 

1. GDI 기반 텍스트의 한계

초기 구현에서는 Windows GDI를 사용한 텍스트 렌더링을 사용했었다.

// Text.cpp - GDI 기반 텍스트 렌더링
void Text::CreateTextTexture()
{
    // Windows GDI를 사용한 텍스트 렌더링
    HDC hdc = CreateCompatibleDC(NULL);
    
    // 폰트 생성
    HFONT hFont = CreateFont(
        fontHeight, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE,  // ClearType 시도했지만...
        m_fontName.c_str()
    );
    
    // 32비트 DIB 섹션 생성
    BITMAPINFO bmi = {};
    bmi.bmiHeader.biBitCount = 32;
    // ...
    
    // 단순한 TextOut 함수로 렌더링
    TextOut(hdc, textX, textY, m_text.c_str(), static_cast<int>(m_text.length()));
}

 

주요 문제점 

  • 안티앨리어싱 품질 저하 : GDI의 제한적인 텍스트 렌더링
  • 픽셀 아티팩트 : 특히 작은 글꼴에서 계단 현상 발생
  • 한글 렌덜이 품질 : 한글 폰트에서 가독성 저하

 

 

 

2. Direct2D 텍스트 렌더링 시스템

2.1 Direct2D + DirectWrite 도입

// D2DTextRenderer.cpp - Direct2D 기반 고품질 렌더링
bool D2DTextRenderer::Init()
{
    // D2D Factory 생성 (하드웨어 가속 지원)
    D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, m_d2dFactory.GetAddressOf());
    
    // DirectWrite Factory 생성 (고급 타이포그래피)
    DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, 
                       __uuidof(IDWriteFactory),
                       m_writeFactory.GetAddressOf());
    
    // WIC Factory 생성 (고품질 이미지 처리)
    CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
                    IID_PPV_ARGS(m_wicFactory.GetAddressOf()));
}

 

2.2 텍스트 텍스쳐 생성 과정

텍스트를 DirectX 텍스쳐로 변환하는 핵심 로직

shared_ptr<Texture> D2DTextRenderer::CreateTextTexture(...)
{
    // DirectWrite TextLayout으로 정밀한 텍스트 측정
    ComPtr<IDWriteTextLayout> textLayout;
    m_writeFactory->CreateTextLayout(
        text.c_str(),
        static_cast<UINT32>(text.length()),
        textFormat.Get(),
        static_cast<float>(textWidth - padding * 2),
        static_cast<float>(textHeight - padding * 2),
        textLayout.GetAddressOf()
    );
    
    // 정확한 텍스트 메트릭 계산
    DWRITE_TEXT_METRICS metrics;
    textLayout->GetMetrics(&metrics);
    
    textWidth = static_cast<int>(ceil(metrics.width)) + padding * 2;
    textHeight = static_cast<int>(ceil(metrics.height)) + padding * 2;
}

 

2.3 하드웨어 가속 렌더링

// WIC 비트맵에 Direct2D로 직접 렌더링
ComPtr<ID2D1RenderTarget> renderTarget;
m_d2dFactory->CreateWicBitmapRenderTarget(wicBitmap.Get(), rtProps, renderTarget.GetAddressOf());

renderTarget->BeginDraw();
renderTarget->Clear(D2D1::ColorF(0, 0, 0, 0)); // 완벽한 투명 배경

// 고품질 외곽선 렌더링
if (outlineWidth > 0 && outlineBrush) {
    for (int x = -1; x <= 1; x++) {
        for (int y = -1; y <= 1; y++) {
            if (x == 0 && y == 0) continue;
            D2D1_POINT_2F outlineOrigin = D2D1::Point2F(
                origin.x + x * outlineWidth, origin.y + y * outlineWidth
            );
            renderTarget->DrawTextLayout(outlineOrigin, textLayout.Get(), outlineBrush.Get());
        }
    }
}

// 메인 텍스트 렌더링 (서브픽셀 정확도)
renderTarget->DrawTextLayout(origin, textLayout.Get(), textBrush.Get());

 

 

3. 셰이더 기반 아웃라인 텍스트 처리

3.1 텍스트 전용 셰이더 구현

Direct2D로 생성된 텍스쳐를 GPU에서 후처리하는 셰이더

#ifndef _TEXT_FX_
#define _TEXT_FX_

#include "00. Global.fx"

cbuffer TextMaterialBuffer
{
    float4 TextColor;
    float4 OutlineColor;
    float4 BackgroundColor;
    float TextAlpha;
    float OutlineWidth;
    float2 TextPadding;
};

struct PixelInput
{
    float4 position : SV_POSITION;
    float2 uv : TEXCOORD;
    float4 color : COLOR;
    float4 screenPos : TEXCOORD1; // 스크린 좌표 추가
};

float4 PS_OutlineText(PixelInput input) : SV_TARGET
{
    float2 uv = input.uv;
    
    // 텍스처에서 원본 색상 샘플링
    float4 texColor = DiffuseMap.Sample(LinearSampler, uv);
    
    // 텍셀 크기를 동적으로 계산
    float2 texelSize;
    DiffuseMap.GetDimensions(texelSize.x, texelSize.y);
    texelSize = (OutlineWidth / texelSize);
    
    // 8방향 외곽선 샘플링
    float outline = 0.0f;
    outline += DiffuseMap.Sample(LinearSampler, uv + float2(-texelSize.x, -texelSize.y)).a;
    outline += DiffuseMap.Sample(LinearSampler, uv + float2(0.0f, -texelSize.y)).a;
    outline += DiffuseMap.Sample(LinearSampler, uv + float2(texelSize.x, -texelSize.y)).a;
    outline += DiffuseMap.Sample(LinearSampler, uv + float2(-texelSize.x, 0.0f)).a;
    outline += DiffuseMap.Sample(LinearSampler, uv + float2(texelSize.x, 0.0f)).a;
    outline += DiffuseMap.Sample(LinearSampler, uv + float2(-texelSize.x, texelSize.y)).a;
    outline += DiffuseMap.Sample(LinearSampler, uv + float2(0.0f, texelSize.y)).a;
    outline += DiffuseMap.Sample(LinearSampler, uv + float2(texelSize.x, texelSize.y)).a;
    outline /= 8.0f;
    
    // 최종 색상 결정
    float4 finalColor;
    
    if (texColor.a > 0.5f)
    {
        // 텍스트 내부: 지정된 텍스트 색상 사용
        finalColor = input.color;
        finalColor.a *= texColor.a;
    }
    else if (outline > 0.1f)
    {
        // 외곽선 영역: 외곽선 색상 사용
        finalColor = OutlineColor;
        finalColor.a *= outline * TextAlpha;
    }
    else
    {
        discard;
    }
    
    return finalColor;
}

technique11 TextTech
{
    pass P0
    {
        SetBlendState(AlphaBlend, float4(0, 0, 0, 0), 0xFF);
        SetVertexShader(CompileShader(vs_5_0, VS_Text()));
        SetPixelShader(CompileShader(ps_5_0, PS_OutlineText()));
    }
}
#endif

 

4. 최적화 및 성능 고려사항

4.1 텍스쳐 캐싱

동일한 텍스트의 중복 생성을 방지

wstring D2DTextRenderer::GenerateCacheKey(const wstring& text, const wstring& fontName,
    float fontSize, const Vec4& textColor, TextAlignment alignment) const
{
    return text + L"_" + fontName + L"_" + to_wstring((int)fontSize) + L"_" +
        to_wstring((int)(textColor.x * 255)) + L"_" + to_wstring((int)alignment);
}

void D2DTextRenderer::ClearCache()
{
    s_textureCache.clear();
}

 

4.2 업데이트 최적화

// D2DText.cpp - 성능을 위한 지연 업데이트
void D2DText::Update()
{
    float currentTime = TIME->GetGameTime();
    
    // 지연된 업데이트 처리 (과도한 텍스트 변경 방지)
    if (m_hasPendingUpdate && (currentTime - m_lastUpdateTime) >= m_updateInterval) {
        m_text = m_pendingText;
        m_needUpdate = true;
        m_hasPendingUpdate = false;
        m_lastUpdateTime = currentTime;
    }
    
    // 필요한 경우에만 텍스처 재생성
    if (m_needUpdate) {
        CreateTextTexture();
        UpdateMaterial();
        m_needUpdate = false;
    }
}

void D2DText::SetTextDelayed(const wstring& text)
{
    // 즉시 업데이트 대신 지연 업데이트로 성능 향상
    if (m_text != text) {
        m_pendingText = text;
        m_hasPendingUpdate = true;
    }
}

 

5. 품질 비교

기존 Text
D2DText

결론

  1. Direct2D 텍스트 렌더링
    1. 고품질 폰트 렌더링과 아웃라인 효과
    2. 효율적인 캐싱 시스템으로 최적화