KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

このエントリーは、KLab Advent Calendar 2016 の12/19の記事です。 やまだです。 昨年はWebGLをつかったソートを実装してみましたが今年はWebGLを使った画像圧縮を実装してみます。

はじめに

WebGLのみならずアプリ開発において画像のフォーマットの選択は重要です。 とくにゲームの大部分を占める画像データはゲームのダウンロード時間に直結するため可能な限り圧縮しておきたいものです。

WebGLではGPUにテクスチャをアップロードする際にはgl.texImage2Dを使用します。 gl.texImage2DTexImageSourceをアップロードできます。 このTexImageSourceというのが面白くてテクスチャデータとしてHTMLImageElementが使えます。 すなわちGIF/JPEG/PNGといったフォーマットだけでなくGoogleが開発した高圧縮率フォーマットであるWebPも使用可能です。 さらにはHTMLVideoElementもテクスチャフォーマットとして使用できるため、動画さえもテクスチャにできます。 ダウンロードサイズの削減が目的であればGIF/JPEG/PNG/WEBPから適切なフォーマットを選ぶのがよいでしょう。

一方で今日ではモバイルデバイスもターゲットとなってくるため、 ビデオメモリの容量も気にしたいところで、画素あたりのビット数も削減したいところです。 WebGLにおいてはPNGなどを用いた場合、1画素あたり32bit使用することが多いでしょう。 その場合、1024x1024のテクスチャ1枚当たり4MByteのサイズとなります。 そうなってくると選ばれるフォーマットはETCやPVRTCでしょう。 例えばETC2の8bit/pixelを用いれば1024x1024のテクスチャも1/4のサイズである1MByteまで削減できます。 ですが、これらはWebGLでも使用可能ですがWebGL 1.0ではエクステンションのため使用できる保証がありません。 ETC2はWebGL 2.0で使用可能とのことなので今後はETC2を積極的に使用していきたいところです。 ETCやPNG等を使用するのも手ではありますが前述のとおりいくつかの無視できない課題が存在するため、 今回はこれらとは異なるアプローチで画像圧縮を試みてみました。

YCgCo色空間による圧縮

Unity向けではありますがこちらYCgCo色空間を使用して画像の圧縮を試みている方がいらっしゃいました。 記事で解説されている通りではありますが画像をLuma(明度)とChroma(彩度)に分割し、 人間の目はLumaに比べてChromaの変化に対して鈍感である性質を利用してChromaの情報量を落として圧縮しています。

元画像

lena

Lumaのみの画像

luma_only

Chromaのみの画像

chroma_only

人間の目はLumaのみの画像に対してChromaの変化に鈍感であることが実感できると思います。 このアイディアそのままにWebGLによる再実装を行いました。

色空間を変換するシェーダ

RGBAをYCgCoAに変換する

YCgCoとRGBの色空間は行列による変換が可能です。なのでシェーダとも相性がよくシェーダでも行列を使用して変換を行います。 1つだけ注意しないといけないところはYCgCoのYの値域は[0, 1]に対してCgCoの値域は[-0.5, +0.5]のためテクスチャに保存する際に0.5を加算します。 この加算含めて4x4行列にまとめるとRGBAをYCgCoAに変換するシェーダは以下のように書けます。

元となる実装と同じようにYをAlpha8に割り当て、CgCoAをRGB565のフォーマットに書き込むことを考えて swizzleを使用して(Y,Cg,Co,A) => (A,R,B,G)のように割り当てます。 さらに変換後の画像をさらにRGBとAに分割し、RGB画像の解像度を1/4に縮小します。 結果1画素あたりのビット深度はAlpha8(8bit) + RGB565(16bit) / 4 = 12bitとなります。

precision mediump float;

uniform sampler2D u_map;

varying vec2 v_uv;

void main(void) {
    mat4 m = mat4(
        0.25, -0.25, 0.5, 0.0,
        0.5, 0.5, 0.0, 0.0,
        0.25, -0.25, -0.5, 0.0,
        0.0, 0.5, 0.5, 1.0
    );

    vec4 c = texture2D(u_map, v_uv);
    vec3 ycc = (m * vec4(c.rgb, 1.0)).xyz;
    float alpha = c.a;
    vec4 ycca = vec4(ycc, alpha);

    gl_FragColor = ycca.ywzx;
}

Yのみのアルファ画像

lena_y

縮小したCgCoA画像

lena_cca

YCgCoAをRGBAに変換する

YCgCoAに変換した2枚のテクスチャをシェーダで合成し、RGBAに変換します。 その際にVRAMの節約のためにCgCoA画像はRGB565のテクスチャで、Y画像はAlpha8のテクスチャとしてアップロードします。

