华为利用市场在2022年HDC大会期间公布了一款3D水流主题,基于华为HMS Core Scene Kit服务能力,展示平面灵动的水流岛屿,可追随用户指尖实现实时流体稳定成果,既趣味又解压。

让变幻莫测的物质来实现咱们在影视和游戏等多种利用场景中的奇思妙想,从晚期步骤沉重的特效制作演变到现在,曾经有了更为轻量易用的解题范式,只需破费10分钟便可打造一个真切的3D流体成果。

什么是Scene Kit流体模仿?

Scene Kit即图形引擎服务,提供轻量级3D图形渲染引擎,能够为游戏、AR & VR等挪动端利用提供易于应用的渲染接口,助力打造粗劣酷炫的视觉体验。

Scene Kit的3D流体技术,目前反对挪动端水、油、岩浆等不同类型的物理实在流体模仿。服务中蕴含原子化接口,场景话接口,离线简模插件,实时光追插件等。

性能优越:基于三维图形渲染框架和算法,提供高性能低功耗的三维平面场景构建能力。

轻量易用:提供场景化挪动利用接口,简化三维图形利用开发,易于为二维图形利用拓展构建三维平面场景。

成果真切:基于物理的渲染能力,提供高画质三维场景成果和沉迷式图形体验。

实操环节:用3D流体实现真切水流成果

开发环境

装置Android Studio 3.6.1及以上。

JDK 1.8(举荐)。

您的利用应满足以下条件:

minSdkVersion 19及以上。

targetSdkVersion 30(举荐)。

compileSdkVersion 30(举荐)。

Gradle 5.4.1及以上(举荐)。

如果同时应用多个HMS Core的服务,则须要应用各个Kit对应的最大值。

测试利用的设施:Android 4.4及以上。

开发配置

具体筹备步骤请参考图形引擎服务开发者联盟官网。

开发筹备

Android Studio的代码库配置在Gradle插件7.0以下版本、7.0版本和7.1及以上版本有所不同。请依据您以后的Gradle插件版本,抉择对应的配置过程。

  1. 在“buildscript > repositories”中配置HMS Core SDK的Maven仓地址。
  2. 如果App中增加了“agconnect-services.json”文件则须要在“buildscript > dependencies”中减少agcp插件配置。关上我的项目级“settings.gradle”文件,配置HMS Core SDK的Maven仓地址。
buildscript {    repositories {        google()        jcenter()        // 配置HMS Core SDK的Maven仓地址。        maven {url 'https://developer.huawei.com/repo/'}    }    dependencies {        ...        // 减少agcp插件配置,举荐您应用最新版本的agcp插件。        classpath 'com.huawei.agconnect:agcp:1.6.0.300'    }}dependencyResolutionManagement {...repositories {google()jcenter() // 配置HMS Core SDK的Maven仓地址。maven {url 'https://developer.huawei.com/repo/'}}}

增加权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"<uses-permission android:name="android.permission.CAMERA" />

开发步骤

  1. 创立两个Activity:MainActivity和SampleActivity。其中MainActivity负责实现SceneKit初始化,SampleActivity用于包容渲染视图,并出现最终成果。
  2. 在MainActivity中增加初始化标识和初始化办法。在初始化办法中设置SceneKit全局属性,并应用同步初始化接口initializeSync初始化SceneKit。
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();    }}
  1. 复写MainActivity的onActivityResult办法,解决降级后果
// resultCode为-1时代表降级胜利,其余resultCode均代表降级失败。private static final int RES_CODE_UPDATE_SUCCESS = -1;@Overrideprotected 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();        }}}
  1. 在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>
  1. 在MainActivity中增加按钮回调。
