Unity画像最適化ツール- Ditherのディザリングに対する改善方法

Unityモバイルプラットフォーム向けゲーム開発おいて、画像アセットはいつも一番リソースを占めているアセットです。同じ視覚効果を維持すると同時に、どのように画像アセットをできるだけ減らせることが、開発チームのよくある問題です。通常、3Dオブジェクトのテクスチャに対して、ETC/PVRTCなど圧縮率高いアルゴリズムで処理してもよいですが、細部に厳しいUIテクスチャに対して、このような処理は画質の歪みで品質基準に満たないことが多いです。このような画像アセットは、画質歪みの少ない16bitの画像形式で保存することを検討しましょう。

 
しかし階調の比較的に高いテクスチャにとっては、Unityのデフォルト変換方法では画質の劣化を招きます。この問題に対して、keijiroはDither4444というアルゴリズムで実現しました。下記図1でわかるように,細部滑らかな画像に対して、当該アルゴリズムはグラデーションがつぶれている現象は軽減できたが、若干ジャリジャリとしたノイズが混ざる形となっています。

[caption id="attachment_711" align="aligncenter" width="710"] <図1> 左側:オリジナル画像 右側:keijiroの Dither4444イメージ図[/caption]

本ブログの作者はkeijiroのアルゴリズムに基づいて改良しました。RGB24Bitの画像をディザリング後、RGB565へビットシフトする方法を提供し、ほぼ肉眼で見える画質劣化現象が解消されました。実際なイメージは図2をご参照。

[caption id="attachment_712" align="aligncenter" width="696"] <図2> 左側:オリジナル図 右側:作者の Dither565イメージ図[/caption]

実際、作者のプロジェクト開発時に、ETC/PVRTC圧縮に適しない画像処理はほぼ本章で紹介されたRGB565もしくはRGB565+A8のアルゴリズムで処理し、肉眼レベルで画質劣化を生じないように、アセットの最適化を行った。

 

最後にOnPostprocessTextureコードを参照:

void OnPostprocessTexture (Texture2D texture) 
{
           if(assetPath.Contains ("_dither565"))
           {
                    var texw = texture.width;
                    var texh = texture.height;
                 var pixels = texture.GetPixels ();
                 var offs = 0;

                 var k1Per31 = 1.0f / 31.0f;
                 var k1Per32 = 1.0f / 32.0f;
                 var k5Per32 = 5.0f / 32.0f; 
                 var k11Per32 = 11.0f / 32.0f;
                 var k15Per32 = 15.0f / 32.0f;

                 var k1Per63 = 1.0f / 63.0f;

                 var k3Per64 = 3.0f / 64.0f;
                 var k11Per64 = 11.0f / 64.0f;
                 var k21Per64 = 21.0f / 64.0f;
                 var k29Per64 = 29.0f / 64.0f;

                 var k_r = 32; //R&B 5桁まで圧縮,2の5乗を取る 
                 var k_g = 64; //G 6桁まで圧縮, 2の6乗を取る

                 for(var y = 0; y < texh; y++){
                          for(var x = 0; x < texw; x++){
                                  float r = pixels [offs].r; 
                                  float g = pixels [offs].g; 
                                  float b = pixels [offs].b; 
                                  var r2 = Mathf.Clamp01 (Mathf.Floor (r * k_r) * k1Per31); 
                                  var g2 = Mathf.Clamp01 (Mathf.Floor (g * k_g) * k1Per63); 
                                  var b2 = Mathf.Clamp01 (Mathf.Floor (b * k_r) * k1Per31); 
                                   var re = r - r2; 
                                  var ge = g - g2; 
                                  var be = b - b2; 

                                  var n1 = offs + 1; 
                                  var n2 = offs + texw - 1; 
                                  var n3 = offs + texw; 
                                  var n4 = offs + texw + 1;
 
                                  if(x < texw - 1){
                                             pixels [n1].r += re * k15Per32;
                                             pixels [n1].g += ge * k29Per64;
                                             pixels [n1].b += be * k15Per32;
                                   }
                                    if(y < texh - 1){
                                             pixels [n3].r += re * k11Per32;
                                             pixels [n3].g += ge * k21Per64;
                                             pixels [n3].b += be * k11Per32;
                                             if(x > 0){
                                                       pixels [n2].r += re * k5Per32;
                                                       pixels [n2].g += ge * k11Per64;
                                                       pixels [n2].b += be * k5Per32;
                                             }
                                              if(x < texw - 1){
                                                       pixels [n4].r += re * k1Per32;
                                                       pixels [n4].g += ge * k3Per64;
                                                       pixels [n4].b += be * k1Per32;
                                             }
                                   }
                                   pixels [offs].r = r2;
                                   pixels [offs].g = g2;
                                   pixels [offs].b = b2;
                                   offs++;
                          }
                }
                texture.SetPixels (pixels);
                EditorUtility.CompressTexture (texture, TextureFormat.RGB565, TextureCompressionQuality.Best);
       }
}