creator贴图纹理压缩(creator2.4.x 实现ETC2和ASTC)

本文详细探讨了Cocos Creator中纹理压缩技术,包括ETC2和ASTC格式的优缺点、兼容性分析、内存消耗测试,以及如何在2.4.x版本中实现ASTC支持。通过实例展示了如何使用插件自动压缩资源,提升游戏性能并解决常见问题。
摘要由CSDN通过智能技术生成

目录

1. creator游戏开发之纹理压缩

2. 常用的压缩纹理格式

3. 测试

4. ETC2 格式测试

6. ASTC格式测试

送上下载链接 不修改引擎的实现ASTC格式加载.zip-cocos2D文档类资源-CSDN下载

7.  最后附上插件代码


1. creator游戏开发之纹理压缩

目的:减少运行内存,减少耗电量发热

2. 常用的压缩纹理格式

  1. 常用压缩格式有 ETC1、ETC2、PVRTC、ASTC
  1. 分析对比:

兼容 、内存、 效果 各方面分析对比

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

ETC2 需要 OpenGL ES 3.0 支撑。

OpenGL ES 3.0的兼容情况,大概是这样的:

  • 软件:
  • android 4.3以上支持ES3.0
  • android  minSdkVersion版本18
  • 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

  

ASTC 6x6的内存容量小于ETC2 4 bits,压缩质量高于ETC2 4 bits。creator3.0以后支持ASTC ,受版本限制creator2.4需要修改c++底层逻辑来加载ASTC

3. 测试

测试的纹理图片1.jpg (720*1600) 位深度24、2.png( 614*902) 位深度8、3.png( 462*689)位深度8

补充知识:

RGBA4444 :每个通道占4位(bit)4*4 = 16位

RGBA8888 :每个通道占8位(bit)32位

RGB565 :占用的存储大小 5+6+5 = 16 位 = 2Byte

RGB888 :每个通道占8位(bit)24位 = 3Byte

纹理内存计算:(12* 720*1600 + 4* 614*902 + 4* 462*689)/(1024*1024) = 16.5M

测试工程:不加载任何纹理 (空包内存):50.6M

显示那3张图片内存:67.3M

4. ETC2 格式测试

  • 准备工作

需要android  minSdkVersion版本18

使用的 ext.COMPRESSED_RGBA8_ETC2_EAC

引擎目录jsb-adapter/bin/jsb-builtin.js可以看到支持的压缩格式

压缩工具在引擎目录:\2.4.3\resources\static\tools\texture-compress\mali\Windows_64\

etcpack.exe

相关工具ImageMagick 中文站

构建后需要关注的变化

查阅 CCMacro.js的 SUPPORT_TEXTURE_FORMATS和 resources\engine\cocos2d\renderer\gfx\ enums.js 的enums

回到构建后的assets目录下资源,压缩需要处理的贴图得到pkm

 0 换成 6@29

  • IOS 需要改用ES3.0的EGLContext 参考 PR #1685

creator2.4支持

creator2.4.3 提示只支持导出,但实际测试是已经支持加载了。可能编辑器没同步更新。

  • 结果

ETC2 fast 内存:减少%40以上

一共花了约2分钟

ETC2 slow 压缩一张图片要3-5分钟,实在太慢了。好在有缓存不用重复压缩,和fast模式差别不大,可能图片质量高一些,看不太出来。

问题

  1. 透明渐变区域有波纹(透明通道)
  1. mac平台纹理压缩 后透明度不对, etc2存在alpha精度丢失

