7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Code
  2. Android SDK

如何在Android APP中使用OpenGL ES

Scroll to top
Read Time: 3 mins

Chinese (Simplified) (中文(简体)) translation by Zhang Xiang Liang (you can also view the original English article)

现在市面上几乎每个Android手机都有一个图形处理单元,或简称为GPU。顾名思义,就是专门处理与3D图形计算相关的硬件单元。 作为一名APP开发人员,你可以利用GPU创建复杂的图形和动画,这些图形和动画可以以非常高的帧速率运行。

目前有两种不同的API可用于与Android设备的GPU交互:VulkanOpenGL ES。Vulkan仅适用于运行Android 7.0或更高版本的设备,而所有Android版本都支持OpenGL ES。

在本教程中,我将帮助你在Android APP中使用OpenGL ES 2.0。

准备工作

为了与本文一致,你需要:

  • 最新版本的Android Studio
  • 支持OpenGL ES 2.0或更高版本的Android设备
  • 最新版本的Blender,或任何其他3D建模软件

1.什么是OpenGL ES?

OpenGL是Open Graphics Library的缩写,它是一个独立于平台的API,可以创建硬件加速的3D图形。OpenGL ES是OpenGL for Embedded Systems的缩写,是OpenGL API的一个子集。

OpenGL ES是一个非常低级的API。换句话说,它不提供任何让你快速创建或操纵3D对象的方法。 相反,在使用它时,你需要手动管理任务,例如创建3D对象的各个顶点和面,计算各种3D变换,以及创建不同类型的着色器。

还值得一提的是,Android SDK和NDK可以让你在Java和C中编写与OpenGL ES相关的代码。

2.项目设置

因为OpenGL ES API是Android框架的一部分,所以不必为项目添加任何依赖关系就能够使用它们。 但是在本教程中,我们将使用Apache Commons IO库来读取几个文本文件的内容。因此,需要在build.gradle文件中的依赖项添加相关依赖:

此外,有些设备不支持OpenGL ES,无法安装你的APP,为了阻止此类Google Play用户,请将<uses-feature>添加到项目的清单文件中:

3.创建画布

Android框架为3D图形画布提供了两个控件:GLSurfaceView和TextureView。 大多数开发人员喜欢使用GLSurfaceView,只有当他们打算在其他View控件上叠加3D图形时才选择TextureView。这里,我们选择GLSurfaceView足够了。

GLSurfaceView控件添加到布局文件。

请注意,我们已经将窗口的宽度与高度设置为相等。这样做很重要,因为OpenGL ES坐标系是一个正方形。 如果你必须使用矩形画布,请记住在计算投影矩阵时使用宽高比。你将在后面了解投影矩阵。

Activity类中通过findViewById()方法初始化GLSurfaceView控件。

此外,我们必须调用setEGLContextClientVersion()方法来明确指定OpenGL ES版本。

4.创建3D对象

虽然可以通过手动编写所有顶点的X,Y和Z坐标来创建Java中的3D对象,但这样做非常麻烦。 相反,使用3D建模工具更容易。Blender就是这样的工具。它开源,功能强大,且非常容易学习。

启动Blender并按X删除默认立方体。接下来,按Shift-A并选择 Mesh > Torus。我们现在有一个相当复杂的3D对象,由576个顶点组成。

Torus in BlenderTorus in BlenderTorus in Blender

为了能够在Android APP中使用环面,我们必须将其导出为Wavefront OBJ文件。因此,进入File > Export > Wavefront (.obj)。 在下一个屏幕中,给OBJ文件一个名称,确保选择了Triangulate FacesKeep Vertex Order选项,然后点击Export OBJ按钮。

Exporting 3D object as Wavefront OBJ fileExporting 3D object as Wavefront OBJ fileExporting 3D object as Wavefront OBJ file

你现在可以关闭Blender并将OBJ文件移动到Android Studio项目的assets文件夹。

5.解析OBJ文件

如果你还没有注意到我们在上一步中创建的OBJ文件是一个文本文件,可以使用任何文本编辑器打开。

OBJ file opened with a text editorOBJ file opened with a text editorOBJ file opened with a text editor

在文件中,以“v”开头的每一行代表单个顶点。类似地,以“f”开始的每一行表示单个三角形面。 当每个顶点线包含顶点的X,Y和Z坐标时,每个面线包含三个顶点的索引,它们一起形成一个面。这就是解析OBJ文件需要知道的一切。