public void onBtnRenderViewDemoClicked(View view) {    // 如果未初始化,先初始化。    if (!initialized) {        initializeSceneKit();        return;    }    // 跳转到SampleActivity。    startActivity(new Intent(this, SampleActivity.class));}
  1. 新建渲染视图子类XRenderView,须要在这个子类中增加相机与灯光组件,详情请见安排场景。
.public class XRenderView extends RenderView {    public XRenderView(Context context) {        //...        prepareScene();    }    //...}
  1. 创立SampleActivity为展现成果页面
public class DemoActivity extends Activity implements SensorEventListener {    private XRenderView renderView;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        renderView = new XRenderView(this);        setContentView(renderView);        //...    }}
  1. 增加createFluidSecene办法用于流体布局

3D流体组件不反对动静加载,只有在流体场景初始化时设置,才失效

private Node fluidNode;private void createFluidScene(Context context) {    // 创立3D流体节点。    fluidNode = renderView.getScene().createNode("fluidNode");    // 增加3D流体组件。    FluidComponent fluidComponent = fluidNode.addComponent(FluidComponent.descriptor());}
  1. 创立流体边界形态,设置流体体积量
private void createFluidScene(Context context) {    // ...    // 创立球形流体边界形态。    SdfSphereShape sphere = fluidComponent.createSdfSphereShape();    // 设置球体半径。    sphere.setRadius(12.0f);    // 设置流体体积量。    fluidComponent.setFluidVolume(0.4f);}
  1. 注册传感器事件,获取加速度传感器信息
private SensorManager sensorManager;private Sensor sensor;private int rotation;private void createFluidScene(Context context) {    // ...    sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);    // 获取加速度传感器。    sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);}@Overrideprotected void onResume() {    super.onResume();    // 注册传感器事件。    sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);    //...}@Overrideprotected void onPause() {    super.onPause();    // 去注册传感器事件。    sensorManager.unregisterListener(this);    //...}
  1. 复写传感器onSensorChanged办法,依据加速度传感器获取的值,更新流体零碎的重力加速度。
@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {}@Overridepublic void onSensorChanged(SensorEvent event) {    if (event.sensor == null) {        return;    }    // 加速度传感器。    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {        int x = (int) event.values[0];        int y = (int) event.values[1];        int z = (int) event.values[2];        int gravityX = 0;        int gravityY = 0;        int gravityZ = 0;        // 屏幕的旋转信息。        rotation = this.getWindowManager().getDefaultDisplay().getRotation();        // 依据屏幕的旋转角度,更新加速度信息。        switch (rotation) {            case Surface.ROTATION_0:                gravityX = -x;                gravityY = -y;                gravityZ = -z;                break;            case Surface.ROTATION_90:                gravityX = y;                gravityY = -x;                gravityZ = -z;                break;            case Surface.ROTATION_180:                gravityX = x;                gravityY = y;                gravityZ = -z;                break;            case Surface.ROTATION_270:                gravityX = -y;                gravityY = x;                gravityZ = -z;                break;            default:                break;        }        FluidComponent fluidComponent = fluidNode.getComponent(FluidComponent.descriptor());        if (fluidComponent != null) {            // 设置流体零碎的重力加速度。            fluidComponent.setGravity(new Vector3(gravityX, gravityY, gravityZ));        }    }}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {}
  1. 新建触屏交互手势事件子类GestureEvent。
public class GestureEvent implements View.OnTouchListener {    private FluidComponent fluidComponent;    private int surfaceWidth;    private int surfaceHeight;    private boolean sceneReady;    public GestureEvent(FluidComponent fluidComponent, int surfaceWidth, int surfaceHeight) {        this.fluidComponent = fluidComponent;        this.surfaceWidth = surfaceWidth;        this.surfaceHeight = surfaceHeight;        sceneReady = fluidComponent != null && surfaceWidth != 0 && surfaceHeight != 0;    }    @Override    public boolean onTouch(View view, MotionEvent event) {        if (!sceneReady) {            return false;        }        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:            case MotionEvent.ACTION_MOVE:                // 流体触屏交互接口,滑动流体,模仿流体晃动的成果。                fluidComponent.setGesture(event.getX() / surfaceWidth, event.getY() / surfaceHeight);                break;            case MotionEvent.ACTION_UP:                fluidComponent.setGesture(-1.0f, -1.0f);                break;            default:                break;        }        return true;    }}
  1. 注册触屏交互的手势事件,反对触屏滑动流体,模仿流动飞溅成果。复写DemoActivity中的onTouchEvent办法。
private GestureEvent gesture;private void createFluidScene(Context context) {    // ...    // 获取屏幕信息。    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();    // 注册手势事件。    gesture = new GestureEvent(fluidComponent, displayMetrics.widthPixels, displayMetrics.heightPixels);}@Overridepublic boolean onTouchEvent(MotionEvent motionEvent) {    if (gesture != null) {        gesture.onTouch(renderView, motionEvent);    }    return true;}
  1. 在DemoActivity的onCreate办法中调用createFluidScene办法,实现流体场景创立。
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {    //...    createFluidScene();}

实现上述步骤后,运行利用,启动DemoActivity,就可能看见流体在不可视的球形边界内流动。细节详情见原子化接口3D流体示例代码。

理解更多详情>>

拜访华为开发者联盟官网
获取开发领导文档
华为挪动服务开源仓库地址:GitHub、Gitee

关注咱们,第一工夫理解 HMS Core 最新技术资讯~