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

在编写自己的shader之前,我觉得有必要提一下OpenGL渲染管线。

理解OpenGL渲染管线,对于学习OpenGL非常重要。下面是OpenGL渲染管线的示意图:(图中淡蓝色区域是可以编程的阶段)

此图是从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 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)的更多相关文章

  1. 基于Cocos2d-x学习OpenGL ES 2.0系列——使用VBO索引(4)

    在上一篇文章中,我们介绍了uniform和模型-视图-投影变换,相信大家对于OpenGL ES 2.0应该有一点感觉了.在这篇文章中,我们不再画三角形了,改为画四边形.下篇教程,我们就可以画立方体了, ...

  2. 基于Cocos2d-x学习OpenGL ES 2.0系列——纹理贴图(6)

    在上一篇文章中,我们介绍了如何绘制一个立方体,里面涉及的知识点有VBO(Vertex Buffer Object).IBO(Index Buffer Object)和MVP(Modile-View-P ...

  3. 基于Cocos2d-x学习OpenGL ES 2.0系列——你的第一个三角形(1)

    前言 在本系列教程中,我会以当下最流行的2D引擎Cocos2d-x为基础,介绍OpenGL ES 2.0的一些基本用法.本系列教程的宗旨是OpenGL扫盲,让大家在使用Cocos2d-x过程中,知其然 ...

  4. 基于Cocos2d-x学习OpenGL ES 2.0系列——OpenGL ES渲染之Shader准备(7)

    Cocos2d-x底层图形绘制是使用OpenGL ES协议的.OpenGL ES是什么呢? OpenGL ES(OpenGl for Embedded System)是OpenGL三维图形API的子集 ...

  5. 基于Cocos2d-x学习OpenGL ES 2.0系列——OpenGL ES渲染之LayerColor(8)

    在前面文章中讲述了Cocos2d-x引擎OpenGL渲染准备Shader方面,本文主要讲解使用LayerColor来讲述OpenGL的渲染过程. 1.LayerColor对象创建 添加LayerCol ...

  6. 基于Cocos2d-x学习OpenGL ES 2.0系列——你的第一个立方体(5)

    在上篇文章中,我们介绍了VBO索引的使用,使用VBO索引可以有效地减少顶点个数,优化内存,提高程序效率. 本教程将带领大家一起走进3D--绘制一个立方体.其实画立方体本质上和画三角形没什么区别,所有的 ...

  7. 基于Cocos2d-x学习OpenGL ES 2.0系列——初识MVP(3)

    在上一篇文章中,我在介绍vertex shader的时候挖了一个坑:CC_MVPMatrix.它其实是一个uniform,每一个Cocos2d-x预定义的shader都包含有这个uniform,但是如 ...

  8. 基于Cocos2d-x学习OpenGL ES 2.0之多纹理

    没想到原文出了那么多错别字,实在对不起观众了.介绍opengl es 2.0的不多.相信介绍基于Cocos2d-x学习OpenGL ES 2.0之多纹理的,我是独此一家吧.~~ 子龙山人出了一个系列: ...

  9. 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, ...

随机推荐

  1. MD5骨骼动画模型加载(一)

    前面我们分析了静态模型OBJ格式,桢动画模型MD2,这篇主要分析骨骼动画MD5的一些概念并且实现. 混合桢动画有计算简单,容易实现等优点,但是在需要比较细致的效果时,则需要更多的关键桢,每桢都添加相同 ...

  2. Numpy 的通用函数:快速的元素级数组函数

    通用函数: 通用函数(ufunc)是一种对ndarray中的数据执行元素级运算的函数.你可以将其看作简单函数(接受一个或多个标量值,并产生一个或度过标量值)的矢量化包装器. 简单的元素级变体,如sqr ...

  3. (转)libhybris及EGL Platform-在Glibc生态中重用Android的驱动

    原文地址:http://blog.csdn.net/jinzhuojun/article/details/41412587 libhybris主要作用是为了解决libc库的兼容问题,目的是为了在基于G ...

  4. Oracle错误IMP-00010: 不是有效的导出文件, 头部验证失败 分类: Oracle 2015-07-09 13:56 20人阅读 评论(0) 收藏

    Oracle 11g的dmp备份文件导入到Oracle 10g,出现错误信息: Import: Release 10.2.0.1.0 - Production on 星期四 7月 9 13:47:04 ...

  5. 3D HTML5 Logo标志 超炫酷旋转特效

    今天又要为大家带来一款超酷的HTML5 Canvas 3D动画特效,是一款可以旋转的HTML5 Logo标志.画面上一共有两块可旋转的区域,第一是可旋转的背景,第二则是可旋转的Logo标志.Logo标 ...

  6. ZooKeeper简述

    一.简介 ZooKeeper是一个高性能,分布式的,开源分布式应用协调服务.它提供了简单原始的功能,分布式应用可以基于它实现更高级的服务,比如同步,集群管理,命名空间,配置维护等.ZooKeeper使 ...

  7. 消息中间件activemq-5.13.0整合spring

    首先说明这里是在qctivemq配置好并启动服务的情况下进行,请先自行配置好.也可关注我的博文(消息中间件qctivemq安全验证配置)进行配置. 1.首先看一下项目结构 2.所需jar包,这里只列出 ...

  8. 正则grep

    什么是正则: 正则就是一串有规律的字符串. 正则三剑客: grep/egrep (egrep是grep的扩展),sed, awk grep grep过滤指定关键词 [root@localhost gr ...

  9. CSS3与动画有关的属性transition、animation、transform对比

    最近应公司需求,需要用css3做动画,终于把以前一直傻傻分不清楚的三个属性理解了. 索性在这里进行一个简单的对比,加深自己的记忆. 浏览器兼容性 CSS3 transform 属性 Internet ...

  10. Blender 编辑模式

    1.如何进入编辑模式 可直接通过“Tab”快捷键进入编辑模式,或者选择界面底部的下拉列表: 如果想退出编辑模式,可再按下“Tab”键退出. 2.编辑选择 进入编辑状态后,我们可以通过鼠标右键来选择某个 ...