1. 程式人生 > >通俗易懂的 OpenGL ES 3.0(二)渲染三角形

通俗易懂的 OpenGL ES 3.0(二)渲染三角形

前言

學習了OpenGL有一段時間,在繪製出屬於自己的三角形之前,會接觸許多理論上的知識。用簡單的方式寫下自己對OpenGL的一些見解。望大家取其精華去其糟粕

最終效果:改變背景色,並且繪製渲染一個暗紅色的三角形

在這裡插入圖片描述

必備知識

OpenGL需要我們至少設定一個頂點和一個片段著色器,

如果不瞭解什麼是著色器和他的座標規則,可以看看上一篇的內容,不然你會看的很難受=v=!!。

如何繪製
  1. 準備好頂點與片段著色器
  2. GLSurfaceView作為OpenGL的載體
  3. 編譯著色器,建立GL程式
  4. 為著色器賦值,並繪製

編寫 頂點著色器與片段著色器

在這裡插入圖片描述

triangle_vertex.glsl
頂點著色器: 接受座標資料 ,目的是為了確定座標位置,並且輸出傳遞一個顏色值

//指定版本號
#version 300 es
//設定了輸入變數的位置值
layout (location = 0) in vec3 aPos;

//out輸出欄位,傳遞給片段著色器的顏色
out vec4 color;
void main()
{
    // gl_Position (內建函式) 賦值位置
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    //賦值暗紅色下面的引數分別是 rgba
    color = vec4(0.5, 0.0, 0.0, 1.0);
}

triangle_fragment.glsl
片段著色器:接受顏色,並輸出最終需要渲染的顏色值

#version 300 es
precision mediump float;

out vec4 fragColor;
//從頂點著色器傳來的輸入變數(名稱相同、型別相同)
in vec4 color;
void main()
{
    //將顏色輸出
    fragColor = color;
}

如果不理解,你就想象這個工廠的流水線。從一開始的頂點填充資料並傳遞,一直到片段接受並輸出渲染,輸入與輸出用 In 和 out表示

GLSurfaceView

OpenGL執行在EGLContext建立的GL環境。而GLSurfaceView與SurfaceView不同之處在於,它擁有這個對EGLContext的管理,建立了GL環境。所以可以用來顯示我們OpenGL的渲染結果

寫佈局

 <android.opengl.GLSurfaceView
        android:id="@+id/gl_surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

在activity的onCreate例項化,並設定一些方法

        val glSurfaceView = findViewById<GLSurfaceView>(R.id.gl_surface)
        //設定版本號 OpenGL ES 3.0
        glSurfaceView.setEGLContextClientVersion(3)
        //設定渲染實現
        glSurfaceView.setRenderer(TriangleRenderer())
        //建立和呼叫requestRender()時才會重新整理
        glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY

編寫我們的渲染實現

   /**
     * onDrawFrame:繪製時呼叫
     * onSurfaceChanged:改變大小時呼叫
     * onSurfaceCreated:建立時呼叫
     */
    inner class TriangleRenderer : GLSurfaceView.Renderer {

        //頂點座標 xyz 一共三個點
        private val vertices = floatArrayOf(
        -0.5f, -0.5f, 0.0f,
         0.5f, -0.5f, 0.0f, 
         0.0f, 0.5f, 0.0f)
         
	    //當前要傳遞給OpenGL轉換好的座標資料
        private val verticesBuffer: FloatBuffer
        //當前程式
        var program = 0

        init {
           //分配空間,並且轉為FloatBuffer
            verticesBuffer = OpenGlUtils.toBuffer(vertices)
        }


        override fun onDrawFrame(gl: GL10?) {
            // 繪製背景顏色 render
            GLES30.glClearColor(0.2f, 0.3f, 0.3f, 1.0f)
            GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

            //將程式加入到OpenGLES2.0環境
            GLES30.glUseProgram(program)
            //繪製三角形
            GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3)
            //禁止頂點陣列的控制代碼
            GLES30.glDisableVertexAttribArray(0)
        }

        override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
            GLES30.glViewport(0, 0, width, height)
        }

        override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
          //編譯頂點與片段指令碼檔案
            program = OpenGlUtils.uCreateGlProgram("triangle_vertex.glsl", "triangle_fragment.glsl", resources)
            //啟用三角形頂點的控制代碼
            GLES30.glEnableVertexAttribArray(0)
            //準備三角形的座標資料
            GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, verticesBuffer)
        }

    }

