Cocos实现对ETC2的支持

Stella981
• 阅读 454

Cocos实现对ETC2的支持

在深圳Cocos沙龙上,有幸结识了社区中大名顶顶的Colin,Shawn在在论坛上第一次看到Colin的团队用CocosCreator制作的《热血暗黑》时就被深深地震撼到了!更为重要的是,Colin将他的技术心得和宝贵开发经验写成文字,每一篇分享都是满满的干活,而且幸运的是Shawn得到Colin的授权许可,将他的文章散播到奎特尔星球,与你一起欣赏一起成长!

Cocos实现对ETC2的支持

1

ETC2简要介绍

etc1的问题是不支持透明通道;而pvr2的问题是透明图片质量太差,且图片大小必须是2的幂和正方形。etc2的出现正好弥补了这两个格式的不足。

etc2不仅兼容etc1,还支持透明通道,并且提供了更多的像素格式。etc2已经是OpenGL ES3.0的标准之一。也就是只要操作系统和硬件支持ES3.0,则必然支持ETC2,不管它是Android还是IOS。

目前市面上使用etc2作为压缩纹理的游戏不多,主要原因是老机器不支持,特别是安卓。而制约其流行起来的原因,其实就是两个:GPU的支持,OS的支持。

我从wikipedia(https://en.wikipedia.org/wiki/OpenGL\_ES#OpenGL\_ES\_3.0)上查了OpenGL ES 3.0的兼容情况,大概是这样的:

  • 软件:

  • android 4.3以上支持ES3.0

  • IOS 7以上支持ES3.0

  • 硬件:

  • Adreno 300 and 400 series (Android, BlackBerry 10, Windows Phone 8, Windows RT)

  • Mali T600 series onwards (Android, Linux, Windows 7)

  • PowerVR Series6 (iOS, Linux)

  • Vivante (Android, OS X 10.8.3, Windows 7)

  • Nvidia (Android, Linux, Windows 7)

  • Intel (Linux)

  • 苹果设备从A7开始支持ES3.0,最低要求的设备是:

  • iPhone 5S

  • iPad Air

  • iPad mini with Retina display

尽管苹果的开发文档说到:

OpenGL ES 3.0 also supports the ETC2 and EAC compressed texture formats; however, PVRTC textures are recommended on iOS devices.

然而我对PVRTC实在是爱不起来,又必须是2的幂,又必须是正方形,最终效果还那么差(4bit一个像素)。所以当A7支持ETC2之后,其实是可以考虑换用ETC2的,这样可以和安卓很好的统一起来。

2

改用ES3.0的EGLContext

如何在cocos中支持etc2,其实这件事由官方来做最好,可能他们考虑到设备的兼容性问题,就没有实现这个特性。好在支持这个并不困难,我就自己动手实现了。

cocos使用的是ES2.0的版本,经测试发现,安卓上如果硬件支持ETC2,context并不用换成3.0。而IOS就必须明确创建3.0的EGLContext,才可以使用ETC2。

minggo之前提交过一个PR支持GLES3,在这里(https://github.com/cocos2d/cocos2d-x/pull/17537),只需要把它修改的文件合并进项目即可,可以两个都合,也可以只合IOS的。

3

PKM2格式说明

ETC2只是一个压缩算法,还需要一种文件格式来包含它,etc1常包含在pkm文件中,etc2也可以在pkm中,只不过etc1的是pkm10版本,而etc2需要pkm20版本,这两个文件版本是兼容的,它的格式如下:

4 byte    magic number: "PKM "2 byte     version "10" or "20"2 byte     format: 0 (ETC1_RGB_NO_MIPMAPS), 1(ETC2_RGB_NO_MIPMAPS), ...16 bit     big endian extended width16 bit     big endian extended height16 bit     big endian original width16 bit     big endian original heightdata, 64bit big endian words.

我修改的时候只加了ETC2_RGB_NO_MIPMAPS和ETC2_RGBA_NO_MIPMAPS的支持,第一个是RGB,和ETC1兼容,一个像素占用4位;第二个是RGBA,提供透明通道,一个像素占用8位;这有点像以前贴子提到的ETC1+Alpha,好处是我们不用再写自定义Shader了。

4

修改Cocos引擎

上面预热了那么久,终于要修改引擎了,这么一步并不麻烦,我们只要依照ETC1的代码添加ETC2的代码就行:

修改Configuration,提供ETC2的支持判断

  • 增加_supportsETC2成员变量,用于判断是否支持ETC2

  • 增加函数checkForEtc2:

    bool Configuration::checkForEtc2() const {     // Only the following two formats are supported    #define GL_COMPRESSED_RGB8_ETC2   0x9274    #define GL_COMPRESSED_RGBA8_ETC2_EAC  0x9278

       GLint numFormats = 0;    glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numFormats);    GLint* formats = new GLint[numFormats];    glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, formats);

       int supportNum = 0;    for (GLint i = 0; i < numFormats; ++i)    {        if (formats[i] == GL_COMPRESSED_RGB8_ETC2 || formats[i] == GL_COMPRESSED_RGBA8_ETC2_EAC)        supportNum++;    }    delete [] formats;

       return supportNum >= 2; }

  • 在Configuration::gatherGPUInfo添加代码:

    supportsETC2 = checkForEtc2(); _valueDict["gl.supports_ETC2"] = Value(_supportsETC2);

  • 加一个访问函数:Configuration::supportsETC2,和ETC1的流程一样,参考一下就懂了。

