天天扯淡,都不知道寫點什麼,今天就分享一個國外大牛的一個unity 紋理壓縮技術吧。本節的壓縮算法叫做色度抽樣算法。在數位圖像處理領域中,色度抽樣是指在表示圖像時使用較亮度信息爲低的分辨率來表示色彩(色度)信息。當對模擬分量視頻或者YUV信號進行數字抽樣時,一般會用到色度抽樣。由於人眼對色度的敏感度不及對亮度的敏感度,圖像的色度分量不需要有和亮度分量相同的清晰度,所以許多視頻系統在色差通道上進行較低(相對亮度通道)清晰度(例如,抽樣頻率)的抽樣。這樣在不明顯降低畫面質量的同時降低了視頻信號的總帶寬。因抽樣而丟失的色度值用內插值,或者前一色度值來替代。
在人類的視覺有三個通道的顏色檢測,併爲許多顏色系統,三個“通道”是足夠的代表大多數顏色。例如:紅色、綠色、藍色、紅色、黃色、青色。但有其他方法來表示顏色。在許多視頻系統中,三個通道是亮度和兩個色度通道。在視頻中,亮度和色度分量形成一個加權伽瑪校正(刺激)而不是線性的特定成分(刺激)的RGB分量。因此,必須區分亮度。還有一些“出血”的亮度和色度分量視頻之間的亮度和顏色信息,誤差最大爲高度飽和的色彩和引人注目的彩條測試圖案的紅色和綠色的條(有色度抽樣的應用),類似的出血也可以發生與γ= 1,從那裏的伽瑪校正和形成的加權總和之間的操作的順序可以沒有差異。色度的影響尤其在像素亮度採樣把沒有色度。插值可以把色度值有有價值存在亮度不相容,並進一步處理,y'cbcr爲特定像素是什麼,最終產生錯誤時顯示的亮度。
這是原圖 沒有進行顏色抽樣的。
下面這張圖是進行了抽樣的,細微的看還是有區別的。原鏈接:https://en.wikipedia.org/wiki/Chroma_subsampling
它可以將32位rgb壓縮成每像素12位,並且壓縮後的圖片與原圖只存在細微差別,並且它還可以流暢的跑在比較老的gpu版本下。
首先我們還是得建一個類,讓它繼承AssetPostProcessor,
class ChromaPackProcessor : AssetPostprocessor
{
}
然後我們再加上主要工具方法。這裏我們的抄一下wiki上面的了:https://en.wikipedia.org/wiki/YCbCr。
class ChromaPackProcessor : AssetPostprocessor
{
static float RGB_Y(Color rgb)
{
return 0.299f * rgb.r + 0.587f * rgb.g + 0.114f * rgb.b;
}
static float RGB_Cr(Color rgb)
{
return 0.5f * rgb.r - 0.418688f * rgb.g - 0.081312f * rgb.b;
}
static float RGB_Cb(Color rgb)
{
return -0.168736f * rgb.r -0.331264f * rgb.g + 0.5f * rgb.b;
}
static float RGB_Ya(Color rgb)
{
if (rgb.a < 0.5f)
return 0;
else
return RGB_Y(rgb) * 255 / 256 + 1.0f / 256;
}
}
添加完了主要工具方法後,我們接下來就要寫今天的主要處理紋理算法了。代碼如下:
class ChromaPackProcessor : AssetPostprocessor
{
static float RGB_Y(Color rgb)
{
return 0.299f * rgb.r + 0.587f * rgb.g + 0.114f * rgb.b;
}
static float RGB_Cr(Color rgb)
{
return 0.5f * rgb.r - 0.418688f * rgb.g - 0.081312f * rgb.b;
}
static float RGB_Cb(Color rgb)
{
return -0.168736f * rgb.r -0.331264f * rgb.g + 0.5f * rgb.b;
}
static float RGB_Ya(Color rgb)
{
if (rgb.a < 0.5f)
return 0;
else
return RGB_Y(rgb) * 255 / 256 + 1.0f / 256;
}
void OnPreprocessTexture()//在處理紋理之前,我們先判斷他圖片的名字是否是由"CP.png"結尾,如果不是我們不予處理,這裏的格式我們可以自己定義。
{
var importer = assetImporter as TextureImporter;
if (!assetPath.EndsWith("CP.png")) return;
importer.textureType = TextureImporterType.GUI;
importer.textureFormat = TextureImporterFormat.RGBA32;
}
void OnPostprocessTexture(Texture2D texture)//當我們提交紋理時,主要思路是對原圖進行對每個像素進行處理,先讓原圖寬度擴大點。
{
var importer = assetImporter as TextureImporter;
if (!assetPath.EndsWith("CP.png")) return;
var hasAlpha = importer.DoesSourceTextureHaveAlpha();
var tw = texture.width; var th = texture.height;
var source = texture.GetPixels();
texture.Resize(tw * 3 / 2, th, TextureFormat.Alpha8, false);
var pixels = texture.GetPixels();
var i1 = 0;
var i2 = 0;
if (hasAlpha)
{
for (var iy = 0; iy < th; iy++)
{
for (var ix = 0; ix < tw; ix++)
{
pixels[i2++].a = RGB_Ya(source[i1++]);
}
i2 += tw / 2;
}
}
else
{
for (var iy = 0; iy < th; iy++)
{
for (var ix = 0; ix < tw; ix++)
{
pixels[i2++].a = RGB_Y(source[i1++]);
}
i2 += tw / 2;
}
}
i1 = 0; i2 = tw;
var i3 = (tw * 3 / 2) * th / 2 + tw;
for (var iy = 0; iy < th / 2; iy++)
{
for (var ix = 0; ix < tw / 2; ix++)
{
var ws = (source[i1] + source[i1 + 1] + source[i1 + tw] + source[i1 + tw + 1]) / 4;
pixels[i2++].a = RGB_Cr(ws) + 0.5f;
pixels[i3++].a = RGB_Cb(ws) + 0.5f;
i1 += 2;
}
i1 += tw; i2 += tw; i3 += tw;
}
texture.SetPixels(pixels);
importer.isReadable = false;
}
}
做完了這些都只是第一步。因爲這些處理之後得到的圖片都是黑白的了,還無法看。接下來需要用shader將其顯示出原圖的樣子,一般一個300k的紋理可以壓縮到它的三分之一或者一半不止,這要看圖片的源像素位數了,如果原圖32位的,那幾乎就是四分之一了,如果爲24位,那就是一半了,因爲壓縮成每像素12位。下面就給出2個對應的shader了。shader代碼如下:
Shader "ChromaPack/Opaque"
{
Properties
{
_MainTex("Base", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half3 YCbCrtoRGB(half y, half cb, half cr)
{
return half3(
y + 1.402 * cr,
y - 0.344136 * cb - 0.714136 * cr,
y + 1.772 * cb
);
}
half4 frag(v2f_img i) : SV_Target
{
float2 uv = i.uv;
half y = tex2D(_MainTex, uv * float2(2.0 / 3, 1.0)).a;
half cb = tex2D(_MainTex, uv * float2(1.0 / 3, 0.5) + float2(2.0 / 3, 0.5)).a - 0.5;
half cr = tex2D(_MainTex, uv * float2(1.0 / 3, 0.5) + float2(2.0 / 3, 0.0)).a - 0.5;
return half4(YCbCrtoRGB(y, cb, cr), 1);
}
ENDCG
SubShader
{
Tags { "Queue"="Geometry" }
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
ENDCG
}
}
}
因爲上面的算法是把rgb轉換成了以ycbcr,那麼如果我們需要還原rgb了。所以上面的shader中就需要把ycbcr轉換成rgb了。正如上面的方法YCbCrtoRGB一樣,然後下面就是一個片段着色器的方法體,這個就沒必要解釋了。另外的一個shader的方法也就類似了。代碼如下:
Shader "ChromaPack/Cutout"
{
Properties
{
_MainTex("Base", 2D) = "white" {}
}
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half3 YCbCrtoRGB(half y, half cb, half cr)
{
return half3(
y + 1.402 * cr,
y - 0.344136 * cb - 0.714136 * cr,
y + 1.772 * cb
);
}
half4 frag(v2f_img i) : SV_Target
{
float2 uv = i.uv;
half y = tex2D(_MainTex, uv * float2(2.0 / 3, 1.0)).a;
half cb = tex2D(_MainTex, uv * float2(1.0 / 3, 0.5) + float2(2.0 / 3, 0.5)).a - 0.5;
half cr = tex2D(_MainTex, uv * float2(1.0 / 3, 0.5) + float2(2.0 / 3, 0.0)).a - 0.5;
clip(y - 1.0 / 256);
y = (y - 1.0 / 256) * 256.0 / 255;
return half4(YCbCrtoRGB(y, cb, cr), 1);
}
ENDCG
SubShader
{
Tags { "Queue"="AlphaTest" }
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
ENDCG
}
}
}
本節就講完了,大家可以下去試試這個插件,這個效果還是蠻明顯的,有的效果很明顯有的就差強人意了。原圖雖然和處理過的圖片有些許差別,但是並不影響我們去使用它。
如果需要討論的朋友隨時可以加我qq:1850761495