OpenGL ES 学习(三) -- 绘制平面图形

news2025/7/15 1:20:16

上一章中,已经对 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,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0
            vertexData) //顶点数据缓冲区

        //通知GL程序使用指定的顶点属性索引
        GLES20.glEnableVertexAttribArray(a_position)

    }

1.4 渲染

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

@Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
      //着色器的加载、赋值
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public 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设置的颜色,刷新Surface
        GLES20.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.Renderer
                    setRenderer(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设置的颜色,刷新Surface
        GLES20.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/38258.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

D. Insert a Progression(绝对值的性质)

Problem - 1671D - Codeforces 给你一个n个整数的序列a1,a2,...,an。你还得到了x个整数1,2,...,x。 每个整数可以插入序列的开头,也可以插入序列的结尾,或者插入序列的任何元素之间。 所得序列a′的得分是其中相邻元素的绝对差异之和(∑i1n…

实验跟踪

管理和跟踪机器学习实验。 Intuition 到目前为止,一直在训练和评估不同的基线,但还没有真正跟踪这些实验。将解决这个问题,但定义一个适当的实验跟踪过程,将用于所有未来的实验(包括超参数优化)。实验跟踪是…

【JavaWeb】CookieSession

文章目录一.Cookie❤️1.Cookie的概念2.创建Cookie3.获取Cookie4.修改Cookie的值5.Cookie的生命周期控制6.Cookie有效路径Path的设置7.免输入用户名登录二.Session❤️1.session的概念2.Session的创建和获取3.Session域数据的存取4.Session的生命周期控制5.浏览器和Session之间…

pytorch深度学习实战lesson28

第二十八课 resnet的梯度计算(如何缓解梯度问题) 沐神说:“假设你在卷积神经网络里面,只要了解一个神经网络的话,你就了解 rest net 就行了。 rest net 是一个很简单的也是很好用的一个网络。这也是大家会经常在实际中…

OpenCV-Python小应用(六):车道线检测

OpenCV-Python小应用(六):车道线检测前言前提条件实验环境基于霍夫变换的车道线检测参考文献前言 本文是个人使用OpenCV-Python的应用案例,由于水平有限,难免出现错漏,敬请批评改正。更多精彩内容&#xff…

【成为红帽工程师】第五天 NFS服务器

目录 一、NFS服务器简介 二、NFS的使用 三、客户端使用autofs自动挂载 四、相关实验 一、NFS服务器简介 NFS(网络文件系统),是FreeBSD支持的文件系统中的一种,它允许网络中的计算机(不同的计算机、不同的操作系统&…

Go学习之路:流程控制语句:for、if、else、switch 和 defer(DAY 1)

文章目录前引流程控制语句:for、if、else、switch 和 defer1.1、for循环语句/语法格式(一)1.2、for循环语句/省略前置后置语句(二)1.3、for循环语句/while(三)1.4、for循环语句/无限循环&#x…

美新科技过会:收入依赖美国、产能利用率低,林东亮等均为香港籍

11月25日,深圳证券交易所创业板披露的信息显示,美新科技股份有限公司(下称“美新科技”)获得上市委会议通过。据贝多财经了解,美新科技于2022年3月31日在创业板递交上市申请。 本次冲刺创业板上市,美新科技…

SpringCloudGateway--谓词(断言)

目录 一、定义 二、谓词使用 1、After 2、Before 3、Between 4、Cookie 5、Header 6、Host 7、Method 8、Path 9、Query 10、RemoteAddr 11、Weight 一、定义 SpringCloudGateway中三个重要词汇: 路由(Route):配置网…

傻白入门芯片设计,芯片键合(Die Bonding)(四)

目录 一、键合( Bonding) 1. 什么是键合(Bonding)? 2. 芯片键合步骤 3.芯片拾取与放置(Pick & Place) 4. 芯片顶出(Ejection)工艺 5. 使用环氧树脂(Epoxy)实现粘合的芯片键合工艺 6. 使用晶片黏结薄膜(DAF)的芯片键合工…

Redis实战篇(三)秒杀

一、全局唯一ID (1)定义 全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一半满足下列特性: 唯一性高可用高性能递增性安全性 为了增加ID的安全性,我们不直接使用Redis自增的数值&#xf…

OpenCV-Python快速入门(十五):霍夫变换

OpenCV-Python快速入门(十五):霍夫变换前言前提条件实验环境霍夫变换基本原理霍夫直线变换(cv2.HoughLines())概率霍夫变换(cv2.HoughLinesP())霍夫圆变换(cv2.HoughCircles()&#…

移动端测试理论

App测试基础 App功能测试及专项测试 前言: 对于APP项目的测试,一般是进行系统测试。 测试主要从业务功能和非业务功能两个方面考虑。业务功能测试 根据软件说明,设计文档或用户需求验证App的各个功能的实现。 专项测试 兼容性测试 兼容性测试的关注点…

阿里Java研发面经(已拿offer)

一、自我总结: 1)首先最重要的一点。对自己的要求高点。不要以简单的实习生来要求自己。你要想 你会的别人都会 你的核心竞争力是什么呢。所以楼主建议以Java高级工程师来要求自己。不会的就学嘛。人面对未知的事物 本能反应是恐惧与退缩。可当你尝试去…

Xxl-Job 初次体验

Xxl-Job 初次体验一、定时任务-前置知识二、演变机制三、xxl-Job 设计思想四、xxl-job 实战1. 调度中心部署2. 编写执行器简单使用一下2.1. 让执行器run起来!2.2. 在调度中心配置任务,调度一下!3. XxlJob 任务的生命周期4. 路由策略5. 父子任…

汇川PLC编程软件AutoShop的使用

文章目录一、数据类型二、系统参数.1、内存容量设置2、“掉电保持范围”设置3、系统设置三、符号表1、编辑符号表2、符号表的打印四、元件监控表1、新建元件监控表2、编辑元件监控表3、复制元件监控表4、快速监控表五、元件使用信息表六、交叉引用表七、软元件内存表1、新建和复…

windows的小米11真机appium微信爬虫

1、下载appium 仓库地址 2、下载python的包 pip install Appium-Python-Client -i https://pypi.tuna.tsinghua.edu.cn/simple 3、下载android-sdk 先下SDK Tools 国内一个镜像网站 参考这个教程 安装好后,运行这个SDK Manager.exe 然后install,同意协…

Alphalens使用方法细节判断

首先alphalens的数据格式: factor: MultiIndex(用stack()方法来转换) prices: DataFrame #转换成MultiIndex factor alpha_mom.stack() print (factor.tail()) datetime 2017-11-20 15:00:00 601857.XSHG 1…

小学生python游戏编程arcade----烟花粒子

小学生python游戏编程arcade----烟花粒子前言烟花粒子1、Vector向量类1.1 arcade中的向量类1.2 应用2、绘制粒子所有纹理图片2.1 给定直径和颜色的圆的纹理2.2 arcade.make_circle_texture函数原码2.3 make_soft_circle_texture 函数原码2.4 公共纹理代码3 效果图4 代码源码获取…

【读点论文】Densely Connected Convolutional Networks用残差连接大力出奇迹,进一步叠加特征图,以牺牲显存为代价

Densely Connected Convolutional Networks Abstract 如果卷积网络在靠近输入的层和靠近输出的层之间包含较短的连接,则卷积网络可以训练得更深入、更准确和有效。在本文中,接受了这种观察,并介绍了密集卷积网络(DenseNet),它以…