現代opengl 設計,模型匯入網格類

2021-09-14 00:55:17 字數 3876 閱讀 2043

本文內容主要參照   和  學習而來。

使用assimp,我們可以載入不同的模型到程式中,但我們要儲存所有資料,需要乙個合理的資料結構。本文介紹如此乙個類,網格類,儲存單個的可繪製實體的必要資料。而整個模型由多個網格組成,或者說網格向量組成我們需要顯示的模型。

首先我們來回顧一下我們目前學到的知識,想想乙個網格最少需要什麼資料。乙個網格需要一系列的頂點,每個頂點包含乙個位置向量、乙個法向量和乙個紋理座標向量。乙個網格還應該包含用於索引繪製的索引,以及紋理形式的材質資料(漫反射/鏡面光貼圖)。

既然我們有了乙個網格類的最低需求,我們可以在opengl中定義乙個頂點了:

struct vertex ;
我們將所有需要的向量儲存到乙個叫做vertex的結構體中,我們可以用它來索引每個頂點屬性。

除了vertex結構體之外,我們還需要將紋理資料整理到乙個texture結構體中。

struct texture ;
我們儲存了紋理的id以及它的型別,比如是漫反射貼圖或者是鏡面光貼圖。

知道了頂點和紋理的實現,我們可以開始定義網格類的結構了:

class mesh ;
你可以看到這個類並不複雜。在構造器中,我們將所有必須的資料賦予了網格,我們在setupmesh函式中初始化緩衝,並最終使用draw函式來繪製網格。注意我們將乙個著色器傳入了draw函式中,將著色器傳入網格類中可以讓我們在繪製之前設定一些uniform(像是鏈結取樣器到紋理單元)。

構造器的內容非常易於理解。我們只需要使用構造器的引數設定類的公有變數就可以了。我們在構造器中還呼叫了setupmesh函式:

mesh(vectorvertices, vectorindices, vectortextures)

這裡沒什麼可說的。我們接下來討論setupmesh函式。

由於有了構造器,我們現在有一大列的網格資料用於渲染。在此之前我們還必須配置正確的緩衝,並通過頂點屬性指標定義頂點著色器的布局。現在你應該對這些概念都很熟悉了,但我們這次會稍微有一點變動,使用結構體中的頂點資料:

void setupmesh()

**應該和你所想得沒什麼不同,但有了vertex結構體的幫助,我們使用了一些小技巧。

c++結構體有乙個很棒的特性,它們的記憶體布局是連續的(sequential)。也就是說,如果我們將結構體作為乙個資料陣列使用,那麼它將會以順序排列結構體的變數,這將會直接轉換為我們在陣列緩衝中所需要的float(實際上是位元組)陣列。比如說,如果我們有乙個填充後的vertex結構體,那麼它的記憶體布局將會等於:

vertex vertex;

vertex.position = glm::vec3(0.2f, 0.4f, 0.6f);

vertex.normal = glm::vec3(0.0f, 1.0f, 0.0f);

vertex.texcoords = glm::vec2(1.0f, 0.0f);

// = [0.2f, 0.4f, 0.6f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f];

由於有了這個有用的特性,我們能夠直接傳入一大列的vertex結構體的指標作為緩衝的資料,它們將會完美地轉換為glbufferdata所能用的引數:

glbufferdata(gl_array_buffer, vertices.size() * sizeof(vertex), &vertices[0], gl_static_draw);
自然sizeof運算也可以用在結構體上來計算它的位元組大小。這個應該是32位元組的(8個float * 每個4位元組)。

結構體的另外乙個很好的用途是它的預處理指令offsetof(s, m),它的第乙個引數是乙個結構體,第二個引數是這個結構體中變數的名字。這個巨集會返回那個變數距結構體頭部的位元組偏移量(byte offset)。這正好可以用在定義glvertexattribpointer函式中的偏移引數:

glvertexattribpointer(1, 3, gl_float, gl_false, sizeof(vertex), (void*)offsetof(vertex, normal));
偏移量現在是使用offsetof來定義了,在這裡它會將法向量的位元組偏移量設定為結構體中法向量的偏移量,也就是3個float,即12位元組。注意,我們同樣將步長引數設定為了vertex結構體的大小。

