Any3DAny3D
·Any3D Team

貼圖,那個吃掉你顯存的大胃王

3d-compressiontexture-compressionwebglwebgpu

上一篇我們把頂點砍了一半,模型小了點,但沒瘦成一道閃電——因為真正的體積大戶還在那兒:貼圖。一個 PBR 模型,貼圖通常佔 80% 以上體積,而且這部分在顯存裡膨脹得最兇。

這一篇專治貼圖的「顯存大胃王」問題。講三件事:為什麼 PNG/JPG 在 GPU 眼裡有原罪;GPU 自己的貼圖格式長什麼樣、為什麼不能直接用;以及 Basis Universal + KTX2 這套組合是怎麼把三者打通的。

先複習一遍:為什麼 JPG 讓顯存爆炸

上一篇給過一個公式:

顯存佔用 = 寬度 * 高度 * 4位元組(RGBA) * 1.333(含mipmap)

一張 4096×4096 的貼圖,無論它在磁碟上是 1.5MB 的 JPG 還是 8MB 的 PNG,進了顯存都是 約 87MB。原因只有一個:GPU 不認識 JPG/PNG

GPU 的貼圖取樣單元(texture sampler)只懂一件事——給定一個 UV 座標,從一個固定大小的像素塊裡讀出顏色。它要求貼圖在顯存裡是「鋪平的原始像素」。所以瀏覽器在把 JPG 上傳到 GPU 之前,必須先用 CPU 把它完全解壓縮成 RGBA 像素,再整塊塞進顯存。

這個過程有三重麻煩:

  1. 顯存爆炸:解壓縮後的原始像素佔用巨大,87MB 不是誇張,是公式算出來的。
  2. 上傳阻塞:大塊像素從 CPU 記憶體搬到 GPU 顯存是個慢操作,會卡住首幀算繪。
  3. CPU 解壓縮開銷:大圖解壓縮本身就費時,行動端尤其明顯。

延續上一篇「壓縮海綿」的比喻:PNG/JPG 就是捏扁了的海綿,方便運輸;一到 GPU 上,海綿吸水膨脹成原樣。下載是快了,顯存一點沒省。

GPU 自己的貼圖格式:天生就在顯存裡壓著

既然 GPU 不接受壓縮好的 PNG,那能不能讓貼圖在顯存裡也保持壓縮狀態?GPU 取樣時即時解碼單個像素塊,幾乎無開銷。

這就是 GPU 原生貼圖格式(GPU-native texture format)做的事。代表家族:

格式家族全稱主要平台特點
BC1-7Block Compression桌面(PC、Mac)老牌,每代 4×4 像素塊壓縮
ETC1/2Ericsson Texture Compression行動(Android/iOS 舊裝置)行動端舊標準
ASTCAdaptive Scalable Texture Compression行動/VR(新裝置)靈活,品質最好,逐塊可調
PVRTCPowerVR舊 iOS已逐步被 ASTC 取代

這些格式的共同點:貼圖按 4×4 像素的小塊(block)壓縮儲存,GPU 在取樣時按需解碼這一小塊,解出來的不是單個像素而是一塊。好處是顯存佔用直接按固定比例縮水,無論內容是什麼。

對比一下:

PNG/JPG(傳統)GPU 原生格式
磁碟大小小(JPG 尤其小)中(按塊壓縮,固定碼率)
顯存佔用大(解壓縮回原始像素)小(塊狀壓縮,常駐)
上傳到 GPU慢(CPU 解壓縮 + 大塊傳輸)快(直接搬,無需解壓縮)
取樣速度快(已是原始像素)快(硬體即時解碼)

看起來 GPU 格式是完美方案。那為什麼不能直接用它們?

問題來了:不同裝置認不同格式

這正是 GPU 貼圖格式最大的坑——碎片化

  • 桌面 PC 認 BC1-7,不認 ASTC
  • 安卓手機認 ETC2/ASTC,多數不認 BC
  • iOS(A7 之後)認 ASTC,舊機型認 PVRTC
  • WebGPU/WebGL 走的還是裝置背後的同一套硬體能力

一張貼圖,如果你想讓它「在所有裝置上都以 GPU 原生格式存在」,就得為每個平台分別準備一份。一個產品要發桌面 + 安卓 + iOS,同一張貼圖要做 BC + ETC2/ASTC 三套版本。包體翻三倍,工程量翻三倍。

更糟的是,Web 端你根本不知道使用者拿什麼裝置打開頁面。預先生成所有格式不現實,執行時偵測又來不及。

Basis Universal:一次編碼,到處轉碼

Basis Universal(簡稱 Basis)就是為解決這個碎片化而生的。它的思路一句話:

先把貼圖編碼成一種「中間格式」,執行時再根據當前裝置的 GPU 能力,轉碼(transcode)成對應的原生格式。

轉碼流程(示意圖):

原始貼圖(PNG/JPG)
      │  一次性離線編碼(慢,只做一次)
      ▼
Basis 中間格式(ETC1S 或 UASTC)
      │  打包進 KTX2 容器
      ▼
發布到 Web ──┬── 桌面 GPU ──→ 執行時轉碼 → BC1/3/7
            ├── Android ───→ 執行時轉碼 → ETC2
            └── iOS/VR ────→ 執行時轉碼 → ASTC

