1. 程式人生 > ><三> 初探opengl, 畫三角形

<三> 初探opengl, 畫三角形

版本 目的 轉換 著色器 jpg open false https bar

  環境搭建好,我們當然就是開始寫代碼,這裏就得先了解opengl的一些工作流程。首先我們得了解三個單詞:

    頂點數組對象(VAO)

    頂點緩沖對象(VBO)

    索引緩沖對象(EBO)

  比較簡單的概括下這節的工作流程。

    1.定義好三角形的三個頂點

    2.綁定VBO,把三角形數據傳入進去

    3.做頂點的鏈接,規定屬性

    4.編寫最簡單的頂點著色器和片段著色器

    5.編譯著色器,並加載鏈接到項目裏

    6.開啟循環渲染模式

 

1.定義頂點

  首先我們定義要先定義好三角形的三個點,由於是傳入給opengl使用,opengl內部是使用標準化設備坐標

,也就是-1到1的坐標體系。因為opengl並不會也不想知道你設備顯示多少尺寸啥的,它只要做好自己的計算,後面顯示會通過轉換為屏幕空間坐標來顯示,這一步在視口變化的時候完成。

float vertices[] = {
        -0.5f, -0.5f, 0,
        0.5f, -0.5f, 0,
        0.0f, 0.5f, 0
};

這裏就有3個頂點,每3個一組,分別是x,y,z,這三個坐標應該來說大家都十分熟悉的吧。定義好後,我們需要把他傳入到圖形渲染管線的第一個處理階段:頂點著色器。opengl通過VBO來管理這個內存,它會在gpu內存中存儲打量頂點。使用它我們就可以把數據一次性傳輸到顯卡裏,而不用一點一點發,加快讀取效率。

2.創建並綁定VBO數據

unsigned int VBO;
glGenBuffers(1, &VBO);
//創建並且聲稱一個vbo

opengl裏的對象都是通過一個id來索引,這有點像當時看的一個叫Genius-X的遊戲框架,此框架就是通過id來確定一個遊戲對象實體,其他遊戲組建通過此id依附到對象上。

此外,opengl操作對象都是需要我們先對其綁定到規定的緩沖對象類型上來進行操作,而不直接對我們索引目表進行操作,所以我們需要把頂點綁定到vbo時,需要這麽寫

glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, 
sizeof(vertices), vertices,GL_STATIC_DRAW);

把vbo綁定到GL_ARRAY_BUFFER緩沖類型上,opengl還有很多其他的對象類型,後面可能會慢慢說道。通過綁定後,我們對GL_ARRAY_BUFFER操作就可以了,然後glBufferData就是進行數據的傳入,四個參數是緩沖對象,頂點數組大小,頂點數組,管理頂點方式。頂點管理方式有3種:

  • GL_STATIC_DRAW :數據不會或幾乎不會改變。
  • GL_DYNAMIC_DRAW:數據會被改變很多。
  • GL_STREAM_DRAW :數據每次繪制時都會改變。

我們暫時不會改數據,所以用static就可以了。

  雖然這裏已經達到了數據綁定的效果,但如果我們需要繪制多個物品的時候,我們需要重復多次重復頂點屬性的設定,所以出現了VAO這個東西來做優化。VAO可以看起來是VBO數組,只是其還附加了其他參數和屬性。使用它也很簡單,和VBO類似。

unsigned int g_VAO;
glGenVertexArrays(1, &g_VAO);
glBindVertexArray(g_VAO);

也是先創建id,再綁定到頂點數組上。其後,我們照常做後面的VBO操作和頂點屬性鏈接即可,vbo會自動存入到vao中,頂點屬性鏈接只需要操作一次即可對所有vbo生效。

3.頂點屬性鏈接

  傳入了頂點數據後,我們要告知opengl怎麽解析這些數據,其本身是不知道如何處理這個數組的

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer的參數一個一個解析

  第一個參數是指定我們在頂點著色器中使用的位置屬性,一般默認為0,我們先傳入0,這個屬性會和頂點著色器裏數據讀取配合使用

  第二個參數是指定頂點屬性的大小,我們由3個值組成

  第三個是頂點的數據類型,我們用了GL_FLOAT的浮點類型

  第四個是是否希望數據被標準化,如果我們設置為GL_TRUE就會被映射到-1 和 1之間,我們設為false,因為我們的數據已經標準化了

  第五個數據是步長,也就是讀取下一個頂點需要跳多少內存,我們這裏是3*sizeof(float)大小,因為我們頂點緊密排列的

  第六個是一個強制轉換,表示數據在緩沖中位置偏移量,因為數據在數組開頭,所以是0

而glEnableVertexAttribArray則是啟用頂點屬性,因為這個屬性默認是禁止的,0跟上面說的第一個參數意義一樣。

4.編寫簡單的頂點著色器

  當我們輸入好了頂點,我們就開始編寫渲染過程中可編程的兩個部分,頂點著色器和片段著色器。在此之前我們先了解下圖形渲染管線的幾個階段。

