一、引子

  手游项目开发日常里,经常有美术同学搞不清Photoshop制图软件与Unity3D游戏引擎之间的图片assets流转逻辑,在工作输出时经常出现如下疑问:

1、要JPG的,还是要PNG的?

2、JPG的要压存为多高质量的?

3、PNG的还要压?引擎不是自动处理的么?

4、为毛非要正方形的?我这个图实在是没法儿做方的怎么弄?

5、图太大,要选哪个压缩方式?有的怎么选了也没效果?有的又压的太糊!

6、这个效果不行,开发没有还原好啊!

  所以开发或者技美同学要经常解释这些问题,这里面的确有些内容比较难以说明白,一个是厂商比较多流派,另一个是有些知识点(技术历史)需要厘清,本文的作用就在于此,但不会很全面。

  下文中出现Texture的地方均指代在Unity3D场景下。

 

二、不要混淆JPG/PNG等图片压缩格式与Texture Compression

JPG/PNG是变长编码格式variable bit length,它有个特点:如下图所示,颜色变化少频率低的部分,编码后占的内存字节数就少。



安卓设备怎么测试gpu_图片压缩


  所以,编码后长度变来变去带来的坏处就是:无法准确的计算出原图一个坐标处的color对应的压缩到了哪里?除非你把整张图片都解压完毕,如下图所示。



安卓设备怎么测试gpu_寻址_02


  这一致命弱点直接导致了GPU 里的Texture Sample算法机制不可用,所以Unity3D引擎里也不会直接使用JPG/PNG这种编码格式来打包图片assets资源。

  结论:JPG/PNG是用来在游戏制作流程中间传递美术内容的,最终在游戏引擎里需要转变成一种固定码率的、可寻址的流式压缩格式,以方便随机寻址和采样。

 

三、PVRTC/ETC等Texture Compression格式,直接被GPU读取到显存,用时无需解压

  是的,无需解压!美术同学可能难以理解,JPG压缩了之后不解压你怎么看?对,GPU就是这么屌!下面举个例子:

  假设一张4M的RGB三通道JPG图片,经过ETC1压缩后变为1M,GPU 里的Fragment渲染模块根据当前的Render State决定去加载Texture Buffer里对应的某一块字节数据,然后经过实时的运算来还原出相应的颜色值,即采样过程,这中间便省去了将整个图片解压还原出来的步骤,因为GPU芯片很擅长这种固定的算法。

  好处显而易见:1. 支持随机采样,不用把整张图load进内存; 2. 即使整个load进内存,也不用解压展开成4M;3.如果你嫌1M还是太大,还能在打包游戏时用ZIP再压缩一把;

 

四、PVRTC 2bpp/4bpp, ETC1, ETC2,  DXT, ASTC 这些都是什么鬼?

  是的,手机市场就是这么乱。GPU芯片提供商有Imagination, ARM, NVIDIA, QualComm,各有各的芯片系统(SoC) IP,我们搞软件开发的要细究起来恐怕会吐血身亡,毕竟是不同的行业。不管那么多,结论有:

1、Imagination是被Intel和Apple两大头持股的,iOS平台得优选他家的PVRTC压缩格式;

2、ETC1基本上是Android采用的公案,所以选它没错,但仅支持RGB三通道,如果还有A通道,得另行单独压一张图;

3、ETC2虽然升级了,但目前的主流OpenGL ES 2.0规格尚不支持,不要多想了;

 

五、浅析ETC1 --- 一个像素2个bits是怎么做到的?

  ETC stands for Ericsson Texture Compression and is an open standard supported in OpenGL and OpenGL ES. The technique allows lossy compression of images at a ratio of 4:1 (depending on input format and compression method).

  ETC1 textures are supported in Android and benefit from GPU hardware decompression.


    没错,它居然是Ericsson公司发明的压缩专利,好在是完全公开的,并被OpenGL ES规范所支持。它基于这样一个事实:人类的视觉系统对明度luminance的敏感度高于色度chrominance。运用这一逆天的道理就能得到逆天的压缩方式,如下图:


安卓设备怎么测试gpu_数据_03


1、先对图片进行分块4*4;

2、每块取出2个base colors(上下各一个,或者左右各一个),形成左图;

3、每块取出明度数据(逐像素的,那就是4*4=16个);

4、算法合成得到右边的压缩后的效果;

  所以,ETC1要做的就是把上面2和3中产生的base colors数据和luminance数据给整起来,一起压缩咯!这真是蛋疼,用2个基色块就能代表16个像素的颜色,你当玩家眼瞎啊!?

  原理不多说了,直接上图:



安卓设备怎么测试gpu_图片压缩_04


1、4*4的像素块分为左右或者上下两个部分,提取2个base colors,采用差分存储,一个存为R5G5B5,差值存为dR3dG3dB3;

2、如果2个base colors相差太大,导致差值溢出,则直接存2个R4G4B4,比如左边红右边绿的这种极端情况;