// Y画像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, gl.ALPHA, gl.UNSIGNED_BYTE, image);
// CgCoA画像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, image);

シェーダで合成する際はCgCoに0.5を加算しているため行列をかけるまえに0.5ひいてやります。

precision mediump float;

uniform sampler2D u_tex0; // CgCoA画像
uniform sampler2D u_tex1; // Y画像

varying vec2 v_uv;

void main(void) {
    mat3 m = mat3(
        1, 1, 1,
        -1, 1, -1,
        1, 0, -1
    );
    vec3 cca = texture2D(u_tex0, v_uv).xyz;
    float y = texture2D(u_tex1, v_uv).w;
    vec4 c = vec4(y, cca.x, cca.z, cca.y) - vec4(0.0, 0.5, 0.5, 0.0);
    gl_FragColor = vec4(m * c.xyz, c.a);
}

合成結果

result

比較

左:元画像、右:合成画像

compare

ほぼ劣化がないように見えます。 念のためちゃんと画像が劣化しているのかを確認するために こちらを参考に差分をとってみます。

$ composite -compose difference lena.png result.png diff.png
$ identify -format "%[mean]" diff.png
428.525

確かに差分が発生しているので画像は劣化してはいるようです。 差分画像を正規化した物がこちらです。

diff_normalized

全体的に差分が発生していることが確認できます。 Chromaのビット数を削減しているので納得できる結果となりました。

最後に

YCgCoによる圧縮は見た目上は効果的であるということはわかりましたが、いくつかの問題点があります。

1つはテクスチャフェッチが2回必要となること。 テクスチャフェッチは重い処理なのでできれば少なくしたいところです。

もう1つはアルファのみを保存する適切なフォーマットが見つからないこと。 Y画像は8bit深度のアルファ画像なのですがPNG画像でアルファ付き画像として保存すると、 32bit深度で保存されてしまい少しもったいないです。

また、原因の特定はできなかったのですがGPUによってはうまく描画できない例も見受けられました。

得られる画像は非常に良いものではあるのですが、現状のWebGLにおいてでは画像の圧縮はダウンロードサイズの削減が目的であればPNG/JPEG/WebPを使用し、 ビデオメモリの節約であればETC2に望みを託す、というのが今はよいかもしれません。

以上です。

この記事は、KLab Advent Calendar 2016 の17日目の記事です。はじめまして、morikenです。

昨日はjukey17さんのVRのゲームを作ってみて気をつけたことでした。

2016年はVR元年

さて、2016年は年始から盛んに「今年はVR元年だ」と言われていましたが、確かに一気にVRが普及したように感じます。

家庭用のVR機器がいくつも発売され、またVRを体験できる場所や機会も増えたことにより、VRの魅力を知った方も増えたと思います。

私自身も、VR ZONEでVRを体験してその魅力に取り憑かれ、PlayStation®VRももちろん購入し、日々バーチャル空間でサイリウムを振ったり、家庭教師の仕事をしたりして楽しんでいます。

ところで、面白いゲームをプレイすると、自分でも作ってみたくなるのがゲーム開発者の性です。そこで、VR元年が終わる前に、自分でもVRのゲームを作ってみることにしました。

VRゲームを作る前に

今回は初めてということで、普段から慣れ親しんだUnityで、スマートフォン向けVRゲームを作ることにしました。

ところが、スマホ向けのVRゲームを作る上で、ある問題に直面しました。それは、プレイヤーにどのようにゲームを操作させるか、です。

というのも、スマホVRでは専用のゴーグルにスマホをセットし、それを覗いてコンテンツを楽しみます。そのため、タッチパネルによる操作はほぼ行うことができません。

それゆえ、VRコンテンツの開発者たちは、ゴーグルに磁石を取り付けてスイッチ代わりにしたり、視線でUIを選択できるようにしたり、首を縦横に振る動きを利用したりと、色々工夫をしてきました。しかし、これらの操作方法では、複雑な操作をさせることは難しいです。

そんな時、自宅にむかし購入したBluetoothのキーボードがあることを思い出しました。Bluetoothなので、スマホとも接続できるはずです。キーボードを使うことができれば、プレイヤーに複雑な操作をさせることができます。

そして、キーボードを使ったゲームと言えば、思い浮かぶものは1つしかありません。

そう、タイピングゲームですね。

VRタイピングゲームの作成手順

手順は以下の通りです。

  1. タイピングゲームを作ります。

  2. 1.で作ったものをVRに対応します。

以上の2ステップです。簡単ですね。

タイピングゲームの作成

今回は、フィールドの中心にいるプレイヤーに向かって、周囲から襲いかかってくるモンスターをタイピングによって撃退するという、ベーシック(?)なタイピングゲームを作成しました。