OpenGlUtils 工具類

class OpenGlUtils {

    companion object {
        private const val TAG = "OpenGlUtils"
		//通過路徑載入Assets中的文字內容
        private fun uRes(mRes: Resources, path: String): String? {
            val result = StringBuilder()
            try {
                val ass = mRes.assets.open(path)
                var ch: Int
                val buffer = ByteArray(1024)
                ch = ass.read(buffer)
                while (-1 != ch) {
                    result.append(String(buffer, 0, ch))
                    ch = ass.read(buffer)
                }
            } catch (e: Exception) {
                return null
            }
            return result.toString().replace("\\r\\n".toRegex(), "\n")
        }


        //建立GL程式
        fun uCreateGlProgram(vertexSource: String, fragmentSource: String, resources: Resources): Int {
            var program = GLES30.glCreateProgram()
            //當讀取到的資料都不為空時才載入程式
            uRes(resources, vertexSource)?.let {
                uRes(resources, fragmentSource)?.run {
                    val vertex = uLoadShader(GLES30.GL_VERTEX_SHADER, it)
                    if (vertex == 0) return 0
                    val fragment = uLoadShader(GLES30.GL_FRAGMENT_SHADER, this)
                    if (fragment == 0) return 0
                    if (program != 0) {
                        GLES30.glAttachShader(program, vertex)
                        GLES30.glAttachShader(program, fragment)
                        GLES30.glLinkProgram(program)
                        val linkStatus = IntArray(1)
                        GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0)
                        if (linkStatus[0] != GLES30.GL_TRUE) {
                            glError(1, "Could not link program:" + GLES30.glGetProgramInfoLog(program))
                            GLES30.glDeleteProgram(program)
                            program = 0
                        }
                    }
                }
            }
            return program
        }

        private fun glError(code: Int, index: Any) {
            if (BuildConfig.DEBUG && code != 0) {
                Log.e(TAG, "glError:$code---$index")
            }
        }

        //載入shader
        private fun uLoadShader(shaderType: Int, source: String): Int {
            var shader = GLES30.glCreateShader(shaderType)
            if (0 != shader) {
                GLES30.glShaderSource(shader, source)
                GLES30.glCompileShader(shader)
                val compiled = IntArray(1)
                GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0)
                if (compiled[0] == 0) {
                    glError(1, "Could not compile shader:$shaderType")
                    glError(1, "GLES30 Error:" + GLES30.glGetShaderInfoLog(shader))
                    GLES30.glDeleteShader(shader)
                    shader = 0
                }
            }
            return shader
        }

        /**
         * Buffer初始化
         */
        fun toBuffer(pos: FloatArray): FloatBuffer {
            val a = ByteBuffer.allocateDirect(pos.count() * 4)
            a.order(ByteOrder.nativeOrder())
            val mVerBuffer = a.asFloatBuffer()
            mVerBuffer.put(pos)
            mVerBuffer.position(0)
            return mVerBuffer
         }
	}
}

總結

做完上面的步驟就可以渲染出屬於自己的三角形啦!!!
其實無非就是編譯著色器,給著色器賦值,然後配合GLSurfaceView,在合適得回撥初始化OpenGL,並渲染。
當然這只是最基本的步驟,一個合理的渲染應該還得配合VAO,VBO,EBO技術,提高我們的渲染效率。下篇就來說說這幾個技術 -v-!!!

強烈推薦的文章,雖然不是Android的,但是裡面有很多關於OpenGL的一些解釋。認真看完吧