Cocos2D-X shader(二) OpenGL渲染管线

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代码

  请先阅读系列第一篇文章:http://blog.csdn.net/operhero1990/article/details/50215635
  头文件代码并没有修改,只是对init()和onDraw()函数做出了修改,代码如下:
<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, 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,0,   //第一个点的坐标
                            size.width, 0,   //第二个点的坐标
                           size.width / 2, size.height};  //第三个点的坐标
    //指定每一个顶点的颜色,颜色值是RGBA格式的,取值范围是0-1
    float color[] = { 0, 1,0, 1,    //第一个点的颜色,绿色
                        1,0,0, 1,  //第二个点的颜色, 红色
                         0, 0, 1, 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, 0, vertercies);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_FLOAT, GL_FALSE, 0, color);
    //绘制三角形,所谓的draw call就是指这个函数调用
    glDrawArrays(GL_TRIANGLES, 0, 3);
    //通知cocos2d-x 的renderer,让它在合适的时候调用这些OpenGL命令
    CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 3);
    //如果出错了,可以使用这个函数来获取出错信息
    CHECK_GL_ERROR_DEBUG();
}</span>

int()中******分隔线下就是修改的代码,运行后效果如图:


fragmentShader中
float gray = dot(v_fragmentColor.rgb, vec3(0.299, 0.587, 0.114)); \n \
		gl_FragColor = vec4(gray, gray, gray, v_fragmentColor.a); \n \

的作用就是使图片颜色置灰。

使用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, const Mat4& parentTransform, 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, vec3(0.299, 0.587, 0.114)); \n \
		gl_FragColor = vec4(gray, 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,0,   //第一个点的坐标
 //                           size.width, 0,   //第二个点的坐标
 //                          size.width / 2, size.height};  //第三个点的坐标
 //   //指定每一个顶点的颜色,颜色值是RGBA格式的,取值范围是0-1
 //   float color[] = { 0, 1,0, 1,    //第一个点的颜色,绿色
 //                       1,0,0, 1,  //第二个点的颜色, 红色
 //                        0, 0, 1, 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, 0, vertercies);
 //   glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_FLOAT, GL_FALSE, 0, color);
 //   //绘制三角形,所谓的draw call就是指这个函数调用
 //   glDrawArrays(GL_TRIANGLES, 0, 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,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);
	GLuint positionLocation = glGetAttribLocation(glProgram->getProgram(), "a_position");
	glEnableVertexAttribArray(positionLocation);
	glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, (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, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);

	glDrawArrays(GL_TRIANGLES, 0, 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变量绑定缓存数据。
最后,绘制结果和上一段代码一致。
  当然,这段代码还可以继续优化,因为没一帧的绘制都会调用onDraw函数。可以重载draw()函数,帮缓存数据的操作放入其中,就减少了重复的操作。具体可以参考子龙山人那篇文章。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值