介绍
利用 Sceneform,可以轻松地在 AR 和非 AR 应用中渲染逼真的 3D 场景,主要包含内容如下:
谷歌官方文档接下来把 Sceneform 和 ARCore 在项目中的配置,又讲了一遍,pass。
开始使用 Sceneform 和创建场景图的最简单方法是使用
ArFragment
,在layout中添加这个组件后即可在逻辑代码中使用。
创建可渲染对象
Renderable
是一个 3D 模型,可置于场景中的任何位置,由网格、材料和纹理组成。
ViewRenderable
,在 3D 场景中被渲染为平面的 2D 卡片,同时可以
通过触摸与它们交互
。
ModelRenderable
中。
以上3种分别对应的是:1、普通2D效果(可导入图片和组件混合);2、导入文件实现的3D立体效果;3、代码实现的3D效果(如一个大圆球)。
以第一种为例,先在 res > layout 中创建一个布局文件,然后构建
ViewRenderable
对象,代码示例如下:
<?xml version="1.0" encoding="utf-8"?>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/testview"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:background="@drawable/ic_launcher"
android:orientation="vertical"
android:padding="4dp"
android:clipChildren="false"
android:clipToPadding="false" >
<TextView
android:id="@+id/orbitHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Orbit Speed:"
android:textAlignment="center" />
<SeekBar
android:id="@+id/orbitSpeedBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp" />
<TextView
android:id="@+id/rotationHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Rotation Speed:"
android:textAlignment="center"
android:paddingTop="2dp" />
<SeekBar
android:id="@+id/rotationSpeedBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp" />
</LinearLayout>
// 需要引入这个类
import com.google.ar.sceneform.rendering.ViewRenderable;
// 后续是拿 testViewRenderable 放到场景中去
private ViewRenderable testViewRenderable;
ViewRenderable.builder()
.setView(this, R.layout.test_view)
.build()
.thenAccept(renderable -> controlViewRenderable = renderable);
可渲染对象创建完后,就是要把它放入场景中。
构建场景并与之互动
在不使用 AR 的情况下渲染场景,可以利用 SceneView
类渲染 3D 场景,无需使用设备的摄像头或 AR 会话。这在没有 AR 功能的应用中预览 3D 对象,或在不支持 AR 的设备上提供替代功能时十分有用。
当用户触摸屏幕时,Sceneform 会将触摸事件传递至连接到节点与场景的事件处理程序和侦听器。不过可以直接利用ArFragment
,这个已经封装了多种触摸事件的处理,ArFragment
已添加对点按(选择)、拖动(移动)、双指张合(缩放)和倾斜(旋转)手势的支持。
创建自定义节点来构建画面,个人感觉这跟浏览器的DOM节点很像,前面创建的可渲染对象就是在这里使用的。节点更新可以使用动画来呈现效果,还可以设置一个灯光的位置,达到物体有阴影的效果。
// 场景更新时执行的方法
scene.addOnUpdateListener(
(FrameTime frameTime) -> {
if (faceRegionsRenderable == null || faceMeshTexture == null) {
return;
Collection<AugmentedFace> faceList =
sceneView.getSession().getAllTrackables(AugmentedFace.class);
// Make new AugmentedFaceNodes for any new faces.
for (AugmentedFace face : faceList) {
if (!faceNodeMap.containsKey(face)) {
AugmentedFaceNode faceNode = new AugmentedFaceNode(face);
faceNode.setParent(scene);
faceNode.setFaceRegionsRenderable(faceRegionsRenderable);
faceNode.setFaceMeshTexture(faceMeshTexture);
// controlNode 是我增加的一个节点
Node controlNode = new Node();
// 放入可渲染对象
controlNode.setRenderable(controlViewRenderable);
// 缩放节点大小
controlNode.setLocalScale(new Vector3(0.25f, 0.25f, 0.25f));
// 设置节点位置
controlNode.setLocalPosition(new Vector3(0.0f, 0.0f, 0.0f));
// 挂在faceNode下
faceNode.addChild(controlNode);
faceNodeMap.put(face, faceNode);
// Remove any AugmentedFaceNodes associated with an AugmentedFace that stopped tracking.
Iterator<Map.Entry<AugmentedFace, AugmentedFaceNode>> iter =
faceNodeMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<AugmentedFace, AugmentedFaceNode> entry = iter.next();
AugmentedFace face = entry.getKey();
if (face.getTrackingState() == TrackingState.STOPPED) {
AugmentedFaceNode faceNode = entry.getValue();
faceNode.setParent(null);
iter.remove();
3D空间如何定位Node
上面讲到把可渲染对象放到 Node 里,然后把 Node 挂载在场景中就可以呈现视图效果。那在现实场景中如何定位一个 Node 呢?分析目前的示例代码,发现做法基本都是在 fragment 更新的时候做的处理:1、监听当前 fragment 在检测平面的时候;2、监听当前 fragment 更新。
arFragment.setOnTapArPlaneListener(this::onPlaneTap);
private void onPlaneTap(HitResult hitResult, Plane unusedPlane, MotionEvent unusedMotionEvent) {
if (andyRenderable == null || hatRenderable == null) {
return;
// Create the Anchor.
Anchor anchor = hitResult.createAnchor();
// ....
arFragment.getArSceneView().getScene().addOnUpdateListener(this::onFrameUpdate);
private void onFrameUpdate(FrameTime unusedframeTime) {
Frame frame = arFragment.getArSceneView().getArFrame();
if (frame == null) {
return;
if (frame.getCamera().getTrackingState() != TrackingState.TRACKING) {
return;
float[] position = { 0, 0, -1f };
float[] rotation = { 0, 0, 0, 0.1f };
Session session = arFragment.getArSceneView().getSession();
Anchor myAnchor = session.createAnchor(new Pose(position, rotation));
anchorNode = new AnchorNode(myAnchor);