修改CCImage,支持ETC2的加载

  • 提供解析PKM2的函数如下:

    // etc2 namespace { #define ETC2_RGB_NO_MIPMAPS           1 #define ETC2_RGBA_NO_MIPMAPS          3

       static const int ETC2_PKM_HEADER_SIZE = 16;    static const char ETC2_PKM_MAGIC[] = { 'P', 'K', 'M', ' ', '2', '0' };

       static const uint32_t ETC2_PKM_FORMAT_OFFSET = 6;    static const uint32_t ETC2_PKM_ENCODED_WIDTH_OFFSET = 8;    static const uint32_t ETC2_PKM_ENCODED_HEIGHT_OFFSET = 10;    static const uint32_t ETC2_PKM_WIDTH_OFFSET = 12;    static const uint32_t ETC2_PKM_HEIGHT_OFFSET = 14;

       static uint32_t read_big_endian_uint16(const uint8_t *pIn) {        return (pIn[0] << 8) | pIn[1];    }

       static bool etc2_pkm_is_valid(const uint8_t* pHeader) {        if (memcmp(pHeader, ETC2_PKM_MAGIC, sizeof(ETC2_PKM_MAGIC))) {            return false;        }        uint32_t format = read_big_endian_uint16(pHeader + ETC2_PKM_FORMAT_OFFSET);        uint32_t encodedWidth = read_big_endian_uint16(pHeader + ETC2_PKM_ENCODED_WIDTH_OFFSET);        uint32_t encodedHeight = read_big_endian_uint16(pHeader + ETC2_PKM_ENCODED_HEIGHT_OFFSET);        uint32_t width = read_big_endian_uint16(pHeader + ETC2_PKM_WIDTH_OFFSET);        uint32_t height = read_big_endian_uint16(pHeader + ETC2_PKM_HEIGHT_OFFSET);        return (format == ETC2_RGB_NO_MIPMAPS || format == ETC2_RGBA_NO_MIPMAPS) &&               encodedWidth >= width && encodedWidth - width < 4 &&               encodedHeight >= height && encodedHeight - height < 4;    }

       static uint32_t etc2_pkm_get_width(const uint8_t * pHeader) {        return read_big_endian_uint16(pHeader + ETC2_PKM_WIDTH_OFFSET);    }

       static uint32_t etc2_pkm_get_height(const uint8_t* pHeader){        return read_big_endian_uint16(pHeader + ETC2_PKM_HEIGHT_OFFSET);    }

       static uint32_t etc2_pkm_get_format(const uint8_t* pHeader) {        return read_big_endian_uint16(pHeader + ETC2_PKM_FORMAT_OFFSET);    } }

有了上面的说明,相信这个代码很容易看懂的。

  • Image::Format增加一种类型: ETC2

  • 在Image::detectFormat增加如下代码:

       else if (isEtc2(data, dataLen))    {        return Format::ETC2;    }

实现isEtc2:

