Scene Kit is a lightweight 3D graphics rendering service, which provides you with advanced descriptive APIs. You can equip your app with this service by integrating its fine-grained graphics SDK or scenario-based graphics SDK.

What You Will Create

In this codelab, you will integrate Scene Kit and use the demo project to experience 3D rendering capabilities that Scene Kit provides for different scenarios. Using the demo project, you can do the following:

  • Create a SceneKit class, then perform necessary tasks, such as loading resources, playing animations, and adding gesture processors, to create a 3D animation playback app.
  • Scenario-based graphics SDK:
  • What You Will Learn

    In this codelab, you will learn how to:

    Hardware Requirements

    Software Requirements

    Required Knowledge

    Scene Kit works only depending on the device, without connecting to HUAWEI AppGallery Connect.

    1. Open Android Studio, go to File > Open, and select the directory where the SceneKitDemo-RenderFoundation or SceneKitDemo-Scenario sample code has been decompressed.

    2. Make sure that the Maven repository address, dependency package, permission, and obfuscation script are correctly configured. These items have been configured in the SceneKitDemo sample code. Modify them as needed in your own project.

    Fine-grained Graphics SDK

    Performing Initialization

    Before calling any fine-grained graphics API, use the SceneKit class to complete initialization. Do not call the initialization method before calling setContentView; otherwise, errors may occur.

    1. Create two activities: MainActivity and SampleActivity. MainActivity is used for initializing the SceneKit class, and SampleActivity is used for holding the rendering view and displaying the final rendering effects.
    2. Add the initialization flag and initialization method to MainActivity. Set the global attribute of the SceneKit class in the initialization method and use the synchronous initialization API initializeSync() to initialize the SceneKit class.
      private static final int REQ_CODE_UPDATE_SCENE_KIT = 10001; private boolean initialized = false; private void initializeSceneKit() { // If the initialization is already complete, do not perform it again. if (initialized) { return; } // Create a SceneKit.Property class and set the app ID and graphics backend API. SceneKit.Property property = SceneKit.Property.builder() .setAppId("${app_id}") .setGraphicsBackend(SceneKit.Property.GraphicsBackend.GLES) .build(); try { // Use the synchronous initialization API for initialization. SceneKit.getInstance() .setProperty(property) .initializeSync(getApplicationContext()); initialized = true; Toast.makeText(this, "SceneKit initialized", Toast.LENGTH_SHORT).show(); } catch (UpdateNeededException e) { // Capture the exception that an upgrade is needed and start the upgrade activity. startActivityForResult(e.getIntent(), REQ_CODE_UPDATE_SCENE_KIT); } catch (Exception e) { // Handle the initialization exception. Toast.makeText(this, "failed to initialize SceneKit: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } }
    3. Override the onActivityResult method of MainActivity to process the upgrade result.
      // If the value of resultCode is –1, the upgrade is successful. Other values indicate an upgrade failure. 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 the upgrade is successful, re-initialize the SceneKit class. 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) { // No upgrade need is detected during re-initialization. Toast.makeText(this, "failed to initialize SceneKit: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } }
    4. Add a button to the Layout file of MainActivity to redirect to 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. Add a button callback in MainActivity.
      public void onBtnRenderViewDemoClicked(View view) { // Perform initialization if you have not. if (!initialized) { initializeSceneKit(); return; } // Redirect to SampleActivity. startActivity(new Intent(this, SampleActivity.class)); }

    Adding a Rendering View

    The rendering view, as the core of the fine-grained graphics SDK, helps you quickly create premium scene rendering effects. The rendering view has implemented a complex rendering process, so what you need to do is only to set the scene layout.

    1. Add a rendering view to the Layout file of 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" 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. Find the rendering view in the onCreate method of SampleActivity and save it to a local variable.
      private RenderView renderView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sample); // Find the rendering view you've added based on the ID and save it to a local variable. renderView = findViewById(R.id.render_view); }
    3. Override the onResume(), onPause(), and onDestroy() methods of SampleActivity and call the resume(), pause(), and destroy() methods of the rendering view. If the three methods are not called, the rendering view cannot work properly.
      @Override protected void onResume() { super.onResume(); // Resume rendering in the rendering view. renderView.resume(); } @Override protected void onPause() { super.onPause(); // Pause rendering in the rendering view. renderView.pause(); } @Override protected void onDestroy() { renderView.destroy(); // Destroy the rendering view. super.onDestroy(); }

    Setting the Scene Layout

    You can set the layout for a scene using the Camera and Light components. To make the rendering view work, you need to add at least one activated Camera component and several Light components, attach them to different nodes, and adjust the node positions to achieve your desired rendering effect.

    1. Add two node variables to store the Camera and Light components, and add the prepareScene() method for setting the scene layout.
      private Node cameraNode; private Node lightNode; private void prepareScene() { // Obtain the screen information for setting the aspect ratio of the camera window. WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); DisplayMetrics displayMetrics = new DisplayMetrics(); windowManager.getDefaultDisplay().getMetrics(displayMetrics); // Obtain the scene instance of the rendering view and create a node to hold the Camera component. cameraNode = renderView.getScene().createNode("mainCameraNode"); // Add the Camera component to the node, and set the projection mode, near clipping plane, far clipping plane, field of view (FOV), aspect ratio of the camera window, and activation status. cameraNode.addComponent(Camera.descriptor()) .setProjectionMode(Camera.ProjectionMode.PERSPECTIVE) .setNearClipPlane(.1f) .setFarClipPlane(1000.f) .setFOV(60.f) .setAspect((float) displayMetrics.widthPixels / displayMetrics.heightPixels) .setActive(true); // Obtain the Transform component of the node and change the node position. cameraNode.getComponent(Transform.descriptor()) .setPosition(new Vector3(0, 5.f, 30.f)); // Obtain the scene instance of the rendering view and create a node to hold the Light component. lightNode = renderView.getScene().createNode("mainLightNode"); // Add a Light component to the node and set the light type, light color, light intensity, and whether to cast shadows. lightNode.addComponent(Light.descriptor()) .setType(Light.Type.POINT) .setColor(new Vector3(1.f, 1.f, 1.f)) .setIntensity(1.f) .setCastShadow(false); // Obtain the Transform component of the node and change the node position. lightNode.getComponent(Transform.descriptor()) .setPosition(new Vector3(3.f, 3.f, 3.f)); }
    2. Call the prepareScene() method in the onCreate() method of SampleActivity to set the layout after SampleActivity is created.
      @Override protected void onCreate(@Nullable Bundle savedInstanceState) { // ...... prepareScene(); }

    Loading and Using Resources

    The fine-grained graphics SDK classifies resources into two types: models and textures (skybox textures and image-based lighting (IBL) textures). Resources are loaded in asynchronous mode. You can determine when to load them. This section will show you how to load and use models, skybox textures, and IBL textures.

    1. Add a resource file to the Assets folder.
    2. Add the activity destroy flag to SampleActivity.
      private boolean destroyed = false; @Override protected void onDestroy() { destroyed = true; renderView.destroy(); super.onDestroy(); }
    3. Add local variables to SampleActivity to store resources and add the resource loading method.
      private Model model; private Texture skyBoxTexture; private Texture specularEnvTexture; private Texture diffuseEnvTexture; private Node modelNode; private void loadModel() { // Specific implementation of this method will be added in the following steps. } private void loadTextures() { // Specific implementation of this method will be added in the following steps. }
    4. Add the implementation of model loading event callback.
      private static final class ModelLoadEventListener implements Resource.OnLoadEventListener<Model> { // Use weak reference to prevent memory leakage. private final WeakReference<SampleActivity> weakRef; public ModelLoadEventListener(WeakReference<SampleActivity> weakRef) { this.weakRef = weakRef; } @Override public void onLoaded(Model model) { SampleActivity sampleActivity = weakRef.get(); // If the activity has been destroyed, release the loaded model immediately. if (sampleActivity == null || sampleActivity.destroyed) { Model.destroy(model); return; } // Save the model resource to a local variable. sampleActivity.model = model; // Load the model to the scene. sampleActivity.modelNode = sampleActivity.renderView.getScene().createNodeFromModel(model); // Obtain the Transform component of the model node and set the node position and scaling. 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. Add the implementation of skybox texture loading event callback.
      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; } // Save the skybox texture to a local variable. sampleActivity.skyBoxTexture = texture; // Set the skybox texture for the scene. 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. Add the implementation of specular map loading event callback.
      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; } // Save the specular map to a local variable. sampleActivity.specularEnvTexture = texture; // Set the specular texture for the scene. 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. Add the implementation of diffuse map loading event callback.
      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; } // Save the diffuse map to a local variable. sampleActivity.diffuseEnvTexture = texture; // Set the diffuse texture for the scene. 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. Implement the resource loading method.
      private void loadModel() { // Obtain the model builder instance, set the resource URI, and load the resource. Model.builder() // Replace the resource URI with the actual one. .setUri(Uri.parse("Spinosaurus_animation/scene.gltf")) .load(this, new ModelLoadEventListener(new WeakReference<>(this))); } private void loadTextures() { // Obtain the texture builder instance, set the resource URI, and load the resource. Texture.builder() // Replace the skybox texture URI with the actual one. .setUri(Uri.parse("Forest/output_skybox.dds")) .load(this, new SkyBoxTextureLoadEventListener(new WeakReference<>(this))); Texture.builder() // Replace the specular map URI with the actual one. .setUri(Uri.parse("Forest/output_specular.dds")) .load(this, new SpecularEnvTextureLoadEventListener(new WeakReference<>(this))); Texture.builder() // Replace the diffuse map URI with the actual one. .setUri(Uri.parse("Forest/output_diffuse.dds")) .load(this, new DiffuseEnvTextureLoadEventListener(new WeakReference<>(this))); }
    9. Call the resource loading method in the onCreate() method of SampleActivity.
      @Override protected void onCreate(@Nullable Bundle savedInstanceState) { // ...... loadModel(); loadTextures(); }

    Playing Animations

    1. After a model is loaded, obtain the Animator component.
      public void onLoaded(Model model) { // .... // Obtain the Animator component. Animator animator = sampleActivity.modelNode.getComponent(Animator.descriptor()); // Determine whether the model node has an Animator component. If yes, the model contains animation data. if (animator != null) { // Prepare to control the animation. // .... } }
    2. Obtain the names of all animations in the model.
      if (animator != null) { // Obtain the names of all animations in the model. List<String> animations = animator.getAnimations(); if (animations.isEmpty()) { return; } // .... }
    3. Set the animation playback parameters.
      if (animator != null) { // ... // Set the animation playback parameters, including whether to play in an inverse order, whether to enable repeat mode, playback speed (here, 1.0), and first animation segment. animator .setInverse(false) .setRecycle(true) .setSpeed(1.0f) .play(animations.get(0)); }

    Processing Gesture Events

    The rendering view can pass Android gesture events. You can add a gesture event listener using the addOnTouchEventListener method to capture gesture events, then process them as needed.

    The following procedure will show you how to implement simple gesture controls, including slide and pinch-in/out.

    1. In SampleActivity, add local variables to store the gesture processors, and add the method for initializing the gesture processors and the method for setting the gesture listener.
      private GestureDetector gestureDetector; private ScaleGestureDetector scaleGestureDetector; private void addGestureEventListener() { // Specific implementation of this method will be added in the following steps. }
    2. Initialize the gesture processors in the addGestureEventListener method.
      private void addGestureEventListener() { // Create a slide gesture processor. gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (modelNode != null) { // Rotate the model node after a slide gesture is captured. modelNode.getComponent(Transform.descriptor()) .rotate(new Quaternion(Vector3.UP, -0.001f * distanceX)); } return true; } }); // Create a pinch gesture processor. scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override public boolean onScale(ScaleGestureDetector detector) { if (modelNode != null) { // Resize the model node after a pinch gesture is captured. float factor = detector.getScaleFactor(); modelNode.getComponent(Transform.descriptor()) .scale(new Vector3(factor, factor, factor)); } return true; } }); }
    3. Add a gesture event listener to the rendering view to capture gesture events, and call the gesture processors to process the events.
      private void addGestureEventListener() { // ...... // Add a new gesture event listener to the rendering view. renderView.addOnTouchEventListener((e) -> { // Pass the gesture event to the pinch gesture processor. boolean result = scaleGestureDetector.onTouchEvent(e); // Pass the gesture event to the slide gesture processor. result = gestureDetector.onTouchEvent(e) || result; // Return whether the event has been processed. return result; }); }
    4. Call the addGestureEventListener() method in the onCreate() method of SampleActivity.
      @Override protected void onCreate(@Nullable Bundle savedInstanceState) { // ...... addGestureEventListener(); }

    Cleaning Up Resources

    If a loaded resource is not needed anymore, call the API for cleaning up the resource; otherwise, memory leakage may occur.

    1. In the onDestroy() method of SampleActivity, call the method for explicitly cleaning up models.
      @Override protected void onDestroy() { // ...... if (model != null) { // Explicitly clean up the model. Model.destroy(model); } super.onDestroy(); }
    2. In the onDestroy() method of SampleActivity, call the method for explicitly cleaning up textures.
      @Override protected void onDestroy() { // ...... if (skyBoxTexture != null) { // Explicitly clean up skybox textures. Texture.destroy(skyBoxTexture); } if (specularEnvTexture != null) { // Explicitly clean up specular maps. Texture.destroy(specularEnvTexture); } if (diffuseEnvTexture != null) { // Explicitly clean up diffuse maps. Texture.destroy(diffuseEnvTexture); } super.onDestroy(); }
    3. In the onDestroy() method of SampleActivity, call the method for recycling resources that were used.
      @Override protected void onDestroy() { // ...... // Trigger garbage collection. ResourceFactory.getInstance().gc(); super.onDestroy(); }

    Scenario-based Graphics SDK

    SceneView

    1. Create a class named SceneSampleView that inherits from SceneView.

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

    2. Override the surfaceCreated method in SceneSampleView and call the super method.

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

    3. In the surfaceCreated method, load 3D scene materials, skybox materials, and lighting maps.

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

    4. (Optional) Override other surface lifecycle methods.

    @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. Create a SceneViewActivity that inherits from Activity and call setContentView in the onCreate method to load the SceneSampleView.

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

    6. In MainActivity, start SceneViewActivity through a button tap to display the rendering result.

    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. Create an ARViewActivity that inherits from Activity. Add a Button to load materials.

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

    2. Add an ARView to the 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. Override the onCreate method of ARViewActivity and obtain the 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. Add a Switch button in the onCreate method to set whether to display the lattice plane.

    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. Add a button callback method. Tapping the button once will load a material, and tapping it again will clear the material.

    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. Override the onPause method of ARViewActivity and call the onPause method of the ARView.

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

    7. Override the onResume method of ARViewActivity and call the onResume method of the ARView.

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

    8. Override the onDestroy method of ARViewActivity and call the destroy method of the ARView.

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

    9. (Optional) After the material is loaded, use setInitialPose to set its initial status (scale and rotation).

    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. Create a FaceViewActivity that inherits from Activity.

    public class FaceViewActivity extends Activity { private FaceView mFaceView; }

    2. Add a FaceView to the 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. Add a Switch to the 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. Override the onCreate method of FaceViewActivity and obtain the FaceView.

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

    5. Create a Switch button in the onCreate method and override the listener method.

    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) { // Load materials. int index = mFaceView.loadAsset("FaceView/fox.glb", LandmarkType.TIP_OF_NOSE); // Optional) Set the initial status of a face. mFaceView.setInitialPose(index, position, rotation, scale); } }});

    6. Override the onPause method of FaceViewActivity and call the onPause method of the FaceView.

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

    7. Override the onResume method of FaceViewActivity and call the onResume method of the FaceView.

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

    8. Override the onDestroy method of FaceViewActivity and call the destroy method of the FaceView.

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

    Customizing 3D Materials

    1. When FaceView loads a material through the loadAsset() method, use Landmark to specify a marker position for applying a 3D cartoon.

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

    2. Set the initial status of the loaded material.

    Different demo projects are provided for the fine-grained graphics SDK and scenario-based graphics SDK.

    Fine-grained Graphics SDK

    The demo project of this SDK is SceneKitDemo-RenderFoundation. After synchronizing this project, click the Run ‘app' icon to run the Android Studio project to generate the APK and install it on the test phone.

    After launching the app, you'll see the following screen:

    Tap the RENDERVIERW DEMO button. The rendering effect is as follows:

    The material used in the RenderView demo is Spinosaurus_animation created by seirogan. We have not made any changes to this material. For its license details, please refer to CC Attribution.

    Scenario-based Graphics SDK

    The demo project of this SDK is SceneKitDemo-Scenario. After synchronizing this project, click the Run ‘app' icon to run the Android Studio project to generate the APK and install it on the test phone.

    After launching the app, you'll see the following screen:

    Tapping SCENE VIEW DEMO, AR VIEW DEMO, and FACE VIEW DEMO buttons will show you the following rendering effects, respectively:

    Well done. You have successfully completed this codelab and learned how to:

    For more information, please click the following links:

    Sample Code Download

    Download source code

    Code copied