日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

unity加载sprite_Unity 分离贴图 alpha 通道实践

發布時間:2025/3/20 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 unity加载sprite_Unity 分离贴图 alpha 通道实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

在做手機游戲時可能會遇到這些問題:

  • UI 同學天天抱怨 iOS 上一些透明貼圖壓縮后模糊不堪

  • 一些古早的 Android 手機上同樣的貼圖吃內存超過其他手機數倍,游戲經常閃退

這篇文章給出了一種手機游戲項目中通用的解決方案:分離貼圖 alpha 通道,及其基于 Unity 引擎的實現過程和細節。其中思路主要來自于 https://zhuanlan.zhihu.com/p/32674470,本文是對該方法的實踐和補充。

為什么要分離

1. 為什么會出現這些問題?

要弄明白這些問題的由來,首先要簡單解釋一下貼圖壓縮格式的基礎概念。

為了讓貼圖在手機中運行時占用盡可能少的內存,需要設置貼圖的壓縮格式,目前 Unity 支持的主要壓縮格式有:android 上的 ETC/ETC2,iOS 上的 PVRTC,以及未來可能會使用的 ASTC。這幾個壓縮格式有自己的特點:

  • ETC:不支持透明通道,被所有 android 設備支持

  • ETC2:支持透明通道,Android 設備的 GPU 必須支持 OpenGL es 3.0 才可以使用,對于不支持的設備,會以未壓縮的形式存在內存中,占用更多內存

  • PVRTC:所有蘋果設備都可以使用,要求壓縮紋理長寬相等,且是 2 的冪次(POT,Power of 2)

  • ASTC:高質量低內存占用,未來可能普遍使用的壓縮格式,現在有一部分機型不支持

一般來說,目前 Unity 的手機游戲 android 上非透明貼圖會使用 RGB Compressed ETC 4bits,透明貼圖可以使用 RGBA Compressed ETC2 8bit,iOS 非透明貼圖使用 RGB Compressed PVRTC 4bits,透明貼圖使用 RGBA Compressed PVRTC 4bits。

這里的 bits 概念的意思為:每個像素占用的比特數,舉個例子,RGB Compressed PVRTC 4bits 格式的 1024x1024 的貼圖,其在內存中占用的大小 = 1024x1024x4 (比特) = 4M (比特) = 0.5M (字節)。

我們可以看到,在 iOS 上,非透明貼圖和透明貼圖都是 4bpp(4bits per pixel)的,多了透明通道還是一樣的大小,自然 4bpp 的透明貼圖壓縮出來效果就會變差,而實機上看確實也是慘不忍睹。這是第一個問題的答案。

一些古早的 android 機,由于不支持 OpenGL es 3.0,因此 RGBA Compressed ETC2 8bit 的貼圖一般會以 RGBA 32bits 的格式存在于內存中,這樣內存占用就會達到原來的 4 倍,在老機器低內存的情況下系統殺掉也不足為奇了。這是第二個問題的答案。當然,需要說明的是,現在不支持 OpenGL es 3.0 的機器的市場占有率已經相當低了(低于 1%),大多數情況下可以考慮無視。

更多的貼圖壓縮格式相關內容可以參考這里:https://zhuanlan.zhihu.com/p/113366420

2. 如何解決問題

要解決上面圖片模糊的問題,可以有這些做法:

  • 透明貼圖不壓縮,內存占用 32bpp

  • 分離 alpha 通道,內存占用 4bpp+4bpp(或 4bpp+8bpp)

不壓縮顯然是不可能的,畢竟 32bpp 的內存消耗對于手機來說過大了,尤其對于小內存的 iOS 設備更是如此。所以我們考慮分離 alpha 通道,將非透明部分和透明部分拆成兩張圖(如下所示)。

至于其內存占用,一般來說會把非透明部分拆成 RGB Compressed PVRTC 4bits,而透明通道部分可以使 RGB Compressed PVRTC 4bits,也可以是 Alpha8 格式(8bpp)。Alpha8 格式似乎不同版本 Unity 對于 Mali 芯片的手機支持度不同,我沒有做深入研究。測試中,我使用了 RGB Compressed PVRTC 4bits 格式來壓縮透明通道貼圖,效果已經完全可以接受了。

如何分離

1. 方案 1

我們很自然而然的會想到,繼承?SpriteRenderer/Image?組件去實現運行時替換材質來達到目的。這種方案有一些缺點,對于已經開發到后期的項目來說,要修改所有的組件成本非常高,更不用說在加入版本控制的項目中,修改 prefab 的合并成本也非常高了;另外對于已經使用自定義材質的組件來說也很不方便。

2. 方案 2

