文档章节

Cocos2dx-OpenGL ES2.0教程:编写自己的shader(2)

乐逍遥jun
 乐逍遥jun
发布于 2016/02/22 21:37
字数 1817
阅读 49
收藏 1

上篇文章中,我给大家介绍了如何在cocos2d-x里面绘制一个三角形,当时我们使用的是cocos2d-x引擎自带的shader和一些辅助函数。在本文中,我将演示一下如何编写自己的shader,同时,我们还会介绍VBO(顶点缓冲区对象)和VAO(顶点数组对象)的基本用法。

在编写自己的shader之前,我觉得有必要提一下OpenGL渲染管线。
理解OpenGL渲染管线,对于学习OpenGL非常重要。下面是OpenGL渲染管线的示意图:(图中淡蓝色区域是可以编程的阶段)

pipelinepipeline

此图是从wiki中拿过来的,OpenGL的渲染管线主要包括:

  1. 准备顶点数据(通过VBO、VAO和Vertex attribute来传递数据给OpenGL)

  2. 顶点处理(这里主要由Vertex Shader来完成,从上图中可以看出,它还包括可选的Tessellation和Geometry shader阶段)

  3. 顶点后处理(主要包括Clipping,顶点坐标归一化和viewport变换)

  4. Primitive组装(比如3点组装成一个3角形)

  5. 光栅化成一个个像素

  6. 使用Fragment shader来处理这些像素

  7. 采样处理(主要包括Scissor Test, Depth Test, Blending, Stencil Test等)

更详细的信息可以参考本网站推荐的阅读材料和Wiki。

编写你的第一个Vertex Shader

首先是创建一个文件,把它命名为myVertextShader.vert, 并输入下列代码:

attribute vec4 a_position;
attribute vec4 a_color;

varying vec4 v_fragmentColor;void main() {
    gl_Position = CC_MVPMatrix * a_position;
    v_fragmentColor = a_color;
}

OpenGL Shader Language,简称GLSL,它是一种类似于C语言的专门为GPU设计的语言,它可以放在GPU里面被并行运行。下面我们来简单解释一下这一小段代码。

首先,每一个Shader程序都有一个main函数,这一点和c语言是一样的。然后这里面有两种类型的变量,一种是attribute,另一种是varying. attribute是从外部传进来的,每一个顶点都会有这两个属性,所以它也叫做vertex attribute(顶点属性)。而varying类型的变量是在vertex shader和fragment shader之间传递数据用的。这里的变量命名规则保持跟c一样就行了,注意gl开头的变量名是系统内置的变量,所以大家在定义自己的变量名时,请不要以gl开头。而CC_MVPMatrix是一个mat4类型的变量,它是在cocos2d-x内部设置进来的。这一点,我们后面再谈。

vertex shader是作用于每一个顶点的,我们本例中有三个点,所以这个vertex shader会被执行三次。

编写你的第一个Fragment Shader

首先,新建一个myFragmentShader.frag并输入下列代码:

varying vec4 v_fragmentColor;void main() {
    gl_FragColor = v_fragmentColor;
}

fragment shader中也有一个main函数,同时我们看到这里也声明了一个与vertex shader相同的变量v_fragmentColor。前面我们讲过,这个变量是用来在vertex shader和fragment shader之间传递数据用的。所以,它们的参数类型必须完全相同。如果一个是vec3,一个是vec4,shader编译的时候是会报错的。而gl_FragColor我们知道它肯定是一个系统内置变量了,它的作用是定义最终画在屏幕上面的像素点的颜色。我们回过头去看上一篇文章中画出来的三角形,我们指定的是三个顶点的颜色,分别为Red,Green和Blue,但是最后的三角形的颜色是通过这三个点的颜色插值出来的。因为最终三角形的像素点可不只有三个,理解这一点非常重要。

最后,让我们修改一下shader progam的创建代码:

//create my own program
 auto program = new GLProgram;
 program->initWithFilenames("myVertextShader.vert", "myFragmentShader.frag");
 program->link(); //set uniform locations
 program->updateUniforms();

