遊戲中紋理壓縮格式之自動壓縮紋理
阿新 • • 發佈:2019-02-14
記載目錄
1.雜言雜語
2.自動處理程式碼
3.接入NGUI實現全自動化
4.專案補充
5.專案工程下載地址
雜言雜語
從事遊戲開發很苦,很累。似乎永遠有開發不完的功能,修不完的bug,加不完的班。為了讓自己更輕鬆點工作和少些做一些重複性的勞作,我想盡可能的利用自動化處理,將所有能夠自動化的東西全部都弄成自動化,解放自己。
自動處理程式碼
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
/**
* 全自動的原理是引用一個圖集後自動設定紋理格式到指定的紋理格式
* 程式碼引用有一個配置檔案
* 還有一個Newtonsoft.Json.dll
* 這份程式碼可以實現自動化,但是效率不高(存在大量的轉換RGBA32),可以再度修改
* 文章最後可以下載整個專案工程
*/
namespace TextureFormat
{
public class AtlerTextureFormat
{
// 配置路徑
readonly static string ETC_SHADER_PATH = "Assets/Resources/Shader/EtcAlpha_1.shader" ;
readonly static string ATLER_FORMAT_CONFIG_PATH = Application.dataPath + "/Editor/AtlerTextureFormat/AtlerFormatConfig.txt";
#region 對圖片紋理進行格式化
/// <summary>
/// 格式化Texture
/// NGUI呼叫入口、生成圖片入口
/// </summary>
/// <param name="tex_path">貼圖路徑</param>
/// <param name="mat_path">材質球路徑</param>
public static void FormatTexture(string tex_path, string mat_path)
{
if (string.IsNullOrEmpty(tex_path) || !System.IO.File.Exists(tex_path))
{
Debug.Log("Can't find tex_path path: " + tex_path);
return;
}
if (string.IsNullOrEmpty(mat_path) || !System.IO.File.Exists(mat_path))
{
Debug.Log("Can't find mat_path path: " + mat_path);
return;
}
// 臨時儲存通道路徑
string alpha_tex_path = "";
// 讀取配置表,獲取壓縮格式
// 根據壓縮格式呼叫對應的壓縮程式碼壓縮圖片
// 壓縮圖片
var tex_name = System.IO.Path.GetFileNameWithoutExtension(tex_path);
var tex_format = GetTextureFormatFromConfig(tex_name);
// 對於rpga16 進行特殊處理。抖動
// 對於etc進行剝離通道
if (tex_format == TextureImporterFormat.RGBA16)
{
SetTextureDither(tex_path);
}
else if (tex_format == TextureImporterFormat.ETC_RGB4)
{
SetTextureFormat(tex_path, TextureImporterFormat.RGBA32);
alpha_tex_path = FormatTextureToRGB4(tex_path);
SetTextureFormat(alpha_tex_path, TextureImporterFormat.ETC_RGB4);
}
// 設定材質球的shader
if ((tex_format == TextureImporterFormat.ETC_RGB4))
{
Texture tex_rgb = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture)) as Texture;
Texture tex_alpha = AssetDatabase.LoadAssetAtPath(alpha_tex_path, typeof(Texture)) as Texture;
Material mat = AssetDatabase.LoadAssetAtPath(mat_path, typeof(Material)) as Material;
Shader shader = AssetDatabase.LoadAssetAtPath(ETC_SHADER_PATH, typeof(Shader)) as Shader;
mat.shader = shader;
mat.SetTexture("_MainTex", tex_rgb);
mat.SetTexture("_MainTex_A", tex_alpha);
}
else
{
Texture tex_rgb = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture)) as Texture;
Material mat = AssetDatabase.LoadAssetAtPath(mat_path, typeof(Material)) as Material;
Shader shader = Shader.Find(NGUISettings.atlasPMA ? "Unlit/Premultiplied Colored" : "Unlit/Transparent Colored");
mat.shader = shader;
mat.SetTexture("_MainTex", tex_rgb);
}
SetTextureFormat(tex_path, tex_format);
}
/// <summary>
/// 嘗試剝離通道
/// </summary>
public static bool TryStripAlphaTexture(string png_file_path)
{
// 檢視該貼圖是否需要進行etc處理
// 讀取.png的設定
// 格式化.png紋理為rgb24
// 剝離通道
// 還原.png 貼圖紋理
var file_name = Path.GetFileNameWithoutExtension(png_file_path);
var format = GetTextureFormatFromConfig(file_name);
if (format != TextureImporterFormat.ETC_RGB4)
{
Debug.Log("This texture can not convert format to ETC_RGB4 ! please check config. ");
return false;
}
SetTextureFormat(png_file_path, TextureImporterFormat.RGBA32);
TextureImporterFormat tex_format = GetTextureFormatFromConfig(Path.GetFileNameWithoutExtension(png_file_path));
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
var alpha_tex_path = FormatTextureToRGB4(png_file_path);
Debug.Log("BuildAlphaTexture:" + png_file_path + " alpha_tex_path:" + alpha_tex_path);
SetTextureFormat(alpha_tex_path, TextureImporterFormat.ETC_RGB4);
SetTextureFormat(png_file_path, tex_format);
return true;
}
/// <summary>
/// 格式化圖片 修改texture的meta檔案
/// 直接設定雙平臺紋理格式
/// 當紋理格式為 ETC_RGB4 時會將iphone設定為pv4
/// </summary>
private static void SetTextureFormat(string texture_path, TextureImporterFormat format)
{
// 進行貼圖的統一的優化設定
// 再設定紋理格式
TextureImporter tex_importer = GetTextureImporter(texture_path);
tex_importer.textureType = TextureImporterType.Default;
tex_importer.mipmapEnabled = false;
tex_importer.isReadable = false;
tex_importer.filterMode = FilterMode.Bilinear;
if(format == TextureImporterFormat.RGBA32)
{
tex_importer.SetPlatformTextureSettings("iPhone",tex_importer.maxTextureSize,TextureImporterFormat.RGBA32);
tex_importer.SetPlatformTextureSettings("Android", tex_importer.maxTextureSize, TextureImporterFormat.RGBA32);
}
else if(format == TextureImporterFormat.RGBA16)
{
tex_importer.SetPlatformTextureSettings("iPhone", tex_importer.maxTextureSize, TextureImporterFormat.RGBA16);
tex_importer.SetPlatformTextureSettings("Android", tex_importer.maxTextureSize, TextureImporterFormat.RGBA16);
}
else if(format == TextureImporterFormat.ETC_RGB4)
{
tex_importer.SetPlatformTextureSettings("iPhone", tex_importer.maxTextureSize, TextureImporterFormat.PVRTC_RGB4,100,false);
tex_importer.SetPlatformTextureSettings("Android", tex_importer.maxTextureSize, TextureImporterFormat.ETC_RGB4,100,false);
}
else
{
Debug.Log("Wrong format : " + format.ToString());
}
tex_importer.SaveAndReimport();
}
/// <summary>
/// Floyd–Steinberg dithering
/// 抖動處理,需要給圖片進行抖動處理
/// 需要注意的是 需要進行抖動處理的是RGBA32的紋理進行抖動處理,再壓縮為16位
/// </summary>
private static void SetTextureDither(string tex_path)
{
SetTextureReadable(tex_path, true);
var texture = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture2D)) as Texture2D;
var texw = texture.width;
var texh = texture.height;
var pixels = texture.GetPixels ();
var offs = 0;
var k1Per15 = 1.0f / 15.0f;
var k1Per16 = 1.0f / 16.0f;
var k3Per16 = 3.0f / 16.0f;
var k5Per16 = 5.0f / 16.0f;
var k7Per16 = 7.0f / 16.0f;
for (var y = 0; y < texh; y++)
{
for (var x = 0; x < texw; x++)
{
float a = pixels [offs].a;
float r = pixels [offs].r;
float g = pixels [offs].g;
float b = pixels [offs].b;
var a2 = Mathf.Clamp01 (Mathf.Floor (a * 16) * k1Per15);
var r2 = Mathf.Clamp01 (Mathf.Floor (r * 16) * k1Per15);
var g2 = Mathf.Clamp01 (Mathf.Floor (g * 16) * k1Per15);
var b2 = Mathf.Clamp01 (Mathf.Floor (b * 16) * k1Per15);
var ae = a - a2;
var re = r - r2;
var ge = g - g2;
var be = b - b2;
pixels [offs].a = a2;
pixels [offs].r = r2;
pixels [offs].g = g2;
pixels [offs].b = b2;
var n1 = offs + 1; // (x+1,y)
var n2 = offs + texw - 1; // (x-1 , y+1)
var n3 = offs + texw; // (x, y+1)
var n4 = offs + texw + 1; // (x+1 , y+1)
if (x < texw - 1)
{
pixels [n1].a += ae * k7Per16;
pixels [n1].r += re * k7Per16;
pixels [n1].g += ge * k7Per16;
pixels [n1].b += be * k7Per16;
}
if (y < texh - 1)
{
pixels [n3].a += ae * k5Per16;
pixels [n3].r += re * k5Per16;
pixels [n3].g += ge * k5Per16;
pixels [n3].b += be * k5Per16;
if (x > 0)
{
pixels [n2].a += ae * k3Per16;
pixels [n2].r += re * k3Per16;
pixels [n2].g += ge * k3Per16;
pixels [n2].b += be * k3Per16;
}
if (x < texw - 1)
{
pixels [n4].a += ae * k1Per16;
pixels [n4].r += re * k1Per16;
pixels [n4].g += ge * k1Per16;
pixels [n4].b += be * k1Per16;
}
}
offs++;
}
}
SetTextureReadable(tex_path, false);
SaveTexture(tex_path, texture, pixels);
}
public static void ConvertTextureFormatToTargetFormat(string tex_path, TextureImporterFormat target_format)
{
var file_name = Path.GetFileNameWithoutExtension(tex_path);
SetTextureReadable(tex_path, true);
Texture2D tex = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture2D)) as Texture2D;
if (tex.format != UnityEngine.TextureFormat.RGBA32)
{
TextureImporter tex_importer = GetTextureImporter(tex_path);
tex_importer.textureType = TextureImporterType.Default;
tex_importer.mipmapEnabled = false;
tex_importer.isReadable = false;
tex_importer.filterMode = FilterMode.Bilinear;
tex_importer.SetPlatformTextureSettings("iPhone", tex_importer.maxTextureSize, target_format);
tex_importer.SetPlatformTextureSettings("Android", tex_importer.maxTextureSize, target_format);
tex_importer.SaveAndReimport();
}
SetTextureReadable(tex_path, false);
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
}
/// <summary>
/// ETC1的特殊處理,分離透明通道並且更改材質為雙貼圖
/// 能剝離通道的圖片紋理格式只有 rgb24 或者 rgba32
/// </summary>
private static string FormatTextureToRGB4(string tex_path)
{
// 生成透明通道的png檔案
// 對通道圖片進行格式紋理設定
// 剝離通道並存儲
var alpha_tex_path = tex_path.Replace(".png", "_alpha.png");
ChangeTextureToAlphaTexture(tex_path,alpha_tex_path);
return alpha_tex_path;
}
/// <summary>
/// 從RGBA32剝離alpha通道
/// </summary>
private static void ChangeTextureToAlphaTexture(string tex_path ,string alpha_tex_path)
{
SetTextureReadable(tex_path, true);
Texture2D sourcetex = AssetDatabase.LoadAssetAtPath(tex_path, typeof(Texture2D)) as Texture2D;
Color[] colors = sourcetex.GetPixels();
SetTextureReadable(tex_path, false);
// 格式化通道
Texture2D tex_alpha = new Texture2D(sourcetex.width, sourcetex.height, UnityEngine.TextureFormat.RGB24, false);
Color[] alpha_colors = new Color[colors.Length];
for (int i = 0; i < colors.Length; ++i)
{
alpha_colors[i].r = colors[i].a;
alpha_colors[i].g = colors[i].a;
alpha_colors[i].b = colors[i].a;
}
SaveTexture(alpha_tex_path, tex_alpha, alpha_colors);
}
private static TextureImporter GetTextureImporter(string path)
{
Debug.Log("path: " + path);
TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
textureImporter.textureType = TextureImporterType.GUI;
textureImporter.npotScale = TextureImporterNPOTScale.ToNearest;
return textureImporter;
}
/// <summary>
/// 設定圖片為可讀格式
/// </summary>
private static void SetTextureReadable(string alpha_tex_path,bool is_allow_read)
{
TextureImporter ti = (TextureImporter)TextureImporter.GetAtPath(alpha_tex_path);
ti.isReadable = is_allow_read;
ti.SaveAndReimport();
//AssetDatabase.ImportAsset(alpha_tex_path);
}
private static void SaveTexture(string tex_path, Texture2D texture, Color[] pixels)
{
SetTextureReadable(tex_path, true);
texture.SetPixels(pixels);
texture.Apply();
var bytes = texture.EncodeToPNG();
SetTextureReadable(tex_path, false);
if (File.Exists(tex_path)) AssetDatabase.DeleteAsset(tex_path);
var stream = File.Create(tex_path);
stream.Write(bytes, 0, bytes.Length);
stream.Close();
AssetDatabase.ImportAsset(tex_path);
}
/// <summary>
/// 讀取配置表獲取貼圖要採用什麼格式格式化
/// </summary>
public static TextureImporterFormat GetTextureFormatFromConfig(string tex_name)
{
// 根據配置檔案TextureFormat裡面的配置尋找圖集名稱
// 找到相關項就轉換成相關的項的主鍵並且轉換成列舉
// 如果找不到就直接返回預設型別
string tex_config_json_str = System.IO.File.ReadAllText(ATLER_FORMAT_CONFIG_PATH, System.Text.Encoding.UTF8);
JavaScriptObject jsonObj = JavaScriptConvert.DeserializeObject<JavaScriptObject>(tex_config_json_str);
TextureImporterFormat format = TextureImporterFormat.ETC_RGB4;
if (jsonObj.ContainsKey("TextureFormat"))
{
var item = (Dictionary<string, object>)jsonObj["TextureFormat"];
foreach (var node in item)
{
var key = node.Key;
if (key.CompareTo("DefaultFarmat") != 0)
{
JavaScriptArray list = (JavaScriptArray)node.Value;
foreach (string list_item in list)
{
if (list_item.CompareTo(tex_name) == 0)
{
return (TextureImporterFormat)System.Enum.Parse(typeof(TextureImporterFormat), (string)(node.Key));
}
}
}
}
if (item.ContainsKey("DefaultFarmat"))
{
return (TextureImporterFormat)System.Enum.Parse(typeof(TextureImporterFormat), (string)(item["DefaultFarmat"]));
}
}
else
{
Debug.LogError("Can't find config. config_path: " + ATLER_FORMAT_CONFIG_PATH);
}
return format;
}
public static void DitherTexture(string png_file_path)
{
var file_name = Path.GetFileNameWithoutExtension(png_file_path);
var format = GetTextureFormatFromConfig(file_name);
if (format == TextureImporterFormat.RGBA16)
{
SetTextureDither(png_file_path);
}
else
{
Debug.Log("can not diter texture ! check config! path: " + png_file_path);
}
}
#endregion
#region 獲取打包圖集的圖片的大小
/// <summary>
/// 獲取打包圖集的圖片的大小
/// </summary>
public static int GetTextrueSize(string atler_name)
{
//var tex_name = System.IO.Path.GetFileNameWithoutExtension(tex_path);
// 根據配置檔案TextureFormat裡面的配置尋找圖集名稱
// 找到相關項就轉換成相關的項的主鍵並且轉換成列舉
// 如果找不到就直接返回預設型別
string tex_config_json_str = System.IO.File.ReadAllText(ATLER_FORMAT_CONFIG_PATH, System.Text.Encoding.UTF8);
JavaScriptObject jsonObj = JavaScriptConvert.DeserializeObject<JavaScriptObject>(tex_config_json_str);
TextureImporterFormat format = TextureImporterFormat.ETC_RGB4;
if (jsonObj.ContainsKey("TextureSize"))
{
var item = (Dictionary<string, object>)jsonObj["TextureSize"];
foreach (var node in item)
{
var key = node.Key;
if (key.CompareTo("DefaultSize") != 0)
{
JavaScriptArray list = (JavaScriptArray)node.Value;
foreach (string list_item in list)
{
if (list_item.CompareTo(atler_name) == 0)
{
return int.Parse(node.Key);
}
}
}
}
if (item.ContainsKey("DefaultFarmat"))
{
return int.Parse((string)item["DefaultFarmat"]);
}
}
else
{
Debug.LogError("Can't find config. config_path: " + ATLER_FORMAT_CONFIG_PATH);
}
return 1024;
}
#endregion
#region 工具欄
/// <summary>
/// 剝離通道
/// </summary>
[MenuItem("TextureFormat/BuildAlphaTexture")]
public static void BuildAlphaTexture()
{
GetSelectionPath(".png", (string file_path) =>
{
TryStripAlphaTexture(file_path);
});
}
/// <summary>
/// 抖動處理圖片
/// </summary>
[MenuItem("TextureFormat/DiterTexture")]
public static void DitherTexture()
{
GetSelectionPath(".png", DitherTexture);
}
/// <summary>
/// 獲取選擇的檔案進行操作
/// </summary>
/// <param name="match_end_with_str">結束匹配欄位</param>
/// <param name="func">找到要找到的欄位後執行的方法</param>
public static void GetSelectionPath(string match_end_with_str,Func func)
{
if(func == null)
{
Debug.Log("no func");
return;
}
if(!string.IsNullOrEmpty(match_end_with_str))
{
var objs = UnityEditor.Selection.objects;
if (objs!= null && objs.Length > 0)
{
for (int i = 0; i < objs.Length; ++i)
{
var file_path = AssetDatabase.GetAssetPath(objs[i]);
if (file_path.EndsWith(match_end_with_str)) func(file_path);
}
}
}
}
public delegate void Func(string selection_path);
#endregion
}
}
這裡是配置檔案記載內容:
{
"TextureFormat": {
"RGBA32": [
"RGBA32Alter"
],
"RGBA16": [
"RGBA16Alter"
],
"ETC_RGB4": [
],
"DefaultFarmat": "ETC_RGB4"
},
"TextureSize": {
"2048": [
"RGBA32"
],
"4086": [
"RGBA16"
],
"DefaultSize": "1024"
},
"註釋": {
"註釋": "TextureFormat 是圖集圖片採用的格式化型別",
"註釋": "TextureFormat 中的 ETC_RGB4 格式代表同時也代表IOS平臺的 PV4格式",
"註釋": "TextureSize 是打包Atler 的圖集大小限定"
}
}
接入NGUI實現全自動化
/**
* UIAtlasMaker.cs
* 直接修改NGUI的打包圖集程式碼
* 這裡寫出修改概要,如果要檢視全部原始碼則可以下載工程
*/
public class UIAtlasMaker
{
// 省略 730 + 行
void OnGUI ()
{
// 省略N行 Ngui原始碼
/**
* 補充程式碼 在NGUI 處理開始圖片時轉換格式先
* NGUI bug
* ngui 圖集轉換格式為 RGBA32
* 手動轉換格式為etc
* 再用NGUI 圖集追加圖片
* 這時候的圖片的部分透明通道就沒有掉了。
* 修復方法,強轉一個RGBA32
*/
if ((delete || update || replace) && NGUISettings.atlas != null && NGUISettings.atlas.texture != null)
{
var tex_path = AssetDatabase.GetAssetPath(NGUISettings.atlas.texture);
TextureFormat.AtlerTextureFormat.ConvertTextureFormatToTargetFormat(tex_path,TextureImporterFormat.RGBA32);
}
if (delete)
{
// 省略NGUI原始碼
}
else if (update) UpdateAtlas(textures, true);
else if (replace) UpdateAtlas(textures, false);
if (NGUISettings.atlas != null && !string.IsNullOrEmpty(selection))
{
// 省略NGUI原始碼
}
else if (update || replace)
{
// 省略NGUI原始碼
}
// 補充程式碼 接入圖片處理
if (delete || update || replace)
{
if (NGUISettings.atlas != null && NGUISettings.atlas.spriteMaterial != null && NGUISettings.atlas.texture != null)
{
// 新圖片就要走普通流程
// 已經更改過紋理的圖片就需要走特殊流程
if (replace && GetIsNewTexture())
{
var tex_path = AssetDatabase.GetAssetPath(NGUISettings.atlas.texture);
var mat_path = AssetDatabase.GetAssetPath(NGUISettings.atlas.spriteMaterial);
TextureFormat.AtlerTextureFormat.FormatTexture(tex_path, mat_path);
}
else
{
var tex_path = AssetDatabase.GetAssetPath(NGUISettings.atlas.texture);
var file_name = System.IO.Path.GetFileNameWithoutExtension(tex_path);
var format = TextureFormat.AtlerTextureFormat.GetTextureFormatFromConfig(file_name);
switch (format)
{
case TextureImporterFormat.RGBA16:
// 先轉成rgba32為
// 進行抖動處理
// 轉化成rgba 16 位
TextureFormat.AtlerTextureFormat.ConvertTextureFormatToTargetFormat(tex_path, TextureImporterFormat.RGBA32);
TextureFormat.AtlerTextureFormat.DitherTexture(tex_path);
TextureFormat.AtlerTextureFormat.ConvertTextureFormatToTargetFormat(tex_path, TextureImporterFormat.RGBA16);
break;
case TextureImporterFormat.ETC_RGB4:
TextureFormat.AtlerTextureFormat.TryStripAlphaTexture(tex_path);
break;
default:
Debug.Log("Ignore format!");
break;
}
TextureFormat.AtlerTextureFormat.TryStripAlphaTexture(tex_path);
}
}
}
}
static public bool UpdateTexture (UIAtlas atlas, List<SpriteEntry> sprites)
{
// 省略NGUI原始碼
if (newTexture)
{
// Create a new texture for the atlas
tex = new Texture2D(1, 1, UnityEngine.TextureFormat.ETC_RGB4, false);
// 在這裡填充程式碼
SetIsNewTexture(true);
}
}
/*** 填充程式碼 ****/
private static bool m_is_new_tex = false;
private static void SetIsNewTexture(bool is_new_tex)
{
m_is_new_tex = is_new_tex;
}
/// <summary> 獲取是否新建立的圖片,只能讀取一次 </summary>
private static bool GetIsNewTexture()
{
// 只能讀取一次
if(m_is_new_tex)
{
m_is_new_tex = false;
return true;
}
return false;
}
}
專案補充
這份程式碼目前還存在一定的bug需要修復,其中之一就是UIpanel,具體修改如下。
還有一個小功能沒有完成,就是圖片限定大小的功能,圖集越大載入速度越慢,所以有些遊戲需要對圖集大小有要求,而且也不想依賴專案規範的,可以使用Config中TextureSize欄位去補充。