文章出處

CSharpGL(7)對VAO和VBO的封裝

2016-08-13

由于CSharpGL一直在更新,現在這個教程已經不適用最新的代碼了。CSharpGL源碼中包含10多個獨立的Demo,更適合入門參考。

為了盡可能提升渲染效率,CSharpGL是面向Shader的,因此稍有難度。

VAO(VBO)

在legacy OpenGL中,渲染圖形是用glVertex()之類的方式實現的。

在modern OpenGL中,則是用VAO和VBO來存儲圖形信息以備渲染的。

VBO(Vertex Buffer Object)是用來存儲頂點屬性的對象。VBO就像一個定長的數組(SomeType[] vbo = new SomeType[100];),只不過是存貯于GPU內存里。我們在編碼時可以通過一個代表它的指針來操作它。

VAO(Vertex Array Object)是用來管理VBO的渲染流程的。簡單地說,就是讓VAO看一次各個VBO是如何完成一次渲染工作的,VAO就記住了這個過程。以后就可以僅借助VAO來完成渲染過程。這就減少了DrawCall。

如果你沒有使用過modern OpenGL,沒有使用過VBO,那么現在看下文是無意義的。你可以先下載(https://github.com/bitzhuwei/CSharpGL)和稍微瀏覽一下其中的代碼,之后再看本文是如何封裝VAO和VBO的。

OpenGL告訴你應該這樣用VBO

首先來仔細觀察OpenGL提供的使用VBO的方式。

下圖是CSharpGL中的一個渲染四面體的例子。我就以渲染這個四面體為例。

 

給出頂點屬性

我們給出這個四面體的頂點數據。其中每3個連續的頂點代表一個三角形,顯然這里有4個三角形。

 1         /// <summary>
 2         /// 金字塔的posotion array.
 3         /// </summary>
 4         static vec3[] positions = new vec3[]
 5     {
 6         new vec3(0.0f, 1.0f, 0.0f),
 7         new vec3(-1.0f, -1.0f, 1.0f),
 8         new vec3(1.0f, -1.0f, 1.0f),
 9         new vec3(0.0f, 1.0f, 0.0f),
10         new vec3(1.0f, -1.0f, 1.0f),
11         new vec3(1.0f, -1.0f, -1.0f),
12         new vec3(0.0f, 1.0f, 0.0f),
13         new vec3(1.0f, -1.0f, -1.0f),
14         new vec3(-1.0f, -1.0f, -1.0f),
15         new vec3(0.0f, 1.0f, 0.0f),
16         new vec3(-1.0f, -1.0f, -1.0f),
17         new vec3(-1.0f, -1.0f, 1.0f),
18     };

 

有了頂點的位置,下面給出每個頂點的顏色。

 1         /// <summary>
 2         /// 金字塔的color array.
 3         /// </summary>
 4         static vec3[] colors = new vec3[]
 5     {
 6         new vec3(1.0f, 0.0f, 0.0f),
 7         new vec3(0.0f, 1.0f, 0.0f),
 8         new vec3(0.0f, 0.0f, 1.0f),
 9         new vec3(1.0f, 0.0f, 0.0f),
10         new vec3(0.0f, 0.0f, 1.0f),
11         new vec3(0.0f, 1.0f, 0.0f),
12         new vec3(1.0f, 0.0f, 0.0f),
13         new vec3(0.0f, 1.0f, 0.0f),
14         new vec3(0.0f, 0.0f, 1.0f),
15         new vec3(1.0f, 0.0f, 0.0f),
16         new vec3(0.0f, 0.0f, 1.0f),
17         new vec3(0.0f, 1.0f, 0.0f),
18     };
View Code

 

準備shader program

在使用VBO前,需要準備好shader program。你只需知道OpenGL會給我們一個ShaderProgram的uint值代表它。VAO、VBO、texture也都有這樣一個uint值。

 1         protected void InitializeShader(out ShaderProgram shaderProgram)
 2         {
 3             var vertexShaderSource = ManifestResourceLoader.LoadTextFile(@"SceneElements.PyramidElement.vert");
 4             var fragmentShaderSource = ManifestResourceLoader.LoadTextFile(@"SceneElements.PyramidElement.frag");
 5 
 6             shaderProgram = new ShaderProgram();
 7             shaderProgram.Create(vertexShaderSource, fragmentShaderSource, null);
 8 
 9             shaderProgram.AssertValid();
10         }

 

這里我們給出vertex shader。此shader里有' in_Position'、' in_Color'兩個in變量,分別與positions和colors的VBO對應。

 1 #version 150 core
 2 
 3 in vec3 in_Position;
 4 in vec3 in_Color;  
 5 out vec4 pass_Color;
 6 
 7 uniform mat4 MVP;
 8 
 9 void main(void) 
10 {
11     gl_Position = MVP * vec4(in_Position, 1.0);
12 
13     pass_Color = vec4(in_Color, 1.0);
14 }

初始化VBO

在這個四面體的例子中,我們需要為positions創建一個VBO,然后為colors再創建一個VBO。現在,positions是CPU內存中的數據,我們要把它上傳到GPU內存中。

 1                 var positionBufferObject = new uint[1];
 2                 // 創建VBO
 3                 GL.GenBuffers(1, this.positionBufferObject);
 4                 // 綁定VBO(選中此VBO)
 5                 GL.BindBuffer(BufferTarget.ArrayBuffer, positionBufferObject[0]);
 6                 // 將托管的數據轉到非托管內存
 7                 UnmanagedArray<vec3> positionArray = new UnmanagedArray<vec3>(positions.Length);
 8                 for (int i = 0; i < positions.Length; i++)
 9                 {
10                     positionArray[i] = positions[i];
11                 }
12 
13                 // 將position屬性上傳到GPU內存,即為VBO填入數據
14                 GL.BufferData(BufferTarget.ArrayBuffer, positionArray, BufferUsage.StaticDraw);
15 
16                 positionArray.Dispose();

 

 

這里的GL.GenBuffersGL.BindBufferGL.BufferData完成了初始化VBO的工作。

初始化colors的VBO的過程同上。

VBO初始化完成后,內存中的數組就可以刪掉了。

用VBO進行渲染

渲染時的操作如下:

指定頂點屬性

1                 // 綁定VBO(選中此VBO)
2                 GL.BindBuffer(BufferTarget.ArrayBuffer, positionBufferObject[0]);
3                 //獲取shader中的’in_Position’的指針。
4                 uint positionLocation = shaderProgram.GetAttributeLocation(strin_Position);
5                 // 當前VBO(此時為positions的VBO)對應ShaderProgram里的'in_Position'in變量。每3個float(即一個頂點)為一個數據單元。
6                 GL.VertexAttribPointer(positionLocation, 3, GL.GL_FLOAT, false, 0, IntPtr.Zero);
7                 // 啟用此頂點屬性數組。
8                 GL.EnableVertexAttribArray(positionLocation);

這是指定頂點的position屬性的過程,指定頂點的color屬性的過程同上。

無索引渲染

各個屬性都指定好了,就該開始渲染了:

1 // 指定要使用哪個VAO。
2 GL.BindVertexArray(vao[0]);
3 // 依照VBO中的順序渲染12個頂點(用渲染三角形的方式)。
4 GL.DrawArrays(GL.GL_TRIANGLES, 0, 12);
5 // 不再指定VAO。
6 GL.BindVertexArray(0);

有索引渲染

上面的渲染方式不能重復利用同一個頂點,而下面的用索引進行渲染的方式則可以。

為了使用索引進行渲染,我們先要初始化一個索引的VBO。這與初始化頂點屬性VBO是同樣的步驟。

 1                 var indexBufferObject = new uint[1];
 2                 GL.GenBuffers(1, indexBufferObject);
 3                 GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexBufferObject[0]);
 4                 UnmanagedArray<uint> indexArray = new UnmanagedArray<uint>(12);
 5                 for (int i = 0; i < indexArray.Length; i++)
 6                 {
 7                     indexArray[i] = (uint)i;
 8                 }
 9                 GL.BufferData(BufferTarget.ElementArrayBuffer, indexArray, BufferUsage.StaticDraw);
