1、ETC1图片是androID下通用的压缩纹理,几乎所有的androID机器都支持,是opengles2.0的标准。不像pvrtc4只是部分powervr的显卡支持。
ETC1图片不支持半透明(有替代方案可以使etc1图片兼容半透明显示),内存占用只有正常RGBA8888的八分之一(一个像素0.5个字节),并且具备极高的加载速度。ETC1的图片大小只跟图片尺寸相关,在大小上无法媲美jpg或者png8的图片。
2、cocos2d-x早期使用androID提供的ETC1Util来加载纹理,后面经过一次优化,改变成直接读取文件的加载方式。也就是说ETC1文件前面16个字节是文件头,包含文件宽高等信息。除开这16个字节,剩下的就是图片像素数据,这些数据可以直接传递给显卡使用glCompressedTexImage2D来创建纹理。
3、同样在这次优化中,加入了软件解压ETC1的功能,这样windows等桌面平台也可以使用ETC1的图片了(虽然没有任何优势可言)。但是实现有一些BUG,导致不兼容非2的整次幂的图片。修改如下
[cpp]
vIEwplain
copy
//ifitisnotglesordevicedonotsupportETC,decodetexturebysoftware
intbytePerPixel=3;
GLenumfallBackType=GL_UNSIGNED_BYTE;
/*boolfallBackUseShort=false;
if(fallBackUseShort)
{
bytePerPixel=2;
fallBackType=GL_UNSIGNED_SHORT_5_6_5;
}
*/
unsignedintstrIDe=_wIDth*bytePerPixel;
std::vector<unsignedchar>decodeImageData(((strIDe+3)&~3)*((_height+3)&~3));
etc1_decode_image(etcfileData+ETC_PKM_header_SIZE,&decodeImageData[0],_wIDth,_height,bytePerPixel,((strIDe+3)&~3));
//setdecodeddatatogl
glGenTextures(1,&_name);
glBindTexture(GL_TEXTURE_2D,_name);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_liNEAR);
glTexImage2D(GL_TEXTURE_2D,GL_RGB,fallBackType,&decodeImageData[0]);
delete[]etcfileData;
etcfileData=NulL;
returntrue;
注意其中两句
分配内存必须能够容纳下图片数据,而ETC1图片会进行4字节对齐(圆整),所以宽高不能直接使用原始图片数据。当然,不修改的话对于2的整次幂的图片也是没有问题的,因为本身就是对齐的,不需要圆整了。
4、androID下部分机器兼容非2的整次幂的etc1图片,但是同样也有部分机器不兼容。遇到非2的整次幂的图片会渲染错误甚至崩溃。所以androID下使用etc1图片需要进行2的整次幂的扩展。如果大量零碎文件的话,考虑使用TexturePacker打包图片
5、etc1对透明图片的支持。etc1不支持透明图片,同样cocos2d-x对etc1也不支持透明图片的显示。虽然图片格式上面不支持,但是我们可以通过技术手段间接达到透明etc1图片渲染的目的。详细内容可以参考这里。
有两种方案可以选择,一种是通过Mali工具生成pkm文件时选择Createatlas,这样就生成了一张拼接在一起的纹理。这张纹理上半部分是原始图片(无Alpha信息),下半部分是Alpha信息图片。在渲染的时候使用特殊的shader进行渲染。这个改动是比较小的。
另一种方案是创建两张分离的图片,分别是原始图片和Alpha图片。渲染时加载这两张纹理,然后Alpha图片当做参数传递给原始图片的shader。
我使用的是第一种方案。修改后的shader如下(注意,这个shader是新增的,并且是只有这种打包的etc1图片才使用这个shader,未打包的无透明色的etc1图片和png图片依然使用原来的shader)只需要修改像素着色器代码,顶点着色器代码不变。由于现在etc支持透明显示了,所以boolCCTexture2D::initWithETCfile(constchar*file)中m_bHasPremultiplIEdAlpha要置为false,开启Alphablend来渲染图片
#ifdefGL_ES
precisionlowpfloat;
#endif
varyingvec4v_fragmentcolor;
varyingvec2v_texCoord;
uniformsampler2DCC_Texture0;
voIDmain()
gl_Fragcolor=vec4(texture2D(CC_Texture0,vec2(v_texCoord.x,v_texCoord.y)).xyz,texture2D(CC_Texture0,v_texCoord.y+0.5)).r);
6、使用etc1图片可以极大的减少内存,并且加快加载速度。我做过一个简单的测试,80k的png8的图片加载需要消耗117ms,同样的etc1图片(经过扩展有1mb大小)加载消耗40ms。这个已经是极限情况。一般来说同样大小的etc1图片加载速度要快5~10倍。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~下面新的研究成果~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7、关于PremultiplIEdAlpha的理解。cocos2d-x的CCTexture2D中有一个m_bHasPremultiplIEdAlpha的属性。我们使用TexturePacker中导出pvr图片时也有提示开启PVRImagesHavePremultiplIEdAlpha这个选项。虽然PremultiplIEdAlpha就是图片的颜色在输出的时候已经预先乘以Alpha色了,所以渲染的时候图片的RGB就需要再次乘以Alpha色了,这个在一定程度上可以提高运行效率。所以TexturePacker推荐开启PremultiplIEdAlpha选项,XCode导出png图片的时候以及UIImage加载图片的时候都会使用PremultiplIEdAlpha。这个有一点恶心的地方就是,我们无法通过一个图片属性判断它是否是是PremultiplIEdAlpha的,只能通过肉眼或者是一个并不准确的公式来判断。
我们还可以进一步去理解这个设置。一般来说,半透明图片渲染使用的是Alphablend参见CCSprite::updateBlendFunc()这个函数。
voIDCCSprite::updateBlendFunc(voID)
CCAssert(!m_pobBatchNode,"CCSprite:updateBlendFuncdoesn'tworkwhenthespriteisrenderedusingaCCSpriteBatchNode");
//itispossibletohaveanuntexturedsprite
if(!m_pobTexture||!m_pobTexture->hasPremultiplIEdAlpha())
m_sBlendFunc.src=GL_SRC_Alpha;
m_sBlendFunc.dst=GL_ONE_MINUS_SRC_Alpha;
setopacityModifyRGB(false);
else
m_sBlendFunc.src=CC_BLEND_SRC;
m_sBlendFunc.dst=CC_BLEND_DST;
setopacityModifyRGB(true);
正常来说,半透明图片渲染使用的是GL_SRC_AlphaGL_ON_MINUS_SRC_Alpha这个选项代表的意思就是:源(图片)像素*源因子(源Alpha)+目标(屏幕)像素*目标因子(1-源Alpha)。通过这个公式可以达到渲染半透明图片的目的。
如果图片有PremultiplIEdAlpha,再使用这个公式就不对了,图片明显变暗,因为图片的rgb已经乘以Alpha了,再乘一次图片自然就变黑一点。这个时候渲染的公式就变为:
源像素+目标像素*(1-源Alpha)。虽然图片依然是半透明的,但是处理源像素时不再分别乘Alpha了。
8、为什么要特意提这个属性呢?因为ETC1图片在加载的时候默认开启了PremultiplIEdAlpha,一般不透明的图片处理起来没有问题(正常的etc1图片就是不透明的),但是参见上面我们的透明etc1图片渲染解决方案,实际图片在渲染的时候是可以达到半透明的效果的。所以我们有两个选择,一个是默认关闭PremultiplIEdAlpha,另一个是默认开启PremultiplIEdAlpha然后shader中分别把rgb乘以Alpha。具体是Alphablend效率高还是shader中效率高我还没有测试。
9、使用上面的shader在渲染的时候windows下正常,但是androID下会出现大量的锯齿。一开始以为是mipmap没有开启的缘故,但是使用mipmap(后面会介绍)后,依然无法解决问题。后面发现cocos2d-x中shader默认使用的低精度浮点数
#ifdefGL_ES\n\
precisionlowpfloat;\n\
#endif\n\
低精度浮点数有效位数因显卡而异,但是不高是肯定的。如果我们没有特殊的运算,低精度足够使用。但是一旦我们有*0.5之类的运算,那么低精度浮点数很容易丢失数据,那表现出来就是各种锯齿。所以在新的shader代码中删除了这个指令。另外某些文档说,使用低精度无助于效率提升,因为最终渲染的时候还是要转回中精度(中精度是默认选项,部分高级显卡支持高精度)
10、最终修改后的shader如下
顶点shader(我们把部分运算移动到顶点shader中,而不是每个像素进行计算,这个可以提升运行效率)
attributevec4a_position;
attributevec2a_texCoord;
attributevec4a_color;
varyingvec2v_AlphaCoord;
gl_position=CC_MVPMatrix*a_position;
v_fragmentcolor=a_color;
v_texCoord=a_texCoord*vec2(1.0,1.0);
v_AlphaCoord=v_texCoord+vec2(0.0,0.5);
像素shader
vec4v4Colour=texture2D(CC_Texture0,v_texCoord);
v4Colour.a=texture2D(CC_Texture0,v_AlphaCoord).r;
v4Colour.xyz=v4Colour.xyz*v4Colour.a;
gl_Fragcolor=v4Colour*v_fragmentcolor;
//gl_Fragcolor=vec4(texture2D(CC_Texture0,Simsun; Font-size:14px; line-height:30px">关于shader需要说明三点,在顶点shader中有这么一条指令v_texCoord=a_texCoord*vec2(1.0,1.0);因为ETC1需要2的整次幂,所以我们的图片基本上都有扩展,那也就意味着会设置setTextureRect,如果设置了这个,那么a_texCoord就是我们指定的大小,所以这里去的是(1.0,1.0),如果没有setTextureRect,那么a_texCoord就是全部的贴图大小,也就是两倍的正常大小,那么这个时候取的就应该是(1.0,0.5)。最终我的解决方法是所有的使用这个shader的图片都设置一下大小。这样shader就统一了。
在像素着色器代码中有v4Colour.xyz=v4Colour.xyz*v4Colour.a;这个就跟上面说的PremultiplIEdAlpha有关系。我们在shader中预先乘以Alpha。
这个shader的使用条件,只有带透明的etc1图片(通过工具导出时进行了自动拼接)才能使用这个shader进行渲染,否则都会出错。这个我们要在代码中进行判断。
11、关于图片拼接时的黑边问题。
这个可以单独开一个话题,但是由于是我处理ETC1图片时遇到的,所以统一都在这里解析了。网上经常看到有人说图片拼接的时候有黑边,比如tilemap地图拼接的时候。这个分三种情况,一种是最简单的图片对齐计算有问题,拼接的时候由于浮点数计算多了一个像素或者是少了一个像素,这个计算的时候有意向做移动一个像素就可以解决。
第二种是图片导出的问题(使用TexturePacker),不仅仅是地图拼接黑边,可能其他资源也会有黑的虚线,这个在导出的时候选择--border2--shape2(TexturePacker中有对应的设置,默认为0,但是我之前手欠给修改成了0)。另外还有一个Exclude选项也是用来解决这个问题的。
第三种是最本质的问题,比如我碰到的使用png图片渲染正常,但是使用etc1图片渲染就出现黑边,若隐若现,一拖动界面就出现。这个可以在纹理创建的时候设置这个来解决(png等图片创建的时候有设置,但是etc1没有)
glTexParameteri(target,Simsun; Font-size:14px; line-height:30px">一般来说,纹理通过
if(isMipmapped){
/*EnablebilinearmipmapPing*/
}else{
这个来进行抗锯齿等 *** 作,但是如果在图片边缘的时候计算就会有问题,因为外部没有像素了,而图片本身像素为半透明,那么计算的时候很有可能计算出黑色,那么就显示出黑边了。
12、最后要说下mipmap,mipmap就是图片如果有缩小,那么渲染的时候使用小的图片(比如256*256的图片如果缩小一半来渲染,就取128*128的图片),这个小的图片可以直接使用函数生成
voIDCCTexture2D::generateMipmap()
CCAssert(m_uPixelsWIDe==ccNextpot(m_uPixelsWIDe)&&m_uPixelsHigh==ccNextpot(m_uPixelsHigh),"MipmaptextureonlyworksinPottextures");
ccGLBindTexture2D(m_uname);
glGenerateMipmap(GL_TEXTURE_2D);
m_bHasMipmaps=true;
也可以在生成图片的时候直接创建mipmap的图片。etc1貌似不支持内存中直接生成。开启mipmap进行渲染会多30%左右的内存开销,但是如果图片缩小渲染的话,会提高运行效率,并且会提高画质(直接缩小可能某些像素通过11中提到的纹理过滤计算起来会有偏差,但是使用预先缩小的图片就可以达到自己满意的效果)。而etc1的话在创建图片的时候开启mipmap会多创建n张缩小纹理,对应文件体积就增大了,最大会增加30%~50%。这个我们看情况使用,部分核心的重要的图片开启mipmap。加载图片成为mipmap比较简单glTexImage2D(GL_TEXTURE_2D,s_compressFormat_RGBA,(GLsizei)pixelsWIDe,(GLsizei)pixelsHigh,GL_RGBA,GL_UNSIGNED_BYTE,data);这个是提交纹理数据的函数,其中第二是mipmap等级,穿n就对应n级的mipmap,也就是说,如果pkm的etc1图片要支持mipmap,就需要自己写代码,另加载1~n张纹理,然后使用glTexImage2D这个函数把这n张纹理提交给显卡。
13、cocos2d-x中对etc1的支持比较初级。既没有透明色的支持,也不支持mipmap。
etc1图片格式有两种,一种是pkm,这种是简单的etc1格式,现在cocos2d-x支持的就是这种格式。另外一种是ktx格式,这个是opengles组织提供的官方格式。可以把多个mipmap打包到一个ktx文件里面。现在我的代码里面使用的就是ktx格式。使用ktx图片需要到
这里下载ktx的loader库,把这个库加入到cocos2d-x中,核心加载代码如下
boolCCTextureETC::initWithKtxData(etc1_byte*pData,intlen)
gluinttexture=0;
GLenumtarget;
GLbooleanisMipmapped;
GLenumglerror;
glubyte*pKvData;
unsignedintkvDataLen;
KTX_dimensionsdimensions;
KTX_error_codektxerror;
KTX_hash_tablekvtable;
Glintsign_s=1,sign_t=1;
ktxerror=ktxLoadTextureM(pData,len,&_name,&target,&dimensions,&isMipmapped,&glerror,&kvDataLen,&pKvData);
if(KTX_SUCCESS==ktxerror){
_wIDth=dimensions.wIDth;
_height=dimensions.height;
ktxerror=ktxHashtable_Deserialize(kvDataLen,pKvData,&kvtable);
glubyte*pValue;
unsignedintvalueLen;
if(KTX_SUCCESS==ktxHashtable_FindValue(kvtable,KTX_ORIENTATION_KEY,
&valueLen,(voID**)&pValue))
chars,t;
if(_snscanf((constchar*)pValue,valueLen,KTX_ORIENTATION2_FMT,&s,&t)==2){
if(s=='l')sign_s=-1;
if(t=='d')sign_t=-1;
ktxHashtable_Destroy(kvtable);
free(pKvData);
//加载成功
glBindTexture(target,0);//这句很重要,否则会有一些诡异的渲染问题
returnfalse;
} 总结
以上是内存溢出为你收集整理的关于cocos2d-x对etc1图片支持的分析全部内容,希望文章能够帮你解决关于cocos2d-x对etc1图片支持的分析所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)