You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在DirectX 11中使用FreeType绘制文本的转换难题

解决FreeType位图转DirectX11纹理并实现my_draw_bitmap

我来帮你搞定这个FT_Bitmap转ID3D11Texture2D的问题,还有my_draw_bitmap函数的实现——这确实是FreeType+DirectX11文本渲染里最关键的一步。下面是一步步的落地方案:

1. 先搞懂格式匹配逻辑

FreeType输出的FT_Bitmap默认是8位Alpha通道数据(pixel_mode为FT_PIXEL_MODE_GRAY),0代表完全透明,255代表完全不透明。我们可以用DirectX的DXGI_FORMAT_A8_UNORM格式完美匹配这个数据。

2. 核心转换与渲染函数实现

下面是my_draw_bitmap的完整逻辑,包含FT_Bitmap到D3D11纹理的转换,以及单个字形的渲染:

#include <DirectXMath.h>
#include <d3d11.h>
#include <vector>

// 辅助函数:把FT_Bitmap转成D3D11纹理和对应的着色器资源视图
HRESULT ConvertFTBitmapToD3DTexture(ID3D11Device* pDevice, ID3D11DeviceContext* pContext, 
                                    const FT_Bitmap& bitmap, ID3D11Texture2D** ppTexture, 
                                    ID3D11ShaderResourceView** ppSRV)
{
    // 1. 填充纹理描述
    D3D11_TEXTURE2D_DESC texDesc = {};
    texDesc.Width = bitmap.width;
    texDesc.Height = bitmap.rows;
    texDesc.MipLevels = 1;
    texDesc.ArraySize = 1;
    texDesc.Format = DXGI_FORMAT_A8_UNORM; // 对齐FreeType的Alpha通道格式
    texDesc.SampleDesc.Count = 1;
    texDesc.Usage = D3D11_USAGE_DYNAMIC; // 动态更新,适配不同字形
    texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

    // 2. 处理像素对齐:D3D要求纹理行宽是4的倍数,FreeType的pitch可能不满足
    UINT rowPitch = (bitmap.width * sizeof(unsigned char) + 3) & ~3;
    std::vector<unsigned char> alignedPixels(rowPitch * bitmap.rows, 0);

    // 把FreeType的原始数据拷贝到对齐后的缓冲区
    for (UINT y = 0; y < bitmap.rows; ++y)
    {
        memcpy(&alignedPixels[y * rowPitch], bitmap.buffer + y * bitmap.pitch, bitmap.width);
    }

    // 3. 准备初始数据
    D3D11_SUBRESOURCE_DATA initData = {};
    initData.pSysMem = alignedPixels.data();
    initData.SysMemPitch = rowPitch;

    // 4. 创建纹理
    HRESULT hr = pDevice->CreateTexture2D(&texDesc, &initData, ppTexture);
    if (FAILED(hr)) return hr;

    // 5. 创建着色器资源视图,让着色器能采样这个纹理
    D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
    srvDesc.Format = texDesc.Format;
    srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Texture2D.MipLevels = 1;

    return pDevice->CreateShaderResourceView(*ppTexture, &srvDesc, ppSRV);
}

