Unity/WebGLでVRM表示
ロボスタの勉強会に出席してみて、VRM(というかUniVRM)は気軽だな、と再認識したので試してみた。
せっかくなので、WebGL ビルドで。
ロボスタの勉強会に出席してみて、VRM(というかUniVRM)は気軽だな、と再認識したので試してみた。
せっかくなので、WebGL ビルドで。
簡単なデバッグ用スクリーンインジケータを作りました。
ゲーム開発でよく見る奴です。
車輪の再発明というか、標準の Profiler でよくね?と思いますが、私もだいたい同意です。

プロファイル、描画等で動的なメモリ割り当ては行なっていません。固定バッファでやりくりしています。
例外は、 yield WaitForEndOfFrame を行なっている部分で、Unity側の MoveNext () で毎フレーム16byte確保されます。
誰か防ぐ方法教えてください。
低機能なStringモドキです。使い方は例を見ればわかると思います。
var r = new RayString(100);
r.AddStr ("Foo").AddInt (10).AddStr ("Bar").AddFloat (0.5f, 0, 1);
Assert.AreEqual(r.ToString (), "Foo10Bar0.5");
上の例では、ToString ()でnew stringされていますが、char[] Bufferを参照することで動的メモリ割り当て無しで中身が取り出せます。
実装上の都合で(floatの内部表現が面倒なので)、AddFloat の精度は低いです。float の持つ精度を超える小数部の桁数を指定した場合、
C# のライブラリでは0でフィルされますが、このAPIでは精度以上の部分はゴミが入ります。
バッファが溢れそうなときは拡張を行うので、このときは動的メモリ割り当てを行います。
デバッグ用簡易Canvasです。文字列と矩形しか表示しません。
Mesh 用のデータを静的に持っておくことで、動的割り当てを回避する実装になっています。
データサイズは静的になりますが、表示内容が少なくても多くてもメモリ消費も描画コストもかかります。
パッケージとソースはgithubにあります。
Unity5.3, 5.4, 5.5. 5,6でパッケージを作りましたが(疲れました)、ソースは同一です。サンプルprefabをそれぞれのバージョンで生成しただけです。
下段として通常の圧縮を試してみます。その前に、先人の知恵を見て見ましょう。
参考:.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は存在しましたが、なんとなく。
仮定
アーカイブ全体からファイル名部分を検索するのには、有名な Boyer-Moore String Search アルゴリズムを使います。
wiki にソースすらあるので、それを参考にしても良いですが、今回は StackOverflow の記事から使用させていただきました。
参考:Search longest pattern in byte array in C#
ファイル名で区切られた領域をブロックとみなし、ファイル中の全ブロックと全ブロックを比べ、同じオフセットで同じデータ
が入る部分を抽出し、出力は、データブロックと参照ブロックが並ぶ構成にしました。頭の悪い辞書圧縮という感じです。
正確にデータ部分が抽出できれば、PVRTC/ETC1ともに8byteが1blockなので、それを利用して、オフセットによらず圧縮テクスチャ特化型辞書圧縮もできそうですが、手間をかけない方針なのでやりません。
| 方法 | サイズ |
|---|---|
| RAW | 4198973 |
| LZ4 | 876341 |
| LZMA | 609787 |
| 圧縮 | 2381911 |
大部分が同じデータなのでだいたい半分にはなりましたが、通常の圧縮をしていないのでまだまだ大きいですね。
(4)へ続く。
データは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)へ続く。