直接修改?Sprite?的?RenderData,讓其關聯的?texture,alphaTexture?等信息直接在打包時被正確打入包內。

這樣做的好處就是不需要去修改組件了,只要整個打包流程定制化好以后就能夠一勞永逸了。而對于大多數商業項目來說,定制打包流程基本是必須的,所以這個也就不算是什么問題了。

實現細節

首先說明一下,本方案在 2017.4 測試通過,其中打圖集是采用已經廢棄的 Sprite Packer 的方式,至于 Sprite Atlas 的方式,我沒有研究過,但我覺得應該都可以實現,只是可能要改變不少流程。

下面說明一下具體實現,在打包之前大致流程如下:

大致解釋一下上面的流程:

  • UpdateAtlases:強制刷新圖集緩存(需要分離 alpha 通道的圖集要修改其壓縮格式為去掉 A 通道的)

  • FindAllEntries:找到所有的 sprite,檢查其 PackingTag,分類整理所有 sprite 和圖集的信息

  • GenerateAlphaTextures/SaveTextureAssets:根據圖集的信息繪制 alpha 通道的紋理并保存文件

  • AssetDatabase.Refresh():實踐中如果不重新刷新的話,可能導致某個貼圖無法找到

  • ReloadTextures:從文件加載紋理,作為寫入 RenderData 的數據

  • WriteSpritesRenderData:最重要的一步,將?texture,alphaTexture?等信息寫入?Sprite?的?RenderData

  • 最后,在打包前,禁用 SpritePacker,避免其在打包時重寫打了圖集并覆寫了?Sprite?的?RenderData

其中,關于生成 Alpha 通道貼圖,需要注意的是使用圖集中的散圖位置等信息,將壓縮前的頂點信息直接渲染到貼圖上,這樣透明通道貼圖就不會受到壓縮的影響。

// 臨時渲染貼圖

var rt = RenderTexture.GetTemporary(texWidth, texHeight,0, RenderTextureFormat.ARGB32);

Graphics.SetRenderTarget(rt);

GL.Clear(true, true, Color.clear);
GL.PushMatrix();
GL.LoadOrtho();foreach (var spriteEntry in atlasEntry.SpriteEntries){var sprite = spriteEntry.Sprite;var uvs = spriteEntry.Uvs;var atlasUvs = spriteEntry.AtlasUvs;// 將壓縮前 sprite 的頂點信息渲染到臨時貼圖上
mat.mainTexture = spriteEntry.Texture;
mat.SetPass(0);
GL.Begin(GL.TRIANGLES);var triangles = sprite.triangles;foreach (var index in triangles){
GL.TexCoord(uvs[index]);
GL.Vertex(atlasUvs[index]);}

GL.End();

}

GL.PopMatrix();

// 最終的 alpha 貼圖

var finalTex = new Texture2D(texWidth, texHeight, TextureFormat.RGBA32, false);

finalTex.ReadPixels(new Rect(0, 0, texWidth, texHeight), 0, 0);

// 修改顏色

var colors = finalTex.GetPixels32();

var count = colors.Length;

var newColors = new Color32[count];

for (var i = 0; i < count; ++i){

var a = colors[i].a;

newColors[i] = new Color32(a, a, a, 255);

}


finalTex.SetPixels32(newColors);

finalTex.Apply();

RenderTexture.ReleaseTemporary(rt);

在將透明通道貼圖寫文件有一點需要注意的是:由于可能打的圖集會產生多個?Page,這些?Page?的貼圖名都是相同的,如果直接保存可能造成錯誤覆蓋,所以需要使用一個值來區分不同?Page,這里我們使用了 Texture 的 hash code。

// 支持多 page 圖集

var hashCode = atlasEntry.Texture.GetHashCode();

// 導出 alpha 紋理

if (atlasEntry.NeedSeparateAlpha){

var fileName = atlasEntry.Name + "_" + hashCode + "_alpha.png";var filePath = Path.Combine(path, fileName);File.WriteAllBytes(filePath, atlasEntry.AlphaTexture.EncodeToPNG());

atlasEntry.AlphaTextureAssetPath = Path.Combine(assetPath, fileName);

}

接下來再說明一下最重要的寫?SpriteRenderData?部分。

var spr = spriteEntry.Sprite;

var so = new SerializedObject(spr);

// 獲取散圖屬性

var rect = so.FindProperty("m_Rect").rectValue;

var pivot = so.FindProperty("m_Pivot").vector2Value;

var pixelsToUnits = so.FindProperty("m_PixelsToUnits").floatValue;

var tightRect = so.FindProperty("m_RD.textureRect").rectValue;

var originSettingsRaw = so.FindProperty("m_RD.settingsRaw").intValue;

