• 定义顶点和着色器


    一.前言

      在这里,我会通过一个空气曲棍球游戏来一步步介绍OpenGL ES 3.0的相关内容。空气曲棍球游戏的规则是:我们首先需要一个有两个球门的长方形桌子,一个冰球和两个用来击打冰球的木槌;在每个回合开始前,冰球都会放在桌子的中间,每个玩家要尽力把冰球击进对方的球门,同时要防御对方的进攻,每进一球得一分,获得7分后就意味着该玩家获得了游戏的胜利。

    二.定义空气曲棍球的桌子结构

      在桌子绘制在屏幕之前,我们需要告诉OpenGL要画什么。开发过程的第一步,我们需要以OpenGL可以理解的形式定义一个桌子,在OpenGL中,所有东西的结构都是从一个顶点开始。接下来,我们给出顶点的定义:简单的说,一个顶点就是代表几何对象拐角的点,这个点有许多的属性,最重要的属性就是位置。为了简单起见,我们用一个长方形代表桌子结构,那么我们只需要定义4个顶点即可。

    三.OpenGL中的点,直线和三角形

      OpenGL只支持绘制点,直线和三角形。三角形是最基本的几何图形,因为它的结构非常稳定,拿掉一个点之后就成了直线了,再拿掉一个点之后就只剩一个点了。点和直线可以用于某些效果,只有三角形才能用来构建拥有复杂对象和纹理的场景。在OpenGL中,我们把一系列的点放到一个数组里去构建三角形,然后告诉OpenGL如何去连接这些点。我们想要构建的所有物体都需要用点,直线和三角形定义,现在我们想要绘制一个长方形,但OpenGL不能直接绘制长方形,所以我们可以绘制两个三角形来拼凑一个长方形。接下来,我们需要给桌子添加一个中间线,并绘制两个点来表示木槌,这是很容易做到的。

    四.使数据可以被OpenGL存取

      我们已经完成了顶点的定义了,但是在OpenGL存取他们之前,我们还需要完成另外一步。这里存在的主要问题是我们所编写的代码的运行环境和OpenGL的运行环境使用了不同的语言,我们编写的java/kotlin代码运行在Dalvik虚拟机上,运行在虚拟机上的代码不能直接访问本地环境,除非通过特定的api。而且Dalvik虚拟机还使用了垃圾回收机制,当虚拟机检测到一个变量,对象或其他内存片段不再使用的时候,就会把这些内存释放掉以备重用。但OpenGL是运行在本地环境中的,本地环境并不是这样工作的,它不期望内存块会被移来移去或者自动释放,也就是说本地环境是没有垃圾回收机制的。那么,我们所编写的代码运行在虚拟机上,它怎么和OpenGL通信呢?有两种技术,一种是JNI技术,当调用android.opengl.GLES30包里面的方法时,实际上就是通过JNI技术在后台调用本地系统库的方法。第二种技术是改变内存的分配方式,java有一个特殊的类集合,可以分配本地内存块,并且把java的数据复制到本地内存,本地内存可以被本地环境存取,而不受垃圾回收器的管控。传输数据的方式如下图所示:

       下面给出定义长方形顶点和分配本地内存的代码:

    复制代码
       private var vertexData:FloatBuffer
        init{
            val tableVertices=floatArrayOf(
                //Triangle one
                0f,0f,
                9f,14f,
                0f,14f,
                //Triangle two
                0f,0f,
                9f,0f,
                9f,14f,
                //Mid Line
                0f,7f,
                9f,7f,
                //Mallets
                4.5f,2f,
                4.5f,12f
            )
            //分配本地内存块
            vertexData=ByteBuffer.allocateDirect(tableVertices.size* BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())//按照本地字节序组织内容
                .asFloatBuffer()
            vertexData.put(tableVertices)
        }
        companion object{
            private val POSITION_COMPONENT_COUNT=2//记录一个顶点有两个分量
            private val BYTES_PER_FLOAT=4//每个浮点数4个字节
        }
    复制代码

    五.引入OpenGL管道

      现在,我们已经定义了空气曲棍球桌子的结构,并把这些数据复制到了OpenGL可以存取的本地内存,在把曲棍球桌子画到屏幕上之前,他需要在OpenGL管道中传递,这就需要使用着色器了。这些着色器会告诉图形处理单元如何绘制这些数据,有两种类型的着色器,在绘制任何内容到屏幕上之前,都需要定义他们。

    • 顶点着色器:生成每个顶点的最终位置,针对每个顶点,它都会执行一次,一旦最终位置确定,OpenGL会将这些顶点组装成点,直线和三角形
    • 片段着色器:为组成点,直线,三角形的每个片段生成最终的颜色,针对每个片段,它都会执行一次,一个片段是一个小的、单一颜色的长方形区域,类似于计算机屏幕上的一个像素

      一旦最终的颜色生成了,OpenGL就会把他们写在一个称为帧缓冲区的内存块,然后Android会把这个帧缓冲区显示在屏幕上。整个流程如下图所示:

       光栅化图元是指将每个点,直线和三角形分解成大量的小片段,他们可以映射到移动设备显示屏的像素上,从而生成一副图像。

      接下来,我们需要创建顶点着色器和片段着色器,这需要用到GLSL语言,他是OpenGL的着色语言,和c语言类似。我们需要在res文件夹下新建一个raw资源文件夹,然后在下面新建一个simple_vertex_shader.glsl文件,内容如下:

    #version 300 es
    layout(location=0) in vec4 a_Position;
    void main() {
    gl_Position=a_Position;
    gl_PointSize=10.0;
    }

      开头先申明opengl es的版本为3.0,in关键字用于声明输入变量,通常在顶点着色器中接收顶点数据,或者在片段着色器中接收插值后的数据。layout关键字用于指定输入和输出变量的位置,gl_Position是OpenGL中一个内建的变量,用于指定顶点的位置。vec4是一个包含4个分量的向量,在这里分别指x,y,z,w,其中x,y,z表示一个三维位置,w是一个特殊的坐标,后续会进行说明。如果这些值没有指定,默认情况下,前三个会赋值为0,w会赋值为1。

      然后,我们再定义一个片段着色器,命名为simple_fragment_shader.glsl,这个着色器会为每个片段生成最终的颜色,片段着色器的内容如下:

    #version 300 es
    layout(location=0) uniform vec4 u_Color;
    out vec4 fragColor;
    void main() {
    fragColor=u_Color;
    }

      uniform关键字声明的变量的指一般由cpu端的应用程序设置,而不能在着色器内部直接修改。out关键字用于声明输出变量,一般是指从顶点着色器传递给片段着色器的数据,没有out变量则会直接输出,fragColor是一个向量,在这里包括四个分量,分别指红绿蓝和透明度。

     

  • 相关阅读:
    ET-B33H-M@GB插4G卡后如何访问网页界面?
    质数和约数
    拜耳阵列(Bayer Pattern)以及常见彩色滤波矩阵(CFA)
    微信小程序| 基于ChatGPT+明基屏幕挂灯实现超智能家居物联网小程序
    JUC并发编程(5)(自定义线程池 + 共享模型之工具2)
    frp内网穿透保姆级配置流程,让客户端电脑可以通过域名或者IP访问本地程序接口
    (二)pytest自动化测试框架之添加测试用例步骤(@allure.step())
    开启海外“新副本”,中旭未来有几道“关卡”要闯?
    Nginx配置Https证书
    MySQL MVCC 多版本并发控制机制 工作原理
  • 原文地址:https://www.cnblogs.com/luqman/p/17969492/shader