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の高さを指定する
}
}
上記のTestRenderer
をCanvasTexture
のコンストラクタに渡してテクスチャーを作成し、それを使ってGVRSceneObject
を作成します。注意点としてCanvasTexture
はGVRExternalTexture
を継承していて、描画をするためには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シーンに現れます。