1. 程式人生 > >六 cocos2dx(3.X)中使用shader

六 cocos2dx(3.X)中使用shader

一 shader的基本概念

1 什麼是shader

shader即著色器,就是專門用來渲染3D圖形的一種技術。
通過shader,可以自己編寫顯示卡渲染畫面的演算法,使畫面更漂亮、更逼真。

2 shader分類

shader又分兩種,一種是頂點shader(3D圖形是由三角形組成的,頂點shader就是計算頂點位置,併為後期畫素渲染做準備的),
另一種是畫素shader,就是以畫素為單位,計算光照、顏色的一系列演算法。

3 shader語言

幾個不同的圖形API有各自的shader語言:
在DirectX中,頂點shader叫做vertex shader,畫素shader叫做pixel shader;
在OpenGL中,頂點shader也叫做vertex shader,但畫素shader叫做fragment shader。
此外顯示卡晶片廠商NVIDIA還推出CG顯示卡程式語言,也支援shader。

二 shader開發流程

這裡寫圖片描述

  1. 編寫vertex Shader和fragment shader原始碼。
  2. 建立兩個shader 例項:GLuint glCreateShader(GLenum type); [gl.createShader]
  3. 給Shader例項指定原始碼。 glShaderSource [gl.shaderSource]
  4. 編譯shaer原始碼 void glCompileShader(GLuint shader) [gl.compileShader]
  5. 建立shader program – GLuint glCreateProgram(void) [gl.createProgram]
  6. 繫結shader到program 。 void glAttachShader(GLuint program, GLuint shader)。每個program必須繫結一個vertex shader 和一個fragment shader。 [gl.attachShader]
  7. 連結program 。 void glLinkProgram(GLuint program) [gl.linkProgram]
  8. 使用porgram 。 void glUseProgram(GLuint program) [gl.useProgram]

對於使用獨立shader編譯器編譯的二進位制shader程式碼,可使用glShaderBinary來載入到一個shader例項中。

三 shader程式

1 語言glsl

glsl即OpenGL Shading Language(OpenGL著色語言),是用來在OpenGL中著色程式設計的語言。

2 頂點著色器

// vert.vsh
// 頂點著色器,VBO/VAO提供的每個頂點都執行一遍頂點著色器,輸出一個varying和gl_Position等

// 變數修飾:
// attribute: 只讀,隨不同頂點變化的全域性變數,應用程式傳入,只能用在頂點著色器中
// uniform: 只讀,隨不同圖元變化的全域性變數,應用程式傳入,
// varying: 在頂點shader中可寫,在片斷shader中只讀,用於在頂點著色器和片段著色器之間傳遞資料

// 輸入: attribute, 輸出:varying+gl_positon + gl_Position + gl_PointSize

attribute vec4 a_position;
attribute vec4 a_color;

varying vec4 v_fragmentColor;

// 每一個Shader程式都有一個main函式
void main()
{
    // gl開頭的變數名是系統內建的變數
    gl_Position = CC_MVPMatrix * a_position;// 每個點固有的Varying,表示點的空間位置。
    v_fragmentColor = a_color;
}

3 片段著色器

// frag.fsh
// 片元著色器,光柵化輸出的每個片元都執行一遍片段著色器,生成一個或多個(多重渲染)顏色值作為輸出
// 輸入: varying, 輸出: gl_FragColor + gl_FragDepth

//用於在頂點著色器和片段著色器之間傳遞資料,因此型別必須完全一直
varying vec4 v_fragmentColor;

// 每一個Shader程式都有一個main函式
void main()
{
    // gl開頭的變數名是系統內建的變數
    gl_FragColor = v_fragmentColor;// gl_FragColor 定義最終畫在螢幕上面的畫素點的顏色
}

三 程式呼叫

新建cocos工程,將上面兩個檔案放到Resource/shaders資料夾下,修改程式碼如下:

// .h
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"

USING_NS_CC;

class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();

    CREATE_FUNC(HelloWorld);

    virtual void visit(Renderer *renderer, const Mat4& parentTransform, uint32_t parentFlags);
    void onDraw();

private:
    CustomCommand _customCommand;

    GLuint _vao;
    GLuint _vertVBO;
    GLuint _colorVBO;
};

#endif // __HELLOWORLD_SCENE_H__
// .cpp
#include "HelloWorldScene.h"

USING_NS_CC;

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()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }

    /*
    在OpenGL中,GLSL的shader使用的流程與C語言相似,每個shader類似一個C模組,首先需要單獨編譯(compile),
    然後一組編譯好的shader連線(link)成一個完整程式。
    */
    auto program = CCGLProgram::createWithFilenames("shader/vert.vsh", "shader/frag.fsh");
    program->link();
    program->updateUniforms();
    this->setGLProgram(program);

    /*
    使用VBO和VAO的步驟都差不多,步驟如下:
    1 glGenXXX
    2 glBindXXX
    */

    // 建立和繫結vao
    glGenVertexArrays(1, &_vao); 
    glBindVertexArray(_vao);

    // 建立和繫結vbo
    glGenBuffers(1, &_vertVBO);// 生成VBO
    glBindBuffer(GL_ARRAY_BUFFER, _vertVBO); // 關聯到當前的VAO

    auto size = Director::getInstance()->getVisibleSize();
    float vertercies[] = {// 三角形頂點位置
        0, 0, // 第1個點座標
        size.width, 0, // 第2個點座標
        size.width / 2, size.height // 第3個點座標
    };

    // 給VBO設定資料
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertercies), vertercies, GL_STATIC_DRAW);

    // 獲得變數a_position在記憶體中的位置
    GLuint positionLocation = glGetAttribLocation(program->getProgram(), "a_position");
    glEnableVertexAttribArray(positionLocation);
    // 提交包含資料的陣列指標
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);

    // 設定顏色
    float color[] = {// 三角形頂點顏色RGBA
        0, 1, 0, 1,
        1, 0, 0, 1,
        0, 0, 1, 1
    };
    glGenBuffers(1, &_colorVBO);
    glBindBuffer(GL_ARRAY_BUFFER, _colorVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);

    // 獲得變數a_color在記憶體中的位置
    GLuint colorLocation = glGetAttribLocation(program->getProgram(), "a_color");
    glEnableVertexAttribArray(colorLocation);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0);

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    return true;
}

void HelloWorld::visit(cocos2d::Renderer *renderer, const Mat4 &transform, uint32_t parentFlags)
{
    Layer::draw(renderer, transform, parentFlags);

    _customCommand.init(_globalZOrder);
    _customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this);
    renderer->addCommand(&_customCommand);
}

void HelloWorld::onDraw()
{
    auto glProgram = getGLProgram();
    glProgram->use();
    glProgram->setUniformsForBuiltins();

    /*
    VAO裡的VBOs都設定好了以後,在繪製的地方只需要設定當前繫結的VAO是哪個,
    就能按照初始化的VAO來繪製,即呼叫glDrawArrays
    */

    // 設定當前繫結的VAO
    glBindVertexArray(_vao);

    // 繪製三角形
    glDrawArrays(GL_TRIANGLES, 0, 3);

    // 解綁當前VAO,但並不釋放
    glBindVertexArray(0);

    CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 3);
    CHECK_GL_ERROR_DEBUG();
}

效果如下:

這裡寫圖片描述

win7預設關閉VAO,需要手動開啟:libcocos2d工程-》屬性-》C/C++-》前處理器,在前處理器定義中新增CC_TEXTURE_ATLAS_USE_VAO=1