关于android:如何用3D流体实现逼真水流效果

8次阅读

共计 7720 个字符,预计需要花费 20 分钟才能阅读完成。

华为利用市场在 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;

@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();}
}
}
  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);
}

@Override
protected void onResume() {super.onResume();
    // 注册传感器事件。sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
    //...
}
@Override
protected void onPause() {super.onPause();
    // 去注册传感器事件。sensorManager.unregisterListener(this);
    //...
}
  1. 复写传感器 onSensorChanged 办法,依据加速度传感器获取的值,更新流体零碎的重力加速度。
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public 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));
        }
    }
}


@Override
public 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);
}


@Override
public boolean onTouchEvent(MotionEvent motionEvent) {if (gesture != null) {gesture.onTouch(renderView, motionEvent);
    }
    return true;
}
  1. 在 DemoActivity 的 onCreate 办法中调用 createFluidScene 办法,实现流体场景创立。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    //...
    createFluidScene();}

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

理解更多详情 >>

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

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

正文完
 0