添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

OpenGL 学习教程
Android OpenGL ES 学习(一) – 基本概念
Android OpenGL ES 学习(二) – 图形渲染管线和GLSL
Android OpenGL ES 学习(三) – 绘制平面图形
Android OpenGL ES 学习(四) – 正交投屏
Android OpenGL ES 学习(五) – 渐变色
Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序
Android OpenGL ES 学习(七) – 纹理
代码工程地址: https://github.com/LillteZheng/OpenGLDemo.git

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

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

看下面一张图:
在这里插入图片描述
在写程序之前,先有个认知,就是我们的写的程序是在 client ,就是cpu 的部分,那怎么跟 GPU (server) 通信呢?
从图看,它是通过顶点着色器的数据进行通信的,比如 attributes (in) 或 uniforms 的数据,在变成着色器程序后,就可以操作 gpu ,实现快速绘制的效果。

一. 完整程序编写

知道上面通信的机制,所以,第一步,就是顶点着色器的代码编写。

1.1 着色器代码代码编写

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

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

  • 顶点着色器(Vectex Shader) :gl_Position(位置) 和 gl_pointSize (大小)

所以,点的顶点着色器代码为:
在这里插入图片描述
其中,#version 300 es 表明 gl 的版本
顶点着色器的代码字符串为(后面用):

        //注意 #version 这里,一定要第一行,不然gl识别不到
        private val VERTEX_SHADER = """#version 300 es
           layout(location=0) in vec4 a_Position;
            void main(){
                gl_Position = a_Position;
                gl_PointSize = 100.0;

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

private val FRAGMENT_SHADER = """#version 300 es
         // 定义所有浮点数据类型的默认精度;有lowp、mediump、highp 三种,但只有部分硬件支持片段着色器使用highp。(顶点着色器默认highp)
         precision mediump float;
         //颜色是4分量,如果没有设置,则默认黑色 RGBA
         out vec4 u_Color;
         void main(){
             u_Color = vec4(1.0,0.0,0.0,1.0);

1.2 着色的创建/编译

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

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

1.2.1 编译着色器

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

可以使用 GLES30.glGetShaderInfoLog(shaderId) 获取执行错误的信息。

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

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

1.2.3 使用程序

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

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

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

1.3.1 定义顶点

这里先用一个点来表示,上面说道,顶点有4个分量,但是点是x,y 两个分量,所以设置为0,0 即可,如下

        //定点的数据,只有一个点,就放中心即可
        private val POINT_DATA = floatArrayOf(0f, 0f)
         * Float类型占4Byte
        private val BYTES_PER_FLOAT = 4
         * 每个顶点数据关联的分量个数:当前案例只有x、y,故为2
        private val POSITION_COMPONENT_COUNT = 2

加载顶点数据到内存

    //通过nio ByteBuffer把设置的顶点数据加载到内存
    private var vertexData =ByteBuffer
        // 分配顶点坐标分量个数 * Float占的Byte位数
        .allocateDirect(POINT_DATA.size * BYTES_PER_FLOAT)
        // 按照本地字节序排序
        .order(ByteOrder.nativeOrder())
        // Byte类型转Float类型
        .asFloatBuffer()
        .put(POINT_DATA)
        //将缓冲区的指针指到头部,保证数据从头开始
        .position(0)
//后面封装成 vertexData = BufferUtil.createFloatBuffer(POINT_DATA)

1.3.1 关联顶点数据

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

完成代码如下:

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        //白色背景
        GLES30.glClearColor(1f, 1f, 1f, 1f)
        // 编译着色器相关程序
        makeProgram(VERTEX_SHADER, FRAGMENT_SHADER)
        // 关联顶点坐标属性和缓存数据,参数说明如下:
        GLES30.glVertexAttribPointer(
            0, //位置索引
            POSITION_COMPONENT_COUNT,//用几个分量描述一个顶点
            GLES30.GL_FLOAT,//分量类型
            false, //固定点数据值是否应该被归一化
            0, //指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0
            vertexData
        ) //顶点数据缓冲区
        //通知GL程序使用指定的顶点属性索引
        GLES30.glEnableVertexAttribArray(0)

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) {
        GLES30.glViewport(0, 0, width, height);
    @Override
    public void onDrawFrame(GL10 gl) {
        GLES30.glClear(GL_COLOR_BUFFER_BIT);
        GLES30.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) {
        //填充整个页面
        GLES30.glViewport(0, 0, width, height)
    override fun onDrawFrame(gl: GL10?) {
        Log.d(TAG, "onDrawFrame() call")
        //步骤1:使用glClearColor设置的颜色,刷新Surface
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        // 1.绘制的图形类型;2.从顶点数组读取的起点;3.从顶点数组读取的顶点个数 ,这里只绘制一个点
        GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 1)

GLSurfaceView 的使用

glSurfaceView = GLSurfaceView(this@MainActivity).apply {
				//设置 GL 的版本
                    setEGLContextClientVersion(3)
                    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
        GLES30.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:按顺序将所有的点连接起来,不包括首位相连
        GLES30.glUniform4f(uniformColor, 1f, 0f, 0f, 1f)
        GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, drawIndex)
    private fun drawPoint() {
        GLES30.glUniform4f(uniformColor, 0f, 0f, 1f, 1f)
        GLES30.glDrawArrays(GLES30.GL_POINTS, 0, drawIndex)
    private fun drawTriangle() {
        // GL_TRIANGLES:每3个点构成一个三角形
        // GL_TRIANGLE_STRIP:相邻3个点构成一个三角形,不包括首位两个点
        // GL_TRIANGLE_FAN:第一个点和之后所有相邻的2个点构成一个三角形
        GLES30.glUniform4f(uniformColor, 1f, 1f, 0f, 1f)
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, drawIndex)

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

参考:
https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/
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

上一章中,已经对 OpenGL 的编程语言 GLSL 和渲染模式有了一定的了解,今天,将运用之前的知识,完成一些平面图形的操作。效果如下:如果你对 OpenGL 的基本概念或者渲染流程不清晰,建议先看和这两篇文章。先直接上两张图:可以看到,我们需要先编写着色器的代码,才能把 OpenGL 的数据,传递到渲染管线上。
含有以下4个pdf,主要介绍基本概念和用法,对初学者非常有用。 Android OpenGL ES 简明开发教程.pdf Android_OpenGL_ES_book.pdf OpenGL ES 2 for Android.pdf Apress.Pro.OpenGL.ES.for.Android.Feb.2012.pdf
资源名称:Android维程序设计-基于OpenGL ES的图应用程序设计内容简介:本书详细阐述了与 Android移动设备以及 OpenGL ES开发相关的基本解决方案,主要包括 ES 2.0基础知识、3D建模、 Blender软件应用、纹理和着色、 Tank Fence游戏开发等内容。此外,本书还提供了丰富的示例以及代码,以帮助读者进一步理解相关方案的实现过程。本书适合作为高等院校计算机及 资源太大,传百度网盘了,链接在附件中,有需要的同学自取。
选择绘制作为OpenGL ES 2.0的第一个实例,是因为前文中提到的,点、线、OpenGL ES世界的图基础。无论多么复杂的几何物体,在OpenGL ES的世界里都可以用拼成。关于Android OpenGL ES 绘制,在Android官方文档中有详细的说明和步骤,本文实例也是依照官方文档步骤绘制。 依照官方文档中的说明,Android中利用OpenGL ES 2.0绘制的步骤为: 1. 在AndroidManifest.xml文件中设置使用的OpenGL ES的版本: <!-- Tell the system this app re
网上很多介绍OpenGL ES的文章,但由于OpenGL ES内容太多,所以这些文章难免过于臃肿杂乱,很难抓住重点,对于初学者来说最后还是云里雾里。很多人(包括笔者本人)开始深入了解OpenGL ES是因为其涉及到实时滤镜的应用,通常都会参考开源框架GPUImage的实现。如果没有掌握基本的OpenGL Es的开发知识,很难弄懂其中代码缘由。 目前很流行的短视频特效处理也有涉及到OpenGL...
一、纹理简介   在Android OpenGL基础(一、绘制四边)一文中,我们简单介绍了如何绘制纯色和四边。现在介绍如何把一张图片贴到四边上。   在OpenGL中,我们把需要贴合到物体上的图片称为纹理。纹理是一个2D图片(甚至也有1D和3D的纹理),可以把纹理理解为一个细节更丰富的颜色的集合。与之前例子中纯色四边不同的是,纹理细节更加丰富,OpenGL可以根据纹理计算得到四边每个点应该绘制的颜色。 1.1 纹理坐标系   为了能够把纹理映射到四边上,我们需要指定四边的每个顶点各
目录前言:OpenGL是什么:如何使用:1.设置OpenGL版本2.创建GLSurfaceView实例3.实现Renderer接口4.绘制定义图绘制5.投影和相机视图6.增加动画7.项目地址:总结: 前段时间,闲来无事,打算研究一下自定义camera开发,发现用到了OpenGL,所以打算自学一下,顺便写几篇文章记录一下。 OpenGL是什么: 学习OpenGl先了解一下,它是一个什么东西? OpenGL 是一种跨平台的图 API,用于为 3D 图处理硬件指定标准的软件接口。 你好,我开始也是用这种方法,但我开始为6个面添加同一张纹理的时候遇到了一个问题,纹理坐标无法复用!当我为正方体前表面粘贴纹理的时候,右下角对应纹理贴图的(0,1)坐标,为正方体右表面使用纹理贴图的时候,这个面的左下角,也就是前面的右下角的纹理坐标变成了(0,0),也就是说一个位置坐标对应了多个纹理坐标,使用索引绘制的时候只对顶点进行了索引,而无法对应多个纹理坐标,每个三角面都使用不同纹理的模型使用不了索引对象(IBO),我看了大多数教程都只是简单的给一个顶点一个颜色值,如果一个顶点有多个颜色值,来使一个顶点在不同的面上呈现不同颜色没有几个博主写到,网上找了一大圈也没有关于这方面的解决方案。最终我还是采用为每一个三角面都写三个独立的坐标,抛弃了索引对象,感觉它就是个多余的,除非它能实现对每一个VBO都分配一个IBO,然而事实不是如此,一个VAO里只能有一个IBO,多个VBO共用一个IBO Android 音视频开发(六) -- Android Mediaprojection 截屏和录屏 羊羊羊_咩咩咩: 排名高的用户写的博客就是不一样。表情包