Xamarin.AndroidでCardboardアプリを作成する方法について


Xamarin.AndroidでCardboardアプリを作成する方法について説明します。Cardboardアプリとはスマホで手軽にVR体験できる以下のようなアプリです。

Cardboardアプリを作成するためには、Cardboard SDKを利用する必要があります。Cardboard SDKは以前はソースコードは提供されず、AARやJARのようなライブラリで提供されていました。その後2019年11月6日、オープンソース化が発表され、現在はNDKでの提供に変更されました。

しかし、Googleから提供されているSDKは、Xamarinでは直接、利用できません。AARやJARを「Xamarinバインドライブラリ」という仕組みで利用したり、NDK経由で呼び出す必要があり、簡単に利用することができませんでした。

ある時、rassunさんがCardboard SDKをデコンパイルして、Javaソースコードにしたものを、githubで公開しているのを見つけました。今回、このソースコードをC#で書き直し、Xamarin上で動作させることができましたので、以下のリポジトリにて公開します。
https://github.com/secile/CardboardXamarin

このソースコードを利用すれば、従来のようにAARやJARを利用することなく、Cardboardアプリを作成することができます。赤い三角形がくるくる回るサンプルプログラムが含まれていますので、すぐに動作確認することもできます。実行しても赤い三角形が見つからない場合は、左右をキョロキョロ探してください。ただし、このソースコードは、作成の元になったCardboard SDKのバージョンが古く、最新のCardboard SDKにある機能の一部はありませんが、基本的なVRアプリの作成には問題ないと思います。

なお、Cardboardアプリの作成には、3DグラフィックライブラリであるOpenGLの知識を必要とします。サンプルプログラムでは、そのままOpenGLを利用するのではなく、C#でOpenGLを利用するためのライブラリ「OpenTK」を利用しています。

サンプルプログラムの解説

MainActivityはCardboardActivityを継承します。OnCreateでCardboardViewとVrRendererを作成します。

public class MainActivity : CardboardActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        // CardboarViewの作成。
        var glview = new CardboardView(this);
        SetCardboardView(glview);

        // Rendrerの作成。
        var render = new VrRenderer();
        glview.SetRenderer(render);

        SetContentView(glview);
    }
}

VrRendererは描画を担当するクラスです。CardboardView.StereoRendererインタフェースを実装します。

class VrRenderer : Java.Lang.Object, CardboardView.StereoRenderer
{
    private Shader Shader;
    private Shape Shape;

    public void OnSurfaceCreated(Javax.Microedition.Khronos.Egl.EGLConfig config)
    {
        GL.ClearColor(0.1f, 0.1f, 0.4f, 1.0f); // 背景色の設定。
        GL.Enable(EnableCap.DepthTest); // Depthバッファの有効化(Z座標で手前に表示)

        Shader = new Shader();
        Shape = new Triangle(0.5f);
    }

    public void OnSurfaceChanged(int width, int height)
    {
        GL.Viewport(0, 0, width, height);
    }

    private float Rotate = 0;

    public void OnNewFrame(HeadTransform transform)
    {
        Shader.Activate();

        // 回転角度を更新する。
        Rotate++;
    }

    public void OnDrawEye(EyeTransform transform)
    {
        // 画面をクリアする。
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        float[] prj = transform.GetPerspective();
        OpenTK.Matrix4 prjMat = MatrixFromArray(ref prj);
        Shader.SetProjection(prjMat);

        // 手前から、Y軸を上に、原点を見る視点を作成。
        OpenTK.Matrix4 lookat = Matrix4.LookAt(-Vector3.UnitZ, Vector3.Zero, Vector3.UnitY);
        // eyeMatをかけて左目または右目の視点にする。
        float[] eye = transform.GetEyeView();
        OpenTK.Matrix4 eyeMat = MatrixFromArray(ref eye);
        Shader.SetLookAt(lookat * eyeMat);

        // 図形を描画する。
        Shader.IdentityMatrix();
        Shader.Rotate(Rotate, Vector3.UnitY);
        Shader.SetMaterial(OpenTK.Graphics.Color4.Red);
        Shape.Draw(Shader);
    }

    public void OnFinishFrame(Viewport viewport) { }

    public void OnRendererShutdown() { }

    private static Matrix4 MatrixFromArray(ref float[] m)
    {
        return new Matrix4(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13], m[14], m[15]);
    }
}

VrRendererが作成されると、OnSurfaceCreatedが呼ばれますので、初期化を実行します。ここで使用しているShaderとShapeは私が作成したクラスで、Shaderは描画を、Shapeは三角形を表します。

画面の更新が開始されると、OnNewFrame→OnDrawEye(左目)→OnDrawEye(右目)→OnFinishFrameの順番で繰り返し呼ばれます。OnNewFrameで状態を更新して、OnDrawEyeで描画します。今回のサンプルでは、OnNewFrameで描画する三角形の回転角度を更新しています。

OnDrawEyeは、現在の視点にtransform.GetEyeView()をかけると、自動的に左目または右目の視点になりますので、今どっちの目を描画しているのか意識する必要はありません。

まとめ

以上、簡単でしたが、XamarinでCardboardアプリを作成する方法を説明しました。Cardboardは現在ではあまりクローズアップされることはありませんが、初めて体験したときの感動は忘れられません。ぜひ皆さんも、Cardboardアプリの作成にチャレンジしてみてください。