LoginSignup
1
1

More than 5 years have passed since last update.

Gear VR FrameworkでCanvasを使ったテクスチャー描画を行う

Posted at

AndroidにはCanvasという2D描画の仕組みがありますが、これをGear VR Frameworkの3Dシーンの描画にも応用できたら便利だなと思って、やってみました。3Dシーンの描画というより、3Dシーンの中にあるオブジェクトのテクスチャーをCanvasで描画するというアプローチです。

まず、テクスチャーを作成します。CanvasTextureと名付けました。

import android.graphics.Canvas;
import android.graphics.SurfaceTexture;
import android.view.Surface;

import org.gearvrf.GVRContext;
import org.gearvrf.GVRDrawFrameListener;
import org.gearvrf.GVRExternalTexture;

import java.lang.ref.WeakReference;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class CanvasTexture extends GVRExternalTexture {

    private final Renderer renderer;

    public CanvasTexture(GVRContext gvrContext, Renderer renderer) {
        super(gvrContext);
        this.renderer = renderer;
        gvrContext.registerDrawFrameListener(new Listener(this));
    }

    public interface Renderer {
        boolean isDirty();

        void draw(Canvas canvas);

        int width();

        int height();
    }

    private static class Listener implements GVRDrawFrameListener {
        private final GVRContext context;
        private final WeakReference<CanvasTexture> ref;
        private SurfaceTexture surfaceTexture;
        private Surface surface;

        private Listener(CanvasTexture canvasTexture) {
            this.context = canvasTexture.getGVRContext();
            this.ref = new WeakReference<>(canvasTexture);
        }

        @Override
        public void onDrawFrame(float v) {

            CanvasTexture canvasTexture = ref.get();

            if (canvasTexture == null) {
                context.unregisterDrawFrameListener(this);
                release();
                return;
            }

            Future<Integer> futureId = canvasTexture.getFutureId();
            if (!futureId.isDone()) return;

            try {
                int id = futureId.get();

                if (surfaceTexture == null) {
                    surfaceTexture = new SurfaceTexture(id);
                }

                if (surface == null) {
                    surface = new Surface(surfaceTexture);
                }

                Renderer renderer = canvasTexture.renderer;
                if (renderer.isDirty()) {
                    surfaceTexture.setDefaultBufferSize(renderer.width(), renderer.height());

                    Canvas canvas = surface.lockCanvas(null);

                    if (canvas != null) {
                        renderer.draw(canvas);
                        surface.unlockCanvasAndPost(canvas);
                        surfaceTexture.updateTexImage();
                    }
                }

            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        private void release() {
            if (surfaceTexture != null) {
                surfaceTexture.release();
                surfaceTexture = null;
            }

            if (surface != null) {
                surface.release();
                surface = null;
            }
        }
    }
}

CanvasTextureはコンストラクターにRendererインターフェースを受け取ります。Rendererの実装クラスはdrawメソッドでCanvasを受け取り、テクスチャーの描画を行います。必要無いのに毎フレーム再描画を行うのは処理負荷が大きくなりすぎてしまうので、isDirtyメソッドで再描画が必要かどうかをチェックして必要な時だけ描画処理が行われるようになっています。

実装例
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;

public class TestRenderer implements CanvasTexture.Renderer {

    private final Paint paint = new Paint();
    private boolean dirty = true;

    public TestRenderer() {
        paint.setColor(Color.WHITE);
    }

    @Override
    public boolean isDirty() {
        return dirty;
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        canvas.drawText(System.currentTimeMillis() + "s", 0, 64, paint);
//        dirty = false; // falseに設定しないとisDirtyがtrueを返し続け、毎フレーム描画を行う
    }

    @Override
    public int width() {
        return 128; // Canvasの幅を指定する
    }

    @Override
    public int height() {
        return 128; // Canvasの高さを指定する
    }
}

上記のTestRendererCanvasTextureのコンストラクタに渡してテクスチャーを作成し、それを使ってGVRSceneObjectを作成します。注意点としてCanvasTextureGVRExternalTextureを継承していて、描画をするためにはOESシェーダーを指定しなければならないということです。

GVRContext ctx;
CanvasTexture.Renerer renderer = new TestRenderer();
CanvasTexture texture = new CanvasTexture(ctx, renderer);
GVRSceneObject obj = new GVRSceneObject(ctx, 1, 1, texture, GVRMaterial.GVRShaderType.OES.ID);
obj.getTransform().setPosition(0, 0, -5);
ctx.getMainScene().addSceneObject(obj);

これで、Canvasで描画されたテクスチャー(現在時刻のミリ秒が白文字で表示される)を持ったオブジェクトが3Dシーンに現れます。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1