unity 图片资源导入优化

        天天扯淡,都不知道写点什么,今天就分享一个国外大牛的一个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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章