Android OpenGLES3绘图 - LiquidFun 流体库

kepz0-oim7h.gif

在看Box2D游戏引擎时,发现了一个很有意思的液体引擎 LiquidFun ,它是Box2D的扩展。给Box2D加上了粒子系统,并且粒子能实现液体的特性。

LiquidFun Github是一个Google的开源项目,LiquidFun官网里面有很多有趣的演示和很多资料,包括它的原理PPT讲解:它其实是在Box2D物理引擎的力学计算(弹力、摩擦力)基础上,给Body(液体粒子)添加了跟液体有关的几种力:压力、粘性力、排斥力、表面张力等,能够很好地模拟液体。

由于增加了很多力学计算,并且模拟液体需要大量粒子,这会给CPU带来巨大的压力。对此,LiquidFun 里面使用了一项非常强大的优化技术!

我们来学习和使用一下这个库!

1 编译

LiquidFun项目跟Box2D一样是用C语言写的,然而Box2D有Java版本的JBox2D它没有,如果都自己写JNI接口去调用会很麻烦,好在它提供了Swig,Swig能给C代码自动生成Java接口。咱们可以编译生成一下,也可以使用它生成好的。

至于动态链接库 libliquidfun.so,我用自己编译的遇到了崩溃的问题,不知道是参数还是哪里没配置好。好在Google有一个上架的App:LiquidFun Paint 使用了这个库,我直接从它的apk包提取了 .so 文件使用。LiquidFun Paint 也是开源的,在Github和Google Play商店都能搜到,它是一个功能非常完整的液体绘图app,源码很值得学习。

2 代码实现

2.1 创建工程和导入

新建一个Android工程,添加 libliquidfun.so 和 libliquidfun_jni.so 这两个文件,在 MainActivity中 loadLibrary,并将swig生成的包名为com.google.liquidfun的包和里面的所有Java文件添加到代码中。

    static {
        System.loadLibrary("liquidfun");
        System.loadLibrary("liquidfun_jni");
    }

2.2 添加物理类

由于LiquidFun里面包含了Box2D,就不用再添加后者的库了,直接使用即可。创建一个LiquidManager类管理物理相关的,创建Box2D的World世界边界和ParticleSystem粒子系统。这里主要介绍ParticleSystem,如果不熟悉Box2D可以去搜索相关资料。

下面是创建ParticleSystem的方法,它跟创建Box2D里面的其他物体相似,其中setMaxParticleCount和setRadius这两个方法比较重要,关系到创建粒子的数量和大小。

PolygonShape会在提供的形状区域内创建所有液体粒子,注意如果给它提供的范围小而粒子数很多,会直接崩溃!

ParticleGroupDef的setFlags和setGroupFlags可以改变粒子的行为,比如可以不创建液体,创建一个软体!

创建完成之后通过particleSystem可以获取每个粒子的参数,包括位置、颜色等,当然一个个粒子获取和刷新界面效率太低了,它提供了 updatePosition(ByteBuffer buffer) 方法将所有粒子位置刷新到一个 ByteBuffer中,后面可以直接提供给OpenGL使用。

    private void createLiquid(float w, float h) {
        ParticleSystemDef psd = new ParticleSystemDef();
        psd.setDensity(1.2f);
        psd.setGravityScale(0.4f);
        psd.setRadius(PARTICAL_RADIUS);
        psd.setRepulsiveStrength(0.5f);
        particleSystem = world.createParticleSystem(psd);
        particleSystem.setMaxParticleCount(MAX_COUNT);

        PolygonShape shape = new PolygonShape();
        shape.setAsBox(w / 2, h / 2, 0f, 0f, 0f);

        ParticleGroupDef pd = new ParticleGroupDef();

//        // 软体
//        pd.setFlags(1 << 4);
//        pd.setGroupFlags(1 << 0);
        pd.setFlags(0);
        pd.setGroupFlags(0);
        pd.setLinearVelocity(new Vec2(0,0));
        pd.setShape(shape);

        // signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x25
        // Cause: null pointer dereference
        particleSystem.createParticleGroup(pd);

        psd.delete();
        shape.delete();
        pd.delete();

        Log.d("chao", "create particles " + particleSystem.getParticleCount());
    }

