Series Article Directory
- LearnOpenGL Notes - Getting Started 01 OpenGL
- LearnOpenGL Notes - Getting Started 02 Create Window
- LearnOpenGL Notes - Getting Started 03 Hello, Windows
- LearnOpenGL Notes - Getting Started 04 Hello, Triangle
- OpenGL - How to understand the relationship between VAO and VBO
Article directory
1 Introduction
After a period of learning OpenGL, we have mastered how to use glwf to draw simple graphics on the desktop. Now let's turn our attention to the mobile terminal and see how to use OpenGL to draw graphics on Android. This article assumes that you have a basic understanding of Android, and use Kotlin to write sample demos. You can find the code of the project in Android_OpenGLES3_Demo .
This article refers to the following information
- Android OpenGL development - detailed image drawing
- Detailed explanation of the OpenGL ES 3.0 instance under the android platform vertex buffer object (VBO) and vertex array object (VAO)
2.OpenGL ES 3.0
OpenGL ES (OpenGl for Embedded System) is a subset of OpenGL provided by Khronos for mobile devices. It is used to render 2D and 3D graphics in embedded devices and mobile devices. It is designed to meet the needs of low-power devices. OpenGL ES removes some redundant APIs and retains the core and useful parts.
Android supports multiple versions of OpenGL ES API, starting from Android 1.0 supporting OpenGL ES 1.0 and 1.1, starting from Android 2.2 supporting OpenGL ES 2.0, starting from Android 4.3 supporting OpenGL ES 3.0, starting from Android 5.0 supporting OpenGL ES 3.11. The version support of Android corresponding to OpenGL ES is shown in the table below
Android version | OpenGL ES version |
---|---|
Android 1.0 | OpenGLES 1.0/1.1 |
Android 2.2 | OpenGL ES 2.0 |
Android 4.3 | OpenGL ES 3.0 |
Android 5.0 | OpenGL ES 3.1 |
Due to historical reasons and some compatibility issues, many projects use OpenGL ES version 2.0 instead of 3.0. Comparing the two versions, they have the following differences
-
In terms of performance, OpenGL ES 3.0 adds some new features and optimizations compared to OpenGL ES 2.0, such as larger buffers, more formats, more uniform variables, instance rendering, pixel buffer objects, and occlusion queries. These functions can improve the efficiency and quality of graphics rendering, but may also increase the complexity and resource consumption of development.
-
In terms of compatibility, OpenGL ES 3.0 is backward compatible with OpenGL ES 2.0, which means that applications written in 2.0 can continue to be used in 3.0. However, not all devices support OpenGL ES 3.0, so if you want to consider cross-platform compatibility, you may need to use OpenGL ES 2.0 or do some adaptation work.
-
In terms of ease of use, both OpenGL ES 3.0 and OpenGL ES 2.0 are programmable graphics pipelines, and developers can write their own codes for the two stages of the vertex shader and fragment shader in the graphics pipeline. This allows for more flexibility and creativity, but also requires more programming skills and knowledge. Compared to the fixed-function pipeline of the OpenGL ES 1.x series, OpenGL ES 2.0/3.0 does not support the fixed-function fixed-point units supported by OpenGL ES 1.x.
So why choose OpenGL ES 3.0? A very straightforward reason: the OpenGL Shader Language (GLSL) version used in ES 3.0 is 300, while the LearnOpenGL tutorial uses GLSL 330, there is almost no difference between the two, and it can be mastered with minimal learning costs. But if you use ES 2.0, then you have to spend some time learning and adapting to different versions of GLSL.
3 GLSurfaceView 和 GLSurfaceView.Render
In Android, GLSurfaceView is a class based on SurfaceView, which is specially used to display the content rendered by OpenGL. And GLSurfaceView.Render is responsible for rendering the screen specifically. To put it simply, GLSurfaceView is similar to the concept of window in glwf. It is responsible for the display of the screen, the management of OpenGL Context, the control of the rendering thread, etc., while GLSurfaceView.Render is the abstraction of the logic of the OpenGL API call. You need to create your own Render to draw graphics.
Or explain the code
class MyOpenGLSurfaceView(context: Context?, attrs :AttributeSet?) : GLSurfaceView(context, attrs) {
constructor(context: Context?) : this(context, null)
var render = MyOpenGLSurfaceRender()
init {
setEGLContextClientVersion(3)
setRenderer(render)
}
}
MyOpenGLSurfaceView
Inherited fromGLSurfaceView
, pay attention to implement theGLSurfaceView(Context context, AttributeSet attrs)
constructor , we can use it directly in the layout.xml layout fileMyOpenGLSurfaceView
, otherwise there will beBinary XML file line # : Error inflating class
an errorinit
InsetEGLContextClientVersion(3)
means we use OpenGL ES version 3.0init
In ,setRenderer
set the Render
class MyOpenGLSurfaceRender : GLSurfaceView.Renderer {
// ...
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
//设置背景颜色
GLES30.glClearColor(0.0f, 0.5f, 0.5f, 0.5f)
//...
}
override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
}
override fun onDrawFrame(p0: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
// ...
}
}
MyOpenGLSurfaceRender
inherits fromGLSurfaceView.Renderer
, needs to implementonSurfaceCreated
,onSurfaceChanged
andonDrawFrame
onSurfaceCreated
,onSurfaceChanged
andonDrawFrame
must be executed in the same rendering thread, you can determine this through Log, for exampleLog.d("xxx", "onSurfaceCreated thread " + Thread.currentThread())
onSurfaceCreated
Called when the surface is created or recreated, put some logic for creating resources in this function, such as setting vbo and vao, compiling shader, etc.onSurfaceChanged
Called when the size of the surface changes, we can set the viewport hereonDrawFrame
Responsible for rendering the current frame. For analogy, the code inwhile
the loop placed here
4. Draw the triangle
The way to call the OpenGL ES API in Android is almost the same as the OpenGL API. It is similar to the code in LearnOpenGL and given the Kotlin code, but there are some details that need attention. Next, we will discuss the code.
The first is the tool Shader
class Shader(private val vertexShaderSource: String, private val fragmentShaderSource: String) {
private val TAG = "Shader"
var id : Int = 0
fun prepareShaders(): Int{
// compile vertex shader
val vertexShader = createAndCompileShader(GLES30.GL_VERTEX_SHADER, vertexShaderSource)
if(vertexShader == -1){
return -1
}
// compile fragment shader
val fragmentShader = createAndCompileShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderSource)
if(fragmentShader == -1){
return -1
}
id = createShaderProgram(vertexShader, fragmentShader)
if(id == -1){
return -1
}
return 0
}
private fun createAndCompileShader(type: Int, source: String): Int{
val success: IntArray = intArrayOf(0)
val shader = GLES30.glCreateShader(type)
GLES30.glShaderSource(shader, source)
GLES30.glCompileShader(shader)
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, success, 0)
if (success[0] == 0) {
val log = GLES30.glGetShaderInfoLog(shader)
Log.e(TAG, "compile vertex source failed.$log")
GLES30.glDeleteShader(shader)
return -1
}
return shader
}
private fun createShaderProgram(vertexShader: Int, fragmentShader: Int): Int{
val success: IntArray = intArrayOf(0)
val program = GLES30.glCreateProgram()
GLES30.glAttachShader(program, vertexShader)
GLES30.glAttachShader(program, fragmentShader)
GLES30.glLinkProgram(program)
GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, success, 0)
if (success[0] == 0) {
val log = GLES30.glGetShaderInfoLog(program)
Log.e(TAG, "link shader program failed. $log")
GLES30.glDeleteProgram(program)
return -1
}
GLES30.glDeleteShader(vertexShader)
GLES30.glDeleteShader(fragmentShader)
return program
}
}
- This class refers to our own shader class - LearnOpenGL , input vertex and fragment shader source code, responsible for shader compilation and linking
- Call
prepareShaders
to compile and link the shader, if it fails, it will return -1; if the call is successful, useid
to refer to the current shader program
Triangle class:
class Triangle {
private val vertices = floatArrayOf(
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
)
val vaos: IntArray = intArrayOf(0)
val vbos: IntArray = intArrayOf(0)
fun prepareData(){
// prepare vbo data
val vertexBuffer = ByteBuffer.allocateDirect(vertices.size * Float.SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer()
vertexBuffer.put(vertices)
vertexBuffer.position(0)
// generate vao and vbo
GLES30.glGenVertexArrays(1, vaos, 0)
GLES30.glGenBuffers(1, vbos, 0)
GLES30.glBindVertexArray(vaos[0])
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbos[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
Float.SIZE_BYTES * vertices.size,
vertexBuffer,
GLES30.GL_STATIC_DRAW
)
GLES30.glVertexAttribPointer(
0, 3, GLES30.GL_FLOAT, false,
3 * Float.SIZE_BYTES, 0
)
GLES30.glEnableVertexAttribArray(0)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
GLES30.glBindVertexArray(0)
}
}
- Note
vertexBuffer
that because Kotlin data is stored using BigEnd, while OpenGL data is little-endian, so when using OpenGL under Android, you must do big-endian conversion. - The other parts are step by step, create vbo and vao, set vbo data, set vao attributes, etc., no more details
class MyOpenGLSurfaceRender : GLSurfaceView.Renderer {
private val tri = Triangle()
private val shader: Shader = Shader(
VertexShaderSource.vertexShaderSource,
FragmentShaderSource.fragmentShaderSource
)
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
GLES30.glClearColor(0.0f, 0.5f, 0.5f, 0.5f)
tri.prepareData()
shader.prepareShaders()
}
override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
}
override fun onDrawFrame(p0: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
GLES30.glUseProgram(shader.id)
GLES30.glBindVertexArray(tri.vaos[0])
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3)
}
}
- In
onSurfaceCreated
the function , create the data and shader needed for the triangle - In
onDrawFrame
the function ,glUseProgram
use the compiled shader id;glBindVertexArray
use the set vao for rendering - Note that
GLES30.
all function calls must operate in the rendering thread, that is to say, these functions must be called inonSurfaceCreated
, andonSurfaceChanged
, otherwise the rendering will fail.onDrawFrame
Finally, you can directly refer to it MyOpenGLSurfaceView
as , just as convenient as Button, for example
<com.xinging.opengltest.MyOpenGLSurfaceView
android:layout_width="match_parent"
android:layout_height="500dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
5. Summary
This article introduces how to use OpenGL ES 3.0 to draw triangles under Android. By customizing GLSurfaceView and GLSurfaceView.Render, call OpenGL API under Android to realize drawing; all codes in this article are in Android_OpenGLES3_Demo