label.vertex
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
void main()
{
gl_Position = CC_MVPMatrix * a_position;
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
}
LabelEffect::NORMAL
正常:距离场_useDistanceField: SHADER_NAME_LABEL_DISTANCEFIELD_NORMAL
正常:_useA8Shader: SHADER_NAME_LABEL_NORMAL
setTTFConfigInternal会触发这里逻辑
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform vec4 u_textColor;
void main()
{
int textureAlpha = texture2D(CC_Texture0, v_texCoord).a;
gl_FragColor = v_fragmentColor * vec4(
u_textColor.rgb,// RGB from uniform
u_textColor.a * textureAlpha // A from texture & uniform
);
}
正常:有阴影 _shadowEnabled: SHADER_NAME_POSITION_TEXTURE_COLOR
- label.vertex
- frament
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
void main()
{
gl_FragColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
}
其他情况 SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP
void main()
{
gl_Position = CC_PMatrix * a_position;
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
}
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
void main()
{
gl_FragColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
}
LabelEffect::OUTLINE
SHADER_NAME_LABEL_OUTLINE
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform vec4 u_effectColor;
uniform vec4 u_textColor;
uniform int u_effectType;
void main()
{
vec4 sample = texture2D(CC_Texture0, v_texCoord);
// fontAlpha == 1 means the area of solid text (without edge)
// fontAlpha == 0 means the area outside text, including outline area
// fontAlpha == (0, 1) means the edge of text
float fontAlpha = sample.a;
// outlineAlpha == 1 means the area of 'solid text' and 'solid outline'
// outlineAlpha == 0 means the transparent area outside text and outline
// outlineAlpha == (0, 1) means the edge of outline
float outlineAlpha = sample.r;
if (u_effectType == 0) // draw text
{
gl_FragColor = v_fragmentColor * vec4(u_textColor.rgb, u_textColor.a * fontAlpha);
}
else if (u_effectType == 1) // draw outline
{
// multipy (1.0 - fontAlpha) to make the inner edge of outline smoother and make the text itself transparent.
gl_FragColor = v_fragmentColor * vec4(u_effectColor.rgb, u_effectColor.a * outlineAlpha * (1.0 - fontAlpha));
}
else // draw shadow
{
gl_FragColor = v_fragmentColor * vec4(u_effectColor.rgb, u_effectColor.a * outlineAlpha);
}
}
暂时不考虑:LabelEffect::GLOW
_useDistanceField SHADER_NAME_LABEL_DISTANCEFIELD_GLOW
这部分也需要改动,就像unity那样,有一个大的字符纹理图集,里面会填充所有的特征的字符纹理,加粗,阴影,描边也不例外,本质上都是字符纹理
-
距离场:????
-
加粗:???? 是glyph会生成加粗的纹理
-
阴影是2个纹理偏移
-
描边是用
glyph.FT_Stroker_Set()生成描边纹理后、贴在本体的后边
FontFreeType关联FontAtlas的几个属性:
_fontAscender:
在prepareLetterDefinitions函数中,只有ttf在使用这个属性
fontAscender是一个字体属性,指定字体中字符的上升高度。当我们在使用字体来渲染文本时,这个值告诉我们应该将字符的基线放在哪里,以及字符的顶部如何与其他字符对齐。具体而言,它通常用于计算字符的布局和位置,以确保文本在页面上正确地呈现。
- _lineHeight
outlineSize
会影响pixelFormat
getEncoding
文本转码
保证在同一个纹理里面
- FontAtlas现状:cocos现在是将相同特征的字符纹理统一放到了一张纹理里面,每个FontTexture都是某个特征的字符纹理图集,它有很多个slot,当该特征的字符把第一个纹理填满时,就会再开辟第二个纹理
pixel format的选择
描边和非描边纹理的format是不同的,描边的是AI88,不描边是A8
原来的时候是用了2个textureAtlas分开存储,相当于归了一个类别,本质上还是要占用不可缺少的内存空间。
采用AI88
纹理有2个通道:
- 一个通道:描边纹理
- 一个通道:非描边纹理
优点:
- 不用修改shader,可以在一个像素点里面同时取到2个纹理信息,sample.a、sample.r,也就是一个像素点同时取到了描边和非描边的情况
缺点:
- 没有描边的时候,只使用了一个通道,另一个通道浪费了
- 布局问题,要把描边的宽度考虑进去,但是因为不知道描边多宽,所以也没办法确认这个字符纹理的宽高,也就导致无法排布,也不可能非描边纹理生成多份和描边纹理一一对应,这样浪费的就多,这个缺点直接导致只能使用A8
采用A8
只有一个通道,shader也要修改:
- 不同描边宽度的纹理也采用A8存储,并且排布在一个纹理里面,纹理没有冗余
- 需要传入2个纹理,来确定之前的Luminace、Alpha信息,也能达到同样的效果
- [
?] 如何将描边纹理数据和普通纹理数据对齐
- [
事实上unity就是这么干的
继承包含关系
erDiagram
Label ||--o{ FontAtlas:has
FontAtlas ||--o{ Font:has
Font ||--o{ FontFreeType:inherit
Font ||--o{ FontBMFont:inherit
Font ||--o{ FontCharMap:inherit
FontFreeType/FontBMFnt/FontCharMap都是继承自Font,FontAtlas都会绑定对应的Font,FontAtlas里面掺杂了Font子类的逻辑,需要剥离出来
FontAtlas的职能发生变化
- 不支持ttf合批:根据ttfConfig,通过对应的FontFreeType渲染出字符纹理,并同步到bind
a8/ai88texture - 支持ttf合批:根据ttfConfig,查找对应的FontFreeType后渲染出字符纹理,并将字符纹理同步到ttf
a8texture
会抽象出FontFreeType的管理类
FontAtlas的改动方向
FontFreeType这个类可以渲染某种特征的字体,所以我们需要有很多这样的类,用来渲染不同特征的字符纹理,而不是FontAtlas对应一个FrontFreeType,label对应一个FontAtlas
- label
- FontAtlas
- FontFreeType1
- FontFreeType2
- FontAtlas
同时也需要管理起来,根据label的ttfconfig,查找对应的FrontFreeType来进行渲染该特征的纹理
这样修改后,fontAtlas就需要根据ttfConfig从多个FontFreeType查找
字符信息的记录结构发生变化
需要记录每个特征的字符,出现在哪个纹理里面,以方便渲染的时候知道取大纹理的哪部分,copy到一个渲染纹理的做法也不可取,这样会造成内存浪费。
当渲染某个特征的字符时,我需要一个key知道是否曾经生成过这个字符纹理,因为现在大家都混在了一个纹理里面,并且记录在了一起,所以记录字符纹理信息的key应该是
为了更加直观的看到FontAltasCache里面的字符纹理图集,我需要开发一个字符纹理图集(atlasMap)查看工具,对后续排错非常有帮助,而且对于后续性能优化也能起到非常大的帮助,unity好像就不能查看字符纹理图集,只能借助renderdoc
bmfont和charmap在addLetterDefinition的时候只有charID信息,它没有TTFConfig参数,但是ttf需要这个TTFConfig参数作为key,因为它们目前不在同一个图集,所以
label又同时兼顾到了这三种情况,ttf模式下单靠一个charID是无法区分的,
- 办法1:是将这个逻辑分发到具体的子类里面
- 办法2:bmfont/charmap的key和ttf保持一致
很显然采用第二种办法更优,改动更小,因为大家的key对齐方式一样了,底层的处理也就一样了,无非是bmfont/charmap的ttfConfig永远是一个默认值,而ttf的是有效值,对key的生成本质上没有变化
但是这种办法因为key是string,显然有点冗余,但是为了对齐也少不了,后期可以考虑string to hash,字符串比较会消耗比较多的时间
label
label的_fontAtlas里面存储的是相同特征的图集,而label里面的每个文字都是具有相同特征,所以label里面的字符一般都是在一个相同的_fontAtlas里面
lable要实现不同特征的ttf合批,就需要将所有字符都搞到一个texture里面,多纹理也能办到合批,但是有点不现实
_batchNodes
_batchNodes的数量始终会和_fontAtlas的的纹理个数对齐,也就是说一个batchNode管理的就是fontAtlas的某个slot texture
一般来说,label的string都会出现在一个texture里面,这样就只有一个batchNode,当然也就只会触发一次drawQuads
label.string如果是str1+str2,也会发生
- str1在slot1 texture
- str2在slot2 texture
这时label就会产生2个batchNode,那么就会触发2个drawcall
这是一个非常特殊的临界情况,label.string被存储在了2个不同的slot texture导致
所以提交渲染的逻辑就变成了
for(auto& batchNode : _batchNodes){
QuadCommand cmd;
renderer->addCommand(cmd);
}
有几个batchNode,就有几个quad command(因为quad command能够和triangle command合批),虽然和batchNode关联,但是很明显batchNode身上有自己的batchCommand,我不想干扰这个逻辑,顺藤摸瓜,发现和TextureAtlas关联也是个不错的主意,发现TextureAtlas也预留了一个rendererCommand的位置,很明显,原作者当初也考虑到给TextureAtlas映射一个RendererCommand,但是没实现。
经过验证,这个方法可行,已经可以顺利将多个label顺利合批,遇到一个问题,label的颜色是在uniform中定义的
uniform vec4 u_textColor;
void main()
{
gl_FragColor = v_fragmentColor * vec4(
u_textColor.rgb,// RGB from uniform
u_textColor.a * texture2D(CC_Texture0, v_texCoord).a// A from texture & uniform
);
}
uniform的方案不支持不同的颜色,现在的顶点颜色字段,是被_displayedColor属性占用着,对应shader的v_fragmentColor,设计意图,它是一个基础颜色,和textColor不是一个概念,不能混用。
那解决办法只能把textColor的数据放到顶点数据中,但是这么做会增加顶点数据量,而且现在好像也不支持vertex format,顶点数据格式是固定的,看样子得支持下vertex format
struct CC_DLL V3F_C4B_T2F
{
/// vertices (3F)
Vec3 vertices; // 12 bytes
/// colors (4B)
Color4B colors; // 4 bytes
// tex coords (2F)
Tex2F texCoords; // 8 bytes
Vec4 outlineColor;// 描边颜色
Vec4 textColor; // 字体颜色
};
如果不改动顶点颜色,那么就需要纹理带颜色,同样会带来纹理冗余的问题,需要看下unity这块是怎么实现的,应该是放到了顶点里面
FontAtlas获取发生变化:
label对应一个fontAtlas,这个fontAtlas也是从cache中获取,不同的模式对应的cache key
- charmap:
- plistFile
- textureID, width, height, startChar
- charMapFile, width, height, startChar
- bmfont:
- bmfontFile, imageOffset
- ttf:
- ttfConfig
为保证所有的字符纹理都在一个FontAtlas里面,这个key就不能以字体的特性作为基准,那么这个key应该就是一个固定的key了,因为这个FontAtlasCache只有label在使用,所以直接改造这个问题不大。
RenderCommand
TrianglesCommand
- 顶点 polygon.triangles
描边发生了2次draw,现在得一次完成,想办法把shader的逻辑合并。
outline.a=texture_outline.a
font.a=texture_font.a
if(font.a > 0){
// 字体纹理有颜色就使用字体纹理的颜色
return font_color;
}else if(outline.a > 0){
// 描边
return outline_color;
}else{
return null;
}
不描边时 texture1==texture2
unity
实际测试unity的字符纹理图集发现,unity会根据字符纹理数量,自动扩充字符纹理图集的大小,从512*512到1024*1024再到2048*2048再到4096*4096