图形引擎服务(Scene Kit)是华为面向开发者提供的轻量级3D图形场景渲染服务,为开发者提供高级描述性API。

您可选择使用原子化接口SDK或场景化接口SDK集成图形引擎服:

您将建立什么

跟随本篇Codelab您将学会如何快速集成华为图形引擎服务,并使用已经创建好的Demo Project体验图形引擎服务在不同的使用场景提供的3D渲染能力。

通过Demo Project您可以体验到:

您将会学到什么

跟随本篇Codelab您将学会:

硬件要求

软件要求

需要的知识点

华为图形引擎服务为纯端侧能力,无须接入华为AGC即可使用。

1、打开Android Studio,选择"File"-> "Open",选择SceneKitDemo-RenderFoundation或SceneKitDemo-Scenario示例代码解压后的路径。

2、查看仓库路径、依赖包、权限、混淆脚本的配置。示例代码中已经配置完成,在您自己的工程中,可参照Demo中进行配置或修改。

原子化接口

初始化

在调用任意原子化接口前,需要先使用SceneKit类进行初始化。请勿在setContentView之前调用初始化方法,以防不必要的错误。

  1. 创建两个Activity:MainActivity和SampleActivity。其中MainActivity负责完成SceneKit初始化,SampleActivity用于容纳渲染视图,并呈现最终效果。
    private static final int REQ_CODE_UPDATE_SCENE_KIT = 10001; private boolean initialized = false; private void initializeSceneKit() { // 如果已经初始化,不再重复初始化。 if (initialized) { return; } // 创建SceneKit属性,配置AppId与图形后端API。 SceneKit.Property property = SceneKit.Property.builder() .setAppId("${app_id}") .setGraphicsBackend(SceneKit.Property.GraphicsBackend.GLES) .build(); try { // 使用同步接口进行初始化。 SceneKit.getInstance() .setProperty(property) .initializeSync(getApplicationContext()); initialized = true; Toast.makeText(this, "SceneKit initialized", Toast.LENGTH_SHORT).show(); } catch (UpdateNeededException e) { // 捕获需要升级异常,拉起升级Activity。 startActivityForResult(e.getIntent(), REQ_CODE_UPDATE_SCENE_KIT); } catch (Exception e) { // 处理初始化异常。 Toast.makeText(this, "failed to initialize SceneKit: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }
  2. 在MainActivity中添加初始化标识和初始化方法。在初始化方法中设置SceneKit全局属性,并使用同步初始化接口initializeSync()进行SceneKit初始化。
  3. 复写MainActivity的onActivityResult方法,处理升级结果。
    // resultCode为-1时代表升级成功,其他resultCode均代表升级失败 private static final int RES_CODE_UPDATE_SUCCESS = -1; @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); // 如果升级成功,尝试重新初始化。 if (requestCode == REQ_CODE_UPDATE_SCENE_KIT && resultCode == RES_CODE_UPDATE_SUCCESS) { try { SceneKit.getInstance() .initializeSync(getApplicationContext()); initialized = true; Toast.makeText(this, "SceneKit initialized", Toast.LENGTH_SHORT).show(); } catch (Exception e) { // 重新尝试初始化时不再捕获升级异常。 Toast.makeText(this, "failed to initialize SceneKit: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } }
  4. 在MainActivity的Layout文件中添加按钮,用于跳转至SampleActivity。
    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/btn_render_view_demo_text" android:onClick="onBtnRenderViewDemoClicked"/> </LinearLayout>
  5. 在MainActivity中添加按钮回调。
    public void onBtnRenderViewDemoClicked(View view) { // 如果未初始化,先初始化。 if (!initialized) { initializeSceneKit(); return; } // 跳转到SampleActivity。 startActivity(new Intent(this, SampleActivity.class)); }

添加渲染视图

渲染视图是原子化接口的核心。其内部已经实现了复杂的渲染流程,您只需要将精力放在场景布置管理,构建想要的渲染场景。

  1. 在SampleActivity的Layout文件中添加渲染视图。
    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.huawei.hms.scene.sdk.render.RenderView android:id="@+id/render_view" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
  2. 在SampleActivity的onCreate方法中寻找渲染视图,并将其保存至局部变量。
    private RenderView renderView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample); // 根据id查找添加的渲染视图并将其保存至局部变量。 renderView = findViewById(R.id.render_view); }
  3. 复写SampleActivity的onResume()、onPause()、onDestroy()方法,并调用渲染视图的resume()、pause()、destroy()方法,如果不调用上述三个方法,渲染视图将无法正常工作。
    @Override protected void onResume() { super.onResume(); // 通知渲染视图继续渲染 renderView.resume(); } @Override protected void onPause() { super.onPause(); // 通知渲染视图暂停渲染 renderView.pause(); } @Override protected void onDestroy() { renderView.destroy(); // 通知渲染视图销毁 super.onDestroy(); }

场景布置

场景的布置工作主要在相机组件、灯光组件的设置上。如果需要让渲染视图工作,需要添加至少一个激活的相机组件与若干个灯光组件,将它们挂载到不同的节点上,并调整节点位置,达到理想效果。

  1. 添加两个节点变量用于存放相机组件和灯光组件,添加prepareScene()方法用于布置场景。
    private Node cameraNode; private Node lightNode; private void prepareScene() { // 获取屏幕信息,用于设置相机视窗比例。 WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); DisplayMetrics displayMetrics = new DisplayMetrics(); windowManager.getDefaultDisplay().getMetrics(displayMetrics); // 获取渲染视图的场景实例,并创建一个节点用于挂载相机组件。 cameraNode = renderView.getScene().createNode("mainCameraNode"); // 为节点添加相机组件,并设置投影模式、近裁剪平面、远裁剪平面、视场角、视窗比例、是否激活。 cameraNode.addComponent(Camera.descriptor()) .setProjectionMode(Camera.ProjectionMode.PERSPECTIVE) .setNearClipPlane(.1f) .setFarClipPlane(1000.f) .setFOV(60.f) .setAspect((float) displayMetrics.widthPixels / displayMetrics.heightPixels) .setActive(true); // 获取节点的变换组件,修改相机节点位置。 cameraNode.getComponent(Transform.descriptor()) .setPosition(new Vector3(0, 5.f, 30.f)); // 获取渲染视图的场景实例,并创建一个节点用于挂载灯光节点。 lightNode = renderView.getScene().createNode("mainLightNode"); // 为节点添加灯光组件,并设置灯光类型、颜色、光照强度、是否投射阴影。 lightNode.addComponent(Light.descriptor()) .setType(Light.Type.POINT) .setColor(new Vector3(1.f, 1.f, 1.f)) .setIntensity(1.f) .setCastShadow(false); // 获取节点的变换组件,修改灯光节点位置。 lightNode.getComponent(Transform.descriptor()) .setPosition(new Vector3(3.f, 3.f, 3.f)); }
  2. 在SampleActivity的onCreate()方法中调用prepareScene()方法,在SampleActivity创建后布置场景。
    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { // ...... prepareScene(); }

资源加载与使用

在原子化接口中,资源分为模型和纹理两类。资源的加载是异步的,加载时机可由您自行选择。本小节将分别介绍模型资源、天空盒纹理资源、环境光照纹理资源的加载与使用。

  1. 添加资源文件至Assets文件夹。
  2. 在SampleActivity中添加Activity销毁标识。
    private boolean destroyed = false; @Override protected void onDestroy() { destroyed = true; renderView.destroy(); super.onDestroy(); }
  3. 在SampleActivity中添加局部变量用于存储资源,添加资源加载方法。
    private Model model; private Texture skyBoxTexture; private Texture specularEnvTexture; private Texture diffuseEnvTexture; private Node modelNode; private void loadModel() { // 后面的步骤中会补充方法主体。 } private void loadTextures() { // 后面的步骤中会补充方法主题。 }
  4. 添加模型资源加载事件回调实现。
    private static final class ModelLoadEventListener implements Resource.OnLoadEventListener<Model> { // 使用弱引用防止内存泄漏。 private final WeakReference<SampleActivity> weakRef; public ModelLoadEventListener(WeakReference<SampleActivity> weakRef) { this.weakRef = weakRef; } @Override public void onLoaded(Model model) { SampleActivity sampleActivity = weakRef.get(); // 如果Activity已经销毁,及时释放加载的资源。 if (sampleActivity == null || sampleActivity.destroyed) { Model.destroy(model); return; } // 保存模型资源至局部变量。 sampleActivity.model = model; // 加载模型至场景。 sampleActivity.modelNode = sampleActivity.renderView.getScene().createNodeFromModel(model); // 获取模型节点的变换组件,设置节点位置与缩放。 sampleActivity.modelNode.getComponent(Transform.descriptor()) .setPosition(new Vector3(0.f, 0.f, 0.f)) .scale(new Vector3(0.02f, 0.02f, 0.02f)); } @Override public void onException(Exception e) { SampleActivity sampleActivity = weakRef.get(); if (sampleActivity == null || sampleActivity.destroyed) { return; } Toast.makeText(sampleActivity, "failed to load model: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }
  5. 添加天空盒纹理资源加载事件回调实现。
    private static final class SkyBoxTextureLoadEventListener implements Resource.OnLoadEventListener<Texture> { private final WeakReference<SampleActivity> weakRef; public SkyBoxTextureLoadEventListener(WeakReference<SampleActivity> weakRef) { this.weakRef = weakRef; } @Override public void onLoaded(Texture texture) { SampleActivity sampleActivity = weakRef.get(); if (sampleActivity == null || sampleActivity.destroyed) { Texture.destroy(texture); return; } // 保存纹理资源至局部变量。 sampleActivity.skyBoxTexture = texture; // 为场景设置天空盒纹理。 sampleActivity.renderView.getScene().setSkyBoxTexture(texture); } @Override public void onException(Exception e) { SampleActivity sampleActivity = weakRef.get(); if (sampleActivity == null || sampleActivity.destroyed) { return; } Toast.makeText(sampleActivity, "failed to load texture: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }
  6. 添加镜面反射纹理资源加载事件回调实现。
    private static final class SpecularEnvTextureLoadEventListener implements Resource.OnLoadEventListener<Texture> { private final WeakReference<SampleActivity> weakRef; public SpecularEnvTextureLoadEventListener(WeakReference<SampleActivity> weakRef) { this.weakRef = weakRef; } @Override public void onLoaded(Texture texture) { SampleActivity sampleActivity = weakRef.get(); if (sampleActivity == null || sampleActivity.destroyed) { Texture.destroy(texture); return; } // 保存纹理资源至局部变量。 sampleActivity.specularEnvTexture = texture; // 为场景设置镜面反射环境光照纹理。 sampleActivity.renderView.getScene().setSpecularEnvTexture(texture); } @Override public void onException(Exception e) { SampleActivity sampleActivity = weakRef.get(); if (sampleActivity == null || sampleActivity.destroyed) { return; } Toast.makeText(sampleActivity, "failed to load texture: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }
  7. 添加漫反射纹理资源加载事件回调实现。
    private static final class DiffuseEnvTextureLoadEventListener implements Resource.OnLoadEventListener<Texture> { private final WeakReference<SampleActivity> weakRef; public DiffuseEnvTextureLoadEventListener(WeakReference<SampleActivity> weakRef) { this.weakRef = weakRef; } @Override public void onLoaded(Texture texture) { SampleActivity sampleActivity = weakRef.get(); if (sampleActivity == null || sampleActivity.destroyed) { Texture.destroy(texture); return; } // 保存纹理资源至局部变量。 sampleActivity.diffuseEnvTexture = texture; // 为场景设置漫反射环境光照纹理。 sampleActivity.renderView.getScene().setDiffuseEnvTexture(texture); } @Override public void onException(Exception e) { SampleActivity sampleActivity = weakRef.get(); if (sampleActivity == null || sampleActivity.destroyed) { return; } Toast.makeText(sampleActivity, "failed to load texture: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }
  8. 实现资源加载方法。
    private void loadModel() { // 获取模型建造者实例、设置资源URI并加载。 Model.builder() // 素材路径根据实际路径修改。 .setUri(Uri.parse("Spinosaurus_animation/scene.gltf")) .load(this, new ModelLoadEventListener(new WeakReference<>(this))); } private void loadTextures() { // 获取纹理建造者实例,设置资源URI并加载 Texture.builder() // 天空盒素材路径根据实际路径修改。 .setUri(Uri.parse("Forest/output_skybox.dds")) .load(this, new SkyBoxTextureLoadEventListener(new WeakReference<>(this))); Texture.builder() // 镜面反射素材路径根据实际路径修改。 .setUri(Uri.parse("Forest/output_specular.dds")) .load(this, new SpecularEnvTextureLoadEventListener(new WeakReference<>(this))); Texture.builder() // 漫反射素材路径根据实际路径修改。 .setUri(Uri.parse("Forest/output_diffuse.dds")) .load(this, new DiffuseEnvTextureLoadEventListener(new WeakReference<>(this))); }
  9. 在SampleActivity的onCreate()方法中调用资源加载方法。
    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { // ...... loadModel(); loadTextures(); }

动画播放

  1. 在模型资源加载后,获取动画播放器组件。
    public void onLoaded(Model model) { // .... // 获取动画播放器组件 Animator animator = sampleActivity.modelNode.getComponent(Animator.descriptor()); // 判断模型节点是否包含动画播放器组件,如果包含则说明模型资源中包含动画数据。 if (animator != null) { // 准备进行动画控制。 // .... } }
  2. 获取模型资源中所有动画名称。
    if (animator != null) { // 获取模型资源中所有动画名称。 List<String> animations = animator.getAnimations(); if (animations.isEmpty()) { return; } // .... }
  3. 设置动画播放属性与状态。
    if (animator != null) { // ... // 设置非倒序播放、设置循环播放、设置播放速度1.0,设置播放素材中的第一段动画。 animator .setInverse(false) .setRecycle(true) .setSpeed(1.0f) .play(animations.get(0)); }

手势事件处理

渲染视图本身支持Android手势事件的传递,您可以使用addOnTouchEventListener方法添加手势事件监听器,实现自己需要的功能。

下面的例子将实现简单的滑动、缩放手势,并作用于物体。

  1. 在SampleActivity中添加局部变量存储手势处理器,添加手势处理器初始化与手势监听器设置方法。
    private GestureDetector gestureDetector; private ScaleGestureDetector scaleGestureDetector; private void addGestureEventListener() { // 下面的步骤中会补充方法本体。 }
  2. 在addGestureEventListener方法中初始化手势处理器。
    private void addGestureEventListener() { // 创建滑动手势处理器。 gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (modelNode != null) { // 当捕获到滑动手势后,旋转模型节点。 modelNode.getComponent(Transform.descriptor()) .rotate(new Quaternion(Vector3.UP, -0.001f * distanceX)); } return true; } }); // 创建缩放手势处理器。 scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override public boolean onScale(ScaleGestureDetector detector) { if (modelNode != null) { // 当捕获到缩放手势后,缩放模型节点。 float factor = detector.getScaleFactor(); modelNode.getComponent(Transform.descriptor()) .scale(new Vector3(factor, factor, factor)); } return true; } }); }
  3. 添加手势事件监听器至渲染视图,并调用手势处理器进行处理。
    private void addGestureEventListener() { // ...... // 添加一个新的手势事件监听器至渲染视图。 renderView.addOnTouchEventListener((e) -> { // 先传递手势事件给缩放手势处理器处理。 boolean result = scaleGestureDetector.onTouchEvent(e); // 传递手势事件给滑动手势处理器处理。 result = gestureDetector.onTouchEvent(e) || result; // 返回事件是否已经被消费。 return result; }); }
  4. 在SampleActivity的onCreate()方法中调用addGestureEventListener()方法。
    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { // ...... addGestureEventListener(); }

资源清理

已经加载的资源在无需再使用时,需要及时调用资源清理接口完成清理,否则可能会面临内存泄漏的风险。

  1. 在SampleActivity的onDestroy()方法中调用模型显式清理方法。
    @Override protected void onDestroy() { // ...... if (model != null) { // 显式清理模型资源。 Model.destroy(model); } super.onDestroy(); }
  2. 在SampleActivity的onDestroy()方法中调用纹理显式清理方法。
    @Override protected void onDestroy() { // ...... if (skyBoxTexture != null) { // 显式清理天空盒纹理资源。 Texture.destroy(skyBoxTexture); } if (specularEnvTexture != null) { // 显式清理镜面反射环境光照纹理资源。 Texture.destroy(specularEnvTexture); } if (diffuseEnvTexture != null) { // 显式清理漫反射环境光照纹理资源。 Texture.destroy(diffuseEnvTexture); } super.onDestroy(); }
  3. 在SampleActivity的onDestroy()方法中调用垃圾回收方法。
    @Override protected void onDestroy() { // ...... // 触发垃圾回收。 ResourceFactory.getInstance().gc(); super.onDestroy(); }

场景化接口

本节将为为您展示如何使用场景化接口SDK进行应用开发,分别指引您如何使用SceneView、ARView与FaceView。

SceneView

1、创建一个类取名为SceneSampleView,使其继承自SceneView。

public class SceneSampleView extends SceneView { public SceneSampleView(Context context) { super(context); } public SceneSampleView(Context context, AttributeSet attributeSet) { super(context, attributeSet); } }

2、重写surfaceCreated函数,并调用super方法。

@Override public void surfaceCreated(SurfaceHolder holder) { super.surfaceCreated(holder); }

3、在surfaceCreated函数中加载素材和纹理贴图。

loadScene("SceneView/scene.gltf"); loadSkyBox("SceneView/skyboxTexture.dds"); loadSpecularEnvTexture("SceneView/specularEnvTexture.dds"); loadDiffuseEnvTexture("SceneView/diffuseEnvTexture.dds");

4、(可选)重写其他surface生命周期函数。

@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { super.surfaceChanged(holder, format, width, height); } @Override public void surfaceDestroyed(SurfaceHolder holder) { super.surfaceDestroyed(holder); } @Override public boolean onTouchEvent(MotionEvent motionEvent) { return super.onTouchEvent(motionEvent); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); }

5、创建一个继承自Activity的SceneViewActivity,并在onCreate函数中调用setContentView加载SceneSampleView。

public class SceneViewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new SceneSampleView(this)); } }

6、在MainActivity中可通过Button点击事件触发SceneViewActivity,显示渲染结果。

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onBtnSceneViewDemoClicked(View view) { startActivity(new Intent(this, SceneViewActivity.class)); } }

ARView

1、创建一个ARViewActivity,使其继承自Activity。添加一个Button按钮用于加载素材。

public class ARViewActivity extends Activity { private ARView mARView; private Button mButton; private boolean isLoadResource = false; }

2、将ARView添加到Layout布局中。

<com.huawei.hms.scene.sdk.ARView android:id="@+id/ar_view" android:layout_width="match_parent" android:layout_height="match_parent"> </com.huawei.hms.scene.sdk.ARView>

3、重写onCreate方法,并获取ARView。

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ar_view); mARView = findViewById(R.id.ar_view); mButton = findViewById(R.id.button); }

4、可在onCreate方法中使用一个Switch按钮控制辅助显示平面是否打开。

Switch mSwitch = findViewById(R.id.show_plane_view); mSwitch.setChecked(true); mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mARView.enablePlaneDisplay(isChecked); } });

5、增加按钮回调方法。首次点击按钮加载素材,再次点击按钮清除素材。

public void onBtnClearResourceClicked(View view) { if (!isLoadResource) { mARView.loadAsset("ARView/scene.gltf"); isLoadResource = true; mButton.setText(R.string.btn_text_clear_resource); } else { mARView.clearResource(); mARView.loadAsset(""); isLoadResource = false; mButton.setText(R.string.btn_text_load); } }

6、重写onPause方法,并调用ARView的onPause方法。

@Override protected void onPause() { super.onPause(); mARView.onPause(); }

7、重写onResume方法,并调用ARView的onResume方法。

@Override protected void onResume() { super.onResume(); mARView.onResume(); }

8、重写onDestroy方法,并调用ARView的destroy方法。

@Override protected void onDestroy() { super.onDestroy(); mARView.destroy(); }

9、【可选】加载完素材后,可通过setInitialPose设置3D素材初始的缩放系数与旋转四元数。

float[] scale = new float[] { 0.1f, 0.1f, 0.1f }; float[] rotation = new float[] { 0.707f, 0.0f, -0.707f, 0.0f }; mARView.setInitialPose(scale, rotation);

FaceView

1、创建一个FaceViewActivity,使其继承自Activity。

public class FaceViewActivity extends Activity { private FaceView mFaceView; }

2、将FaceView添加到Layout布局中。

<com.huawei.hms.scene.sdk.FaceView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/face_view" app:sdk_type="AR_ENGINE"> </com.huawei.hms.scene.sdk.FaceView>

3、将Switch添加到Layout布局中。

<Switch android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/switch_view" android:layout_alignParentTop="true" android:layout_marginTop="15dp" android:layout_alignParentEnd="true" android:layout_marginEnd ="15dp" android:text="@string/face_view" android:theme="@style/AppTheme" tools:ignore="RelativeOverlap" />

4、重写onCreate方法,并获取FaceView。

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_face_view); mFaceView = findViewById(R.id.face_view); }

5、在onCreate方法中创建Switch按钮,并重写监听方法。

final float[] position = { 0.0f, 0.0f, 0.0f }; final float[] rotation = { 1.0f, 0.0f, 0.0f, 0.0f }; final float[] scale = { 1.0f, 1.0f, 1.0f }; Switch mSwitch = findViewById(R.id.switch_view); mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mFaceView.clearResource(); if (isChecked) { // 加载素材 int index = mFaceView.loadAsset("FaceView/fox.glb", LandmarkType.TIP_OF_NOSE); // (可选)设置面部AR位姿的初始值 mFaceView.setInitialPose(index, position, scale, rotation); } }});

6、重写onPause方法,并调用FaceView的onPause方法。

@Override protected void onPause() { super.onPause(); mFaceView.onPause(); }

7、重写onResume方法,并调用FaceView的onResume方法。

@Override protected void onResume() { super.onResume(); mFaceView.onResume(); }

8、重写onDestroy方法,并调用FaceView的destroy方法。

@Override protected void onDestroy() { super.onDestroy(); mFaceView.destroy(); }

自定义3D素材

1、FaceView使用loadAsset()方法加载素材时,通过Landmark指定不同的面部AR挂载点。

int index = mFaceView.loadAsset("scene.gltf", LandmarkType.TIP_OF_NOSE);

2、设置3D素材加载时的初始位姿。

原子化接口与场景化接口分别提供了不同的demo。

原子化接口

原子化接口demo:SceneKitDemo-RenderFoundation,同步完成后,点击"Run‘app'"图标 ,运行Android Studio工程打包生成APK,并安装在测试手机上。

运行结果如下:

点击"RENDERVIERW DEMO"按钮,渲染效果如下:

RenderView Demo渲染效果图中使用的渲染素材为Spinosaurus_animation,作者:seirogan,未进行修改,License:CC Attribution,详情请见CC Attribution

场景化接口

场景化接口Demo:SceneKitDemo-Scenario同步完成后,点击"Run‘app'"图标 ,运行Android Studio工程打包生成APK,并安装在测试手机上。

运行结果如下:

分别点击"SCENE VIEW DEMO"按钮、"AR VIEW DEMO"按钮、"FACE VIEW DEMO"按钮,渲染效果如下图:

干得好,您已经成功完成了Codelab所有步骤并学到了:

您可以阅读下面链接,了解更多相关的信息。

下载 source code

已复制代码