通俗易懂的 OpenGL ES 3.0(二)渲染三角形
阿新 • • 發佈:2019-01-22
前言
學習了OpenGL有一段時間,在繪製出屬於自己的三角形之前,會接觸許多理論上的知識。用簡單的方式寫下自己對OpenGL的一些見解。望大家取其精華去其糟粕
最終效果:改變背景色,並且繪製渲染一個暗紅色的三角形
必備知識
OpenGL需要我們至少設定一個頂點和一個片段著色器,
如果不瞭解什麼是著色器和他的座標規則,可以看看上一篇的內容,不然你會看的很難受=v=!!。
如何繪製
- 準備好頂點與片段著色器
- GLSurfaceView作為OpenGL的載體
- 編譯著色器,建立GL程式
- 為著色器賦值,並繪製
編寫 頂點著色器與片段著色器
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的一些解釋。認真看完吧