基于Cocos2d-x学习OpenGL ES 2.0系列——编写自己的shader(2)
在上篇文章中,我给大家介绍了如何在Cocos2d-x里面绘制一个三角形,当时我们使用的是Cocos2d-x引擎自带的shader和一些辅助函数。在本文中,我将演示一下如何编写自己的shader,同时,我们还会介绍VBO(顶点缓冲区对象)和VAO(顶点数组对象)的基本用法。
在编写自己的shader之前,我觉得有必要提一下OpenGL渲染管线。
理解OpenGL渲染管线,对于学习OpenGL非常重要。下面是OpenGL渲染管线的示意图:(图中淡蓝色区域是可以编程的阶段)

此图是从wiki中拿过来的,OpenGL的渲染管线主要包括:
准备顶点数据(通过VBO、VAO和Vertex attribute来传递数据给OpenGL)
顶点处理(这里主要由Vertex Shader来完成,从上图中可以看出,它还包括可选的Tessellation和Geometry shader阶段)
顶点后处理(主要包括Clipping,顶点坐标归一化和viewport变换)
Primitive组装(比如3点组装成一个3角形)
光栅化成一个个像素
使用Fragment shader来处理这些像素
采样处理(主要包括Scissor Test, Depth Test, Blending, Stencil Test等)

更详细的信息可以参考本网站推荐的阅读材料和Wiki。
编写你的第一个Vertex Shader
首先是创建一个文件,把它命名为myVertextShader.vert, 并输入下列代码:
attribute vec2 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();
编译并运行,此时你会得到和之前效果一样的三角形。
下图解释了我们的顶点数据是如何渲染成最终屏幕上面的像素的:

VAO和VBO初探
VBO,全名Vertex Buffer Object。它是GPU里面的一块缓冲区,当我们需要传递数据的时候,可以先向GPU申请一块内存,然后往里面填充数据。最后,再通过调用glVertexAttribPointer把数据传递给Vertex Shader。而VAO,全名为Vertex Array Object,它的作用主要是记录当前有哪些VBO,每个VBO里面绑定的是什么数据,还有每一个vertex attribute绑定的是哪一个VBO。
使用VBO和VAO的步骤都差不多,步骤如下:
1. glGenXXX
2. glBindXXX
让我们修改之前的代码:
auto size = Director::getInstance()->getVisibleSize();
float vertercies[] = {
,, //第一个点的坐标
size.width, , //第二个点的坐标
size.width / , size.height
}; //第三个点的坐标
float color[] = {
,,,,
,,,,
,,,
}; //创建和绑定vao
glGenVertexArrays(, &vao);
glBindVertexArray(vao); //创建和绑定vbo
glGenBuffers(, &vertexVBO);
glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
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, , GL_FLOAT, GL_FALSE, , (GLvoid*)); //set for color
glGenBuffers(, &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, , GL_FLOAT, GL_FALSE, , (GLvoid*)); //for safty
glBindVertexArray(); // 取消vao绑定
glBindBuffer(GL_ARRAY_BUFFER, ); // 取消vbo绑定
这里glBufferData把我们定义好的顶点和颜色数据传给VBO,此时,注意glVertexAttribPointer的最后一个参数,这里传递的都是(GLvoid*)0。而不像之前一样传的是vertex和color的数组地址。这一点是使用VBO和不使用VBO时要特别注意的。
顶点数据是怎么传递的
要弄明白程序里面定义的数组是怎么传递到vertex shader的,我们需要先弄清楚vertex attribute。
attribute vec2 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, , );
glBindVertexArray(); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(, );
这里可以看出,VAO对于简化程序作用是很大的。
好了,编译并运行,还是原来的三角形。

