12オーディンズ等ではiOSの画像圧縮に”減色・画像最適化のためのツールとして業界でデファクトスタンダードの地位を獲得したグラフィックス・オプティマイザ「OPTPiX imésta 7 シリーズ」”のClearPVRTCを使用しています。
実はUnityのPVRはかなり汚いノイズが走ってしまうのですが、使用メモリは圧縮したいし…描画も高速化したいよね…というわけで白羽の矢が立ったのがこちらのツール「OPTPiX imésta 7 for Mobile & Social」になります。
なかなかいいお値段しているのですが、その分効果は抜群です。
今回はこのツールをどうやって使っているのかというお話をしたいと思います。
概要
12オーディンズはソーシャルゲームですからものすごい頻度でリリースがあるのです。
そのたびに手動で差分を更新していたのではバカみたいですよね。テストもできないし…
そこでJenkinsおじさんと連携してAssetBundleを作成するときに自動でプロジェクト内の画像ファイルのpvrに置き換えてもらおう!となるわけです。
OPTPiX imésta 7 for Mobile & Social だけではJenkinsと連携ができずOPTPiX MacroActor 7 for Mobile & Socialを利用することになります。
さらにさらに、OPTPiX imésta 7 for Mobile & Socialは大変貴重なリソースですので全プロジェクトでの共有リソースになります。
ですので、プロジェクトのJenkinsのスレーブなんかにできるわけもなくOPTPiX imésta 7 for Mobile & Socialは専用のJenkinsを構築してプロジェクトのJenkinsと連携してもらうという形になります。
そういうわけで作業手順はこんな感じ
- プロジェクト側JenkinsにAssetBundle作成指示が来る
- プロジェクト側Jenkinsがimésta側JenkinsにClearPVRTCの作成指示
- imésta側JenkinsがClearPVRTCを作成
- imésta側Jenkinsがプロジェクト側Jenkinsに完了通知
- プロジェクト側JenkinsがiOSのみClearPVRTCを使ってAssetBundleのビルド(Androidはpngで…)
今回の記事は3と5について解説していきます。
imésta側JenkinsがClearPVRTCを作成
ClearPVRTCの作成コマンド自体は簡単です
%Optpix% %WORKSPACE%\config\macro.ops /input_dir optpix /output_dir optpix
%Optpix%はMacroActorのパスを環境変数にしています。おそらく以下のパスになります。
「”C:\Program Files (x86)\Web Technology\OPTPiX imesta 7 for Mobile Contents\ISMACRO.EXE”」
設定ファイルはmacro.opsはこれでとりあえず使えると思います。
難しい設定はよくわからないのですが大事な設定がいくつか
- ファイル出力設定のPVR垂直Flipをonにする
- suffixを必ずつける(_iosにしています)
あとお好みで画質は一番いいやつにしています。
これは簡単なんですけどねーいい感じに圧縮する代償としてかなり時間がかかります。鬼のような時間がかかります。
12オーディンズの場合対象となる画像をすべて変換すると3時間くらいかかります。
毎回AssetBundle作るたびにそんなことやってる暇はないので差分だけ更新するようにしましょう。
前回の最後の成功ビルドのコミットは「GIT_PREVIOUS_SUCCESSFUL_COMMIT」で取れるのですが…
バグっててうまく取れないので自前でファイルに管理します。
さらに、それでもだめだった時のために明示的に人間がコミットハッシュを指定できるようにもしておきます。
結果として差分を取って該当ファイルをoptpixディレクトリにコピーするのは以下のスクリプトになります。
ちなみにiméstaの「更新ファイルのみ処理」はうまく差分を検出できなかった…のでこの方法を使っています。
PREV=${GIT_PREVIOUS_SUCCESSFUL_COMMIT:-HEAD}
if [ -n "${PrevCommit}" ] ; then
echo ${PrevCommit} > previous_success.txt
fi
if [ -e "previous_success.txt" -a "`cat previous_success.txt`" ] ; then
PREV=`cat previous_success.txt.txt`
fi
#targets.txtは行区切りで対象ディレクトリが入っています
TARGET=`tr -s '\r\n' '|' diff.txt
egrep -e "${TARGET%|}" diff.txt | grep ".png" | grep -v ".meta" | tee files.txt | :
[ -z "`cat files.txt`" ] && exit 0
cat files.txt | xargs -i{} cp --parents -v {} optpix || exit 0
これでoptpixディレクトリに差分画像が入るので上記コマンドで変換できます。
変換したのを戻してあげればOK
ちなみに変換したものをもとのディレクトリに戻すのはこんな感じでやります。
cd optpix
"C:\Program Files\Git\usr\bin\find.exe" . -type f | xargs -i{} cp --parents -v {} ../
Assets\target\a.pngが対象ファイルであればAssets\target\a_ios.pvrというファイルがコミットされるはずです。
あとはコミットすれば無事ClearPVRTCがgit上にプッシュされます。
プロジェクト側JenkinsがiOSのみClearPVRTCを使ってAssetBundleのビルド
これでついにiOSのAssetBundleを作成できる状態になりました。
個々で肝になるのはpngをpvrにすげ替えることです。
すげ替えは以下のメソッドでやっています。
やっていることはすべてのマテリアルを舐めて対象のpngがあればそれをpvrに切り替える動作をします。
private static bool IsValidPvr(string pvrPath,Texture tex)
{
if (tex.width != tex.height)
{
Debug.LogError ("pvr is not square:" + pvrPath);
return false;
}
var bits = tex.width;
//bit count
bits = (bits & 0x55555555) + (bits >> 1 & 0x55555555);
bits = (bits & 0x33333333) + (bits >> 2 & 0x33333333);
bits = (bits & 0x0f0f0f0f) + (bits >> 4 & 0x0f0f0f0f);
bits = (bits & 0x00ff00ff) + (bits >> 8 & 0x00ff00ff);
//Is w 2^n ?
if(bits != 1)
{
Debug.LogError ("pvr is not 2^n size:" + pvrPath);
return false;
}
return true;
}
///
/// 全マテリアルのmainTexのtextureのパスに対応したpvrがあればそちらにすげ替える
/// *.xxxファイルに対応するpvrは*_ios.pvrである
///
///
[MenuItem("File/Jenkins/Art/SwitchPvr(Material)")]
public static void SwitchMaterialToPvr()
{
var materialPaths = Directory.GetFiles ("Assets", "*.mat", SearchOption.AllDirectories);
float p = 0f;
try {
//
foreach (var path in materialPaths) {
EditorUtility.DisplayProgressBar ("マテリアル書き換え中", path, p += 1.0f / materialPaths.Length);
Material mat = AssetDatabase.LoadAssetAtPath (path);
Texture texture = mat.mainTexture;
if (texture == null)
continue; //maintexなし
var imgPath = AssetDatabase.GetAssetPath (mat.mainTexture);
var ext = Path.GetExtension (imgPath);
if (string.IsNullOrEmpty (ext))
return;//unity_builtin_extraとかでる
var pvrPath = imgPath.Replace (Path.GetExtension (imgPath), "_ios.pvr");
if (!File.Exists (pvrPath))
continue;//pvrがない
Texture2D tex = AssetDatabase.LoadAssetAtPath (pvrPath);
if(IsValidPvr(pvrPath,tex))
{
mat.mainTexture = tex;
Debug.Log ("書き換え完了。:" + path + "(" + imgPath + "->" + pvrPath + ")");
}else{
Debug.LogError ("書き換え取り消し。:" + path + "(" + imgPath + "->" + pvrPath + ")");
}
}
} catch (Exception) {
}finally{
EditorUtility.ClearProgressBar ();
}
}
これでMaterialのすげ替えは終わりました。
あとは直接読む(Resources.Load等)ものでpvrが読み込まれるようにします。
Unityの読み込みは拡張子を指定しないのでもともとのpngと名前を同じにしてあげます。
(つまり_iosを取り去る)
なぜ_iosがついていたのかというとこの拡張子をつけないで指定することが理由で、Unityだと同名のリソースがあるとどちらを読めばいいかわからなくなってエラーになってしまうからですね。
それで開発に支障が出るためにこのような方法を取っています。
/// 同名のpngとpvrがある場合にiOSならばpvrをさもなくばpngを残すように片方を削除します。
///
[MenuItem("File/Jenkins/Art/PickPlatform")]
public static void PickPlatformTexture()
{
var target = BatchBuilder.GetCommandArgsValueForKey("target");
target = (target == "" && EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) ? "ios" : target;
var pvrs = Directory.GetFiles("Assets", "*.pvr", SearchOption.AllDirectories);
Debug.Log("Delete format :" + ((target == "ios")?"png":"pvr"));
foreach (var path in pvrs)
{
var pngPath = GetPairPngPath(path);
if (target == "ios")
{
if (!IsValidPvr(path, AssetDatabase.LoadAssetAtPath(path)))
{
continue;
}
File.Delete(pngPath);
File.Delete(pngPath + ".meta");
if (path.EndsWith ("_ios.pvr")) { //基本的に通る _ios.pvrを.pvrに書き換える
var targetFileName = path.Replace("_ios.pvr", ".pvr");
var targetMetaFileName = (path + ".meta").Replace ("_ios.pvr.meta", ".pvr.meta");
if (File.Exists(targetFileName))
{
File.Delete(targetFileName);
}
if (File.Exists(targetMetaFileName))
{
File.Delete(targetMetaFileName);
}
File.Move (path, targetFileName);
File.Move (path + ".meta", targetMetaFileName);
}
Debug.Log ("Delete Texture:"+pngPath);
}
else
{
if (!File.Exists(pngPath))
{
Debug.LogException(new Exception("pngが見つかりません。" + pngPath));
}
File.Delete(path);
File.Delete(path + ".meta");
Debug.Log ("Delete Texture:"+path);
}
}
}
///
/// pvrのpathからpngの相方のパスを返します
///
/// The png path.
/// Pvr path.
private static string GetPairPngPath(string pvrPath){
var prefix = "_ios";
var @base = Path.GetDirectoryName(pvrPath) + "/" + Path.GetFileNameWithoutExtension(pvrPath);
if (@base.EndsWith (prefix))
@base = @base.Substring (0, @base.Length - prefix.Length);
return @base + ".png";
}
これでAssetBundleを作ってあげればClearPVRTCのiOSアプリができます!
ところで
12オーディンズの運営中にですね。
OPTPiX ClearPVRTC for Unity 5
というツールがリリースされたようでこちらのほうがお手軽かもしれません。