解决方式(Mac 升级工具链

6. ASTC格式测试

ASTC 6x6的内存容量小于ETC2 4 bits,压缩质量高于ETC2 4 bits。

需要参考creator3.x的实现方式自行修改 参考这里

送上下载链接 不修改引擎的实现ASTC格式加载.zip-cocos2D文档类资源-CSDN下载

7.  最后附上插件代码

插件说明:插件实现在构建的时候自动将项目贴图资源压缩,无感操做

完整构建插件:packages\pack-render

核心代码文件(main.js):

/*main.js*/
/* eslint-disable no-undef */
const Fs = require('fs');
const Path = require('path');
const FsExtra = require('fs-extra');
const { resolve } = require('path');

const settingPath = "packages://pack-render/config/setting.json"
const astc = "packages://pack-render/astcenc/astcenc-avx2"
var isCompress = true;
var platform = "android"

// 开始构建
function onBeforeBuildStart (options, callback) {
  isCompress = options.isCompress
  platform = options.platform
  Editor.log("pack-render:start::", isCompress);
  if (!isCompress) {
    callback();
    return;
  }
  Editor.Ipc.sendToMain("pack-render:doWork", "start", function (event, args) {
    Editor.log("doWork  start >>> Complete");
    callback();
  });
}
// 构建完成
function onBeforeBuildFinish (options, callback) {
  callback();
}

function onBuildFinish (options, callback) {
  isCompress = options.isCompress
  platform = options.platform
  Editor.log("pack-render:finish::", isCompress);
  if (!isCompress) {
    callback();
    return;
  }

  Editor.Ipc.sendToMain("pack-render:setBuildResults", options.bundles);

  Editor.Ipc.sendToMain("pack-render:doWork", "finish", function (event, args) {
    Editor.log("doWork finish >>> Complete");
    callback();
  });
}

const Types = {
  NONE: "none",
  ETC2_RGBA: "etc2",
  ASTC_6_6: "astc_6_6",
  ASTC_8_8: "astc_8_8"
}

function autoSetTypeSelectWithPlatform (setting, p) {
  if (p === 'ios') {
    setting.typeSelect = Types.ASTC_6_6
  } else if (p === 'android') {
    setting.typeSelect = Types.ETC2_RGBA
  }
}

module.exports = {
  data: {
    setting: {},
    platformSettings: {},
    cmd: "start",
    buildBundles: null
  },
  messages: {
    'open' () {
      Editor.Panel.open('pack-render');
      // 面板加载时,直接显示缓存数据.
      setTimeout(() => {
        Editor.Ipc.sendToPanel("pack-render", "loadConfigs", "arg1", "arg2");
      }, 2000);
    },
    'onStart' (event, arg1, arg2) {
      Editor.log("onStart", arg1, arg2);
    },
    'setBuildResults' (event, bundles) {
      // Editor.log("setBuildResults:", bundles);
      this._setBuildResults(bundles);
    },
    async 'doWork' (event, arg1) {
      // this.encNativeTexture("D:/build/jsb-default/assets/testui/native/2e/2ef15057-867c-45ec-a6b1-1dabeaeec6bc.c4c12.png");
      // return;
      Editor.log("doWork:", arg1);
      await this.updateSetting()
      this.cmd = arg1
      let isAstc = this.isASTC()
      if (this.cmd == "start") {
        if (this.setting.include && this.setting.include.length > 0) {
          // if (isAstc) {
          //   setTimeout(() => {
          //     if (event.reply) {
          //       event.reply(null, 'Fine, thank you!');
          //     }
          //   }, 1000);
          // } else {
          this.findPaths(this.setting.include, event)
          // }
        }
      } else if (this.cmd == "finish") {
        if (this.setting.include && this.setting.include.length > 0) {
          if (isAstc) {
            this.findImports(this.setting.include, event)
          } else {
            this.findPaths(this.setting.include, event)
          }
        }
      }
    }
  },

  load () {
    this.updateSetting();
    Editor.Builder.on('build-start', onBeforeBuildStart); // 构建开始时触发。
    // 在构建结束 之前 触发,此时除了计算文件 MD5、生成 settings.js、原生平台的加密脚本以外,大部分构建操作都已执行完毕。我们通常会在这个事件中对已经构建好的文件做进一步处理。
    Editor.Builder.on('before-change-files', onBeforeBuildFinish);
    Editor.Builder.on('build-finished', onBuildFinish); // 构建完全结束时触发。
  },
  unload () {
    Editor.Builder.removeListener('build-start', onBeforeBuildStart);
    Editor.Builder.removeListener('before-change-files', onBeforeBuildFinish);
    Editor.Builder.removeListener('build-finished', onBuildFinish);
  },
  addLog (...str) {
    Editor.Ipc.sendToPanel("pack-render", "addLog", str.join(':'));
    Editor.log(str.join(':'))
  },
  // 同步配置
  updateSetting () {
    return new Promise(resolve => {
      // 检查配置文件
      Fs.access(Editor.url(settingPath), Fs.constants.R_OK | Fs.constants.W_OK, (err) => {
        if (err) {
          autoSetTypeSelectWithPlatform(this.setting, platform)
          Fs.writeFile(Editor.url(settingPath), JSON.stringify({}), 'utf-8');
          return resolve(this.setting)
        } else {
          // Editor.log(`${settingPath} exists, and it is read-writable`);
          Fs.readFile(Editor.url(settingPath), 'utf-8', (err, res) => {
            if (err) {
              return resolve(this.setting);
            }
            const settingObj = res ? JSON.parse(res) : {};
            autoSetTypeSelectWithPlatform(settingObj, platform)
            Editor.log("update setting", JSON.stringify(settingObj));
            this.setting = settingObj
            return resolve(this.setting)
          });
        }
      })
    });
  },
  isASTC () {
    return this.setting.typeSelect == Types.ASTC_6_6 || this.setting.typeSelect == Types.ASTC_8_8
  },
  _setBuildResults (bundles) {
    this.buildBundles = bundles
  },
  // 查询资源
  async findPaths (include, event) {
    //
    for (let index = 0; index < include.length; index++) {
      const config = include[index];
      let path = "db://assets/" + config.path + "/**/*"
      // "db://assets/testui/**/*"
      let results = await this.queryAssets(path)
      if (!results) {
        continue;
      }
      for (let itemidx = 0; itemidx < results.length; itemidx++) {
        const item = results[itemidx];
        if (this.isExcludePath(item.url)) {
          this.addLog("排除:", item.url);
          continue
        }
        // Editor.log("绝对路径:", item.path);
        let uuid = item.uuid;
        this.addLog("设置纹理格式:", item.url);
        await this.changeMeta(item.uuid, item.url)
      }
      if (index == include.length - 1) {
        this.addLog(">>> Complete");
        if (event.reply) {
          event.reply(null, 'Fine, thank you!');
        }
      }
    }
  },
  isExcludePath (url) { // 排除
    for (let index = 0; index < this.setting.exclude.length; index++) {
      const path = this.setting.exclude[index].path;
      if (url.indexOf(path) >= 0) {
        return true;
      }
    }
    return false;
  },
  queryAssets (urlPath) {
    Editor.log("查询目录:", urlPath);
    return new Promise(resolve => {
      Editor.assetdb.queryAssets(urlPath, "texture", (err, results) => {
        if (err) {
          Editor.log("error:", err);
          return resolve(null)
        }
        Editor.log("找到文件个数:", results.length);
        return resolve(results)
      });
    });
  },
  // 修改meta
  changeMeta (uuid, url) {
    return new Promise(resolve => {
      let type = this.setting.typeSelect
      this.platformSettings = {
        android: {
          formats: []
        }
      }
      if (this.cmd == "finish") {
        this.platformSettings = {};
      } else {
        if (type == Types.ETC2_RGBA) {
          this.platformSettings.android.formats.push({ name: "etc2", quality: "fast" })
        } else if (type == Types.ETC2_RGB) {
          this.platformSettings.android.formats.push({ name: "etc2_rgb", quality: "fast" })
        } else {
          this.platformSettings = {};
        }
      }

      var platformSettingsTemp = this.platformSettings
      // Editor.log("修改meta:", JSON.stringify(this.platformSettings));
      // Editor.log("修改meta:", uuid);
      Editor.Ipc.sendToMain("asset-db:query-meta-info-by-uuid", uuid, function (err, info) {
        let metaInfo = JSON.parse(info.json)
        // Editor.log("修改前meta:", platformSettingsTemp);
        metaInfo.platformSettings = platformSettingsTemp
        // Editor.log("修改后meta:", JSON.stringify(metaInfo))
        Editor.assetdb.saveMeta(metaInfo.uuid, JSON.stringify(metaInfo, null, 2), function (err, info) {
          resolve()
        });
      })
    });
  },

  // 查询import
  async findImports (include, event) {
    if (!this.isASTC()) {
      return;
    }
    for (let index = 0; index < this.buildBundles.length; index++) {
      const bundle = this.buildBundles[index];
      // Editor.log("bundle:", bundle)
      if (bundle.name == "internal" || !bundle.root) {
        continue
      }
      var buildResults = bundle.buildResults;
      //
      let querypath = bundle.root + "/**/*"
      let results = await this.queryImports(querypath)
      if (!results) {
        continue;
      }

      for (let j = 0; j < results.length; ++j) {
        let item = results[j];
        if (this.isExcludePath(item.url)) {
          this.addLog("排除:", item.url);
          continue
        }
        Editor.log("ready astcEnc :", item.url)
        let uuid = item.uuid
        var nativePath = buildResults.getNativeAssetPath(uuid);
        let importUrl;
        if (buildResults._md5Map) {
          let import_md5 = buildResults._md5Map[uuid];
          importUrl = bundle.dest + "/import/" + uuid.slice(0, 2) + "/" + uuid + "." + import_md5 + ".json"
        } else {
          importUrl = bundle.dest + "/import/" + uuid.slice(0, 2) + "/" + uuid + ".json"
        }
        await this.astcEnc(importUrl, nativePath);
      }
    }

    if (event.reply) {
      event.reply(null, 'Fine, thank you!');
    }
  },
  queryImports (urlPath) {
    Editor.log("查询目录:", urlPath);
    return new Promise(resolve => {
      Editor.assetdb.queryAssets(urlPath, "texture", (err, results) => {
        if (err) {
          Editor.log("error:", err);
          return resolve(null)
        }
        return resolve(results)
      });
    });
  },
  async astcEnc (importUrl, nativeUrl) {
    Editor.log("importUrl:", importUrl);
    await this.changeImportJson(importUrl)
    Editor.log("nativeUrl:", nativeUrl);
    await this.encNativeTexture(nativeUrl)
    return new Promise(resolve => {
      resolve();
    });
  },
  // 废弃
  getAssetPath (uuid, bundelName) {
    for (let index = 0; index < this.buildBundles.length; index++) {
      const bundle = this.buildBundles[index];
      // if (bundle.name != bundelName) {
      //   continue
      // }
      var buildResults = bundle.buildResults;
      // Editor.log("bundle:", JSON.stringify(bundle));
      // Editor.log("buildResults:", buildResults);
      // 获得指定资源依赖的所有资源
      // var depends = buildResults.getDependencies(uuid);
      // Editor.log("depends:", depends);

      // 获得构建后的原生资源路径(原生资源有图片、音频等,如果不是原生资源将返回空)
      var nativePath = buildResults.getNativeAssetPath(uuid);
      let importUrl;
      if (buildResults._md5Map) {
        let import_md5 = buildResults._md5Map[uuid];
        importUrl = bundle.dest + "/import/" + uuid.slice(0, 2) + "/" + uuid + "." + import_md5 + ".json"
      } else {
        importUrl = bundle.dest + "/import/" + uuid.slice(0, 2) + "/" + uuid + ".json"
      }
      // Editor.log("importPath:", importUrl);

      // 获取资源类型
      // var type = buildResults.getAssetType(uuid);
      // Editor.log("type:", type);

      return { nativeUrl: nativePath, importUrl: importUrl }
    }
  },
  changeImportJson (importUrl) {
    return new Promise(resolve => {
      Fs.readFile(importUrl, 'utf-8', (err, res) => {
        if (err) {
          return resolve();
        }
        let obj = JSON.parse(res)
        if (this.setting.typeSelect == Types.ASTC_6_6) {
          obj[5][0] = "7@34,9729,9729,33071,33071,0,0,1"
        } else if (this.setting.typeSelect == Types.ASTC_8_8) {
          obj[5][0] = "7@37,9729,9729,33071,33071,0,0,1"
        }
        // Editor.log("changeImportJson:", JSON.stringify(obj));
        Fs.writeFileSync(importUrl, JSON.stringify(obj), 'utf-8');
        return resolve(obj)
      });
    });
  },
  encNativeTexture (nativeUrl) {
    return new Promise(resolve => {
      // var cmd = Editor.url('packages://pack-render/astcenc/a.bat') + " " + nativeUrl;
      // var cmd = Editor.url('packages://pack-render/astcenc/do.bat') + " " + nativeUrl;
      let outfile = nativeUrl.replace(/\.\w+$/, ".astc");
      let sel = "6x6"
      if (this.setting.typeSelect == Types.ASTC_6_6) {
        sel = "6x6"
      } else if (this.setting.typeSelect == Types.ASTC_8_8) {
        sel = "8x8"
      }
      var cmd = "python " + Editor.url('packages://pack-render/astcenc/runner.py') + ` -cl ${nativeUrl} ${outfile} ${sel} -medium`;
      this.exec(cmd, () => {
        Fs.unlink(nativeUrl, (err) => {
          if (err) {
            Editor.error(err)
          }
          Editor.log('>>> encNativeTexture complete');
          return resolve();
        });
      })

      // let pythonFile = Editor.url('packages://pack-render/') + "test.py"
      // this.python(pythonFile, [nativeUrl, Editor.url('packages://pack-render/')], () => {
      //   Editor.log('>>> encNativeTexture complete');
      // })
    });
  },
  exec (cmd, callback) {
    var exec = require('child_process').exec;
    Editor.log('exec::' + cmd);
    exec(cmd, function (err, stdout, stderr) {
      if (err) {
        Editor.log('execall:: error:' + stderr);
      } else {
        // var data = JSON.parse(stdout);
        Editor.log(stdout)
        callback()
      }
    });
  },
  shell (cmd, callback) {
    var callfile = require('child_process');
    var ip = '1.1.1.1';
    var username = 'test';
    var password = 'pwd';
    var newpassword = 'newpwd';

    callfile.execFile('xxx.sh', ['-H', ip, '-U', username, '-P', password, '-N', newpassword], null, function (err, stdout, stderr) {
      // callback(err, stdout, stderr);
    });
  },
  python (pythonUrl, pramas, callback) {
    var exec = require('child_process').exec;
    exec('python ' + pythonUrl + ' ' + pramas[0] + ' ' + pramas[1] + ' ', function (error, stdout, stderr) {
      if (stdout.length > 1) {
        Editor.log('you offer args:', stdout);
      } else {
        Editor.log('you don\'t offer args');
      }
      if (error) {
        Editor.info('python call error::' + stderr);
      }
    });
  }

}

参考

1.

Cocos Creator 纹理压缩插件 - Creator - Cocos中文社区

2. etc2

【技术分享之三】cocos实现对ETC2的支持 - Creator - Cocos中文社区

astc

大佬们有没有计划或者方案在2.4.x上使用astc - Creator - Cocos中文社区

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值