關鍵點:

  • 離線編碼只做一次,得到一個緊湊的中間表示
  • 執行時轉碼極快(純計算、幾毫秒級),而且轉的是塊格式,不需要逐像素解壓縮
  • 轉碼後送進顯存的就是真正的 GPU 原生格式,顯存佔用按塊壓縮算,和 GPU 原生格式一致

Basis 提供兩種中間編碼模式,下一篇會展開講,這裡先記住名字:

  • ETC1S:壓縮率極高,適合漫反射/albedo 等顏色貼圖
  • UASTC:品質更高,適合法線等對精度敏感的貼圖

KTX2:裝 GPU 貼圖的標準容器

到這裡還有一個工程問題:編碼後的 Basis 資料放哪、怎麼標記、怎麼和 glTF 關聯?答案是 KTX2

KTX2(Khronos Texture 2)不是又一種圖片格式,而是一個容器格式——就像 .zip 不關心裡面裝的是文件還是圖片,KTX2 只負責把 GPU 貼圖資料(包括 Basis 編碼的)按標準結構打包,並附上元資訊(格式、mipmap 層級、色彩空間等)。

在 glTF 裡,KTX2 透過擴展 KHR_texture_basisu 接入:貼圖不再是 PNG 檔案,而是一個 KTX2 檔案,裡面是 Basis 編碼。載入時引擎偵測裝置能力,轉碼成對應的 BC/ETC/ASTC。

三者關係理一下,別混了:

名字角色類比
Basis Universal編碼方案(怎麼把貼圖壓成中間格式)一種「壓縮演算法」
KTX2容器格式(怎麼把編碼後的資料打包)一個「包裝盒」
KHR_texture_basisuglTF 擴展(告訴引擎這是 Basis 貼圖)一個「標籤」

一個 KTX2 檔案,內部可以是 Basis 編碼(跨平台),也可以是某種原生格式(比如直接裝 BC7)。Web 上 99% 的情況是裝 Basis,因為我們要的就是「一次編碼、到處轉碼」。

VRAM 實例:4096 貼圖的對比

把前面的公式和 GPU 格式疊在一起,看一張 4096×4096 RGBA 貼圖在不同方案下的真實佔用:

方案磁碟大小顯存佔用(含 mipmap)上傳速度跨平台
PNG~8MB~87MB慢(需解壓縮)
JPG~1.5MB~87MB慢(需解壓縮)
WebP~2MB~87MB慢(需解壓縮)
KTX2 (ETC1S)~2-3MB~11-14MB✅(轉碼)
KTX2 (UASTC)~6-8MB~22MB✅(轉碼)

顯存數字怎麼來的:GPU 塊壓縮通常按 4bpp(每像素 4 位)或 8bpp 算。4096×4096 在 4bpp 下約 8MB,含 mipmap 再乘 1.333 ≈ 11MB。UASTC 多數轉碼到 8bpp,所以約 22MB。

重點不是某一行的精確數字,而是這兩條:

  1. 傳統格式(PNG/JPG/WebP)顯存佔用幾乎一樣——都是解壓縮後的原始像素,87MB。磁碟再小,顯存不省。
  2. KTX2 顯存直接降到 1/4 到 1/8,且磁碟大小也並不吃虧。

這也是為什麼 VR、行動端 Web 幾乎必上 KTX2——87MB 一張貼圖在 2GB 顯存的手機能放幾張?而 11MB 能放 7 張。

平台支援矩陣:哪些 GPU 認哪些格式

雖然 Basis 幫我們屏蔽了細節,但了解底層映射有助於排障。這是目前主流裝置對原生格式的支援情況:

平台 / 裝置BC1-7ETC2ASTCPVRTC
桌面 PC(D3D11/12、Vulkan、WebGPU)部分(新 GPU)
macOS(Metal)✅(新機型)
Android(主流)
iOS(A8+)✅(舊機型)
WebGL 2取決於擴展部分
WebGPU✅(桌面)✅(視裝置)

Basis 在執行時會探測這些能力,把同一份中間編碼轉成最合適的那一個。這也是為什麼 Basis 這一層在 Web 上幾乎不可替代——你沒法在發布前預知使用者裝置。

上傳流程對比:傳統 vs GPU 格式

最後用一張流程圖把差別固化下來。

傳統 PNG/JPG:

PNG 檔案 ──下載──> CPU 記憶體 ──CPU 解壓縮(慢)──> RGBA 像素塊 ──上傳(大,慢)──> 顯存(87MB)

KTX2 + Basis:

KTX2 檔案 ──下載──> CPU 記憶體 ──執行時轉碼(快)──> GPU 塊格式 ──上傳(小,快)──> 顯存(11MB)

後者少了「CPU 逐像素解壓縮」這一大塊,上傳的資料量也小一個數量級。首幀算繪快、顯存省,是這套方案的核心價值。

下一步

道理講完了,下一篇動手。我們用 toktxgltf-transform 把貼圖實際壓成 KTX2,在 Three.js / Babylon.js 裡載入出來,再聊聊 ETC1S 和 UASTC 怎麼選、壓縮參數怎麼調。

贊助支持