Intermediate: Integrate Huawei Scene Kit Fine-Grained Graphics APIs in Android App
Overview
In this article, I will create a Demo application which represent implementation of Fine-Grained Graphics APIs which is powered by Scene Kit. In this application I have implemented Scene Kit. It represent a demo of premium and rich graphics app.
Introduction: Scene Kit Fine-Grained Graphics
Scene Kit is a lightweight rendering engine that features high performance and low consumption. It provides advanced descriptive APIs for you to edit, operate, and render 3D materials. Furthermore, Scene Kit uses physically based rendering (PBR) pipelines to generate photorealistic graphics.
HMS Fine-Grained Graphics SDK comprises a set of highly scalable graphics rendering APIs, using which developer can build complex graphics functions into their apps, such as 3D model animation playback and AR motion capture and display.
Prerequisite
- AppGallery Account
- Android Studio 3.X
- SDK Platform 19 or later
- Gradle 4.6 or later
- HMS Core (APK) 5.0.0.300 or later
- Huawei Phone EMUI 8.0 or later
- Non-Huawei Phone Android 7.0 or later
App Gallery Integration process
Sign In and Create or Choose a project on AppGallery Connect portal.
Navigate to Project settings and download the configuration file.
Navigate to General Information, and then provide Data Storage location.
App Development
Create A New Project, choose Empty Activity > Next.
Configure Project Gradle.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' }
}
dependencies {
classpath "com.android.tools.build:gradle:3.6.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Configure App Gradle.
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.huawei.scenekit:scenekit-render-foundation:5.1.0.300'
implementation 'com.huawei.scenekit:scenekit-render-extension:5.1.0.300'
}
APIs Overview
Before calling any fine-grained graphics API, initialize the Scene Kit class first. This class provides two initialization APIs: synchronous API and asynchronous API.
Synchronous API initializeSync: Throw an UpdateNeededException, from which you can obtain an UpdateNeededException instance. Then call the getIntent method of the instance to obtain the update Intent.
public void initializeSync(Context context): Initializes synchronously.
Asynchronous API initialize: Trigger the callback method onUpdateNeeded of SceneKit.OnInitEventListener, and pass the update Intent as an input parameter.
public void initialize(Context context, SceneKit.OnInitEventListener listener): Initializes asynchronously.
MainActivity.java
package com.huawei.hms.scene.demo.render;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;import com.huawei.hms.scene.common.base.error.exception.UpdateNeededException;
import com.huawei.hms.scene.sdk.render.SceneKit;public class MainActivity extends AppCompatActivity {
private static final int REQ_CODE_UPDATE_SCENE_KIT = 10001;
private static final int RES_CODE_UPDATE_SUCCESS = -1; private boolean initialized = false; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} public void onBtnRenderViewDemoClicked(View view) {
if (!initialized) {
initializeSceneKit();
return;
}
startActivity(new Intent(this, RenderViewActivity.class));
} private void initializeSceneKit() {
if (initialized) {
return;
}
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) {
startActivityForResult(e.getIntent(), REQ_CODE_UPDATE_SCENE_KIT);
} catch (Exception e) {
Toast.makeText(this, "failed to initialize SceneKit: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
} @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();
}
}
}
}
RenderViewActivity.java
package com.huawei.hms.scene.demo.render;import android.net.Uri;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.WindowManager;
import android.widget.Toast;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import com.huawei.hms.scene.math.Quaternion;
import com.huawei.hms.scene.math.Vector3;
import com.huawei.hms.scene.sdk.render.Animator;
import com.huawei.hms.scene.sdk.render.Camera;
import com.huawei.hms.scene.sdk.render.Light;
import com.huawei.hms.scene.sdk.render.Model;
import com.huawei.hms.scene.sdk.render.Node;
import com.huawei.hms.scene.sdk.render.RenderView;
import com.huawei.hms.scene.sdk.render.Renderable;
import com.huawei.hms.scene.sdk.render.Resource;
import com.huawei.hms.scene.sdk.render.Texture;
import com.huawei.hms.scene.sdk.render.Transform;import java.lang.ref.WeakReference;
import java.util.List;public class RenderViewActivity extends AppCompatActivity {
private static final class ModelLoadEventListener implements Resource.OnLoadEventListener<Model> {
private final WeakReference<RenderViewActivity> weakRef; public ModelLoadEventListener(WeakReference<RenderViewActivity> weakRef) {
this.weakRef = weakRef;
} @Override
public void onLoaded(Model model) {
RenderViewActivity renderViewActivity = weakRef.get();
if (renderViewActivity == null || renderViewActivity.destroyed) {
Model.destroy(model);
return;
} renderViewActivity.model = model;
renderViewActivity.modelNode = renderViewActivity.renderView.getScene().createNodeFromModel(model);
renderViewActivity.modelNode.getComponent(Transform.descriptor())
.setPosition(new Vector3(0.f, 0.f, 0.f))
.scale(new Vector3(0.02f, 0.02f, 0.02f)); renderViewActivity.modelNode.traverseDescendants(descendant -> {
Renderable renderable = descendant.getComponent(Renderable.descriptor());
if (renderable != null) {
renderable
.setCastShadow(true)
.setReceiveShadow(true);
}
}); Animator animator = renderViewActivity.modelNode.getComponent(Animator.descriptor());
if (animator != null) {
List<String> animations = animator.getAnimations();
if (animations.isEmpty()) {
return;
}
animator
.setInverse(false)
.setRecycle(true)
.setSpeed(1.0f)
.play(animations.get(0));
}
} @Override
public void onException(Exception e) {
RenderViewActivity renderViewActivity = weakRef.get();
if (renderViewActivity == null || renderViewActivity.destroyed) {
return;
}
Toast.makeText(renderViewActivity, "failed to load model: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
} private static final class SkyBoxTextureLoadEventListener implements Resource.OnLoadEventListener<Texture> {
private final WeakReference<RenderViewActivity> weakRef; public SkyBoxTextureLoadEventListener(WeakReference<RenderViewActivity> weakRef) {
this.weakRef = weakRef;
} @Override
public void onLoaded(Texture texture) {
RenderViewActivity renderViewActivity = weakRef.get();
if (renderViewActivity == null || renderViewActivity.destroyed) {
Texture.destroy(texture);
return;
} renderViewActivity.skyBoxTexture = texture;
renderViewActivity.renderView.getScene().setSkyBoxTexture(texture);
} @Override
public void onException(Exception e) {
RenderViewActivity renderViewActivity = weakRef.get();
if (renderViewActivity == null || renderViewActivity.destroyed) {
return;
}
Toast.makeText(renderViewActivity, "failed to load texture: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
} private static final class SpecularEnvTextureLoadEventListener implements Resource.OnLoadEventListener<Texture> {
private final WeakReference<RenderViewActivity> weakRef; public SpecularEnvTextureLoadEventListener(WeakReference<RenderViewActivity> weakRef) {
this.weakRef = weakRef;
} @Override
public void onLoaded(Texture texture) {
RenderViewActivity renderViewActivity = weakRef.get();
if (renderViewActivity == null || renderViewActivity.destroyed) {
Texture.destroy(texture);
return;
} renderViewActivity.specularEnvTexture = texture;
renderViewActivity.renderView.getScene().setSpecularEnvTexture(texture);
} @Override
public void onException(Exception e) {
RenderViewActivity renderViewActivity = weakRef.get();
if (renderViewActivity == null || renderViewActivity.destroyed) {
return;
}
Toast.makeText(renderViewActivity, "failed to load texture: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
} private static final class DiffuseEnvTextureLoadEventListener implements Resource.OnLoadEventListener<Texture> {
private final WeakReference<RenderViewActivity> weakRef; public DiffuseEnvTextureLoadEventListener(WeakReference<RenderViewActivity> weakRef) {
this.weakRef = weakRef;
} @Override
public void onLoaded(Texture texture) {
RenderViewActivity renderViewActivity = weakRef.get();
if (renderViewActivity == null || renderViewActivity.destroyed) {
Texture.destroy(texture);
return;
} renderViewActivity.diffuseEnvTexture = texture;
renderViewActivity.renderView.getScene().setDiffuseEnvTexture(texture);
} @Override
public void onException(Exception e) {
RenderViewActivity renderViewActivity = weakRef.get();
if (renderViewActivity == null || renderViewActivity.destroyed) {
return;
}
Toast.makeText(renderViewActivity, "failed to load texture: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
} private boolean destroyed = false; private RenderView renderView; private Node cameraNode;
private Node lightNode; private Model model;
private Texture skyBoxTexture;
private Texture specularEnvTexture;
private Texture diffuseEnvTexture;
private Node modelNode; private GestureDetector gestureDetector;
private ScaleGestureDetector scaleGestureDetector; @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
renderView = findViewById(R.id.render_view);
prepareScene();
loadModel();
loadTextures();
addGestureEventListener();
} @Override
protected void onResume() {
super.onResume();
renderView.resume();
} @Override
protected void onPause() {
super.onPause();
renderView.pause();
} @Override
protected void onDestroy() {
destroyed = true;
renderView.destroy();
super.onDestroy();
} private void loadModel() {
Model.builder()
.setUri(Uri.parse("Spinosaurus_animation/scene.gltf"))
.load(this, new ModelLoadEventListener(new WeakReference<>(this)));
} private void loadTextures() {
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)));
} 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));
} 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;
}
});
renderView.addOnTouchEventListener(motionEvent -> {
boolean result = scaleGestureDetector.onTouchEvent(motionEvent);
result = gestureDetector.onTouchEvent(motionEvent) || result;
return result;
});
}
}
App Build Result
Tips and Tricks
- The fine-grained graphics SDK provides feature-rich graphics APIs, any of which developer can choose to integrate into their app separately as needed to create premium graphics apps.
- Developer can use either the fine-grained graphics SDK or the scenario-based graphics SDK as needed, but not both in an app.
- The scenario-based graphics SDK provides highly encapsulated and intuitive graphics APIs, which enables you to implement desired functions for specific scenarios with little coding.
Conclusion
In this article, we have learned how to integrate Scene Kit with Fine Grained Graphics API in android application.
Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.
References
HMS Scene Kit Docs — https://developer.huawei.com/consumer/en/doc/development/graphics-Guides/fine-grained-overview-0000001073484401