在上篇文章中,我给你们介绍了如何在cocos2d-x里面绘制一个三角形,当时咱们使用的是cocos2d-x引擎自带的shader和一些辅助函数。在本文中,我将演示一下如何编写本身的shader,同时,咱们还会介绍VBO(顶点缓冲区对象)和VAO(顶点数组对象)的基本用法。html
在编写本身的shader以前,我以为有必要提一下OpenGL渲染管线。
理解OpenGL渲染管线,对于学习OpenGL很是重要。下面是OpenGL渲染管线的示意图:(图中淡蓝色区域是能够编程的阶段)
编程
此图是从wiki中拿过来的,OpenGL的渲染管线主要包括:函数
准备顶点数据(经过VBO、VAO和Vertex attribute来传递数据给OpenGL)学习
顶点处理(这里主要由Vertex Shader来完成,从上图中能够看出,它还包括可选的Tessellation和Geometry shader阶段)网站
顶点后处理(主要包括Clipping,顶点坐标归一化和viewport变换)ui
Primitive组装(好比3点组装成一个3角形)spa
光栅化成一个个像素设计
使用Fragment shader来处理这些像素code
采样处理(主要包括Scissor Test, Depth Test, Blending, Stencil Test等)
更详细的信息能够参考本网站推荐的阅读材料和Wiki。
首先是建立一个文件,把它命名为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会被执行三次。
首先,新建一个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();
编译并运行,此时你会获得和以前效果同样的三角形。
下图解释了咱们的顶点数据是如何渲染成最终屏幕上面的像素的:
VBO,全名Vertex Buffer Object。它是GPU里面的一块缓冲区,当咱们须要传递数据的时候,能够先向GPU申请一块内存,而后往里面填充数据。最后,再经过调用glVertexAttribPointer把数据传递给Vertex Shader。而VAO,全名为Vertex Array Object,它的做用主要是记录当前有哪些VBO,每一个VBO里面绑定的是什么数据,还有每个vertex attribute绑定的是哪个VBO。关于VBO和VAO更详细的介绍,请参考此文
使用VBO和VAO的步骤都差很少,步骤以下:
glGenXXX
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返回的值。
最后,为了避免让这些生成和绑定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对于简化程序做用是很大的。
好了,编译并运行,仍是原来的三角形。
下一篇文章,咱们将讲一下OpenGL各类坐标系及其变换。固然,最重要的是World-to-Model变换,Model-to-View变换和View-to-Projection变换。