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就是这么干的
继承包含关系
FontFreeType/FontBMFnt/FontCharMap都是继承自Font,FontAtlas都会绑定对应的Font,FontAtlas里面掺杂了Font子类的逻辑,需要剥离出来
FontAtlas的职能发生变化
- 不支持ttf合批:根据ttfConfig,通过对应的FontFreeType渲染出字符纹理,并同步到bind
a8
/ai88
texture
- 支持ttf合批:根据ttfConfig,查找对应的FontFreeType后渲染出字符纹理,并将字符纹理同步到ttf
a8
texture
会抽象出FontFreeType的管理类
FontAtlas的改动方向
FontFreeType这个类可以渲染某种特征的字体,所以我们需要有很多这样的类,用来渲染不同特征的字符纹理,而不是FontAtlas对应一个FrontFreeType,label对应一个FontAtlas
- label
- FontAtlas
- FontFreeType1
- FontFreeType2
同时也需要管理起来,根据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