// 散圖(tight)在散圖(full rect)中的位置和寬高

var tightOffset = new Vector2(tightRect.x, tightRect.y);

var tightWidth = tightRect.width;

var tightHeight = tightRect.height;

// 計算散圖(full rect)在圖集中的 rect 和 offset

var fullRectInAtlas = GetTextureFullRectInAtlas(atlasTexture,

spriteEntry.Uvs, spriteEntry.AtlasUvs);

var fullRectOffsetInAtlas = new Vector2(fullRectInAtlas.x, fullRectInAtlas.y);

// 計算散圖(tight)在圖集中的 rect

var tightRectInAtlas = new Rect(fullRectInAtlas.x + tightOffset.x, fullRectInAtlas.y + tightOffset.y, tightWidth, tightHeight);

// 計算 uvTransform

// x: Pixels To Unit X

// y: 中心點在圖集中的位置 X

// z: Pixels To Unit Y

// w: 中心點在圖集中的位置 Y

var uvTransform = new Vector4(

pixelsToUnits,
rect.width * pivot.x + fullRectOffsetInAtlas.x,
pixelsToUnits,

rect.height * pivot.y + fullRectOffsetInAtlas.y);

// 計算 settings

// 0 位:packed。1 表示 packed,0 表示不 packed

// 1 位:SpritePackingMode。0 表示 tight,1 表示 rectangle

// 2-5 位:SpritePackingRotation。0 表示不旋轉,1 表示水平翻轉,2 表示豎直翻轉,3 表示 180 度旋轉,4 表示 90 度旋轉

// 6 位:SpriteMeshType。0 表示 full rect,1 表示 tight

// 67 = SpriteMeshType(tight) + SpritePackingMode(rectangle) + packed

var settingsRaw = 67;

// 寫入 RenderData
so.FindProperty("m_RD.texture").objectReferenceValue = atlasTexture;
so.FindProperty("m_RD.alphaTexture").objectReferenceValue = alphaTexture;
so.FindProperty("m_RD.textureRect").rectValue = tightRectInAtlas;
so.FindProperty("m_RD.textureRectOffset").vector2Value = tightOffset;
so.FindProperty("m_RD.atlasRectOffset").vector2Value = fullRectOffsetInAtlas;
so.FindProperty("m_RD.settingsRaw").intValue = settingsRaw;
so.FindProperty("m_RD.uvTransform").vector4Value = uvTransform;

so.ApplyModifiedProperties();

// 備份原數據,用于恢復

spriteEntry.OriginTextureRect = tightRect;
spriteEntry.OriginSettingsRaw = originSettingsRaw;

需要修改的部分的含義,這里面的注釋已經寫的很清楚了,簡單看一下能夠大致理解。其中還有幾個概念需要說明一下:

在?Sprite?的導入設置中,會被要求設置?MeshType,默認的是?Tight,其效果會基于?alpha?盡可能多的裁剪像素,而?Full Rect?則表示會使用和圖片紋理大小一樣的矩形。

這兩個選項在達成圖集時,如果你的散圖周圍的?alpha?部分比較多,使用?full rect?時就會看到圖片分的很開,而使用?tight,表現出來的樣子就會很緊湊,效果為下面幾張圖:

上面這個散圖原圖,可以看到周圍透明部分較多

上面這個是使用?Tight?的?mesh type?打成的圖集,可以看到中間的間隔較少

上面這個是使用?full rect?的?mesh type?打成的圖集,可以看到中間的間隔較大。

一般我們會使用?Tight,那么我在上面代碼中就需要對?tight?相關的一些數值做計算,具體如何計算直接看代碼嗎,應該不難理解。

其中還有一個獲取計算散圖(full rect)在圖集中的?rect?的方法?GetTextureFullRectInAtlas,代碼如下:

private static Rect GetTextureFullRectInAtlas(Texture2D atlasTexture, Vector2[] uvs, Vector2[] atlasUvs){var textureRect = new Rect();// 找到某一個 x/y 都不相等的點var index = 0;var count = uvs.Length;for (var i = 1; i < count; i++){if (Math.Abs(uvs[i].x - uvs[0].x) > 1E-06 && Math.Abs(uvs[i].y - uvs[0].y) > 1E-06){
index = i;break;}}// 計算散圖在大圖中的 texture rectvar atlasWidth = atlasTexture.width;var atlasHeight = atlasTexture.height;
textureRect.width = (atlasUvs[0].x - atlasUvs[index].x) / (uvs[0].x - uvs[index].x) * atlasWidth;
textureRect.height = (atlasUvs[0].y - atlasUvs[index].y) / (uvs[0].y - uvs[index].y) * atlasHeight;
textureRect.x = atlasUvs[0].x * atlasWidth - textureRect.width * uvs[0].x;
textureRect.y = atlasUvs[0].y * atlasHeight - textureRect.height * uvs[0].y;