10                 indexArray.Dispose();

然后用索引進行渲染:

1             GL.BindVertexArray(vao[0]);
2             // 依照依照索引VBO中給定的順序渲染12個頂點(用渲染三角形的方式)
3             GL.DrawElements(GL.GL_TRIANGLES, 12, GL.GL_UNSIGNED_INT, IntPtr.Zero);
4             GL.BindVertexArray(0); 

OpenGL是如何看待VBO的

從上面的初始化和渲染過程來看,OpenGL里有這樣一些概念。

頂點屬性(vertex property)

在OpenGL中,想渲染一個頂點,至少需要知道它的位置和顏色這兩個信息。位置顏色這樣的信息就稱為頂點的屬性。當然,像法線等也都是屬性。你也可以根據業務需求自定義一些屬性。總之,一個VBO描述的是一組頂點的某個屬性。例如上文我們定義了一個描述頂點position屬性的VBO和一個描述頂點color屬性的VBO。

但是,描述索引的VBO并不是頂點的屬性,證據是,索引VBO可以重復引用同一個頂點。這就提醒我們,每個頂點都有且只有1份的才是頂點屬性。描述索引的VBO長度不一定等于描述其他頂點屬性的VBO的長度。

所以,VBO有描述頂點屬性和描述索引兩大種類。

顯式索引和隱式索引

聲明,顯式索引和隱式索引是我自創的兩個名詞,你沒聽過并不奇怪。

