Archive: 2017/03

タグ・月別アーカイブ

AssetBundle の圧縮(4) 圧縮下段

圧縮

下段として通常の圧縮を試してみます。その前に、先人の知恵を見て見ましょう。

参考:.NET Compression Libraries Benchmark

何も考えずに.NETのDeflateと、性能が良さそうなLZMAを試して見ることにします。

LZMAはPublicDomainの7Zip SDKを使用します。

結果

方法 サイズ
RAW 4198973
LZ4 876341
LZMA 609787
圧縮 2381911
二段圧縮(.NET) 409940
二段圧縮(LZMA) 318962

ようやく、素のLZMAの半分くらいになりました。DeflateとLZMAは、速度とサイズのトレードオフでしょうか。

サンプルソースは、 https://github.com/Bugfire/test_assetbundlecompress に置いておきました。

追記

2016年のOSS圧縮ツール選択カタログ を読んだら、zstdのC#版が欲しくなってきました。native wrapperは存在しましたが、なんとなく。

AssetBundle の圧縮(3) 圧縮上段

圧縮の方針

仮定

  • アーカイブ内に存在するファイルの内容は、同一オフセットには同一のデータが入ることが多い。

実装

アーカイブ全体からファイル名部分を検索するのには、有名な Boyer-Moore String Search アルゴリズムを使います。
wiki にソースすらあるので、それを参考にしても良いですが、今回は StackOverflow の記事から使用させていただきました。

参考:Search longest pattern in byte array in C#

ファイル名で区切られた領域をブロックとみなし、ファイル中の全ブロックと全ブロックを比べ、同じオフセットで同じデータ
が入る部分を抽出し、出力は、データブロックと参照ブロックが並ぶ構成にしました。頭の悪い辞書圧縮という感じです。

正確にデータ部分が抽出できれば、PVRTC/ETC1ともに8byteが1blockなので、それを利用して、オフセットによらず圧縮テクスチャ特化型辞書圧縮もできそうですが、手間をかけない方針なのでやりません。

参考:Disunity on github

結果

方法 サイズ
RAW 4198973
LZ4 876341
LZMA 609787
圧縮 2381911

大部分が同じデータなのでだいたい半分にはなりましたが、通常の圧縮をしていないのでまだまだ大きいですね。

(4)へ続く。

AssetBundle の圧縮(2) 現状の計測

実験

データはUnityChanをベースに、ImageMagickでAlphaチャネルを削除しました。

$ convert portrait_kohaku_02.png -background black -alpha remove portrait_kohaku_02a.png
$ convert portrait_kohaku_01.png -background black -alpha remove portrait_kohaku_02b.png

画像サイズは2524x3189ですが、2048x2048, MipMap なしとして取り込みました。
PVRTC/ETC1の4bppを使用で、2048x2048x4/8が2枚で4MBとなります。

スクリプト

AssetBundle は以下の簡単なスクリプトで生成

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public static class AssetBundleCompressTest
{
    static string[] _assetNames = new string[2] {
        "Assets/AssetBundleTest/UnityChan/portrait_kohaku_01a.png",
        "Assets/AssetBundleTest/UnityChan/portrait_kohaku_02a.png",
    };

    static AssetBundleBuild[] CreateBuildMap (string mode, BuildTarget target)
    {
        AssetBundleBuild[] buildMap = new AssetBundleBuild[1];
        buildMap [0].assetBundleName = string.Format ("Test_{0}_{1}.unity3d", mode, target);
        buildMap [0].assetNames = _assetNames;
        return buildMap;
    }

    [MenuItem ("AssetBundlesCompress/RunTest")]
    static void BuildAssetBundles ()
    {
        var outputPath = "AssetBundles";

        System.IO.Directory.CreateDirectory (outputPath);

        //BuildTarget.Android,
        //BuildTarget.iOS,
        var target = EditorUserBuildSettings.activeBuildTarget;
        BuildPipeline.BuildAssetBundles (
            outputPath: outputPath,
            builds: CreateBuildMap ("RAW", target),
            assetBundleOptions: BuildAssetBundleOptions.UncompressedAssetBundle,
            targetPlatform: target);
        BuildPipeline.BuildAssetBundles (
            outputPath: outputPath,
            builds: CreateBuildMap ("LZ4", target),
            assetBundleOptions: BuildAssetBundleOptions.ChunkBasedCompression,
            targetPlatform: target);
        BuildPipeline.BuildAssetBundles (
            outputPath: outputPath,
            builds: CreateBuildMap ("LZMA", target),
            assetBundleOptions: BuildAssetBundleOptions.None,
            targetPlatform: target);
    }
}

