在上篇文章中,我給你們介紹瞭如何在cocos2d-x裏面繪製一個三角形,當時咱們使用的是cocos2d-x引擎自帶的shader和一些輔助函數。在本文中,我將演示一下如何編寫本身的shader,同時,咱們還會介紹VBO(頂點緩衝區對象)和VAO(頂點數組對象)的基本用法。html
在編寫本身的shader以前,我以爲有必要提一下OpenGL渲染管線。
理解OpenGL渲染管線,對於學習OpenGL很是重要。下面是OpenGL渲染管線的示意圖:(圖中淡藍色區域是能夠編程的階段)
編程
此圖是從wiki中拿過來的,OpenGL的渲染管線主要包括:函數
準備頂點數據(經過VBO、VAO和Vertex attribute來傳遞數據給OpenGL)學習
頂點處理(這裏主要由Vertex Shader來完成,從上圖中能夠看出,它還包括可選的Tessellation和Geometry shader階段)網站
頂點後處理(主要包括Clipping,頂點座標歸一化和viewport變換)ui
Primitive組裝(好比3點組裝成一個3角形)spa
光柵化成一個個像素設計
使用Fragment shader來處理這些像素code
採樣處理(主要包括Scissor Test, Depth Test, Blending, Stencil Test等)
更詳細的信息能夠參考本網站推薦的閱讀材料和Wiki。
首先是建立一個文件,把它命名爲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會被執行三次。
首先,新建一個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();
編譯並運行,此時你會獲得和以前效果同樣的三角形。
下圖解釋了咱們的頂點數據是如何渲染成最終屏幕上面的像素的:
VBO,全名Vertex Buffer Object。它是GPU裏面的一塊緩衝區,當咱們須要傳遞數據的時候,能夠先向GPU申請一塊內存,而後往裏面填充數據。最後,再經過調用glVertexAttribPointer把數據傳遞給Vertex Shader。而VAO,全名爲Vertex Array Object,它的做用主要是記錄當前有哪些VBO,每一個VBO裏面綁定的是什麼數據,還有每個vertex attribute綁定的是哪個VBO。關於VBO和VAO更詳細的介紹,請參考此文
使用VBO和VAO的步驟都差很少,步驟以下:
glGenXXX
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返回的值。
最後,爲了避免讓這些生成和綁定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對於簡化程序做用是很大的。
好了,編譯並運行,仍是原來的三角形。
下一篇文章,咱們將講一下OpenGL各類座標系及其變換。固然,最重要的是World-to-Model變換,Model-to-View變換和View-to-Projection變換。