cocos2dx如何让label支持合批

简介: cocos2dx如何让label支持合批

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就是这么干的


继承包含关系


image.png


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


image.png


unity


实际测试unity的字符纹理图集发现,unity会根据字符纹理数量,自动扩充字符纹理图集的大小,从512*5121024*1024再到2048*2048再到4096*4096

目录
相关文章
|
Linux Shell 数据安全/隐私保护
超简单五步实现Linux虚拟机CentOS 7系统Root密码忘记重置
超简单五步实现Linux虚拟机CentOS 7系统Root密码忘记重置
1669 0
|
存储 JSON 缓存
CocosCreator3.8研究笔记(十五)CocosCreator 资源管理Asset Bundle
CocosCreator3.8研究笔记(十五)CocosCreator 资源管理Asset Bundle
985 0
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
714 3
|
SQL 关系型数据库 MySQL
推荐一款高颜值的MySQL管理工具:Sequel Pro
推荐一款高颜值的MySQL管理工具:Sequel Pro
365 9
|
存储 安全 Cloud Native
如何在银行核心系统中安全地搭建微服务架构?
微服务作为现代互联网应用的主流架构风格,已在很多行业应用中获得广泛的成功,而银行核心系统由于其复杂性和风险敏感性,主流架构依然在从单体式 SOA 到真正的微服务分布式架构的转型期。
540 1
|
存储
[✔️] cocos2dx label合批探讨
[✔️] cocos2dx label合批探讨
288 0
|
算法 atlas
[✔️]cocos2x label ttf合批
[✔️]cocos2x label ttf合批
233 0
|
Android开发 索引
Cocos Creator3.8 项目实战(七)Listview 控件的实现和使用(1)
Cocos Creator3.8 项目实战(七)Listview 控件的实现和使用
928 0
|
缓存 Android开发
Skia深入分析5——skia文字绘制的实现
文字绘制主要包括编码转换(主要是中文)、字形解析(点线或image)和实际渲染三个步骤。在这个过程中,字形解析和实际渲染均是耗时步骤。Skia对文字解析的结果做了一套缓存机制。在中文字较多,使用多种字体,绘制的样式(粗/斜体)有变化时,这个缓存会变得很大,因此Skia文字缓存做了内存上的限制。 1、SkPaint 文字绘制与SkPaint的属性相关很大,先回头看下SkPaint相关
7328 0
「TypeScript」你必须要知道的 TS 高级技能点 —— Utility Types
「TypeScript」你必须要知道的 TS 高级技能点 —— Utility Types