OpenGL ES 学习(三) -- 绘制平面图形
创始人
2024-02-17 21:21:31

上一章中,已经对 OpenGL 的编程语言 GLSL 和渲染模式有了一定的了解,今天,将运用之前的知识,完成一些平面图形的操作。效果如下:
在这里插入图片描述

如果你对 OpenGL 的基本概念或者渲染流程不清晰,建议先看 OpenGL ES 学习(一) – 基本概念 和 OpenGL ES 学习(二) – 渲染模式和GLSL
这两篇文章。

先直接上两张图:
在这里插入图片描述
在这里插入图片描述

可以看到,我们需要先编写着色器的代码,才能把 OpenGL 的数据,传递到渲染管线上。

一. 完成程序编写

首先从最简单的一个点来写。

1.1 着色器代码代码编写

先新建一个 GLSL 的文件,编写顶点着色器的代码,对 GLSL 不熟悉,可以先 OpenGL ES 学习(二) – 渲染模式和GLSL 。

一个着色器的代码,都是一个执行片段,所以,都是 main 函数为入口。
一个点绘制,需要位置,大小和颜色,而着色的内置参数为:

  • 顶点着色器(Vectex Shader):gl_position(位置) 和 gl_pointSize (大小)
  • 片段着色器(Fragment Shader):gl_FragColor (颜色值)

所以,点的顶点着色器代码为:
在这里插入图片描述
顶点着色器的变量为:

        private val VERTEX_SHADER = """//位置需要自己指定attribute vec4 a_Position;void main(){gl_Position = a_Position;//注意PointSize 是浮点类型,这里直接写死测试gl_PointSize = 100.0;}"""

片段着色器这里,只需要涂上一个颜色即可,这个颜色值由程序给,内置参数 gl_FragColor

  /*** 片段着色器*/private val FRAGMENT_SHADER = """// 定义所有浮点数据类型的默认精度;有lowp、mediump、highp 三种,但只有部分硬件支持片段着色器使用highp。(顶点着色器默认highp)precision mediump float;uniform vec4 u_Color;void main(){gl_FragColor = u_Color;}"""

除了直接写好,也可以把 glsl 的文件,放到 assert ,再读取。

1.2 着色的创建/编译

先看一张图:
在这里插入图片描述
所以,一个着色器的代码生成,可以理解为:

  1. 使用 glCreateProgram 拿到 OpenGL 对象
  2. 使用 glAttachShader 关联着色器代码,着色器的代码构建:glCreateShader -> glShaderSource -> glCompileShader 组成
  3. glLinkProgram 将着色器的程序关联到 OpenGL 对象,组成一个 OpenGL 程序
  4. 使用 GLES20.glUseProgram,使用上述的 OpenGL 程序