在开始之前,创建一个名为Torus的新Java类,并添加两个List对象作为成员变量,一个用于顶点,一个用于面。

读取OBJ文件的所有行的最简单方法是使用Scanner类及其nextLine()方法。当循环遍历行并填充两个列表时,你可以使用String类的startsWith()方法来检查当前行是否以“v”或“f”开头。

6.创建缓冲区对象

你不能将顶点和面的列表直接传递给OpenGL ES API中的方法。你必须先将其转换为缓冲对象。 要存储顶点坐标数据,我们需要一个FloatBuffer对象。对于仅由顶点索引组成的面部数据,ShortBuffer对象就足够了。

因此,将以下成员变量添加到Torus类中:

要初始化缓冲区,我们必须首先使用allocateDirect()方法创建ByteBuffer对象。对于顶点缓冲区,为每个坐标分配四个字节,坐标是浮点数。 一旦ByteBuffer对象被创建,你可以通过调用它的asFloatBuffer()方法将其转换成FloatBuffer

同样,为面缓冲区创建另一个ByteBuffer对象。这一次,为每个顶点索引分配两个字节,因为索引是unsigned short类型。 此外,请确保使用asShortBuffer()方法将ByteBuffer对象转换为 ShortBuffer

填充顶点缓冲区需要循环遍历verticesList内容,从每项中提取X,Y和Z坐标,并调用put()将数据放入缓冲区。因为verticesList只包含字符串,所以我们必须使用parseFloat()将坐标从字符串转换为float值。

请注意,在上述代码中,我们使用position()方法来重置缓冲区的位置。

填充面缓冲区略有不同。你必须使用parseShort()方法将每个顶点索引转换为一个short类型的值。 另外,由于索引从一开始而不是从零开始,因此你必须记住在将它们放入缓冲区之前从它们中减一。

7.创建着色器

为了能够渲染我们的3D对象,我们必须为它创建一个顶点着色器和片段着色器。现在,可以将着色器看成一个非常简单的程序,用类似C语言的语言编写,称为OpenGL着色语言,简称GLSL。

机智如你,顶点着色器负责处理3D对象的顶点。片段着色器(也称为像素着色器)负责着色3D对象的像素。

步骤1:创建顶点着色器

在项目的res/raw文件夹中创建一个名为vertex_shader.txt的新文件。

顶点着色器必须有一个attribute全局变量,以便从Java代码接收顶点位置数据。此外,添加一个uniform全局变量以便从Java代码接收视图投影矩阵。

在顶点着色器的main()方法内部,你必须设置gl_position值,它是GLSL内置变量,该变量决定顶点的最终位置。现在,你可以简单地将其值设置为的产品uniformattribute全局变量。

将以下代码添加到文件中:

步骤2:创建片段着色器

在项目的res/raw文件夹中创建一个名为fragment_shader.txt的新文件。

为了简化本教程,我们现在创建一个非常简约的片段着色器,只需将橙色分配给所有像素。要将一个颜色分配给一个像素,在片段着色器的main()方法内部,使用gl_FragColor内置的变量。

在上面的代码中,第一行指定浮点数精度很重要,因为片段着色器对没有任何默认的精度。

第3步:编译着色器

回到Torus类,你现在必须添加代码来编译你创建的两个着色器。但是,在此之前,必须先将它们从原始资源转换为字符串。 IOUtils类(是Apache Commons IO 库的一部分)有一个toString()可以做到。代码如下:

着色器的代码必须添加到OpenGL ES着色器对象中。要创建一个新的着色器对象,请使用GLES20类的glCreateShader()方法。 根据要创建的着色器对象的类型,你可以传递GL_VERTEX_SHADER或者GL_FRAGMENT_SHADER给它。该方法返回一个整数,该整数作为着色器对象的引用。 新创建的着色器对象不包含任何代码。要将着色器代码添加到着色器对象,必须使用glShaderSource()方法。

以下代码是顶点着色器和片段着色器创建着色器对象:

我们现在可以将着色器对象传递给glCompileShader()方法来编译它们包含的代码。

8.创建一个程序

不要直接使用着色器渲染3D对象。而是将它们附加到程序然后再使用。因此,向Torus类添加一个成员变量用来存储OpenGL ES程序的引用。

