挺久以前有个腾讯的面试, 也是基于游戏引擎开发东西的, 问到一个图片是怎样加载渲染的, 我最后说到压缩的图片直接进到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 运行得到的数据.

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/tiancaiwrk/p/13645595.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!