結果

方法 サイズ
RAW 4198973
LZ4 876341
LZMA 609787

2x2048x2048x4/8は4194304なので、(4198973 - 4194304) = 4669byteがヘッダ等メタデータとなりますね。

アーカイブの中身を見た感じ、未圧縮の形式では、ファイルの前にかならず、(文字列長) + (ファイル名)が、
入るようなので、ここをファイルの区切りとして圧縮を試して見ます。

(3)へ続く。

AssetBundle の圧縮(1) 目的と方針

目的

AssetBundleはLZMAやLZ4で圧縮されますが、扱うデータの特性がわかっている場合はより特殊化した
圧縮が期待できるのではないかと考えました。

例えば目パチのようなアニメーションの画像データでは以下のようなアプローチがあります。

  • 目だけを上書きする透過画像を生成して上に乗せる。
    透過面積が多い画像であれば標準のSpritePackerや TexturePacker で効率的なAtlas化が可能です。
  • 差分画像を生成する。例えば 宴 のダイシング機能。

しかし、今回は制約があり上の手法は使いません。

前提

制約は以下の通りです。

  • 使う側で特別なコードは一切書きたくない。
  • TextureでありSpriteではない。
  • 機種依存圧縮(PVRTC, ETC)を使いたい。
  • 透過なしで1パスで描画を行いたい。

PVRTC, ETC のバイト列を生成し、Unity の Texture としてロードができればよいのですが、
ちょっとわからなかったので別の方法を試します。(*1)

手法

AssetBundle をただ圧縮し、インストール時に展開するというアプローチを試してみます。

テクスチャ圧縮はGPUの機能的制約から固定長で同じサイズのテクスチャであれば、同じ座標のデータは
同じブロックに存在します。したがって、局所性が高く、同一のピクセル構成部分であれば、同一のバイナリ構成になる可能性が高いと予想されます。
(PVRTC は近辺のブロックも参照を行うので、影響範囲は少し大きめになりますが、距離に応じて影響は低くなります)

(*1) のPVRTCからテクスチャ生成ができれば、AssetBundle 内で圧縮するアプローチを取ることができるのですが、Nativeでテクスチャを生成する以外の方法があれば、誰か教えてください...。

問題点

  • 圧縮前はロスレス画像でなければならない。
    見た目が同じでもLossyな圧縮を行なった結果、微妙に画像データが異なると困ります。
  • ストレージでの消費容量は無圧縮なので比較的大きい。
  • 複数のバリエーションをロードするとGPU側のメモリ消費は多い。
  • 暗号化を行えない。暗号化を先に行うと圧縮が難しい。
    圧縮してから暗号化だと、ストレージでは圧縮を展開したデータになるため暗号化がかかっていない状態になります。

(2)へ続く。

Hexoのインストール

ふと思い立って、Github pages を使って Blog を作ってみた。
環境は何でもよかったが、nodejs ベースの Hexo を使ってみることにしました。

nvm/nodejs のインストール

すでにインストールされていたnodejsが0.12だったので、ついでにnvmをインストール。

$ git clone git://github.com/creationix/nvm.git ~/.nvm

.profile に1行追加

$ echo 'if [[ -s ~/.nvm/nvm.sh ]] ; then source ~/.nvm/nvm.sh ; fi' >> ~/.bash_profile 
$ source ~/.bash_profile 

バージョンは適当に選択

$ nvm --version
0.33.1
$ nvm ls-remote
(いっぱい)
$ nvm install v5.12.0
$ node --version
v5.12.0

Hexo のインストール

npm install --save hexo

Blog の作成

$ ./node\_modules/hexo/bin/hexo init blog
$ cd blog
$ npm install

Theme の fork

一応。github から landscape を fork しておく。

Theme の submodule 化

$ cd themes/
$ git submodule add https://github.com/bugfire/hexo-theme-landscape.git landscape