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

前言

学习了OpenGL有一段时间,在绘制出属于自己的三角形之前,会接触许多理论上的知识。用简单的方式写下自己对OpenGL的一些见解。望大家取其精华去其糟粕

最终效果:改变背景色,并且绘制渲染一个暗红色的三角形

在这里插入图片描述

必备知识

OpenGL需要我们至少设置一个顶点和一个片段着色器,

如果不了解什么是着色器和他的坐标规则,可以看看上一篇的内容,不然你会看的很难受=v=!!。

通俗易懂的 OpenGL ES 3.0(一)入门必备知识!!

可以去blog看:通俗易懂的 OpenGL ES 3.0(二)渲染三角形

如何绘制
  1. 准备好顶点与片段着色器
  2. GLSurfaceView作为OpenGL的载体
  3. 编译着色器,创建GL程序
  4. 为着色器赋值,并绘制

本文deme: OpenGl30Demo

编写 顶点着色器与片段着色器

在这里插入图片描述

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-!!!

demo:OpenGl30Demo

强烈推荐的文章,虽然不是Android的,但是里面有很多关于OpenGL的一些解释。认真看完吧

Learnopengl-cn 你好三角形

发布了13 篇原创文章 · 获赞 46 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/a8688555/article/details/83722463