目录
送上下载链接 不修改引擎的实现ASTC格式加载.zip-cocos2D文档类资源-CSDN下载
1. creator游戏开发之纹理压缩
目的:减少运行内存,减少耗电量发热
2. 常用的压缩纹理格式
- 常用压缩格式有 ETC1、ETC2、PVRTC、ASTC
- 分析对比:
兼容 、内存、 效果 各方面分析对比
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模式差别不大,可能图片质量高一些,看不太出来。
问题 :
- 透明渐变区域有波纹(透明通道)
- 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