要创建一个新程序,请使用glCreateProgram()方法。要将顶点和片段着色器对象附加到它,请使用glAttachShader()方法。

此时,你可以链接程序并开始使用它。使用glLinkProgram()和glUseProgram()方法来做。

9.绘制3D对象

随着着色器和缓冲区的准备,我们有了绘制环面所需要的一切。在Torus类中添加一个新方法draw

在前面的步骤中,我们在顶点着色器内部定义了一个position变量来接收来自Java代码的顶点位置数据。现在是将顶点位置数据发送给它的时候了。 为此,我们必须首先使用glGetAttribLocation()方法获取Java代码中的position变量的句柄。此外,必须使用glEnableVertexAttribArray()方法启用句柄。

draw()方法中添加以下代码:

我们必须使用glVertexAttribPointer()方法来将position句柄指向我们的顶点缓冲区。除了顶点缓冲区本身之外,该方法还要求每个顶点的坐标数,坐标的类型以及每个顶点的字节偏移量。 因为我们每个顶点有三个坐标,每个坐标是一个float类型,所以字节偏移必须是3 * 4

我们的顶点着色器还需要一个视图投影矩阵。虽然这样的矩阵并不总是必需的,但使用它可以更好地控制3D对象的渲染方式。

视图投影矩阵只是视图和投影矩阵的乘积。视图矩阵允许你指定相机的位置和它正在查看的点。 另一方面,投影矩阵不仅能让你将OpenGL ES的平方坐标系映射到Android设备的矩形屏幕,还可以指定观察 viewing frustum的近平面和远平面。

要创建矩阵,可以简单地创建三个float类型的数组,每个数组大小为16

要初始化投影矩阵,可以使用Matrix类的frustumM()方法。它需要左,右,底,顶,近和远剪辑平面的位置。 因为我们的画布已经是一个正方形,所以左和右的值为-11,顶和底的值也为-1和1。对于近和远的剪辑平面,请随时尝试不同的值。

要初始化视图矩阵,请使用setLookAtM()方法。它需要相机的位置和它正在看的点。你再次自由尝试不同值。

最后,使用multiplyMM()方法计算产品矩阵。

要将产品矩阵传递给顶点着色器,你必须使用glGetUniformLocation()方法获取matrix变量的句柄。一旦有了句柄,就可以使用glUniformMatrix()方法将其指向产品矩阵。

你要知道我们还没有使用faces缓冲区。这意味着我们还没有告诉OpenGL ES如何连接顶点来形成三角形,这将作为我们3D对象的面。

glDrawElements()方法让你使用faces缓冲区来创建三角形。需要的参数有,顶点索引的总数,每个索引的类型和面缓冲区。

最后,记得禁用attribute handler,之前将顶点数据传递给顶点着色器的时候被你打开了的。

10.创建渲染器

我们的GLSurfaceView控件需要一个GLSurfaceView.Renderer对象来渲染3D图形。可以使用setRenderer()将渲染器与其关联。

在渲染器的onSurfaceCreated()方法内,你必须指定3D图形呈现的频率。现在,只有当3D图形发生变化时,我们才能渲染。 为此,将RENDERMODE_WHEN_DIRTY常数传递给setRenderMode()方法。此外,初始化Torus对象的新实例。

在渲染器的onSurfaceChanged()方法内,可以使用glViewport()方法定义 viewport的宽度和高度。

在渲染器的onDrawFrame()方法内,调用Torus类的draw()方法来实际绘制圆环。

现在,运行应用程序来查看橙色圆环。

App displaying torusApp displaying torusApp displaying torus

结语

你现在知道如何在Android APP中使用OpenGL ES了。在本教程中,你还学到了如何解析Wavefront OBJ文件并从中提取顶点和面数据。 我建议你使用Blender生成更多的3D对象,并尝试在APP中渲染。

虽然我们只关注OpenGL ES 2.0,但是也要知道OpenGL ES 3.x向后兼容了OpenGL ES 2.0。这意味着,如果你喜欢在你的APP中使用OpenGL ES 3.x中,你可以用类GLES30GLES31类简单地替换GLES20类。

要了解有关OpenGL ES的更多信息,可以参考其它页面。要了解有关Android APP开发的更多信息,请在Envato Tuts +上查看我们的其他教程!


关注我们的公众号
Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.