2.3 OpenGL绘制

添加一个LiquidRender类进行OpenGL绘制。液体粒子没有形状的属性,直接使用点方式绘制。创建一个ByteBuffer保存所有点的位置,前面说的particleSystem可以直接将数据刷新到这个mParticlePositionBuffer里面。

    ByteBuffer mParticlePositionBuffer;

//        //分配内存空间,每个浮点型占4字节空间
        mParticlePositionBuffer = ByteBuffer
                .allocateDirect(2 * 4 * LiquidManager.MAX_COUNT)
                .order(ByteOrder.nativeOrder());

        vao = new int[1];
        glGenVertexArrays(1, vao, 0);
        glBindVertexArray(vao[0]);

        vbo = new int[1];
        glGenBuffers(1, vbo, 0);
        glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
//        glBufferData(GL_ARRAY_BUFFER, vertices.length * 4, vertexBuffer, GL_STATIC_DRAW);
        glBufferData(GL_ARRAY_BUFFER, LiquidManager.MAX_COUNT * 4 * 2, mParticlePositionBuffer, GL_STREAM_DRAW);

刷新方法如下:
用 world.step() 更新位置,然后updatePosition,再用glBufferSubData方法刷新vbo数据,注意要重新绑定vbo一下,否则不会生效。最后绘制即可。

    @Override
    public void onDrawFrame(GL10 gl) {
        liquidManager.initWorld(width, height);
        liquidManager.updatePosition(mParticlePositionBuffer);

        // Clear the color buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 刷新vbo数据
        glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
        glBufferSubData(GL_ARRAY_BUFFER, 0, LiquidManager.MAX_COUNT * 4 * 2,  mParticlePositionBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, 0);


        // Use the program object
        glUseProgram(program);
        glBindVertexArray(vao[0]);

        glDrawArrays(GL_POINTS, 0, LiquidManager.MAX_COUNT);
    }

创建vbo的方法glBufferData(GL_ARRAY_BUFFER, LiquidManager.MAX_COUNT * 4 * 2, mParticlePositionBuffer, GL_STREAM_DRAW)最后一个参数,理论上在每一帧数据都变化时应该使用GL_STREAM_DRAW,也许它能自动刷新GPU里的缓存数据?但我这里设置了并没有生效,跟GL_STATIC_DRAW效果一样。我只能用glBufferSubData去手动刷新。

2.4 Shader 实现

着色器里面根据传入的位置绘制点即可

顶点着色器:shader_base_v.glsl
里面根据实际调整点的大小 gl_PointSize

#version 300 es
layout (location = 0) in vec2 vPosition;

out vec2 vPos;

void main() {
     vPos = vPosition / 10.0f;
     gl_PointSize = 12.0f;
     gl_Position  = vec4(vPos, 0.0f, 1.0f);
}

片段着色器:shader_base_v.glsl
根据位置做了一个简单的颜色渐变

#version 300 es
precision mediump float;

in vec2 vPos;

out vec4 fragColor;

void main() {
     fragColor = vec4((vPos + 1.0f) * 0.5f, 0.5f, 1.0f);
}

如果想要更好的液体展示效果,就不能用简单的点绘制,应该使用Texture纹理贴图和blend混合,这在LiquidFun Paint项目里可以看到。

3 ARM处理器的Neon优化

ub06f-33jm1.gif

调整粒子的数量和大小能得到更精细的展示效果,上图是8000个粒子,在我的骁龙865手机上运行非常流畅,这得益于LiquidFun的计算优化。

在LiquidFun的源码里,我发现了b2ParticleAssembly.neon.s这个文件,它里面居然是汇编语言!查阅了一下,这正是ARM处理器的Neon优化,它可以让ARM处理器并行处理数据,能大幅提高CPU计算速度。项目编译时可以选择是否开启Neon,我打了一个不开启Neon的so跟正常开启的对比,发现开启Neon优化的计算速度能提升4倍左右!

捕获.PNG

4 Github地址

完整项目在SurfacePaint项目下的liquidapp模块里。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容