如何用3D流体实现逼真水流效果?

news2024/11/24 8:29:59

华为应用市场在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 最新技术资讯~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/89666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

sentinel 规则持久化

原始模式 如果不做任何修改&#xff0c;Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中&#xff1a; 这种做法的好处是简单&#xff0c;无依赖&#xff1b;坏处是应用重启规则就会消失&#xff0c;仅用于简单测试&#xff0c;不能用于生产环境。 se…

【Android】数据存储

一、文件存储 特点&#xff1a;openFileInput()和openFileOutput()读取设备上的文件。 优点&#xff1a;适用于存储大量的数据&#xff0c;可以存储图片、视频、文本等数据。 缺点&#xff1a;如果采用内部存储的方式&#xff0c;存储过量的数据可能会导致内存的不足&#xff…

2022年度手机行业排行榜(年度手机行业分析)

如今&#xff0c;随着手机普及率的不断增长&#xff0c;当前手机市场在逐渐饱和。在这一的态势下&#xff0c;手机行业中细分市场成为发展的必然趋势&#xff0c;随着市场细分和目标人群锁定的不断明确&#xff0c;手机市场中中小品牌手机的生存空间在逐渐被挤压&#xff0c;手…

消息队列,Unix的通信机制之一

最简单的消息内存的使用流程 ①ftok函数生成键值 ②msgget函数创建消息队列 ③msgsnd函数往消息队列发送消息 ④msgrcv函数从消息队列读取消息 ⑤msgctl函数进行删除消息队列 一个消息数据应该由以下一个结构体组成&#xff0c;举个例子 struct mymesg{long int mtype; /…

教你如何使用eBPF追踪Linux内核

【推荐阅读】 浅析linux内核网络协议栈--linux bridge 深入理解SR-IOV和IO虚拟化 深入linux内核架构--进程&线程 1. 还是先进入内核目录&#xff0c;执行下面的命令&#xff0c;确保内核代码是干净的。 $ make mrproper 2. 执行以下命令&#xff0c;开始对内核进行配…

Feign的性能优化

Feign底层的客户端实现有三种模式 1&#xff09;URLConnection&#xff1a;默认实现&#xff0c;不支持连接池&#xff1b;&#xff08;Feign发送http请求时&#xff0c;默认使用的客户端&#xff09; 2&#xff09;Apache HttpClient &#xff1a;支持连接池&#xff1b; 3&…

深度学习Week10-YOLOv5-Backbone模块实现(Pytorch)

● &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 ● &#x1f366; 参考文章&#xff1a;Pytorch实战 |第P9周&#xff1a;YOLOv5-Backbone模块实现(训练营内部成员可读) ● &#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 类似于上周内…

也谈特征值和特征向量的几何意义

在当前的大数据分析时代&#xff0c;数据降维是一个重要的分析技术。而谈到数据降维&#xff0c;就离不开一门最为抽象难懂的数学学科分支——线性代数。有人可能会问&#xff1a;一堆向量和矩阵符号的线性代数到底有鸟用&#xff1f;简单地不科学地说&#xff0c;线性代数就是…

【tiktok小店运营小知识】 tiktok小店也会被封吗?有哪些原因呢?

最近开tiktok小店的小伙伴越来越多&#xff0c;运营过程中也会碰到很多问题。有小伙伴问&#xff0c; tiktok小店也会被封吗&#xff1f;有哪些原因呢&#xff1f; tiktok小店也会被封吗&#xff1f;有哪些原因呢&#xff1f; 答案是肯定的。TikTok需要进一步规范店铺经营&…

SRM是什么意思?盘点4个顶级SRM系统

SRM是什么意思&#xff1f;SRM系统&#xff0c;一般指供应商关系管理系统。供应商管理系统是采购管理系统的一个重要模块&#xff0c;强调企业与供应商之间协作共赢。相信在市场动荡的今天&#xff0c;企业与供应商之间的强关联、共命运对于企业来说不失为稳固根基、扩张业务的…

JavaScript系列之ES6默认导出与默认导入

文章の目录一、默认导出二、默认导入三、按需导出四、按需导入五、直接导入并执行模块代码写在最后一、默认导出 语法&#xff1a; export default 默认导出的成员每个模块中&#xff0c;只允许使用唯一的一次 export default&#xff0c;否则会报错&#xff01; 二、默认导入…

Generative Modeling by Estimating Gradients of the Data Distribution阅读笔记

目录概述传统score-based generative modeling介绍score matchingLangevin dynamics传统score-based generative modeling存在的问题流型假设上的问题低密度区域的问题Noise Conditional Score Network噪声条件分数网络(Noise Conditional Score Networks)annealed Langevin dy…

Kafka 架构、核心机制和场景解读

摘要 Kafka 是一款非常优秀的开源消息引擎&#xff0c;以消息吞吐量高、可动态扩容、可持久化存储、高可用的特性&#xff0c;以及完善的文档和社区支持成为目前最流行的消息队列中间件。 Kafka 的开发社区一直非常活跃&#xff0c;在消息引擎的领域取的不俗成绩之后&#xf…

组装式应用新基建——小程序容器技术

近年来&#xff0c;面对不断变化的业务环境和快速迭代的业务需求&#xff0c;“组装式应用”凭借其灵活性、复用性等优势&#xff0c;成为了重要战略技术趋势。 一直以来&#xff0c;传统应用程序开发面临着诸多挑战&#xff1a;一是没有足够的开发能力&#xff1b;二是选错技…

sql中的!=操作符的天坑(务必警觉)(=在处理null时也是同样有坑)

最近在测试数据&#xff0c;偶尔需要写sql进行数据比对&#xff0c;例如这样的语句&#xff1a; if( column_a ! column_b, 1, 0)&#xff0c;万万没想到就是这样的sql语句差点要了我的命。 其实对一般的数据&#xff0c;这条校验语句是没有问题的&#xff0c;最后再筛选一下1的…

Stm32旧版库函数10——A4988 单个步进电机 16拍

#include "stm32f10x_lib.h" #include "motor.h" u8 Step; void GPIO_Key(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0|GPIO_Pin_1; // 选中管脚9 GPIO_InitStructure.GPIO_Mode …

使用java实现 分布式任务调度平台XXL-JOB 部署及使用

XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线&#xff0c;开箱即用。 详细的特性和优点参考官网地址&#xff1a;https://www.xuxueli.com/xxl-job/ 一、任务调度 0.下载官方源…

为什么微服务一定要有网关呢

一、什么是服务网关 服务网关 路由转发 过滤器1、路由转发&#xff1a;接收一切外界请求&#xff0c;转发到后端的微服务上去&#xff1b; 2、过滤器&#xff1a;在服务网关中可以完成一系列的横切功能&#xff0c;例如权限校验、限流以及监控等&#xff0c;这些都可以通过…

Anaconda环境GDAL库基于whl文件的配置方法

本文介绍在Anaconda环境下&#xff0c;基于.whl文件安装Python中高级地理数据处理库GDAL的方法。 在文章Anaconda下Python中GDAL模块的下载与安装方法&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/124307748&#xff09;中&#xff0c;我们介绍了基于…

[附源码]计算机毕业设计的实验填报管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis MavenVue等等组成&#xff0c;B/S模式…