使用這樣的乙個結構體不僅能夠提供可讀性更高的**,也允許我們很容易地拓展這個結構。如果我們希望新增另乙個頂點屬性,我們只需要將它新增到結構體中就可以了。由於它的靈活性,渲染的**不會被破壞。

我們需要為mesh類定義最後乙個函式,它的draw函式。在真正渲染這個網格之前,我們需要在呼叫gldrawelements函式之前先繫結相應的紋理。然而,這實際上有些困難,我們一開始並不知道這個網格(如果有的話)有多少紋理、紋理是什麼型別的。所以我們該如何在著色器中設定紋理單元和取樣器呢?

為了解決這個問題,我們需要設定乙個命名標準:每個漫反射紋理被命名為texture_diffusen,每個鏡面光紋理應該被命名為texture_specularn,其中n的範圍是1到紋理取樣器最大允許的數字。比如說我們對某乙個網格有3個漫反射紋理,2個鏡面光紋理,它們的紋理取樣器應該之後會被呼叫:

uniform sampler2d texture_diffuse1;

uniform sampler2d texture_diffuse2;

uniform sampler2d texture_diffuse3;

uniform sampler2d texture_specular1;

uniform sampler2d texture_specular2;

根據這個標準,我們可以在著色器中定義任意需要數量的紋理取樣器,如果乙個網格真的包含了(這麼多)紋理,我們也能知道它們的名字是什麼。根據這個標準,我們也能在乙個網格中處理任意數量的紋理,開發者也可以自由選擇需要使用的數量,他只需要定義正確的取樣器就可以了(雖然定義少的話會有點浪費繫結和uniform呼叫)。

最終的渲染**是這樣的:

void draw(shader shader) 

glactivetexture(gl_texture0);

// 繪製網格

glbindvertexarray(vao);

gldrawelements(gl_********s, indices.size(), gl_unsigned_int, 0);

glbindvertexarray(0);

}

我們首先計算了每個紋理型別的n-分量,並將其拼接到紋理型別字串上,來獲取對應的uniform名稱。接下來我們查詢對應的取樣器,將它的位置值設定為當前啟用的紋理單元,並繫結紋理。這也是我們在draw函式中需要著色器的原因。我們也將"material."新增到了最終的uniform名稱中,因為我們希望將紋理儲存在乙個材質結構體中(這在每個實現中可能都不同)。

注意我們在將漫反射計數器和鏡面光計數器插入stringstream時,對它們進行了遞增。在c++中,這個遞增操作:variable++將會返回變數本身,之後再遞增,而++variable則是遞增,再返回值。在我們的例子中是首先將原本的計數器值插入stringstream,之後再遞增它,供下一次迴圈使用。

你可以在這裡找到mesh類的完整源**

有了這個網格類,我們就可以以此為基礎,構造3d 模型,並opengl 顯示渲染了。

OpenGL匯入的obj模型透明問題

在利用opengl匯入obj模型後,發現模型是透明的,在旋轉過程中特別難看,於是需要設定成非透明的實體模型。下面的圖是模型為透明的時候,看起來就讓人感覺很亂 由於一開始我們利用 glclear gl color buffer bit gl depth buffer bit 清除了緩衝區,所以只要設定...

OpenGL 光照模型

材質有三種,也就是對光有三種反射 材質被分為了三個屬性,也分別用三個引數來刻畫 對於乙個頂點,有四個向量來刻畫其光照 計算的時候也分為鏡面 漫和環境三類,之後再疊加起來即得到光照下的顏色 向量nlvr 求解顏色 鏡面反射 is rs max v r a,0 ls 環境反射 ia ra la於是 i ...

opengl 模型載入

乙個非常流行的模型導入庫是assimp,它是open asset import library 開放的資產導入庫 的縮寫 assimp資料結構的 簡化 模型如下 opengl中定義乙個頂點,每個頂點包含乙個位置向量 乙個法向量和乙個紋理座標向量 struct vertex 紋理資料,儲存了紋理的id...