渲染管线工作原理
- 在学习shader之前首先了解一下OpenGL 渲染管线的工作原理, 对于学习 OpenGL 极其重要.(参考资料 wiki):
- 顶点准备阶段 ( VBO, VAO 或者 FBO 传递数据给 OpenGL )
- 顶点处理阶段:
2.1. 每一个顶点都是通过 Vertex Shader 来处理过的, 并从流水线中同步到输出的顶点数据中
2.2. Optional primitive tessellation stages (初始顶点数据组装阶段).
2.3. Optional Geometry Shader primitive processing. The output is a sequence of primitives.( 初始几何处理阶段 ) - 顶点后处理阶段 ( 顶点转换反馈, Primitive Clipping, 采样分割, viewport 转换到窗口上 )
- Primitive组装
- 光栅化成一个个像素
- 使用Fragment shader来处理这些像素
- 采样处理(主要包括Scissor Test, Depth Test, Blending, Stencil Test等)
Vertex Shader
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 v_fragmentColor;
void main()
{
gl_Position = CC_MVPMatrix * a_position;
v_fragmentColor = a_color;
}
Fragment Shader
varying vec4 v_fragmentColor;
void main()
{
gl_FragColor = v_fragmentColor;
}
总体代码
// on "init" you need to initialize your instance
bool HelloWorld::init()
{
//////////////////////////////
// 1. super init first
if ( !Layer::init() )
{
return false;
}
auto program = new GLProgram();
program->initWithByteArrays(vertex_shader.c_str(), fragment_shader.c_str());
if ( !program->link() )
{
printf("link shader error\n");
}
//set uniform locations
program->updateUniforms();
this->setGLProgram(program);
program->release();
return true;
}
void HelloWorld::onDraw()
{
auto _program = getGLProgram();
_program->use();
//设置该shader的一些内置uniform,主要是MVP,即model-view-project矩阵
_program->setUniformsForBuiltins();
// 创建和绑定 vao
GLuint vao = 0;
glGenVertexArraysOES(1, &vao);
glBindVertexArrayOES(vao);
// 创建和绑定 vbo
GLuint vertexVBO = 0;
glGenBuffers(1, &vertexVBO);
glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
auto size = Director::getInstance()->getWinSize();
//指定将要绘制的三角形的三个顶点,分别位到屏幕左下角,右下角和正中间的顶端
float vertercises[] = {
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}; //第三个点的颜色, 蓝色
glBufferData(GL_ARRAY_BUFFER, sizeof(vertercises), vertercises, GL_STATIC_DRAW);
// 获取 vertex attribute "a_position" 的入口点
GLuint a_position = glGetAttribLocation(_program->getProgram(), "a_position");
assert(_program->getProgram());
// 打开 "a_position" 入口点
glEnableVertexAttribArray(a_position);
// 传递数据给 "a_position", 注意最有一个参数是数组的偏移
glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);
// set for color
GLuint colorVBO = 0;
glGenBuffers(1, &colorVBO);
glBindBuffer(GL_ARRAY_BUFFER, colorVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);
GLuint a_color = glGetAttribLocation(_program->getProgram(), "a_color");
glEnableVertexAttribArray(a_color);
glVertexAttribPointer(a_color, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);
// for safty
// glBindVertexArrayOES(0);
// glBindBuffer(GL_ARRAY_BUFFER, 0);
// 以下主要为 vao 的数据源分配内存以及初始化内存
GLuint ibuffer;
glGenBuffers(1, &ibuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer);
// static GLubyte indexData[3] = {0, 1, 2};
static GLubyte indexData[3] = {2, 0, 1};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * sizeof(GLubyte), indexData, GL_STATIC_DRAW);
// 分配 vao 并 绘制
glBindVertexArrayOES(0);
glBindVertexArrayOES(vao);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, (GLvoid*)0);
//绘制三角形,所谓的draw call就是指这个函数调用
// glDrawArrays(GL_TRIANGLES, 0, 3);
//通知cocos2d-x 的renderer,让它在合适的时候调用这些OpenGL命令
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 3);
//如果出错了,可以使用这个函数来获取出错信息
CHECK_GL_ERROR_DEBUG();
}
vertex 渲染流程
关于 VAO 和 VBO
- VBO ( Vertex Buffer Object )
它是 GPU 里面的一块缓冲区, 生成步骤向 GPU 申请i 一块内存,然后往里面填充数据 - VAO
它主要用来记录 VBO 的绘制顺序, 生成和初始化和 VBO 类似 - 关于他们更详尽的介绍请参考 wiki
顶点数据是如何传递给 shader
要弄明白程序里面定义的数组是怎么传递到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);