编译并运行,此时你会得到和之前效果一样的三角形。

下图解释了我们的顶点数据是如何渲染成最终屏幕上面的像素的:

graphics_piplinegraphics_pipline

VAO和VBO初探

VBO,全名Vertex Buffer Object。它是GPU里面的一块缓冲区,当我们需要传递数据的时候,可以先向GPU申请一块内存,然后往里面填充数据。最后,再通过调用glVertexAttribPointer把数据传递给Vertex Shader。而VAO,全名为Vertex Array Object,它的作用主要是记录当前有哪些VBO,每个VBO里面绑定的是什么数据,还有每一个vertex attribute绑定的是哪一个VBO。关于VBO和VAO更详细的介绍,请参考此文

使用VBO和VAO的步骤都差不多,步骤如下:

  1. glGenXXX

  2. glBindXXX

让我们修改之前的代码:

 //创建和绑定vao    uint32_t vao;    glGenVertexArrays(1, &vao);    glBindVertexArray(vao);        //创建和绑定vbo    uint32_t vertexVBO;    glGenBuffers(1, &vertexVBO);    glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);auto size = Director::getInstance()->getVisibleSize(); float vertercies[] = { 0,0,   //第一个点的坐标 size.width, 0,   //第二个点的坐标 size.width / 2, size.height};  //第三个点的坐标

 float color[] = { 0, 1,0, 1,  1,0,0, 1, 0, 0, 1, 1};

 glBufferData(GL_ARRAY_BUFFER, sizeof(vertercies), vertercies, GL_STATIC_DRAW); //获取vertex attribute "a_position"的入口点
 GLuint positionLocation = glGetAttribLocation(program->getProgram(), "a_position"); //打开入a_position入口点
 glEnableVertexAttribArray(positionLocation); //传递顶点数据给a_position,注意最后一个参数是数组的偏移了。
 glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0); //set for color
 uint32_t colorVBO;
 glGenBuffers(1, &colorVBO);
 glBindBuffer(GL_ARRAY_BUFFER, colorVBO);
 glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);

 GLuint colorLocation = glGetAttribLocation(program->getProgram(), "a_color");
 glEnableVertexAttribArray(colorLocation);
 glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0); //for safty
 glBindVertexArray(0);
 glBindBuffer(GL_ARRAY_BUFFER, 0);

这里glBufferData把我们定义好的顶点和颜色数据传给VBO,此时,注意glVertexAttribPointer的最后一个参数,这里传递的都是(GLvoid)0。而不像之前一样传的是vertex和color的数组地址。*这一点是使用VBO和不使用VBO时要特别注意的。

顶点数据是怎么传递的

要弄明白程序里面定义的数组是怎么传递到vertex shader的,我们需要先弄清楚vertex attribute。

attribute vec4 a_position;
attribute vec4 a_color;

varying vec4 v_fragmentColor;void main() {
    gl_Position = CC_MVPMatrix * a_position;
    v_fragmentColor = a_color;
}

每一个attribute在vertex shader里面有一个location,它是用来传递数据的入口。我们可以通过下列代码获取这个入口值:

GLuint positionLocation = glGetAttribLocation(program->getProgram(), "a_position");
glEnableVertexAttribArray(positionLocation);

glGetAttribLocation是用来获得vertex attribute的入口的,在我们要传递数据之前,首先要告诉OpenGL,所以要调用glEnableVertexAttribArray。最后的数据通过glVertexAttribPointer传进来。它的第一个参数就是glGetAttribLocation返回的值。

重用VAO

最后,为了不让这些生成和绑定VBO和VAO的操作在每一帧都被执行,我们需要把它放在初始化函数里面。最终我们的draw函数如下:

auto glProgram = getGLProgram();

   glProgram->use();   //set uniform values, the order of the line is very important
   glProgram->setUniformsForBuiltins();   auto size = Director::getInstance()->getWinSize();   //use vao,因为vao记录了每一个顶点属性和缓冲区的状态,所以只需要绑定就可以使用了
   glBindVertexArray(vao);

   glDrawArrays(GL_TRIANGLES, 0, 3);

   glBindVertexArray(0);

   CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 3);
   CHECK_GL_ERROR_DEBUG();

