本文出自:【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);
就段代码的功能就是把太阳系的模型插入到场景中。效果如下图所示:
发表评论