寒夏汢

MeshView

0x0 必备知识

阅读本文之前,读者最好具备一定的OpenGL的开发背景,并了解OpenGL渲染流程。学习资源可以参考:

0x1 背景介绍

在项目开发中,经常需要开发一些炫酷的3D效果。在Android系统上,通常来说可以通过系统提供的OpenGL的java接口来实现(如GLES20.java)。android系统并提供了GLSurfaceView等一套框架机制,直接帮助开发者将gl环境准备好,开发者剩下的工作就是建立模型(model)、设计着色算法(shader)。但是GLSurfaceView整套机制相对来说还是比较重型(独立的gl渲染线程),功耗也比较大。然而有这么一些非常简单的3D特效,直接嵌入在用户交互界面里,对功耗的要求尽量要低,这个时候采用GLSrufaceView那套机制并不合适。

经过一些调研,发现android原生的接口Canvas.drawBitmapMesh提供的能力可以帮助实现在任意view上的3D效果。本文介绍的内容就是:如果将OpengGL中建模以及渲染流程的知识和接口Canvas.drawBitmapMesh结合起来,并开发一个小的“渲染引擎”,将这种能力基线化。

示例

face_guide num_picker

0x2 原理介绍

Mesh原理

mesh

Canvas.drawBitmapMesh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 /**
* Draw the bitmap through the mesh, where mesh vertices are evenly distributed across the
* bitmap. There are meshWidth+1 vertices across, and meshHeight+1 vertices down. The verts
* array is accessed in row-major order, so that the first meshWidth+1 vertices are distributed
* across the top of the bitmap from left to right. A more general version of this method is
* drawVertices().
*
* Prior to API level {@value Build.VERSION_CODES#P} vertOffset and colorOffset were ignored,
* effectively treating them as zeros. In API level {@value Build.VERSION_CODES#P} and above
* these parameters will be respected.
*
* @param bitmap The bitmap to draw using the mesh
* @param meshWidth The number of columns in the mesh. Nothing is drawn if this is 0
* @param meshHeight The number of rows in the mesh. Nothing is drawn if this is 0
* @param verts Array of x,y pairs, specifying where the mesh should be drawn. There must be at
* least (meshWidth+1) * (meshHeight+1) * 2 + vertOffset values in the array
* @param vertOffset Number of verts elements to skip before drawing
* @param colors May be null. Specifies a color at each vertex, which is interpolated across the
* cell, and whose values are multiplied by the corresponding bitmap colors. If not
* null, there must be at least (meshWidth+1) * (meshHeight+1) + colorOffset values
* in the array.
* @param colorOffset Number of color elements to skip before drawing
* @param paint May be null. The paint used to draw the bitmap
*/
public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
@NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
@Nullable Paint paint) {
super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset,
paint);
}
  • bitmap:类似于gl中的texture
  • meshWidth:网格纵切的数目
  • meshHeight:网格横切的数目
  • verts:类似于gl中的vertex array
  • vertOffset
  • colors:可用来模拟mask(未demo测试)
  • colorOffset
  • paint

OpenGl的建模和渲染过程

OpenGL中vertex transformation:

vertex_render_process

对应的计算过程:

vertex_render_calculate

通常来讲,通过OpenGL开放的接口,实现3D效果(不考虑着色),需要提供:

  • local space建模数据:即vertex array in local space
  • MVP matrix:Model matrix, View matrix, Project matrix
  • 视窗:即viewport

0x3 工程实践简介

OpenGL流程和MeshiView实践对比

  1. OpengGL的流程
    gl_render_outline
    可以看到在android的OpenGL开发过程中,数据的处理过程是黑盒的(即Not visible to developer),开发人员只需要传入这个几方面的数据:
  • Vertex Model : Local space 对3D物体建模之后的顶点数据 & modle matrix
  • viewport :通过GLES.glviewport设置
  • view matrix: 通过Matrix - GLES20系列接口传递(setLookAtM)
  • projection matrix: 通过Matrix - GLES20系列接口传递(orthoM、frustumM、perspectiveM)
  1. MeshView实践
    meshview_render_outline
    因为Mesh并不走传统的android的OpenGL的流程,所以GLES20的接口都不会被使用,所以需要做如下工作
  • Vertex Model:同OpenGL
  • viewport、setLookAt、ortho、frustum、perspective 等接口
  • 使用内部私有方法@Method:mesh实现 vertex * MVP(其实在OpenGL中一般实在vertex shader中实现,可以灵活实现,但基本上就是vertex * MVP)
  • 将准备好的verts传递给Canvas.drawBitmapMesh处理

模拟接口对比

提供的接口 对应模拟的android系统接口
MeshObject.viewport android.opengl.GLES20.glviewport
MeshObject.ortho android.opengl.Matrix.orthoM
MeshObject.frustum android.opengl.Matrix.frustumM
MeshObject.perspective android.opengl.Matrix.perspectiveM
MeshObject.setLookAt android.opengl.Matrix.setLookAtM

核心计算方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private Vec3 mesh(Vec4 vert, float[] mvpMatrix) {
float[] from = new float[] {vert.x, vert.y, vert.z, vert.w};
float[] to = new float[4];
Matrix.multiplyMV(to,0, mvpMatrix,0, from,0);

Vec4 clip = new Vec4(to[0], to[1], to[2], to[3]);

Vec4 ndc = new Vec4(clip.x/clip.w, clip.y/clip.w, clip.z/clip.w, 1.0f);

ViewPort viewPort = getViewPort();
float x = viewPort.x;
float y = viewPort.y;
float w = viewPort.w;
float h = viewPort.h;
float n = viewPort.n;
float f = viewPort.f;

float wx = w/2 * (ndc.x + 1) + x;
float wy = h/2 * (ndc.y + 1) + y;
float wz = (f - n)/2 * ndc.z + (f + n)/2;

return new Vec3(wx, wy, wz);
}

注:抱歉,完整的工程因个人原因已从github删除。只保留如上最核心的函数。

0x4 遗留问题

  • 目前只是简单的模拟了OpenGL背后的渲染流程,一些高级特性不具备。例如利用深度信息判断点的前后关系并裁剪的能力不具备。这样就要求使用者精确建模,避免出现有不同深度信息的点,否者绘制顺序无法保证最前面的点被后绘制(看到的现象就是物体背后的信息发生了穿透)

    例如下图中,右边的示例底部发生了穿透现象。

ok error

  • 目前本篇博客细节介绍的并不详细,待完善

标签:

专题:

发表于2019-06-19 21:11:00,最后修改于2019-07-20 13:24:03。

本站文章欢迎链接分享,禁止全文转载。


« 上一篇 友情链接 下一篇 » Conan(一):初识Conan(C/C++包管理器)

赞赏支持

请我吃胡萝卜 =^_^=

i ali

支付宝

i wechat

微信

推荐阅读

Big Image