Cocos2d-x 3.x 图形学渲染系列三

来源:互联网 发布:商汤科技人脸算法优势 编辑:程序博客网 时间:2024/05/01 11:17

   笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社 和《Unity3D实战核心技术详解》电子工业出版社 等。

市面上,跨平台引擎使用的底层图形库都是用OpenGL,很多人都认为OpenGL是一个API(Applicatoin Programming Interface,应用程序编程接口),因为它里面包含了一系列操作图形、图像的函数。实际上,OpenGL本身并不是一个API,它是一个由Khronos组织制定并维护的规范。OpenGL规范规定了库中的每个函数如何执行,以及它们的输出值。本书介绍的OpenGL是面向OpenGL3.3以上的版本。

     

OpenGL库是用C语言写的,同时也支持多种语言的派生,但其内核仍是一个C库。由于C的一些语言结构不易被翻译到其它的高级语言,因此OpenGL开发的时候引入了一些抽象层。“对象(Object)”就是其中一个。

在OpenGL中一个对象是指一些选项的集合,它代表OpenGL状态的一个子集。比如,可以用一个对象来代表绘图窗口的设置,之后我们就可以设置它的大小、支持的颜色位数等等。可以把对象看做一个C风格的结构体(Struct):

struct object_name {    GLfloat  option1;    GLuint   option2;    GLchar[] name;};

使用OpenGL时,建议使用OpenGL定义的基元类型。比如使用float时会加上前缀GL(因此写作GLfloat),int 写成GLInt等等。下面通过一段代码给读者介绍,如何理解OpenGL中的对象概念?代码段如下所示:

// 创建对象GLuint objectId = 0;glGenObject(1, &objectId);// 绑定对象至上下文glBindObject(GL_WINDOW_TARGET, objectId);// 设置当前绑定到 GL_WINDOW_TARGET 的对象的一些选项glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);// 将上下文对象设回默认glBindObject(GL_WINDOW_TARGET, 0);

给大家解释一下代码含义:这一小段代码展现了使用OpenGL编写代码时常见的工作流。首先需要创建一个对象,然后用一个id保存它的引用(实际数据被储存在后台),然后将对象绑定至上下文的目标位置(例子中窗口对象目标的位置被定义成GL_WINDOW_TARGET)。接下来设置窗口的选项,最后将目标位置的对象id设回0,从而解绑这个对象。设置的选项将被保存在objectId所引用的对象中,一旦重新绑定这个对象到GL_WINDOW_TARGET位置,这些选项就会重新生效。如下语句:

glBindObject(GL_WINDOW_TARGET,objectId);

函数是绑定对象至上下文,OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行,OpenGL的状态通常被称为OpenGL上下文(Context)。换句话说就是,当更改OpenGL状态,比如设置某些选项后,用OpenGL上下文来渲染。下面再介绍一下在OpenGL的Shader编程中经常使用的着色器。

首先介绍一下着色器,着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的所有特性。

     着色器中定义了输入和输出变量:uniform、varing、attribute以及main函数编写。每个着色器的入口点都是main函数,在这个函数中处理所有的输入变量,并将结果输出到已定义的输出变量中。

     着色器是各自独立的小程序,它们都是一个整体的一部分,每个着色器都有输入和输出,这样才能进行数据交流和传递。GLSL定义了in和out关键字专门来实现这个目的,每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。但在顶点和片段着色器中会有点不同。顶点着色器应该接收的是一种特殊形式的输入,否则就会效率低下。顶点着色器的输入特殊在,它从顶点数据中直接接收输入。而片段着色器,它需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为默认的黑色(或白色)。

   下面介绍一下顶点着色器和片段着色器二者是如何结合在一起的,如果开发者打算从一个着色器向另一个着色器发送数据,那必须在发送方着色器中声明一个输出,在接收方着色器中声明一个相同定义的输入。当类型和名字都一样时,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。以代码为例说明一下,首先给大家展示的是顶点着色器代码:

// position变量的属性位置值为0layout (location = 0) in vec3 position; // 为片段着色器指定一个颜色输出out vec4 vertexColor; voidmain(){// 注意我们如何把一个vec3作为vec4的构造器的参数gl_Position = vec4(position, 1.0);// 把输出变量设置为暗红色        vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f); }

