前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >定义顶点和着色器

定义顶点和着色器

作者头像
故乡的樱花开了
发布2024-01-18 08:28:30
1670
发布2024-01-18 08:28:30
举报
文章被收录于专栏:Android技术专栏

一.前言

  在这里,我会通过一个空气曲棍球游戏来一步步介绍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的数据复制到本地内存,本地内存可以被本地环境存取,而不受垃圾回收器的管控。传输数据的方式如下图所示:

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

代码语言:javascript
复制
   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文件,内容如下:

代码语言:javascript
复制
#version 300 es
layout(location=0) in vec4 a_Position;
void main() {
    gl_Position=a_Position;
}

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

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

代码语言:javascript
复制
#version 300 es
uniform vec4 u_Color;
out vec4 fragColor;
void main() {
    fragColor=u_Color;
}

  uniform声明的变量的指一般由cpu端的应用程序设置,而不能在着色器内部直接修改。fragColor是一个向量,在这里包括四个分量,分别指红绿蓝和透明度。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档