Cocos2dx-OpenGL ES2.0教程:編寫本身的shader(2)

上篇文章中,我給你們介紹瞭如何在cocos2d-x裏面繪製一個三角形,當時咱們使用的是cocos2d-x引擎自帶的shader和一些輔助函數。在本文中,我將演示一下如何編寫本身的shader,同時,咱們還會介紹VBO(頂點緩衝區對象)和VAO(頂點數組對象)的基本用法。html

在編寫本身的shader以前,我以爲有必要提一下OpenGL渲染管線。
理解OpenGL渲染管線,對於學習OpenGL很是重要。下面是OpenGL渲染管線的示意圖:(圖中淡藍色區域是能夠編程的階段)
編程

pipelinepipeline數組

此圖是從wiki中拿過來的,OpenGL的渲染管線主要包括:函數

  1. 準備頂點數據(經過VBO、VAO和Vertex attribute來傳遞數據給OpenGL)學習

  2. 頂點處理(這裏主要由Vertex Shader來完成,從上圖中能夠看出,它還包括可選的Tessellation和Geometry shader階段)網站

  3. 頂點後處理(主要包括Clipping,頂點座標歸一化和viewport變換)ui

  4. Primitive組裝(好比3點組裝成一個3角形)spa

  5. 光柵化成一個個像素設計

  6. 使用Fragment shader來處理這些像素code

  7. 採樣處理(主要包括Scissor Test, Depth Test, Blending, Stencil Test等)

更詳細的信息能夠參考本網站推薦的閱讀材料和Wiki。

編寫你的第一個Vertex Shader

首先是建立一個文件,把它命名爲myVertextShader.vert, 並輸入下列代碼:

attribute vec4 a_position;
attribute vec4 a_color;

varying vec4 v_fragmentColor;void main() {
    gl_Position = CC_MVPMatrix * a_position;
    v_fragmentColor = a_color;
}

OpenGL Shader Language,簡稱GLSL,它是一種相似於C語言的專門爲GPU設計的語言,它能夠放在GPU裏面被並行運行。下面咱們來簡單解釋一下這一小段代碼。

首先,每個Shader程序都有一個main函數,這一點和c語言是同樣的。而後這裏面有兩種類型的變量,一種是attribute,另外一種是varying. attribute是從外部傳進來的,每個頂點都會有這兩個屬性,因此它也叫作vertex attribute(頂點屬性)。而varying類型的變量是在vertex shader和fragment shader之間傳遞數據用的。這裏的變量命名規則保持跟c同樣就好了,注意gl開頭的變量名是系統內置的變量,因此你們在定義本身的變量名時,請不要以gl開頭。而CC_MVPMatrix是一個mat4類型的變量,它是在cocos2d-x內部設置進來的。這一點,咱們後面再談。

vertex shader是做用於每個頂點的,咱們本例中有三個點,因此這個vertex shader會被執行三次。

編寫你的第一個Fragment Shader

首先,新建一個myFragmentShader.frag並輸入下列代碼:

varying vec4 v_fragmentColor;void main() {
    gl_FragColor = v_fragmentColor;
}

fragment shader中也有一個main函數,同時咱們看到這裏也聲明瞭一個與vertex shader相同的變量v_fragmentColor。前面咱們講過,這個變量是用來在vertex shader和fragment shader之間傳遞數據用的。因此,它們的參數類型必須徹底相同。若是一個是vec3,一個是vec4,shader編譯的時候是會報錯的。而gl_FragColor咱們知道它確定是一個系統內置變量了,它的做用是定義最終畫在屏幕上面的像素點的顏色。咱們回過頭去看上一篇文章中畫出來的三角形,咱們指定的是三個頂點的顏色,分別爲Red,Green和Blue,可是最後的三角形的顏色是經過這三個點的顏色插值出來的。由於最終三角形的像素點可不僅有三個,理解這一點很是重要。

最後,讓咱們修改一下shader progam的建立代碼:

//create my own program
 auto program = new GLProgram;
 program->initWithFilenames("myVertextShader.vert", "myFragmentShader.frag");
 program->link(); //set uniform locations
 program->updateUniforms();