顶点着色器中实现了位置的输出以及颜色的输出,下面是片段着色器代码如下所示:

// 从顶点着色器传来的输入变量(名称相同、类型相同)in vec4 vertexColor; // 片段着色器输出的变量名可以任意命名,类型必须是vec4out vec4 color;voidmain(){    color = vertexColor;}

通过顶点着色器和片段着色器代码可以看到,在顶点着色器中声明了一个vertexColor变量作为vec4输出,并在片段着色器中声明了一个相同的vertexColor。由于它们名字相同且类型相同,片段着色器中的vertexColor就和顶点着色器中的vertexColor链接了。它们的链接当然是GPU内部实现的,在这里不需要再继续深入理解,只要知道你原理就可以了。由于在顶点着色器中将颜色设置为深红色,最终的片段也是深红色的。实现的效果如下图2-1所示:



下面介绍一下使用案例:

游戏美工制作的模型由于导出格式加密成二进制的原因,对于大部分开发者模型内容都是不可见的,在这里根据以前的开发经验给读者介绍一下模型内部组成。模型的最基本组成:顶点、法线、UV坐标等数值,模型中的顶点是必须的,因为每个3D模型都是由点组成的三角面或者是四边形,在引擎加载中都是以三角面为基本单元加载的。为了更好的给大家展示,下面自定义一个三角形,也是为了后面模型文件内部的介绍以及模型文件加载铺路,下面是自定义的三角形结构体:

GLfloat vertices[] = {// 位置              // 颜色0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // 右下-0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  // 左下0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f// 顶部};

三角形的定义用了数组的形式,包括顶点的三维坐标和颜色值,将定义的颜色和位置信息通过GPU渲染展示出来,这就需要编写顶点着色器和片段着色器代码,下面是对应的完整顶点着色器代码:

// 位置变量的属性位置值为 0attribute vec3 position; // 颜色变量的属性位置值为 1attribute vec3 color;    // 向片段着色器输出一个颜色varying vec3 ourColor; void main(){gl_Position = vec4(position, 1.0);// 将ourColor设置为我们从顶点数据那里得到的输入颜色    ourColor = color; }

与顶点着色器对应的是片段着色器代码如下所示:

varying vec3 ourColor;void main(){gl_FragColor = vec4(ourColor, 1.0f);}

通过这两个着色器就可以把定义的三角形显示在屏幕上,下面把定义的顶点数组在内存内部的结构体解释一下:定义好的顶点也称为VBO(全称:Vertex Buffer Object)顶点缓存对象在内存的分布图给读者展示效果如下图2-2所示:



学习Shader编程对于顶点的一些存储,至少要明白其在内存是如何存放的。了解了其在内存的布局后,调用OpenGL库的接口函数glVertexAttribPointer更新顶点格式,代码如下所示:

// 位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);// 颜色属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3* sizeof(GLfloat)));glEnableVertexAttribArray(1);

在设置函数参数时,第五个参数的含义是必须向右移动6float其中3个是位置值,另外3个是颜色值。这使我们的步长值为6乘以float的字节数从而指定一个偏移量。对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0颜色属性紧随位置数据之后,所以偏移量就是3 * sizeof(GLfloat)用字节来计算就是12字节。其渲染的效果图如下图2-3所示:


通过给读者展现的代码可以看到Shader的编写还是比较容易掌握的,在以上代码中没有使用任何算法运算,只是为了通过Shader编写渲染一个简单的三角形。

小结:

学习OpenGL编程首先要了解Shader编程语言的基本语法,这与传统的学习语言类似。如果开发者有C语言背景更好,这样学习起来更顺手。掌握语法后,网上有很多这方面的教程,试着拿别人已编写Shader脚本运行,看一下效果,然后再在已有的基础上修改别人的脚本,逐步的去掌握。另外Shader脚本在程序中是不可以调试的,如果遇到问题,只能通过赋值操作或者注释行操作以及使用特定值操作,最终通过排除法找到问题所在。


1 0
原创粉丝点击