其含義很簡單:

顯式索引,就是上文中用GL.DrawElements進行渲染的方式。

隱式索引,就是上文中用GL.DrawArrays進行渲染的方式。這種方式沒有顯式指明索引,但是隱含著的規則是以頂點在VBO中的順序為索引進行渲染。

封裝VBO

有了上面的分析,對VBO的封裝就呼之欲出了。

此類圖符合VBO分為描述屬性和描述索引兩類,索引分為顯示和隱式兩類。

此類圖中的對象是為初始化VBO用的。初始化完成后就可以釋放掉了。所以有如下的代碼:

protected override BufferRenderer CreateRenderer()

{

    uint[] buffers = new uint[1];

    GL.GenBuffers(1, buffers);

    GL.BindBuffer(GL.GL_ARRAY_BUFFER, buffers[0]);

    GL.BufferData(GL.GL_ARRAY_BUFFER, this.ByteLength, this.Header, (uint)this.Usage);

 

    PropertyBufferRenderer renderer = new PropertyBufferRenderer(

    this.VarNameInVertexShader, buffers[0], this.DataSize, this.DataType);

 

    return renderer;

}

 

上述代碼中的BufferRenderer是用來反復執行渲染過程的。其類圖如下,與VertexBuffer的繼承十分對仗。

封裝VAO

一個VAO里有多個VBO,就這么簡單。

  1     /// <summary>
  2     /// 一個vertex array object。(即VAO)
  3     /// <para>VAO是用來管理VBO的。可以進一步減少DrawCall。</para>
  4     /// </summary>
  5     public class VertexArrayObject : IDisposable
  6     {
  7         BufferRenderer[] bufferRenderers;
  8         IndexBufferBaseRenderer indexBufferRenderer;
  9 
 10         /// <summary>
 11         /// 一個vertex array object。(即VAO)
 12         /// <para>VAO是用來管理VBO的。可以進一步減少DrawCall。</para>
 13         /// </summary>
 14         /// <param name="propertyBuffers">給出此VAO要管理的所有VBO。</param>
 15         public VertexArrayObject(params BufferRenderer[] propertyBuffers)
 16         {
 17             this.bufferRenderers = propertyBuffers;
 18             foreach (var item in propertyBuffers)
 19             {
 20                 var renderer = item as IndexBufferBaseRenderer;
 21                 if (renderer != null)
 22                 {
 23                     indexBufferRenderer = renderer;
 24                 }
 25             }
 26         }
 27 
 28         private bool disposedValue;
 29 
 30         /// <summary>
 31         /// 此VAO的ID,由OpenGL給出。
 32         /// </summary>
 33         public uint ID { get; private set; }
 34 
 35         /// <summary>
 36         /// 在OpenGL中創建VAO。
 37         /// </summary>
 38         /// <param name="e"></param>
 39         /// <param name="shaderProgram"></param>
 40         public void Create(RenderEventArgs e, Shaders.ShaderProgram shaderProgram)
 41         {
 42             uint[] buffers = new uint[1];
 43             GL.GenVertexArrays(1, buffers);
 44 
 45             this.ID = buffers[0];
 46 
 47             this.Bind();
 48             foreach (var item in this.bufferRenderers)
 49             {
 50                 item.Render(e, shaderProgram);
 51             }
 52             this.Unbind();
 53         }
 54 
 55         private void Bind()
 56         {
 57             GL.BindVertexArray(this.ID);
 58         }
 59 
 60         private void Unbind()
 61         {
 62             GL.BindVertexArray(0);
 63         }
 64 
 65         public void Render(RenderEventArgs e, Shaders.ShaderProgram shaderProgram)
 66         {
 67             this.Bind();
 68             this.indexBufferRenderer.Render(e, shaderProgram);
 69             this.Unbind();
 70         }
 71 
 72         public override string ToString()
 73         {
 74             return string.Format("VAO ID: {0}", this.ID);
 75         }
 76 
 77 
 78         public void Dispose()
 79         {
 80             this.Dispose(true);
 81             GC.SuppressFinalize(this);
 82         }
 83 
 84         ~VertexArrayObject()
 85         {
 86             this.Dispose(false);
 87         }
 88 
 89         protected virtual void Dispose(bool disposing)
 90         {
 91 
 92             if (this.disposedValue == false)
 93             {
 94                 if (disposing)
 95                 {
 96                     // Dispose managed resources.
 97 
 98                 }
 99 
100                 // Dispose unmanaged resources.
101                 foreach (var item in this.bufferRenderers)
102                 {
103                     item.Dispose();
104                 }
105                 GL.DeleteVertexArrays(1, new uint[] { this.ID });
106             }
107 
108             this.disposedValue = true;
109         }
110 
111     }
VAO 

詳情參考CSharpGL代碼即可。

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()