3、两个标记位,绿色的那位表示是555差分存储呢,还是444独立存储;

4、重点是这个table bits,代表这里两个base color对应的是明度表里的index,正好3个bits,表里也一共2^3=8项;

5、位置刚好够用:16个RGB像素,编码进4个bytes,平均下来每个像素只需2个bits;

  Texture被GPU加载到显存缓冲区之后,怎么寻址,怎么采样,具体细节就不深究了,那是硬件工程师的事情。总之,在这种框架下,ETC的缺点就是:

a、不支持RGBA 4通道的图片压缩;

b、对颜色连续过渡变化的图片压缩后可能有点儿糊(所以美术同学自带的像素眼会说你把他的图搞糊了);

  好了,上面就是对项目中常见的材质压缩问题的终极总结,有了这些知识点,回答前头的日常问题应该心中有数了(结合项目具体情况取舍)。

在unity中。带有透明通道的图片压缩后。均会出现一定的质量的下降。并且带有透明通道的图片占用内存较大。之前一直没有想到解决方案。最近看了一个游戏项目。里面有一个很好的解决方案。我研究了一下。就分享出来了。


  它的具体就是将透明通道和图片内容剥离开来。在用Shader合并。这样就能减少一半的大小。


效果及方法

  首先。我们将图片放入tp中。类型悬着tga。然后导出。如下:


安卓设备怎么测试gpu_unity_05

 




  导出后。我们得到一个tga图片和一个txt配置。我们将tga用ps打开。然后找到图片的通道处:如下:


安卓设备怎么测试gpu_图片压缩_06

 



  如上。我们选中Alpha 1.右键。删除该透明通道。然后将图片存储为bmp图片。


  然后。我们可以在

菜单中后退一步 。或者 重新打开没有删除透明通道的图片 。执行如下操作。


  1.选中Alpha1. 按 ctrl + c 复制改透明通道。


  2.选中 红 通道。ctrl + v 粘贴通道。绿 蓝 通道执行同样的操作。


  3.删除Alpha 1 透明通道。将图片保存为bmp。



  最后得到如下文件


安卓设备怎么测试gpu_数据_07

 



  我们将图片移到Unity中。做成图集。然后给图集的材质球赋值我们的shader(

Shader代码在最下 )。


安卓设备怎么测试gpu_安卓设备怎么测试gpu_08

 



  然后我们来对比下。普通的和剥离的效果区别:


安卓设备怎么测试gpu_unity_09

 



  效果没什么变化。然后。我们在看下另一个数据:


安卓设备怎么测试gpu_安卓设备怎么测试gpu_10

 




安卓设备怎么测试gpu_数据_11

 




  一张只有没剥离的四分之一。然后在加一张透明通道。也只是1M。


  最后。我们看下内存监察的数据图:


安卓设备怎么测试gpu_图片压缩_12

 



  1+1 ?= 8   数学不是很好。你们算算。


1. Shader "Test/AlphaTex"  
2. {  
3.     Properties  
4.     {  
5. "Base (RGB)", 2D) = "white" { }  
6. "AlphaTex",2D) = "white"{}  
7.     }  
8.     SubShader  
9.     {  
10.         Tags  
11.         {  
12. "Queue" = "Transparent+1"  
13.         }  
14.         Pass  
15.         {  
16.             Lighting Off  
17.             ZTest Off  
18.             Blend SrcAlpha OneMinusSrcAlpha  
19.             Cull Off  
20.             CGPROGRAM  
21.             #pragma vertex vert  
22.             #pragma fragment frag  
23. 
24.             #include "UnityCG.cginc"  
25.   
26.             sampler2D _MainTex;  
27.             sampler2D _AlphaTex;  
28.   
29. float _AlphaFactor;  
30.   
31. struct v2f  
32.             {  
33.                     float4  pos : SV_POSITION;  
34.                     float2  uv : TEXCOORD0;  
35.                     float4 color :COLOR;  
36.             };  
37.   
38.             half4 _MainTex_ST;  
39.             half4 _AlphaTex_ST;  
40.   
41.             v2f vert (appdata_full v)  
42.             {  
43.                     v2f o;  
44.                     o.pos = mul (UNITY_MATRIX_MVP, v.vertex);  
45.                     o.uv =  v.texcoord;  
46.                     o.color = v.color;  
47. return o;  
48.             }  
49.   
50.             half4 frag (v2f i) : COLOR  
51.             {  
52.                 half4 texcol = tex2D (_MainTex, i.uv);  
53.                 half4 result = texcol;  
54.      
55. //     result.a = tex2D(_AlphaTex,i.uv).r;  
56.   
57.                 result.a = tex2D(_AlphaTex,i.uv)*i.color.a ;  
58.   
59. return result;  
60.             }  
61.             ENDCG  
62.         }  
63.     }  
64. }