本文源代码下载。
下一篇文章,我们将讲一下OpenGL各种坐标系及其变换。当然,最重要的是World-to-Model变换,Model-to-View变换和View-to-Projection变换。
参考资料
http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Chapter-1:-The-Graphics-Pipeline.html
http://opengl.zilongshanren.com/opengl-tutorial/tut02/zh.html
http://www.opengl.org/wiki/Rendering_Pipeline_Overview
基于Cocos2d-x学习OpenGL ES 2.0系列——编写自己的shader(2)的更多相关文章
- 基于Cocos2d-x学习OpenGL ES 2.0系列——使用VBO索引(4)
在上一篇文章中,我们介绍了uniform和模型-视图-投影变换,相信大家对于OpenGL ES 2.0应该有一点感觉了.在这篇文章中,我们不再画三角形了,改为画四边形.下篇教程,我们就可以画立方体了, ...
- 基于Cocos2d-x学习OpenGL ES 2.0系列——纹理贴图(6)
在上一篇文章中,我们介绍了如何绘制一个立方体,里面涉及的知识点有VBO(Vertex Buffer Object).IBO(Index Buffer Object)和MVP(Modile-View-P ...
- 基于Cocos2d-x学习OpenGL ES 2.0系列——你的第一个三角形(1)
前言 在本系列教程中,我会以当下最流行的2D引擎Cocos2d-x为基础,介绍OpenGL ES 2.0的一些基本用法.本系列教程的宗旨是OpenGL扫盲,让大家在使用Cocos2d-x过程中,知其然 ...
- 基于Cocos2d-x学习OpenGL ES 2.0系列——OpenGL ES渲染之Shader准备(7)
Cocos2d-x底层图形绘制是使用OpenGL ES协议的.OpenGL ES是什么呢? OpenGL ES(OpenGl for Embedded System)是OpenGL三维图形API的子集 ...
- 基于Cocos2d-x学习OpenGL ES 2.0系列——OpenGL ES渲染之LayerColor(8)
在前面文章中讲述了Cocos2d-x引擎OpenGL渲染准备Shader方面,本文主要讲解使用LayerColor来讲述OpenGL的渲染过程. 1.LayerColor对象创建 添加LayerCol ...
- 基于Cocos2d-x学习OpenGL ES 2.0系列——你的第一个立方体(5)
在上篇文章中,我们介绍了VBO索引的使用,使用VBO索引可以有效地减少顶点个数,优化内存,提高程序效率. 本教程将带领大家一起走进3D--绘制一个立方体.其实画立方体本质上和画三角形没什么区别,所有的 ...
- 基于Cocos2d-x学习OpenGL ES 2.0系列——初识MVP(3)
在上一篇文章中,我在介绍vertex shader的时候挖了一个坑:CC_MVPMatrix.它其实是一个uniform,每一个Cocos2d-x预定义的shader都包含有这个uniform,但是如 ...
- 基于Cocos2d-x学习OpenGL ES 2.0之多纹理
没想到原文出了那么多错别字,实在对不起观众了.介绍opengl es 2.0的不多.相信介绍基于Cocos2d-x学习OpenGL ES 2.0之多纹理的,我是独此一家吧.~~ 子龙山人出了一个系列: ...
- Beginning OpenGL ES 2.0 with GLKit Part 1
Update 10/24/12: If you’d like a new version of this tutorial fully updated for iOS 6 and Xcode 4.5, ...
随机推荐
- 关于Unity中物理检测的准备
1.要确定每个物体的碰撞类型,是有碰撞效果的碰撞还是没有碰撞效果的碰撞(is trigger),带不带刚体. 2.给每个物体分层,再设置哪些层会发生碰撞,哪些完全不产生碰撞. 3.给每个物体设置标记, ...
- 第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询
第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询 bool查询说明 filter:[],字段的过滤,不参与打分must:[] ...
- vi 命令集
一:跳到文件第一行 1. 输入:0或:1,然后回车: 2. 在键盘按下小写gg: 二:跳到文件最后一行 1. 输入:$,然后回车: 2. 在键盘按下大写G : 3. 在键盘按Shift + g: ...
- [转]Android--多线程之Handler
原文:http://www.cnblogs.com/plokmju/p/android_Handler.html 前言 Android的消息传递机制是另外一种形式的“事件处理”,这种机制主要是为了解决 ...
- 转换基于Maven的Java项目支持Eclipse IDE
在过去的教程中,使用 Maven 创建了一个Java项目,但是这个项目不能导入到Eclipse IDE中,因为它不是 Eclipse 风格的项目. 这里有一个指南,向您演示如何转换 Maven 生成 ...
- 模式识别之knn---KNN(k-nearest neighbor algorithm)--从原理到实现
用官方的话来说,所谓K近邻算法,即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居),这K个实例的多数属于某个类,就把该输入实例分类到这个类中 ...
- Linux网卡eth0变成eth1修改方法
由于换了主板,集成网卡mac地址变了,70-persistent-net.rules中仍然保留了老网卡的内容,新网卡则被识别为eth1. 将表示老网卡的行注释掉,然后将表示新网卡的行中eth1改成et ...
- 垃圾回收机制GC知识再总结兼谈如何用好GC(其他信息: 内存不足)
来源 图像操作,易内存泄露,边界像素 一.为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1.为对应的资源分配内存 2.初始化内存 3.使用资源 4.清理资源 5.释放内存 应用程序对 ...
- ABBYY FineReader Pro for Mac系统要求
ABBYY FineReader Pro for Mac作为先进的OCR图文识别软件,为各种各样的任务提供全面的解决方案,可轻松将纸质文档.PDF文件和数字文本照片转换为可编辑和可搜索的文件,替代了手 ...
- c语言中左移、右移中的高位需要注意
有符号数,左移可能会破坏符号位. 右移时,要注意高位符号. 0X表示十六进制.十六进制每位数值由 0-f表示.所以0XC0 对应 二进制为 11000000B10进制与16进制间关系:1 -- 0X1 ...