LearnOpenGL - Android OpenGL ES 3.0 draws triangles

Series 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


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)
    }
}
  • MyOpenGLSurfaceViewInherited from GLSurfaceView, pay attention to implement the GLSurfaceView(Context context, AttributeSet attrs)constructor , we can use it directly in the layout.xml layout file MyOpenGLSurfaceView, otherwise there will be Binary XML file line # : Error inflating classan error
  • initIn setEGLContextClientVersion(3)means we use OpenGL ES version 3.0
  • initIn , setRendererset 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)
        // ...
    }
}
  • MyOpenGLSurfaceRenderinherits from GLSurfaceView.Renderer, needs to implement onSurfaceCreated, onSurfaceChangedandonDrawFrame
  • onSurfaceCreated, onSurfaceChangedand onDrawFramemust be executed in the same rendering thread, you can determine this through Log, for exampleLog.d("xxx", "onSurfaceCreated thread " + Thread.currentThread())
  • onSurfaceCreatedCalled 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.
  • onSurfaceChangedCalled when the size of the surface changes, we can set the viewport here
  • onDrawFrameResponsible for rendering the current frame. For analogy, the code in whilethe 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 prepareShadersto compile and link the shader, if it fails, it will return -1; if the call is successful, use idto 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 vertexBufferthat 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 onSurfaceCreatedthe function , create the data and shader needed for the triangle
  • In onDrawFramethe function , glUseProgramuse the compiled shader id; glBindVertexArrayuse 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 in onSurfaceCreated, andonSurfaceChanged , otherwise the rendering will fail.onDrawFrame

Finally, you can directly refer to it MyOpenGLSurfaceViewas , 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"
        />

insert image description here

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


reference

Guess you like

Origin blog.csdn.net/weiwei9363/article/details/129128942