OpenGL渲染管线
Cocos2D-X底层是基于OpenGL的,所以在变现我们自己的Shader之前,得了解一下渲染管线的知识。
如上图所示,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等)
具体过程如下图:
使用OpenGL ES 编写Shader文件
Shader包含两个文件:***.vert和***.frag文件。在工程../cocos2d/cocos/renderer/里,包含了一批cocos自带的shader文件。
***.vert文件是作用于每一个顶点。渲染节点中包含多少个顶点,vert文件就执行多少次。
***.frag文件是定义最终显示在屏幕上每个像素点的颜色。
Shader代码可以以.vert和.frag文件的方式存储,也可以直接以字符串的方式存储在代码中。下面我们分别介绍着两种实现方式:
用独立文件存储Shader代码
这种方式自己没有实现,直接贴出网上的方法链接。(期待后续!)
直接在程序中创建Shader代码
<span style="color:#333333;">bool HelloWorld::init() { if ( !Layer::init() ) { return false; } Size visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); //this->setGLProgram(GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_COLOR));
<span style="white-space:pre"> </span>//*************************************************** GLchar* myVertextShader = "attribute vec4 a_position; \n \ attribute vec4 a_color; \n \ varying vec4 v_fragmentColor; \n \ void main() \n \ { \n \ gl_Position = CC_MVPMatrix * a_position; \n \ v_fragmentColor = a_color; \n \ }"; GLchar* myFragmentShader = "varying vec4 v_fragmentColor; \n \ void main() \n \ { \n \ float gray = dot(v_fragmentColor.rgb,vec3(0.299,0.587,0.114)); \n \ gl_FragColor = vec4(gray,gray,v_fragmentColor.a); \n \ }"; auto program = new GLProgram; program->initWithVertexShaderByteArray(myVertextShader,myFragmentShader); program->link(); program->updateUniforms(); this->setShaderProgram(program); program->release(); return true; } void HelloWorld::visit(Renderer* renderer,const Mat4& parentTransform,uint32_t parentFlags) { Layer::visit(renderer,parentTransform,parentFlags); _command.init(_globalZOrder); _command.func = CC_CALLBACK_0(HelloWorld::onDraw,this); Director::getInstance()->getRenderer()->addCommand(&_command); } void HelloWorld::onDraw() { //获得当前HelloWorld的shader auto glProgram = getGLProgram(); //使用此shader glProgram->use(); //设置该shader的一些内置uniform,主要是MVP,即model-view-project矩阵 glProgram->setUniformsForBuiltins(); auto size = Director::getInstance()->getWinSize(); //指定将要绘制的三角形的三个顶点,分别位到屏幕左下角,右下角和正中间的顶端 float vertercies[] = { 0,//第一个点的坐标 size.width,//第二个点的坐标 size.width / 2,size.height}; //第三个点的坐标 //指定每一个顶点的颜色,颜色值是RGBA格式的,取值范围是0-1 float color[] = { 0,1,//第一个点的颜色,绿色 1,//第二个点的颜色,红色 0,1}; //第三个点的颜色, 蓝色 //激活名字为position和color的vertex attribute GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_COLOR); //分别给position和color指定数据源 glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION,2,GL_FLOAT,GL_FALSE,vertercies); glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR,4,color); //绘制三角形,所谓的draw call就是指这个函数调用 glDrawArrays(GL_TRIANGLES,3); //通知cocos2d-x 的renderer,让它在合适的时候调用这些OpenGL命令 CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,3); //如果出错了,可以使用这个函数来获取出错信息 CHECK_GL_ERROR_DEBUG(); }</span>
int()中******分隔线下就是修改的代码,运行后效果如图:
fragmentShader中
使用VBO缓存数据
顶点数组(Vertex Array)被保存在客户端内存,当执行glDrawArrays或 glDrawElements时,才把它们从客户端内存copy到图形内存。这样占用了大量的内存带宽,Vertex Buffer Objects允许OpengGL ES2.0应用在高性能的图形内存中分配并cache顶点数据,然后从此图形内存中执行render,这样避免了每次画一个原语都要重发送数据。具体的实现过程参考子龙山人的文章。这里先给出完整代码(只修改了onDraw函数):
#ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos2d.h" USING_NS_CC; class HelloWorld : public cocos2d::Layer { public: // there's no 'id' in cpp,so we recommend returning the class instance pointer static cocos2d::Scene* createScene(); // Here's a difference. Method 'init' in cocos2d-x returns bool,instead of returning 'id' in cocos2d-iphone virtual bool init(); // implement the "static create()" method manually CREATE_FUNC(HelloWorld); //重载visit函数 virtual void visit(Renderer* renderer,uint32_t parentFlags) override; //自定义渲染函数 void onDraw(); private: //渲染命令封装类 CustomCommand _command; }; #endif // __HELLOWORLD_SCENE_H__
#include "HelloWorldScene.h" Scene* HelloWorld::createScene() { // 'scene' is an autorelease object auto scene = Scene::create(); // 'layer' is an autorelease object auto layer = HelloWorld::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool HelloWorld::init() { if ( !Layer::init() ) { return false; } Size visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); // add "HelloWorld" splash screen" // auto sprite = Sprite::create("1005.png"); //auto _img = new Image(); //_img->initWithImageFile("1.tga"); //Texture2D* texture = initTextureWithImage(_img,200); //auto sprite2 = Sprite::createWithTexture(texture); // // position the sprite on the center of the screen // sprite2->setPosition(Vec2(visibleSize.width/2 + origin.x,visibleSize.height/2 + origin.y)); //sprite->setPosition(Vec2(visibleSize.width / 2 + origin.x,visibleSize.height / 2 + origin.y)); // // add the sprite as a child to this layer //this->addChild(sprite,0); // this->addChild(sprite2,0); //this->setGLProgram(GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_COLOR)); GLchar* myVertextShader = "attribute vec4 a_position; \n \ attribute vec4 a_color; \n \ varying vec4 v_fragmentColor; \n \ void main() \n \ { \n \ gl_Position = CC_MVPMatrix * a_position; \n \ v_fragmentColor = a_color; \n \ }"; GLchar* myFragmentShader = "varying vec4 v_fragmentColor; \n \ void main() \n \ { \n \ float gray = dot(v_fragmentColor.rgb,this); Director::getInstance()->getRenderer()->addCommand(&_command); } void HelloWorld::onDraw() { // //获得当前HelloWorld的shader // auto glProgram = getGLProgram(); // //使用此shader // glProgram->use(); // //设置该shader的一些内置uniform,主要是MVP,即model-view-project矩阵 // glProgram->setUniformsForBuiltins(); // auto size = Director::getInstance()->getWinSize(); // //指定将要绘制的三角形的三个顶点,分别位到屏幕左下角,右下角和正中间的顶端 // float vertercies[] = { 0,//第一个点的坐标 // size.width,//第二个点的坐标 // size.width / 2,size.height}; //第三个点的坐标 // //指定每一个顶点的颜色,颜色值是RGBA格式的,取值范围是0-1 // float color[] = { 0,//第一个点的颜色,绿色 // 1,红色 // 0,1}; //第三个点的颜色, 蓝色 // //激活名字为position和color的vertex attribute // GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_COLOR); // //分别给position和color指定数据源 // glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION,vertercies); // glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR,color); // //绘制三角形,所谓的draw call就是指这个函数调用 // glDrawArrays(GL_TRIANGLES,3); // //通知cocos2d-x 的renderer,让它在合适的时候调用这些OpenGL命令 // CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,3); // //如果出错了,可以使用这个函数来获取出错信息 // CHECK_GL_ERROR_DEBUG(); GLuint vbo; glGenBuffers(1,&vbo); glBindBuffer(GL_ARRAY_BUFFER,vbo); auto glProgram = getGLProgram(); glProgram->use(); glProgram->setUniformsForBuiltins(); auto size = Director::getInstance()->getWinSize(); float vertercies[] = { 0,size.width,size.width / 2,size.height}; float color[] = { 0,1}; glBufferData(GL_ARRAY_BUFFER,sizeof(vertercies),vertercies,GL_STATIC_DRAW); GLuint positionLocation = glGetAttribLocation(glProgram->getProgram(),"a_position"); glEnableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation,(GLvoid*)0); GLuint colorVBO; glGenBuffers(1,&colorVBO); glBindBuffer(GL_ARRAY_BUFFER,colorVBO); glBufferData(GL_ARRAY_BUFFER,sizeof(color),color,GL_STATIC_DRAW); GLuint colorLocation = glGetAttribLocation(glProgram->getProgram(),"a_color"); glEnableVertexAttribArray(colorLocation); glVertexAttribPointer(colorLocation,(GLvoid*)0); glDrawArrays(GL_TRIANGLES,3); glBindVertexArray(0); CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,3); CHECK_GL_ERROR_DEBUG(); }
onDraw()函数中,注释部分是没有使用VBO的实现方法。
使用VBO时,通过glGenBuffers用于创建一个VBO,glBindBuffer用于绑定VBO与制定缓存类型,glBufferData用于写入缓存数据。
数据缓存后,还需要绑定到Shader上。通过glGetAttribLocation可以获取Shader中attribute的位置,通过glEnableVertexAttribArray打开响应的功能,通过glVertexAttribPointer给相应的attribute变量绑定缓存数据。
最后,绘制结果和上一段代码一致。
原文链接:https://www.f2er.com/cocos2dx/340598.html