// 你的my_draw_bitmap函数:渲染单个字形位图
void my_draw_bitmap(ID3D11Device* pDevice, ID3D11DeviceContext* pContext, 
                    const FT_Bitmap& bitmap, FT_Vector penPosition, 
                    ID3D11Buffer* pVertexBuffer, ID3D11VertexShader* pVS, 
                    ID3D11PixelShader* pPS, ID3D11InputLayout* pInputLayout)
{
    ID3D11Texture2D* pGlyphTex = nullptr;
    ID3D11ShaderResourceView* pGlyphSRV = nullptr;

    // 转换位图为D3D纹理
    if (FAILED(ConvertFTBitmapToD3DTexture(pDevice, pContext, bitmap, &pGlyphTex, &pGlyphSRV)))
    {
        // 这里可以加错误日志
        return;
    }

    // 把FreeType的1/64像素单位转成实际屏幕像素
    float x = static_cast<float>(penPosition.x) / 64.0f;
    float y = static_cast<float>(penPosition.y) / 64.0f;

    // 定义带纹理坐标的顶点结构
    struct Vertex
    {
        DirectX::XMFLOAT2 pos;
        DirectX::XMFLOAT2 uv;
    };

    // 构建字形的四边形顶点(左上、右上、左下、右下,用三角带渲染)
    Vertex vertices[] = {
        { {x, y}, {0.0f, 0.0f} }, // 左上
        { {x + bitmap.width, y}, {1.0f, 0.0f} }, // 右上
        { {x, y - bitmap.rows}, {0.0f, 1.0f} }, // 左下
        { {x + bitmap.width, y - bitmap.rows}, {1.0f, 1.0f} } // 右下
    };

    // 更新顶点缓冲区
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    if (SUCCEEDED(pContext->Map(pVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)))
    {
        memcpy(mappedResource.pData, vertices, sizeof(vertices));
        pContext->Unmap(pVertexBuffer, 0);
    }

    // 设置渲染管线状态
    pContext->IASetInputLayout(pInputLayout);
    pContext->IASetVertexBuffers(0, 1, &pVertexBuffer, nullptr, nullptr);
    pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);

    pContext->VSSetShader(pVS, nullptr, 0);
    pContext->PSSetShader(pPS, nullptr, 0);
    pContext->PSSetShaderResources(0, 1, &pGlyphSRV);

    // 渲染这个字形的四边形
    pContext->Draw(4, 0);

    // 释放临时资源(实际项目建议缓存字形纹理,避免频繁创建销毁)
    pGlyphSRV->Release();
    pGlyphTex->Release();
}

3. 在你的RenderText函数中整合调用

在FontLoader::RenderText里,遍历每个字符,加载字形并调用my_draw_bitmap,同时处理字符间距:

void FontLoader::RenderText(ID3D11Device* p_device, ID3D11DeviceContext* p_context, Text* p_text, Math::Vec2 p_position)
{
    const char* text = p_text->GetText(); // 假设你的Text类提供获取文本的方法
    FT_Face face = /* 你的FreeType字体Face对象,需要提前初始化 */;

    // 初始化笔位置(转成FreeType的1/64像素单位)
    FT_Vector pen;
    pen.x = static_cast<FT_Pos>(p_position.x * 64.0f);
    pen.y = static_cast<FT_Pos>(p_position.y * 64.0f);

    for (size_t i = 0; text[i] != '\0'; ++i)
    {
        // 加载当前字符的字形并渲染成位图
        if (FT_Load_Char(face, text[i], FT_LOAD_RENDER) != 0)
        {
            continue; // 跳过加载失败的字符
        }

        FT_GlyphSlot slot = face->glyph;
        FT_Bitmap& bitmap = slot->bitmap;

        // 调整笔位置:考虑字形的bearing(字形相对于笔位置的偏移)
        FT_Vector glyphPen;
        glyphPen.x = pen.x + slot->bitmap_left * 64;
        glyphPen.y = pen.y - slot->bitmap_top * 64;

        // 渲染当前字形
        my_draw_bitmap(p_device, p_context, bitmap, glyphPen, 
                       /* 你的全局顶点缓冲区 */, /* 你的顶点着色器 */, 
                       /* 你的像素着色器 */, /* 你的输入布局 */);

        // 更新笔位置到下一个字符
        pen.x += slot->advance.x;
        pen.y += slot->advance.y;
    }
}

必加的优化与注意事项

  • 资源缓存:上面的示例每次渲染都创建新纹理,性能很差。实际项目要缓存已经渲染过的字形纹理,比如用std::unordered_map<char, ID3D11ShaderResourceView*>存储。
  • Alpha混合:要开启透明混合才能正确显示文本,需要创建并设置混合状态:
    D3D11_BLEND_DESC blendDesc = {};
    blendDesc.RenderTarget[0].BlendEnable = TRUE;
    blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
    blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
    blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
    blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
    
    ID3D11BlendState* pBlendState = nullptr;
    p_device->CreateBlendState(&blendDesc, &pBlendState);
    p_context->OMSetBlendState(pBlendState, nullptr, 0xFFFFFFFF);
    
  • 字体大小适配:如果需要缩放文本,要提前用FT_Set_Pixel_Sizes调整FreeType的输出位图大小。

内容的提问来源于stack exchange,提问作者aqwerty

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:45:03