bool Image::isEtc2(const unsigned char *data, ssize_t dataLen)
{
    return dataLen >= ETC2_PKM_HEADER_SIZE && etc2_pkm_is_valid(data);
}

修改Image::initWithImageData:

case Format::ETC2:
    ret = initWithETC2Data(unpackedData, unpackedLen);
    break;

实现initWithETC2Data:

bool Image::initWithETC2Data(const unsigned char * data, ssize_t dataLen)
{
    const unsigned char* header = data;

    //check the data
    if (!etc2_pkm_is_valid(header))
    {
        return  false;
    }

    _width = etc2_pkm_get_width(header);
    _height = etc2_pkm_get_height(header);

    if (0 == _width || 0 == _height)
    {
        return false;
    }

    if (Configuration::getInstance()->supportsETC2())
    {
        uint32_t format = etc2_pkm_get_format(header);
        if (format == ETC2_RGB_NO_MIPMAPS)
            _renderFormat = Texture2D::PixelFormat::ETC2_RGB;
        else
            _renderFormat = Texture2D::PixelFormat::ETC2_RGBA;
        _dataLen = dataLen - ETC2_PKM_HEADER_SIZE;
        _data = static_cast<unsigned char*>(malloc(_dataLen * sizeof(unsigned char)));
        memcpy(_data, static_cast<const unsigned char*>(data) + ETC2_PKM_HEADER_SIZE, _dataLen);
        _hasPremultipliedAlpha = false;
        return true;
    }
    CCLOG("cocos2d: Hardware ETC2 decoder not support.");
    return false;
}

修改CCTexture2D,生成ETC2纹理

  • Texture2D::PixelFormat增加两种类型:

    //! ETC2-compressed texture: GL_COMPRESSED_RGB8_ETC2 ETC2_RGB, //! ETC2-compressed texture: GL_COMPRESSED_RGBA8_ETC2_EAC ETC2_RGBA,

  • static const PixelFormatInfoMapValue TexturePixelFormatInfoTablesValue[]增加这两种格式的信息:

    PixelFormatInfoMapValue(Texture2D::PixelFormat::ETC2_RGB, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGB8_ETC2, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)), PixelFormatInfoMapValue(Texture2D::PixelFormat::ETC2_RGBA, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA8_ETC2_EAC, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, true)),

  • Texture2D::getStringForFormat增加格式说明,其实不加也没关系:

       case Texture2D::PixelFormat::ETC2_RGB:        return "ETC2_RGB";    case Texture2D::PixelFormat::ETC2_RGBA:        return "ETC2_RGBA";

  • Texture2D::initWithMipmaps判断压缩纹理处作一点修改:

    if (info.compressed && !Configuration::getInstance()->supportsPVRTC()                    && !Configuration::getInstance()->supportsETC()                    && !Configuration::getInstance()->supportsETC2()) {    CCLOG("cocos2d: WARNING: PVRTC/ETC/ETC2 images are not supported");    return false; }

到此引擎改造完成。

5

结语

Android版本,Android Studio的build.gradle的minSdkVersion改为18,即Android4.3,我没试过不改的话能不能正常使用,你们有兴趣可以试试看。

IOS版本,XCode直接编译,连上真机就可以测,不过要记得上面的EGLContext一定要换成3的,不然不会成功。


欢迎关注「奎特尔星球」微信公众号,有代码、有教程、有视频、有故事,一起来玩吧!

Cocos实现对ETC2的支持

本文分享自微信公众号 - Creator星球游戏开发社区(creator-star)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
2D MMO中角色动画的优化总结
!(https://oscimg.oschina.net/oscnet/bfe2de3d3c6941af8d0df4358a18a696.jpg"1711134845.jpg")在深圳Cocos沙龙上,有幸结识了社区中大名顶顶的Colin,Shawn在社区论坛上第一次看到Colin的团队用CocosCreator制作的《热血暗黑》时就被深深
Stella981 Stella981
2年前
Creator使用压缩纹理
!(https://oscimg.oschina.net/oscnet/8622f533209146389f1414f187b43ea0.jpg"1711134845.jpg")在深圳Cocos沙龙上,有幸结识了社区中大名顶顶的Colin,Shawn在在论坛上第一次看到Colin的团队用CocosCreator制作的《热血暗黑》时就被深深地
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这