编写自己的 shader

渲染管线工作原理

  • 在学习shader之前首先了解一下OpenGL 渲染管线的工作原理, 对于学习 OpenGL 极其重要.(参考资料 wiki):
    管线工作原理示意图(wiki)
管线工作原理示意图( 引自子龙山人的博客 )
  1. 顶点准备阶段 ( VBO, VAO 或者 FBO 传递数据给 OpenGL )
  2. 顶点处理阶段:
    2.1. 每一个顶点都是通过 Vertex Shader 来处理过的, 并从流水线中同步到输出的顶点数据中
    2.2. Optional primitive tessellation stages (初始顶点数据组装阶段).
    2.3. Optional Geometry Shader primitive processing. The output is a sequence of primitives.( 初始几何处理阶段 )
  3. 顶点后处理阶段 ( 顶点转换反馈, Primitive Clipping, 采样分割, viewport 转换到窗口上 )
  4. Primitive组装
  5. 光栅化成一个个像素
  6. 使用Fragment shader来处理这些像素
  7. 采样处理(主要包括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 渲染流程

graphics_pipeline

关于 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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 你好,三角形 图形渲染管线(Pipeline) 3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Pi...
    IceMJ阅读 7,494评论 2 13
  • 1 前言 一直想沿着图像处理这条线建立一套完整的理论知识体系,同时积累实际应用经验。因此有了从使用AVFounda...
    RichardJieChen阅读 5,802评论 5 12
  • 第三章 管线一览 本章我们会学到什么 OpenGL管线的每个阶段做什么的 如果连接着色器和固定功能管线阶段 如果创...
    葭五阅读 6,327评论 2 18
  • 1, OpenGL 编程模型 2, 管线流程(重点,每一个细节都要讲清楚) 渲染流水线,就是一系列有序的处理阶段的...
    rogerwu1228阅读 759评论 0 0
  • 01 宋代美学为现在的很多小伙伴喜欢。 事实上,宋代的画家是很热衷表达季节的,早春图、溪山图、万壑松风图... 尤...
    赵小文儿阅读 1,063评论 21 24