编译着色器

    /*** 编译着色器代码,获取代码Id*/open fun compileShader(type: Int, shaderCode: String): Int {//创建一个shader 对象val shaderId = GLES20.glCreateShader(type)if (shaderId == 0) {Log.d(TAG, " 创建失败")return 0}//将着色器代码上传到着色器对象中GLES20.glShaderSource(shaderId, shaderCode)//编译对象GLES20.glCompileShader(shaderId)//获取编译状态,OpenGL 把想要获取的值放入长度为1的数据首位val compileStatus = intArrayOf(1)GLES20.glGetShaderiv(shaderId, GLES20.GL_COMPILE_STATUS, compileStatus, 0)Log.d(TAG, " compileShader: ${compileStatus[0]}")if (compileStatus[0] == 0) {Log.d(TAG, " 编译失败")GLES20.glDeleteShader(shaderId)return 0}return shaderId}

关联着色器代码,组成可执行程序:

        /*** 关联着色器代码,组成可执行程序*/open fun linkProgram(vertexShaderId: Int, fragmentShaderId: Int): Int {//创建一个 OpenGL 程序对象val programId = GLES20.glCreateProgram()if (programId == 0) {Log.d(TAG, " 创建OpenGL程序对象失败")return 0}//关联顶点着色器GLES20.glAttachShader(programId, vertexShaderId)//关联片段周色漆GLES20.glAttachShader(programId, fragmentShaderId)//将两个着色器关联到 OpenGL 对象GLES20.glLinkProgram(programId)//获取链接状态,OpenGL 把想要获取的值放入长度为1的数据首位val linkStatus = intArrayOf(1)GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, 0)Log.d(TAG, " linkProgram: ${linkStatus[0]}")if (linkStatus[0] == 0) {GLES20.glDeleteProgram(programId)Log.d(TAG, " 编译失败")return 0}return programId;}

最后,使用该程序:

    /*** 生成可执行程序,并使用该程序*/protected fun makeProgram(vertexShaderCode:String,fragmentShaderCode:String):Int{//需要编译着色器,编译成一段可执行的bin,去与显卡交流val vertexShader = compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)//步骤2,编译片段着色器val fragmentShader = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)// 步骤3:将顶点着色器、片段着色器进行链接,组装成一个OpenGL程序programId = linkProgram(vertexShader, fragmentShader)//通过OpenGL 使用该程序GLES20.glUseProgram(programId)return programId}

着色器创建、编译和链接,都可以拿到状态值,大于0的时候,则表示是可用的。这几步都是固定,封装好就可以了。

1.3 获取定编索引和通过 GL 使用索引

获取顶点索引
回去顶点之前,还需要申明顶点的位置,分量个数:

        //着色器中的两个变量,需要我们赋值private val U_COLOR = "u_Color"private val A_POSITION = "a_Position"//定点的数据,只有一个点,就放中心即可private val POINT_DATA = floatArrayOf(0f, 0f)/*** Float类型占4Byte*/private val BYTES_PER_FLOAT = 4/*** 每个顶点数据关联的分量个数:当前案例只有x、y,故为2*/private val POSITION_COMPONENT_COUNT = 2//颜色索引private var u_color = 0

加载顶点数据到内存

    //通过nio ByteBuffer把设置的顶点数据加载到内存private var vertexData: FloatBuffer =ByteBuffer// 分配顶点坐标分量个数 * Float占的Byte位数.allocateDirect(POINT_DATA.size * BufferUtil.BYTES_PER_FLOAT)// 按照本地字节序排序.order(ByteOrder.nativeOrder())// Byte类型转Float类型.asFloatBuffer().put(POINT_DATA)

1.3.1 关联索引

GL 关联索引,使用的是 glVertexAttribPointer 方法,它会把顶点数据和属性关联到 GL 里,然后再通过 glEnableVertexAttribArray,告知 GL 使用指定的顶点属性索引。
完成代码如下:

       override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {//白色背景GLES20.glClearColor(1f,1f,1f,1f)// 编译着色器相关程序val programId = makeProgram(VERTEX_SHADER, FRAGMENT_SHADER)// 步骤5:获取颜色Uniform在OpenGL程序中的索引u_color = GLES20.glGetUniformLocation(programId, U_COLOR)// 步骤6:获取顶点坐标属性在OpenGL程序中的索引val a_position = GLES20.glGetAttribLocation(programId,A_POSITION)//将缓冲区的指针指到头部,保证数据从头开始vertexData.position(0)// 关联顶点坐标属性和缓存数据,参数说明如下:GLES20.glVertexAttribPointer(a_position, //位置索引;POSITION_COMPONENT_COUNT,//用几个分量描述一个顶点GLES20.GL_FLOAT,//分量类型false, //固定点数据值是否应该被归一化0, //指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0vertexData) //顶点数据缓冲区//通知GL程序使用指定的顶点属性索引GLES20.glEnableVertexAttribArray(a_position)}

1.4 渲染

OpenGL 的加载容器使用的是 GLSurfaceView ,基于 SurfaceView ,通过 Render 来加载数据。
因此,我们可以继承 GLSurfaceView.Renderer,重写方法:

@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {//着色器的加载、赋值}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {GLES20.glViewport(0, 0, width, height);}@Overridepublic void onDrawFrame(GL10 gl) {//清屏GLES20.glClear(GL_COLOR_BUFFER_BIT);//绘制GLES20.glDrawArrays(GLES20.GL_POINTS,0,1);}

上面在 onSurfaceCreated 中完成了着色器的加载和复制。
而当有数据来的时候,会回调 onDrawFrame 方法,我们可以在这里,使用 glDrawArrays 去绘制顶点的类型,和个数,该方法的解释为,假如现按顺序有A、B、C、D、E、F一共6个点。
而mode的具体参数值如下:
图片来源https://www.jianshu.com/p/eb11a8346cf6

二. 画几何图形

2.1 画点

完成代码为:

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {//填充整个页面GLES20.glViewport(0,0,width,height)}override fun onDrawFrame(gl: GL10?) {//步骤1:使用glClearColor设置的颜色,刷新SurfaceGLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)// 步骤2:更新u_Color的值,即更新画笔颜色,RGBA,百分百的意思GLES20.glUniform4f(u_color,0f,1f,0f,1f)// 步骤3:使用数组绘制图形:// 1.绘制的图形类型;2.从顶点数组读取的起点;3.从顶点数组读取的顶点个数 ,这里只绘制一个点GLES20.glDrawArrays(GLES20.GL_POINTS,0,1)}

GLSurfaceView 的使用

glSurfaceView = GLSurfaceView(this@MainActivity).apply {//设置 GL 的版本setEGLContextClientVersion(2)setEGLConfigChooser(false)//你继承的  GLSurfaceView.RenderersetRenderer(render)//等待点击才会刷帧renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY}

效果:
在这里插入图片描述

2.2 画多边形

修改顶点数据

        private val POINT_DATA = floatArrayOf(//x,y 一个点,这里相当于一个棱形,自己画个坐标0f, 0f,0f, 0.5f,-0.5f, 0f,0f, -0.5f,0.5f, -0.5f,0.5f, 0f,0.5f, 0.5f,)

坐标时[-1,1] 之间,可以想象一下。
其他不变,在 onDrawFrame 修改绘制的顶点个数,当点击时,刷新个数:

    override fun onDrawFrame(gl: GL10?) {//步骤1:使用glClearColor设置的颜色,刷新SurfaceGLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)drawIndex++// glDrawArrays 可以理解成绘制一个图层,多个图层可以叠加,然后通过onDrawFrame绘制到这一帧上drawTriangle()drawLine()drawPoint();if (drawIndex >= POINT_DATA.size / 2) {drawIndex = 0}}private fun drawLine() {// GL_LINES:每2个点构成一条线段// GL_LINE_LOOP:按顺序将所有的点连接起来,包括首位相连// GL_LINE_STRIP:按顺序将所有的点连接起来,不包括首位相连GLES20.glUniform4f(uniformColor, 1f, 0f, 0f, 1f)GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 0, drawIndex)}private fun drawPoint() {GLES20.glUniform4f(uniformColor, 0f, 0f, 1f, 1f)GLES20.glDrawArrays(GLES20.GL_POINTS, 0, drawIndex)}private fun drawTriangle() {// GL_TRIANGLES:每3个点构成一个三角形// GL_TRIANGLE_STRIP:相邻3个点构成一个三角形,不包括首位两个点// GL_TRIANGLE_FAN:第一个点和之后所有相邻的2个点构成一个三角形GLES20.glUniform4f(uniformColor, 1f, 1f, 0f, 1f)GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, drawIndex)}

效果:
在这里插入图片描述
这样就学习完几何图形的绘制了。更多代码,参考工程:https://github.com/LillteZheng/OpenGLDemo

参考:
https://www.jianshu.com/p/eb11a8346cf6
https://mp.weixin.qq.com/s?__biz=MzU5NjkxMjE5Mg==&mid=2247483783&idx=1&sn=6c8fa673eff0aaffe0872227432c3214&chksm=fe5a30a8c92db9bea01b92d35c37efa16a7acb08237bdf6ad0db510549e3b8a14d692fbac638&scene=21#wechat_redirect

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...