プレイヤーを定点カメラにするのは、VRのデモ作品などによくある形式ですね。この形式は、周囲を 360° 見渡せるためVRと相性が良く、またカメラの移動がないため酔いづらい、というメリットがあります。

リソースにはUnity Technologies製のアセット、Survival Shooterを利用しました。

非VRのタイピングゲーム

VR対応

タイピングゲームがあらかた完成したら、次はVRに対応させます。

VR対応には、Google VR SDK for Unityを使用しました。Googleは以前、Google Cardboard SDKというSDKを配付していましたが、今年の8月に発表されたDaydreamに対応され、リニューアルされたようです。

VR対応はとても簡単で、UnityにインポートしたSDKに含まれるGvrViewerMain.prefabというPrefabをシーンに配置するだけです。これだけで下の画像のように、ゲーム再生時に自動で右目・左目用のカメラを配置し、VRゴーグルを通して見ることで、ゲーム内のオブジェクトが立体的に見えるようになります。

VR対応のデモ

VRでのUIの注意点

さて、VRゲームでは、非VRのゲームとは少しUIを変えなければいけません。

というのも、非VRゲームと同じようにUIを配置しても、UIが適切に表示されないためです。

例えばこのように、ゲーム内テキストと体力ゲージを模したUIを配置します。

オーバーレイのUI

ところが、これを再生すると、下のようにUIが左目、右目の領域にはみ出して表示されてしまいます。

オーバーレイのUI再生時

この問題を解決するため、カメラ画像にオーバーレイさせていたUIを、3D空間上に配置しましょう。

CanvasコンポーネントのRender Modeは、デフォルトではScreen Space - Overlayになっていますが、これをWorld Space に変更します。

Canvasコンポーネントの設定

すると、Canvas以下のUIコンポーネントが通常の3Dオブジェクトと同じようにワールド空間上に配置されるようになり、左目用・右目用の両方のカメラに描画されるようになりました。

VR対応されたUI

完成

こうして、無事にVRタイピングゲームが完成しました。

VR対応されたゲームのGIF

実際にプレイしてみた

さて、実際にプレイする上で、いくつか必要なものがあります。

まずは、Bluetooth接続できるキーボードです。このゲームはタイピングゲームなので、キーボードは必須です。

キーボード

次に、プレイ中はVRゴーグルを手で支えることができません。そのため、頭に固定できるタイプのものを用意する必要があります。

VRゴーグル

またモンスターは360°さまざまな方向から襲いかかってくるため、素早く周囲を見渡せるよう立ってプレイするのが最適です。その際にキーボードを手元に固定できるように、画板のようなものがあると便利です。

画板のようなもの

全て装備するとこのようになります。(本当は某ゲーム機を背負いたかったのですが、怒られそうなので諦めました。)

装備した様子

プレイしている様子です。遊んでいる本人はVRの世界に没入できてとても楽しいのですが、周りからは不審な目で見られるので、人目がないところでプレイするのが良さそうです。

プレイの様子

終わりに

ゲームのクオリティはさておき、VR対応ゲームはこのように簡単に作ることができます。興味を持たれた方はぜひチャレンジしてみてください。

一緒にこれからのVR業界を盛り上げましょう!

KLab Advent Calendar 16日目の記事です

はじめまして、jukey17です
Advent Calendarを書くのは初めてです
よろしくお願いします!

この記事に書いてあること

この記事では、jukey17がVRゲームを初めて作ってみて、気をつけた内容を簡単にまとめています

これからVRのゲームを作ってみようと思っている方の参考に少しでもなってもらえれば幸いです

VRのゲームを作ろうと思ったキッカケ

VR元年と言われた2016年 、個人でも楽しむことができるVR製品が次々に発表・発売されていき「VR製品買いました」「VR楽しい!」「VR凄い」という声が開発仲間の周りから少しずつ聞こえてくるようになりました

私自身全く興味がなかったわけではなく最初は体験ブースなどを見つけたら軽く遊んでみる程度の興味はありました
が、今一歩踏み込めないままでいました

そうこうしているうちについにはゲーム開発とはゆかりのない仕事をしている友人からもVRの話題が聞こえるようになってきてやっと ちょっとこれは実際に作ってみたほうがいいのでは? と思うようになりました

気がついたら今年も残りわずか、 このまま踏み込まないまま1年が終わってしまうのも勿体無い! と思い、VRゴーグルを購入しゲーム開発をはじめました

作ったゲーム 『World Jab VR』

image

正面から飛んでくるジャブ(パンチ)を左右に避けるゲームです
少し昔、TVで話題になっていた某ボクシング3兄弟の父親が行っていた練習の1つをモチーフにしました

