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参数组合后调用第三个重载方法即可。
本次的分析就到这里,感谢。