技術分享圖片

  首先就是我們的頂點,輸入後,頂點著色器會確定頂點大概的位置,並進行一些基本處理。之後,圖元裝配會把 頂點裝配成指定的土元形狀,這裏我用是需要三角形。 然後就是幾何著色器,這個著色器還沒了解是幹嘛,只知道也是一個可編程的,大體就是用於生成其他形狀。然後輸出給光柵化階段,這個階段只是把圖元映射成屏幕上對應的像素位,供片段著色器使用的片段。然後會進行一次裁剪,把視圖看不到的區域給裁掉,提升執行效率。一個片段,就是渲染一個像素所需要的所有數據片段著色器目的就是計算一個像素的最終顏色,這裏用於處理光照啊,陰影啊什麽的。

  最後的數據會進行多種測試,這個在cocos2dx讀書筆記裏有提過,這裏暫且不寫了。(參考 這裏)

1.頂點著色器

  了解到這裏,我們現在開始編寫頂點著色器,他就是處理位置為主的一個編程塊。

#version 330 core //設定使用的opengl版本,我們用3.3的核心模式
layout (location = 0) in vec3 aPos; //定位輸入頂點的位置,這裏和上面說的glVertexAttribPointer第一個參數對應,是一個三分量的對象

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); //原封不動傳出去,第四個是齊次分量,用於3d使用,2d我們使用1.0
}

這是最簡單的一個頂點著色器,把頂點原封不動傳出去,我們可以做各種變換達到我們想要的效果,例如位移,縮放。這段代碼可以開一個txt文本來寫,寫好後放到項目工程一個新目錄裏,例如shader目錄即可,這裏我改名為ver.vs,而不用放入源碼目錄裏,否則項目編譯時候會報錯,我就是踩了這個坑。

 寫好後,我們需要編譯這個著色器。

// 編譯頂點著色器
unsigned int vertexShader;
string vertexShaderSource = readFile("ver.vs"); //讀取著色器代碼字符串
const char* verCode = vertexShaderSource.c_str();
vertexShader = glCreateShader(GL_VERTEX_SHADER); //創建頂點著色器,創建片段則用GL_FRAGMENT_SHADER
glShaderSource(vertexShader, 1, &verCode, NULL); //綁定著色器源碼,1代表只有一段源碼,可傳入多段,第三個就是源碼地址
glCompileShader(vertexShader); //編譯源碼

如果想知道是否編譯成功,可以用glCompileShader來查看

int  success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);

glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); //infoLog裏就有對應的錯誤日誌

2.片段著色器

 編寫片段著色器

#version 330 core
out vec4 FragColor; //輸出的片段變量

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); //設定了某個顏色
} 
//綁定片段著色器
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

3.著色器程序

  寫完2個著色器後,我們需要把他連接合並在一起,供給系統使用。

unsigned int g_ShaderID;
g_ShaderID = glCreateProgram(); //創建著色器程序
glAttachShader(g_ShaderID, vertexShader); //綁定頂點著色器
glAttachShader(g_ShaderID, fragmentShader); //綁定片段著色器
glLinkProgram(g_ShaderID); //鏈接程序
//鏈接後我們可以刪除2個著色器了
glDeleteShader(vertexShader); 
glDeleteShader(fragmentShader);
//之後我們可以通過glUseProgram(g_ShaderID)來使用這個程序

開啟循環渲染模式

  到這裏,我們離渲染出三角形還差一步,就是調用畫圖函數

glUseProgram(g_ShaderID); //使用這個著色器程序
glDrawArrays(GL_TRIANGLES, 0, 3); //畫圖

glDrawArrays第一個參數是我們需要畫的圖形,我們設定為三角形,我估計這裏是給圖形裝配使用的。第二個參數是頂點數組起始索引,這裏填0,第三個是繪制多少個頂點,這裏我們是3個,此時一運行,三角形出來了。

技術分享圖片我們嘗試註釋glUseProgram,可以發現圖形變白,可見他有默認的著色器在運行。

EBO的使用

  當我們想要畫2個三角形的時候,我們可能需要傳入6個點,但如果頂點有重復的時候,就會出現內存的浪費,而3d模型中就是通過很多微小的三角形連接而成,所以出現了EBO。簡單來說,ebo就是對頂點數據進行一個索引

float vertices[] = {
    0.5f, 0.5f, 0,
    0.5f, -0.5f, 0,
    -0.5f, -0.5f, 0,
    -0.5f, 0.5f, 0.0f
};
unsigned int indices[] = {
    0, 1, 3,
    1, 2, 3
};

可以看到我們2個三角形,是用vertices的0,1,3三個點構成,1,2,3構成另一個三角形。

glGenBuffers(1, &g_EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

可以看出和vbo加載差不多,只是他綁定到GL_ELEMENT_ARRAY_BUFFER緩沖區。

然後我們通過替換畫圖函數,即可實現畫圖

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); 
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

glDrawElements就是使用索引來畫圖,第一個參數指定圖形,第二個是需要頂點個數,第三個是索引數據類型,第四個是ebo數組中的起始偏移量,現在就大概過了一遍簡單的渲染過程。

<三> 初探opengl, 畫三角形