游戏引擎Flax Engine源码分析(九)渲染

本文详细分析了DrawText函数的多个重载,包括其参数如字体、文本、颜色、位置和自定义材质的作用。重点讲解了如何使用这些参数进行2D渲染,特别是涉及文本范围和布局选项时的处理。通过实例展示了如何进行字符渲染和布局计算,为理解和使用2D文本渲染提供了清晰的指导。
摘要由CSDN通过智能技术生成

 2021SC@SDUSC


一、概述

        这篇文章我们主要分析一下文本渲染,也就是DrawText()函数及其几个重载。仍然是2D渲染的部分、

二、分析

        DrawText有一下几个重载:

static void DrawText(Font* font, const StringView& text, const Color& color, const Vector2& location, MaterialBase* customMaterial = nullptr);
static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, const Vector2& location, MaterialBase* customMaterial = nullptr);
static void DrawText(Font* font, const StringView& text, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);
static void DrawText(Font* font, const StringView& text, API_PARAM(Ref) const TextRange& textRange, const Color& color, API_PARAM(Ref) const TextLayoutOptions& layout, MaterialBase* customMaterial = nullptr);

        第三第四个重载用于绘制带格式的文本。

        我们简单分析一下函数的几个参数:

font: 使用的字体

text: 被渲染的文本

color: 使用的颜色

location: 二维向量,表示文本的位置

customMaterial:用于字体字符呈现的自定义材质。它必须包含用于取样字体纹理的名为Font的纹理参数

textRange: 输入文本范围(输入文本参数的子字符串范围)

layout:文本布局属性

         下面来看具体的函数实现:


         第一个重载(没有参数textRange和layout):

        检车是否需要进行渲染操作

if (font == nullptr ||
        text.IsEmpty() ||
        (customMaterial && (!customMaterial->IsReady() || !customMaterial->IsGUI())))
        return;

        临时数据:

    uint32 fontAtlasIndex = 0;
    FontTextureAtlas* fontAtlas = nullptr;
    Vector2 invAtlasSize = Vector2::One;
    FontCharacterEntry previous;
    int32 kerning;
    float scale = 1.0f / FontManager::FontScale;

        接下来渲染所有字符:

        首先判断了自定义材质(customMaterial)是否为空,根据情况进行不同的2D渲染调用。(2D渲染调用的类型在之前的博客中已提及)

    FontCharacterEntry entry;
    Render2DDrawCall drawCall;
    if (customMaterial)
    {
        drawCall.Type = DrawCallType::DrawCharMaterial;
        drawCall.AsChar.Mat = customMaterial;
    }
    else
    {
        drawCall.Type = DrawCallType::DrawChar;
        drawCall.AsChar.Mat = nullptr;
    }
    Vector2 pointer = location;

        然后对每一个字符进行渲染:

for (int32 currentIndex = 0; currentIndex <= text.Length(); currentIndex++)

        缓存当前字符

const Char currentChar = text[currentIndex];

        检查是否是换行符,若不是:

  •         获取字符条目
  •         检查是否需要选择/更改字体图集(因为相同字体的字符可能位于不同的地图集中),获取包含当前字符的纹理图谱。
  •         检查字符是否是空格,调整字距,省略空格字符,计算字符大小和地图坐标,添加绘图调用。
  •         移动。

        若是换行符:

        仅执行移动操作。

if (currentChar != '\n')
        {
            font->GetCharacter(currentChar, entry);

            if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex)
            {
          
                fontAtlasIndex = entry.TextureIndex;
                fontAtlas = FontManager::GetAtlas(fontAtlasIndex);
                if (fontAtlas)
                {
                    fontAtlas->EnsureTextureCreated();
                    drawCall.AsChar.Tex = fontAtlas->GetTexture();
                    invAtlasSize = 1.0f / fontAtlas->GetSize();
                }
                else
                {
                    drawCall.AsChar.Tex = nullptr;
                    invAtlasSize = 1.0f;
                }
            }

            const bool isWhitespace = StringUtils::IsWhitespace(currentChar);

            if (!isWhitespace && previous.IsValid)
            {
                kerning = font->GetKerning(previous.Character, entry.Character);
            }
            else
            {
                kerning = 0;
            }
            pointer.X += kerning * scale;
            previous = entry;

            
            if (!isWhitespace)
            {
                
                const float x = pointer.X + entry.OffsetX * scale;
                const float y = pointer.Y + (font->GetHeight() + font->GetDescender() - entry.OffsetY) * scale;

                Rectangle charRect(x, y, entry.UVSize.X * scale, entry.UVSize.Y * scale);

                Vector2 upperLeftUV = entry.UV * invAtlasSize;
                Vector2 rightBottomUV = (entry.UV + entry.UVSize) * invAtlasSize;

             
                drawCall.StartIB = IBIndex;
                drawCall.CountIB = 6;
                DrawCalls.Add(drawCall);
                WriteRect(charRect, color, upperLeftUV, rightBottomUV);
            }

           
            pointer.X += entry.AdvanceX * scale;
        }

        第二个重载(带有参数textRange):

 DrawText(font, StringView(text.Get() + textRange.StartIndex, textRange.Length()), color, location, customMaterial);

        StringView类型将静态文本视图表示为 utf-16字符序列。 可见此处仅是将text和textRange重新组装后再次调用之前的文本渲染函数。


        第三个重载(带有参数layout):

        大体上与第一个重载一样,这里仅分析不同之处:

        首先是在准备阶段,除了判断是否需要进行渲染和声明临时数据外,对文本进行处理以获取行:

    Lines.Clear();
    font->ProcessText(text, Lines, layout);

        注意后面对每个字符渲染的部分在这里做了一些改变,变成了两层循环,及将之前的获取每个字符的for语句改成如下结构:

for (int32 lineIndex = 0; lineIndex < Lines.Count(); lineIndex++)
    {
        const FontLineCache& line = Lines[lineIndex];
        Vector2 pointer = line.Location;
        for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex; charIndex++)

        即首先对上面获取的行进行一次位置的处理,在对每一行的每一个字符进行渲染,以这样的方式完成布局。 


        第四个重载(带有layout和textRange): 

        这里就不再赘述,textRange与text参数组合后调用第三个重载方法即可。


        本次的分析就到这里,感谢。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值