このゲームは Google VR SDK for Unity(※以下SDK)を使ってUnityで作りました

ジャイロセンサーを使用して横の傾きを検出し、プレイヤー自身を左右に動かしてジャブを避けます
何回連続で避けられるかを競うゲームとなっています

初めてのVRゲーム開発だったのですが、実際に開発を進めていく中で気をつけたことの中でやってよかったと思った内容がありました

以下にその内容を紹介します

気をつけて良かったこと

先にUnityEditor上で遊べるようにした

私はまず初めに VRのゲームを作ろう! と勢い良くSDKのサンプルシーンを起動し、実機に転送して遊んで見るところからスタートしました

しかし、プロジェクトをビルドして、端末にバイナリを転送し、起動した状態で端末をVRゴーグルに装着して…
というのを 開発中に何度も繰り返すのはとても大変 だとすぐに気づきました

そこでまずはSDKの導入は一旦せず、UnityEditor上のみで動作が確認できるように開発をはじめました

とはいっても、SDKは簡単にゲームのVR対応ができるように GvrViewerMain.prefab をシーン上に配置するだけでVR対応が済むような構造になっているので、この対応は非常に簡単です

初めはUnity上で開発を進めていき、 ある程度遊べるようになってきたら対象のプレハブをひょいとシーンに置けば良いのです

但し、今回作った World Jab VR はゲームの仕様上プレハブの設置とセットでジャイロセンサーの入力機構も実装しなければなりません

ある程度遊べるようになってからも膨大な微調整の時間がかかるのは容易に想像できます

そこで更にもうひと工夫することにしました

入力操作方法を切り替えられるようにした

今回は3つの入力操作方法を切り替えられるようにしました

  • キーボード操作

PC(UnityEditor)想定
※先程のgifアニメーションがPC操作

  • ジャイロセンサー

スマホ(VR)想定

vr_play

  • タッチ操作

スマホ(非VR)想定

touch_play

最後のタッチ操作はキーボードとジャイロセンサーの出し分けを作ったときに「幾らでも新しいパターンが追加できるな」と思い、サクッと作ってみてしまった副産物です

この3つの入力操作方法は実行前に設定を切り替えられるようにしました

これのおかげで下記のような対応がとてもしやすくなりました

  • VRで遊んでいる間に問題が発覚!
    1. UnityEditorで動作を確認と問題を修正の繰り返し
    2. 修正が完了したらVRで再度遊ぶ
  • 新しい機能を追加したい!
    1. UnityEditorで機能の実装と微調整の繰り返し
    2. 実装が終わったらVRで最終チェック

一度VR対応したせいで細かい動作確認が億劫になり、開発スピードが落ちていく…なんてことは防げるはず!
(モチベーション維持は大事です)

今後の展望

UI上からのパラメータ調整・設定切り替え

いわゆるデバッグ機能です
現在はUnityEditor上のインスペクタから設定できるようにしています

しかしこれだと実機で動作確認したときに微調整をするために一旦UnityEditorに戻らなければなりません
大きな問題の修正や新機能の追加などプログラムを書き換えなければならない内容では仕方ないことですが、パラメータの調整やオプションの切り替えだけで済む内容なのであればなるべくUnityEditorに戻りたくありません

VRゴーグルを装着しているときは 視点ポインターで選択 できるようにしないといけないため歯応えがありそうな内容です

追加したい機能のアイデアももう思い浮かんでおりドンドンと実装をしたいところ...
もあるのですが、こういった 何度も繰り返し時間の掛かる部分は積み重なっていくとかなりの時間を消費していて開発スピードを下げる原因になる ため、足回りの部分の整備も怠らずに進めていきたいところです

ジャブを繰り出す側もプレイヤーにしてしまう

現状はAIが繰り出すジャブをひたすら避けるゲームになっていますが、通信機能を使って2人で遊べるように したら見栄えも含めてもっと面白くなりそうです

避ける側はVRゴーグルを装着、ジャブをする側はスマホにフリック操作してジャブを繰り出す

フェイント機能 などがあると駆け引きが増えて盛り上がりそうです

(※操作方法は暫定)

最後に

初めてのVRのゲームを作ってみましたが、どんな内容のゲームをつくるかを考える際に『どんな場所で遊ぶのか』『遊んでいる人は外から見てどのように見えているのか』など、 ゲームの内容の外側のことも気にしたりしながら作ったので予想以上に難しく・そして楽しかったです

今回は開発を始めていく中での気をつけておくと良いことを紹介しましたが、今後はそういった空間を含めたゲームデザインや新デバイスならではの技術に触れるような内容にもチャレンジしたいと思いました

以上です

↑このページのトップヘ