这里可以看出,VAO对于简化程序作用是很大的。

好了,编译并运行,还是原来的三角形。

triangletriangle

下一篇文章,我们将讲一下OpenGL各种坐标系及其变换。当然,最重要的是World-to-Model变换,Model-to-View变换和View-to-Projection变换。

参考资料

  1. http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Chapter-1:-The-Graphics-Pipeline.html

  2. http://opengl.zilongshanren.com/opengl-tutorial/tut02/zh.html

  3. http://www.opengl.org/wiki/Rendering_Pipeline_Overview

本文转载自:

共有 人打赏支持
乐逍遥jun
粉丝 6
博文 79
码字总数 39510
作品 0
东城
技术主管
私信 提问
零基础使用cocos2dx-lua和skynet全栈式开发网游三(客户端配置)

客户端配置 一、扯几个概念 问题:cocos2dx引擎到底如何运作的? 在解答这个问题前,需要了解几个概念。 1.颜色 在自然界中,存在一种场,叫电磁场。电荷粒子运动状态变化时,就会释放电磁波...

量子出击
2018/06/21
0
0
cocos2dx- call to OpenGL ES API with no current context(logged once per thread)

Android APP 在红米2(Android 5.1系统)上进行全屏应用切换的时候,会出现卡屏,不会闪退并且出现频繁.在红米note和moto x1(Android 4.4系统)中偶尔出现. 其他手机上暂时还未出现全屏切换...

GLancelot
2015/08/14
1K
3
Cocos2d-x2.0 -- 从点,线,面学起

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier] 红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x 本章为我的Cocos2d-x教程一书初...

长平狐
2013/03/19
154
0
Cocos2d-x2.0 -- 从点,线,面学起

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier] 红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x 本章为我的Cocos2d-x教程一书初...

长平狐
2012/11/19
6.6K
0
Xcode6编译Cocos2d-x3.1.1 link错误

Xcode6编译Cocos2d-x3.1.1 link错误 原来苹果公司打算自己整一套OPenGL的东西,所有,有些东西需要根据IOS平台来加入它的东西。这个问题的解决方案如下: 在工程目录下cocos/platform/CCImag...

Nov_Eleven
2015/01/19
0
2

没有更多内容

加载失败,请刷新页面

加载更多

SpringBoot2.x配置Cors跨域

SpringBoot2.x配置Cors跨域 1 跨域的理解 跨域是指:浏览器A从服务器B获取的静态资源,包括Html、Css、Js,然后在Js中通过Ajax访问C服务器的静态资源或请求。即:浏览器A从B服务器拿的资源,...

水木星辰
19分钟前
1
0
一文搞懂TCP与UDP的区别

**摘要:**计算机网络基础 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有。 引言 网络协议是每个前端工程师都必须要掌握的知识,TCP/IP 中有两个具有代表性的传输层协议,分别是 TC...

Fundebug
20分钟前
2
0
Hanlp1.7版本的新增功能一览

Hanlp1.7版本在去年下半年的时候就随大快的DKH1.6版本同时发布了,截至目前1.7大版本也更新到了1.7.1了。本篇分别就1.7.0和1.7.1中新增的功能做一个简单的汇总介绍。 HanLP 是由一系列模型与...

左手的倒影
28分钟前
1
0
Linux之PAM可动态加载的认证模块

在Linux中执行有些程序时,这些程序在执行前首先要对启动它的用户进行认证,符合一定的要求之后才允许执行,例如login, su等。在Linux中进行身份或是状态的验证程序是由PAM来进行的,PAM(P...

城市之雾
28分钟前
1
0
程序员接私活那些坑

前言 最近有很多人问我私活怎么样?有什么坑,我之前也是接了几个私活,当然也有稳定的收入。我们也来分享一下。前几天发现了一个帖子很不错,我们来分享一下 注:本文转自 : http://blog.cs...

终端研发部
36分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部