return textureRect;

}

最后,需要在自定義打圖集規則,并在判斷需要分離?alpha?通道的貼圖,修改其對應壓縮格式,如 RGBA ETC2 改 RGB ETC,RGBA PVRTC 改 RGB PVRTC。這樣做是為了打圖集生成一份不透明貼圖的原圖。大致代碼如下:

// 需要分離 alpha 通道的情況

if (TextureUtility.IsTransparent(settings.format)) {

settings.format = TextureUtility.TransparentToNoTransparentFormat(settings.format); }

至于如何自定義打圖集的規則,可以參考官方文檔:https://docs.unity3d.com/Manual/SpritePacker.html

一些補充

1. 在手機上?UI.Image?顯示的貼圖為丟失材質的樣子

?原因在于?Image?組件使用這套方案時,使用了一個內置的?shader:DefaultETC1,需要在?Editor -> Project Settings -> Graphics?中將其加入到?Always Included Shaders?中去。

2. 分離?alpha?通道的貼圖的?sprite?資源打入包內的形式

通過 AssetStudio 工具看到,下圖是沒有分離?alpha?通道的散圖的情況,可以看到每一個?Sprite?引用了一張?Texture2D

下圖是分離了?Alpha?通道的圖集的情況,可以看到,這個 AssetBundle 包中只有數個?Sprite,以及 2 張?Texture2D(非透明貼圖和透明通道貼圖)。

3. 如何知道需要修改?Sprite?的哪些?Render Data

在實踐嘗試的過程中,通過 UABE 工具來比較不分離?alpha?通道和分離?alpha?通道的兩種情況下?Sprite?內的?Render Data?的不同,來確定需要修改哪些數據來達到目的。

從下圖可以看出(左邊是正常圖集的數據,右邊是我嘗試模擬寫入?RenderData?的錯誤數據),m_RD?中的?texture,alphaTexture,textureRect,textureRectOffset,settingsRaw,uvTransform?這些字段都需要修改。因為我無法接觸到源碼,所以其中一些值的算法則是通過分析猜測驗證得出的。

4.?m_RD.settingsRaw?的值的意義是什么

從 AssetStudio 源碼中可以找到?settingsRaw?的一部分定義:

  • 0?位:packed。1?表示?packed,0?表示不?packed

  • 1?位:SpritePackingMode。0?表示?tight,1?表示?rectangle

  • 2-5?位:SpritePackingRotation。0?表示不旋轉,1?表示水平翻轉,2?表示豎直翻轉,3?表示?180?度旋轉,4?表示?90?度旋轉

  • 6?位:SpriteMeshType。0?表示?full rect,1?表示?tight

其中正常生成的圖集的值?67,表示?SpriteMeshType(tight) + SpritePackingMode(rectangle) + packed。

5. 在 Unity 2017 測試通過,其他版本可以通過嗎

并不確定。通過查看 AssetStudio 源碼,可以看到序列化后有許多跟 Unity 版本相關的不同處理(下圖),如果在不同版本出現問題,可以通過上面對比打好的 AssetBundle 包的?Sprite?的?RenderData?的方式來排查是否需要填寫其他數據。

延伸思考

如果我們把一開始刷新圖集緩存的操作更換成?TexturePacker?的話,是否可以使用?TexturePacker?中的一些特性來為圖集做優化和定制呢?這是可能的,但是這也不是簡單就能做到的東西,還是很繁瑣的,不過的確是一個不錯的思路,有需要的同學可以研究一下。

參考資料

  • IOS 下拆分 Unity 圖集的透明通道(不用 TP):https://zhuanlan.zhihu.com/p/32674470

  • [2018.1] Unity 貼圖壓縮格式設置:https://zhuanlan.zhihu.com/p/113366420

  • (Legacy) Sprite Packer:https://docs.unity3d.com/Manual/SpritePacker.html

文中提到的工具:

  • AssetStudio,一個可以輕松查看 AssetBundle 內容的工具:https://github.com/Perfare/AssetStudio

  • UABE,可以解包/打包 AssetBundle,并查看其中詳細數據的工具:https://github.com/DerPopo/UABE

代碼倉庫:

以上的代碼都會整理在代碼倉庫中,該 demo 包含了一個完整的測試實例

https://github.com/RayRiver/UnityAlphaSeparateDemo

總結

以上是生活随笔為你收集整理的unity加载sprite_Unity 分离贴图 alpha 通道实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。