简单分析ARCore和SceneForm

本文出自:【InTheWorld的博客】 (欢迎留言、交流)

从ARCore去年下半年发布到现在已经半年有余了。之前其实一直有点手痒,想玩玩AR应用开发。之前有参考一本书,玩过一个简单的基于OpenCV和OpenGL的AR应用,但的确太简陋了。然而之前ARCore只支持Pixel之类的亲儿子手机,所以一直没有机会把玩一下。前两周偶然发现mix2s已经支持ARCore了,所以就乘放假的机会玩玩demo,分析一下ARCore的应用开发。

ARCore支持Java, C, Unity以及Unreal这几种开发方式。在最近的Java SDK中新增了SceneForm api来支持渲染。这确实是一个比较大的进步,毕竟裸写OpenGL还是有点麻烦的。本人没弄过Unity和Unreal这些渲染引擎,所以就从Java SceneForm的SDK入手了,毕竟比较熟悉(其实就是懒)。

虽然前文废话已经说了不少,但是我还是想简单的介绍一下AR的概念。Argument Reality在我看来,其实就两个部分——识别跟踪、模型叠加渲染。识别跟踪就是在视频帧中识别出一些特征,比如说识别出桌面、识别出人头等。模型叠加渲染就是在视频帧中叠加出增强的显示信息,比如说在桌子上渲染一只皮卡丘的模型、在头顶渲染出一顶帽子。同理,AR的技术其实也就分两部分,计算机视觉和图形学,用工程技术来说就是OpenCV等来处理场景识别,用Dx或者OpenGL来渲染三维模型。

ARCore把场景识别的实现部分封装在动态库里面了,所以我目前还不知道它确切的思想方式,这部分只能从上层api来看了。我们从基础的地方开始吧!

ArSceneView和Renderer

ArSceneView是一个非常重要的类,使用ARCore SceneForm的时候,你一定需要它的支持。ArSceneView继承自SceneView,SceneView其实就是最终渲染视频的view。SceneView的部分源码如下:

public class SceneView extends SurfaceView implements FrameCallback {
    private static final String TAG = SceneView.class.getSimpleName();
    private Renderer renderer = null;
    private final FrameTime frameTime = new FrameTime();
    private Scene scene;
    private volatile boolean debugEnabled = false;
    private boolean isInitialized = false;

    public SceneView(Context var1) {
        super(var1);
        this.initialize();
    }

    public SceneView(Context var1, AttributeSet var2) {
        super(var1, var2);
        this.initialize();
    }

    private void initialize() {
        if (this.isInitialized) {
            Log.w(TAG, "SceneView already initialized.");
        } else {
            if (!AndroidPreconditions.isMinAndroidApiLevel()) {
                Log.e(TAG, "Sceneform requires Android N or later");
                this.renderer = null;
            } else {
                this.renderer = new Renderer(this);
                this.scene = new Scene(this);
                this.renderer.setCameraProvider(this.scene.getCamera());
            }

            this.isInitialized = true;
        }
    }
}

initialize()函数完成了初始化的工作,包括初始化了Renderer和Scene。Renderer完成了主要的渲染工作,这里SceneView就是它输出的地方。在Renderer的初始化函数中,会完成OpenGL context的创建。其中主干的代码如下:

        Engine engine = EngineInstance.getEngine();
        this.renderer = engine.createRenderer();
        this.scene = engine.createScene();

EngineInstance.getEngine()会保证创建一个OpenGL的context。Renderer的render()函数就是实际完成渲染的地方,函数稍微有点长,但是逻辑还是比较清晰的。

public void render(@Nullable Frame frame, Collection<Plane> updatedPlanes, boolean debugEnabled) {
        synchronized(this) {
            if (this.recreateSwapChain) {
                Engine engine = EngineInstance.getEngine();
                if (this.swapChain != null) {
                    engine.destroySwapChain(this.swapChain);
                }

                this.swapChain = engine.createSwapChain(this.surface);
                this.recreateSwapChain = false;
            }
        }

        ++this.frameCounter;
        ResourceManager.getInstance().getAssetLoader().createAssets();
        if (frame != null) {
            this.planeRenderer.update(updatedPlanes, this.getFocusPoint(frame));
        }

        this.recalculateCameraUvs = this.shouldRecalculateCameraUvs(frame);
        if (this.filamentHelper.isReadyToRender()) {
            this.runQueuedRenderEvents();
            if (this.recalculateCameraUvs && this.cameraStream != null && frame != null) {
                this.cameraStream.recalculateCameraUvs(frame);
                this.recalculateCameraUvs = false;
            }

            this.updateInstances();
            this.updateLights();
            CameraProvider cameraProvider = this.getCameraProvider();
            if (cameraProvider != null) {
                float[] projectionMatrixData = cameraProvider.getProjectionMatrix().data;

                for(int i = 0; i < 16; ++i) {
                    this.cameraProjectionMatrix[i] = (double)projectionMatrixData[i];
                }

                this.camera.setModelMatrix(cameraProvider.getWorldModelMatrix().data);
                this.camera.setCustomProjection(this.cameraProjectionMatrix, (double)cameraProvider.getNearClipPlane(), (double)cameraProvider.getFarClipPlane());
                SwapChain swapChainLocal = this.swapChain;
                if (swapChainLocal == null) {
                    throw new AssertionError("Internal Error: Failed to get swap chain");
                }

                if (this.renderer.beginFrame(swapChainLocal)) {
                    if (cameraProvider.isActive()) {
                        this.renderer.render(this.view);
                    } else {
                        this.renderer.render(this.emptyView);
                    }

                    if (this.screenshot != null && this.screenshot.isDirty()) {
                        this.screenshot.capture();
                    }

                    this.renderer.endFrame();
                }
            }
        }
    }

this.updateInstances(); this.updateLights(); 就两句代码的作用就是更新模型和光照。然后,计算相机投影矩阵,最后渲染到view。

Scene和Renderable

Scene就是渲染的场景,Scene相当于是三维世界里的root view。而Scene继承于NodeParent,Node也继承于NodeParent,Node表示一个渲染对象(相当于视图层级中的一个view)。NodeParent中可以添加子NodeParent,而Scene则是整个渲染场景的根对象。而且ARCore也支持把二维view放到三维空间中渲染,当然更可以把三维模型放到Scene中了。这两种分别对应ViewRenderable和ModelRenderable。Node有一个setRenderable()方法,把这些渲染对象放到Node中,再把Node插入到整个场景中,即可完成AR场景绘制。

          Anchor anchor = hit.createAnchor();
          AnchorNode anchorNode = new AnchorNode(anchor);
          anchorNode.setParent(arSceneView.getScene());
          Node solarSystem = createSolarSystem();
          anchorNode.addChild(solarSystem);

就段代码的功能就是把太阳系的模型插入到场景中。效果如下图所示:

发表评论