挺久以前有个腾讯的面试, 也是基于游戏引擎开发东西的, 问到一个图片是怎样加载渲染的, 我最后说到压缩的图片直接进到GPU, 通过GPU解压获取最终像素, 然后被连着问了两次你确定?
然后我就有点迷糊了, 不是吗? 然后他也没说他为啥这样问, 或者想要的是什么答案, 就略了
现在回想起来, 可能是对于过程的理解或者平台的不同, 也会造成鸡同鸭讲的结果的吧...这里再把图片从创建资源到加载渲染的流程捋一遍.
最原始的位图文件BMP, 常见的压缩格式 JPG / PNG , 不常用格式 ETC / PVR 这些 :
它们都是同一张图片, 只是存的类型不一样, 位图文件就是RGB直接存的, 大小直接就能算出来 :
627 * 310 * 3Byte = 583,110 B => 571KB
可是相比压缩格式它占的空间实在太大了, 跟 JPG 差了几十倍, 不过 JPG 是有损压缩方式压缩的, 当解压回来后图像会损失信息(网络一些图片盗来盗去, 越来越绿就是这个原因), PNG 是无损压缩的, 解压回来还是那个样子, 所以根据不同情况基本这两种压缩就涵盖了有损和无损区别了.
而 ETC / PVR 这些也是有损压缩, 而它们的计算逻辑跟 JPG 也是不同的, ETC / PVR 对应于 GPU 硬件解压逻辑, 我们都知道GPU是并行单元的, 就像超市存包的锁柜, 每个柜子直接用钥匙打开就能取东西了, 可以同时 存/取 N个包, ECT / PVR 的解压是基于一个全局查找表逻辑的, 取出来的结果对照查找表然后进行调制就能得到像素值了:
BMP -> ETC / PVR 在内存上相当于缩小的BMP, 差不多除以6
定长压缩, 直接就能找到对应像素点的 Block, 因为 GPU 很猛, 解压计算很快, 就跟直接读取位图一样没有差别. 所以一般 ETC / PVR 图片还是保持压缩格式躺在 GPU 的内存里. 下面是非常直观的PVRTC还原像素过程 :
而 JPG / PNG 这些非定长压缩的, 你并不能直接从像素推断获取它对应像素点的数据在内存中的位置, GPU 就没有办法并行的去找内存然后解压对应像素信息了, 比如 JPG 自带了数据压缩 :
所以一个 JPG 的解压步骤很多, 把这些用 GPU 实现的话相当困难, 主要是算法复杂 :
上面说的是格式的压缩, 还有另外一个文件的压缩.
GPU 部分的差别大概就是这些了, 然后打包游戏资源的时候, 一般还会把这些压缩格式图片进行一次打包, 而这个打包也会进行数据压缩, 看看打包压缩对它们的影响 :
可以看到压缩对它们还是有很大影响的, JPG / PNG 自带了去冗余过程, 可压缩量不大, 甚至 PNG 反而文件变更大了, 而原始 BMP 文件可压缩量很可观, 当然 ETC / PVR 这些跟BMP 有点类似, 也是能压缩的 :
这是PC上的 DXT 压缩格式打的 AssetBundle 包, 它的 DXT 内存大小是显示在检视面板上的 :
而如果不压缩, 它在内存上的大小是这样的 :
这样看下来, 其实解压就有两种意思了, 一个是文件解压, 像上面的 zip 解压出图片文件, 然后是像素解压, 是把压缩格式的图片还原为像素的过程, 比如 NPG 文件还原为 BMP 格式然后传入 GPU, GPU 就躺着读取就行了, 还有就是 GPU 支持的压缩格式 DXT / ETC / PVR 等, 只需要把压缩格式图片传给GPU, 让 GPU 大大的干活就行了.
然后文件解压阶段, 必然是由 CPU 进行的, 比如加载 AssetBundle, 它就会在读取完文件之后进行解压操作(一般情况), 如果基于块压缩的(ChunkBasedCompression)它可能在读取相关资源的时候再对指定资源解压( AssetBundle.LoadAsset() ).
最后是 GPU 解压阶段 :
1. 如果 GPU 不支持你的压缩格式, 引擎会用 CPU 把图片解压成位图文件, 然后再传给 GPU, 这样又浪费 CPU 资源, 又浪费总线带宽
2. 如果支持的话, 就直接把压缩格式的图片传输给 GPU 了, 可以说是由 GPU 进行解压光栅化.
3. 复杂的压缩格式 JPG / PNG 基本不可能被 GPU 支持, 几乎都由 CPU 解压成位图传递给 GPU的
所以游戏引擎才会直接把扔进去的图片都进行对应平台的转换, 因为追求的是效率和压榨硬件性能, 而 Web 网页这种跨平台又是网络传输的, 一般都使用 JPG / PNG 这些自带压缩的格式, 虽然解压过程比较复杂. 下面是总结的流程图 :
左边就是 PNG 之类的, 右边是 Unity 下的 PNG 转换为 PVR, 左边直接在 CPU 阶段解压成 24/32 bit 的位图了, 传输到 GPU, 右边的一直以压缩格式传输.
这样回到最初的那个问答, 本地资源或是 AssetBundle 中读取出来的图片, 正常来说还是那样, 图片保持压缩格式进入 GPU 解压, 或者叫 Decode 得到像素值, 不过如果从 WWW 之类的获取到的 PNG / JPG 图片, 那就会在 CPU 中进行解压, 然后位图传入 GPU.
再加一个 WWW 下载来的图片跟本地图片加载后的差别吧, 上面的已经看到了本地图片内存占用 64KB(DXT), 而本地位图的话, 占570KB, 同样的图片放到服务器然后看看 :
public class Test : MonoBehaviour { public UnityEngine.UI.RawImage img; void Start() { StartCoroutine(Load(@"http://127.0.0.1:44599/StreamingAssets/PNG001.PNG")); } IEnumerator Load(string url) { var www = new WWW(url); yield return www; img.texture = www.texture; img.texture.name = "PNG001.PNG"; } }
变成2M了, 这是发布后的 exe 运行得到的数据.
文章来源: 博客园
- 还没有人评论,欢迎说说您的想法!