編譯並運行,此時你會獲得和以前效果同樣的三角形。

下圖解釋了咱們的頂點數據是如何渲染成最終屏幕上面的像素的:

graphics_piplinegraphics_pipline

VAO和VBO初探

VBO,全名Vertex Buffer Object。它是GPU裏面的一塊緩衝區,當咱們須要傳遞數據的時候,能夠先向GPU申請一塊內存,而後往裏面填充數據。最後,再經過調用glVertexAttribPointer把數據傳遞給Vertex Shader。而VAO,全名爲Vertex Array Object,它的做用主要是記錄當前有哪些VBO,每一個VBO裏面綁定的是什麼數據,還有每個vertex attribute綁定的是哪個VBO。關於VBO和VAO更詳細的介紹,請參考此文

使用VBO和VAO的步驟都差很少,步驟以下:

  1. glGenXXX

  2. glBindXXX

讓咱們修改以前的代碼:

 //建立和綁定vao    uint32_t vao;    glGenVertexArrays(1, &vao);    glBindVertexArray(vao);        //建立和綁定vbo    uint32_t vertexVBO;    glGenBuffers(1, &vertexVBO);    glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);auto size = Director::getInstance()->getVisibleSize(); 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); //獲取vertex attribute "a_position"的入口點
 GLuint positionLocation = glGetAttribLocation(program->getProgram(), "a_position"); //打開入a_position入口點
 glEnableVertexAttribArray(positionLocation); //傳遞頂點數據給a_position,注意最後一個參數是數組的偏移了。
 glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0); //set for color
 uint32_t colorVBO;
 glGenBuffers(1, &colorVBO);
 glBindBuffer(GL_ARRAY_BUFFER, colorVBO);
 glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);

 GLuint colorLocation = glGetAttribLocation(program->getProgram(), "a_color");
 glEnableVertexAttribArray(colorLocation);
 glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0); //for safty
 glBindVertexArray(0);
 glBindBuffer(GL_ARRAY_BUFFER, 0);

這裏glBufferData把咱們定義好的頂點和顏色數據傳給VBO,此時,注意glVertexAttribPointer的最後一個參數,這裏傳遞的都是(GLvoid)0。而不像以前同樣傳的是vertex和color的數組地址。*這一點是使用VBO和不使用VBO時要特別注意的。

頂點數據是怎麼傳遞的

要弄明白程序裏面定義的數組是怎麼傳遞到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);

glGetAttribLocation是用來得到vertex attribute的入口的,在咱們要傳遞數據以前,首先要告訴OpenGL,因此要調用glEnableVertexAttribArray。最後的數據經過glVertexAttribPointer傳進來。它的第一個參數就是glGetAttribLocation返回的值。

重用VAO

最後,爲了避免讓這些生成和綁定VBO和VAO的操做在每一幀都被執行,咱們須要把它放在初始化函數裏面。最終咱們的draw函數以下:

auto glProgram = getGLProgram();

   glProgram->use();   //set uniform values, the order of the line is very important
   glProgram->setUniformsForBuiltins();   auto size = Director::getInstance()->getWinSize();   //use vao,由於vao記錄了每個頂點屬性和緩衝區的狀態,因此只須要綁定就可使用了
   glBindVertexArray(vao);

   glDrawArrays(GL_TRIANGLES, 0, 3);

   glBindVertexArray(0);

   CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 3);
   CHECK_GL_ERROR_DEBUG();

這裏能夠看出,VAO對於簡化程序做用是很大的。

好了,編譯並運行,仍是原來的三角形。

triangletriangle

下一篇文章,咱們將講一下OpenGL各類座標系及其變換。固然,最重要的是World-to-Model變換,Model-to-View變換和View-to-Projection變換。

參考資料

  1. http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Chapter-1:-The-Graphics-Pipeline.html

  2. http://opengl.zilongshanren.com/opengl-tutorial/tut02/zh.html

  3. http://www.opengl.org/wiki/Rendering_Pipeline_Overview

相關文章
相關標籤/搜索