開(kāi)始
總的來(lái)說(shuō),OpenGL應(yīng)用開(kāi)發(fā)者會(huì)遇到為如下三種數(shù)據(jù)創(chuàng)建Vertex Buffer Object的情形:
任意一個(gè)struct類(lèi)型T data;
任意一個(gè)元素類(lèi)型為struct的數(shù)組T[] array;
任意一個(gè)非托管數(shù)組UnmanagedArray<T> array;
而可創(chuàng)建的Vertex Buffer Object也分為如下的類(lèi)別:
描述頂點(diǎn)屬性(位置、顏色、法線(xiàn)等)的VertexBuffer;
描述索引的IndexBuffer;
描述其他自定義內(nèi)容的各種Buffer;
本文介紹用C#如何實(shí)現(xiàn)上述功能。
非托管數(shù)組->VertexBuffer
最基本的功能是通過(guò)非托管數(shù)組UnmanagedArrayBase創(chuàng)建一個(gè)VBO,我們首先實(shí)現(xiàn)這個(gè)功能。
1 public static VertexBuffer GetVertexBuffer(this UnmanagedArrayBase array, VBOConfig config, string varNameInVertexShader, BufferUsage usage, uint instancedDivisor = 0, int patchVertexes = 0) 2 { 3 uint[] buffers = new uint[1]; 4 glGenBuffers(1, buffers); 5 const uint target = OpenGL.GL_ARRAY_BUFFER; 6 glBindBuffer(target, buffers[0]); 7 glBufferData(target, array.ByteLength, array.Header, (uint)usage); 8 glBindBuffer(target, 0); 9 10 var buffer = new VertexBuffer(11 varNameInVertexShader, buffers[0], config, array.Length, array.ByteLength, instancedDivisor, patchVertexes);12 13 return buffer;14 }
T[] -> VertexBuffer
很多時(shí)候,大家都是在用習(xí)慣了的托管數(shù)組(int[]、Point[]、vec3[]等)。那么能不能直接用托管數(shù)組創(chuàng)建VBO呢?當(dāng)然可以。雖然是托管數(shù)組,但是在內(nèi)存中畢竟也還是連續(xù)存放的一塊內(nèi)存。我們只需找到它的地址就可以了。找地址這件事通過(guò) Marshal.UnsafeAddrOfPinnedArrayElement(); 就可以做到。
1 public static VertexBuffer GetVertexBuffer<T>(this T[] array, VBOConfig config, string varNameInVertexShader, BufferUsage usage, uint instancedDivisor = 0, int patchVertexes = 0) where T : struct 2 { 3 GCHandle pinned = GCHandle.Alloc(array, GCHandleType.Pinned); 4 IntPtr header = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0); 5 UnmanagedArrayBase unmanagedArray = new UnmanagedArray<T>(header, array.Length);// It's not neecessary to call Dispose() for this unmanaged array. 6 VertexBuffer buffer = GetVertexBuffer(unmanagedArray, config, varNameInVertexShader, usage, instancedDivisor, patchVertexes); 7 pinned.Free(); 8 9 return buffer;10 }
T -> VertexBuffer
那么單獨(dú)的一個(gè)struct變量,如何為之創(chuàng)建VBO?只需用一個(gè) var array = new T[1]{ data }; 將其封裝起來(lái),就可以用上面的方法了。
1 public static VertexBuffer GetVertexBuffer<T>(this T data, VBOConfig config, string varNameInVertexShader, BufferUsage usage, uint instancedDivisor = 0, int patchVertexes = 0) where T : struct 2 { 3 var array = new T[] { data }; 4 return GetVertexBuffer(array, config, varNameInVertexShader, usage, instancedDivisor, patchVertexes); 5 // another way to do this: 6 //using (UnmanagedArrayBase unmanagedArray = new UnmanagedArray<T>(1)) 7 //{ 8 // Marshal.StructureToPtr(data, unmanagedArray.Header, false); 9 // VertexBuffer buffer = GetVertexBufferObject(unmanagedArray, config, varNameInVertexShader, usage, instancedDivisor, patchVertexes);10 // return buffer;11 //}12 }
非托管數(shù)組->IndexBuffer
非托管數(shù)組->OneIndexBuffer
從非托管數(shù)組到OneIndexBuffer的思路和上面一致。要注意的是,OneIndexBuffer能接受的元素類(lèi)型只能是byte、ushort、uint三者之一。
1 public static OneIndexBuffer GetOneIndexBuffer(this UnmanagedArray<byte> array, DrawMode mode, BufferUsage usage, int primCount = 1) 2 { 3 return GetOneIndexBuffer(array, mode, usage, IndexElementType.UByte, primCount); 4 } 5 6 public static OneIndexBuffer GetOneIndexBuffer(this UnmanagedArray<ushort> array, DrawMode mode, BufferUsage usage, int primCount = 1) 7 { 8 return GetOneIndexBuffer(array, mode, usage, IndexElementType.UShort, primCount); 9 }10 11 public static OneIndexBuffer GetOneIndexBuffer(this UnmanagedArray<uint> array, DrawMode mode, BufferUsage usage, int primCount = 1)12 {13 return GetOneIndexBuffer(array, mode, usage, IndexElementType.UInt, primCount);14 }15 16 private static OneIndexBuffer GetOneIndexBuffer(this UnmanagedArrayBase array, DrawMode mode, BufferUsage usage, IndexElementType elementType, int primCount = 1)17 {18 if (glGenBuffers == null)19 {20 InitFunctions();21 }22 23 uint[] buffers = new uint[1];24 glGenBuffers(1, buffers);25 const uint target = OpenGL.GL_ELEMENT_ARRAY_BUFFER;26 glBindBuffer(target, buffers[0]);27 glBufferData(target, array.ByteLength, array.Header, (uint)usage);28 glBindBuffer(target, 0);29 30 var buffer = new OneIndexBuffer(buffers[0], mode, elementType, array.Length, array.ByteLength, primCount);31 32 return buffer;33 }
T[] -> OneIndexBuffer
思路同上。
1 public static OneIndexBuffer GetOneIndexBuffer(this byte[] array, DrawMode mode, BufferUsage usage, int primCount = 1) 2 { 3 GCHandle pinned = GCHandle.Alloc(array, GCHandleType.Pinned); 4 IntPtr header = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0); 5 var unmanagedArray = new UnmanagedArray<byte>(header, array.Length);// It's not neecessary to call Dispose() for this unmanaged array. 6 OneIndexBuffer buffer = GetOneIndexBuffer(unmanagedArray, mode, usage, IndexElementType.UByte, primCount); 7 pinned.Free(); 8 9 return buffer;10 }11 12 public static OneIndexBuffer GetOneIndexBuffer(this ushort[] array, DrawMode mode, BufferUsage usage, int primCount = 1)13 {14 GCHandle pinned = GCHandle.Alloc(array, GCHandleType.Pinned);15 IntPtr header = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0);16 var unmanagedArray = new UnmanagedArray<ushort>(header, array.Length);// It's not neecessary to call Dispose() for this unmanaged array.17 OneIndexBuffer buffer = GetOneIndexBuffer(unmanagedArray, mode, usage, IndexElementType.UShort, primCount);18 pinned.Free();19 20 return buffer;21 }22 23 public static OneIndexBuffer GetOneIndexBuffer(this uint[] array, DrawMode mode, BufferUsage usage, int primCount = 1)24 {25 GCHandle pinned = GCHandle.Alloc(array, GCHandleType.Pinned);26 IntPtr header = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0);27 var unmanagedArray = new UnmanagedArray<uint>(header, array.Length);// It's not neecessary to call Dispose() for this unmanaged array.28 OneIndexBuffer buffer = GetOneIndexBuffer(unmanagedArray, mode, usage, IndexElementType.UInt, primCount);29 pinned.Free();30 31 return buffer;32 }
T -> OneIndexBuffer
只有1個(gè)元素的索引數(shù)組,比較奇葩,不過(guò)也是可以實(shí)現(xiàn)的。
1 public static OneIndexBuffer GetOneIndexBuffer(this byte data, DrawMode mode, BufferUsage usage, int primCount = 1) 2 { 3 var array = new byte[] { data }; 4 return GetOneIndexBuffer(array, mode, usage, primCount); 5 } 6 7 public static OneIndexBuffer GetOneIndexBuffer(this ushort data, DrawMode mode, BufferUsage usage, int primCount = 1) 8 { 9 var array = new ushort[] { data };10 return GetOneIndexBuffer(array, mode, usage, primCount);11 }12 13 public static OneIndexBuffer GetOneIndexBuffer(this uint data, DrawMode mode, BufferUsage usage, int primCount = 1)14 {15 var array = new uint[] { data };16 return GetOneIndexBuffer(array, mode, usage, primCount);17 }
ZeroIndexBuffer
這事一個(gè)特殊的Buffer,因?yàn)閷?shí)際上在OpenGL的server端并沒(méi)有真正創(chuàng)建一個(gè)Buffer。但是邏輯上把它也視作一個(gè)Buffer是方便合理的。既然沒(méi)有真正創(chuàng)建Buffer,那么也就不存在用非托管數(shù)組創(chuàng)建ZeroIndexBuffer的情形了。
非托管數(shù)組->自定義Buffer
自定義Buffer都有哪些
所謂自定義Buffer,是那些用途各異的特殊Buffer,目前CSharpGL包含了:
1 AtomicCounterBuffer2 PixelPackBuffer3 PixelUnpackBuffer4 ShaderStorageBuffer5 TextureBuffer6 UniformBuffer
下面以 AtomicCounterBuffer 為例,其他雷同。
非托管數(shù)組->自定義Buffer
思路同上。
public static AtomicCounterBuffer GetAtomicCounterBuffer(this UnmanagedArrayBase array, BufferUsage usage) { return GetIndependentBuffer(array, IndependentBufferTarget.AtomicCounterBuffer, usage) as AtomicCounterBuffer; } private static Buffer GetIndependentBuffer(this UnmanagedArrayBase array, IndependentBufferTarget bufferTarget, BufferUsage usage) { uint[] buffers = new uint[1]; glGenBuffers(1, buffers); var target = (uint)bufferTarget; glBindBuffer(target, buffers[0]); glBufferData(target, array.ByteLength, array.Header, (uint)usage); glBindBuffer(target, 0); Buffer buffer = null; switch (bufferTarget) { case IndependentBufferTarget.AtomicCounterBuffer: buffer = new AtomicCounterBuffer(buffers[0], array.Length, array.ByteLength); break; case IndependentBufferTarget.PixelPackBuffer: buffer = new PixelPackBuffer(buffers[0], array.Length, array.ByteLength); break; case IndependentBufferTarget.PixelUnpackBuffer: buffer = new PixelUnpackBuffer(buffers[0], array.Length, array.ByteLength); break; case IndependentBufferTarget.ShaderStorageBuffer: buffer = new ShaderStorageBuffer(buffers[0], array.Length, array.ByteLength); break; case IndependentBufferTarget.TextureBuffer: buffer = new TextureBuffer(buffers[0], array.Length, array.ByteLength); break; case IndependentBufferTarget.UniformBuffer: buffer = new UniformBuffer(buffers[0], array.Length, array.ByteLength); break; default: throw new NotImplementedException(); } return buffer; }
T[] –> 自定義Buffer
思路同上。
1 public static AtomicCounterBuffer GetAtomicCounterBuffer<T>(this T[] array, BufferUsage usage) where T : struct 2 { 3 GCHandle pinned = GCHandle.Alloc(array, GCHandleType.Pinned); 4 IntPtr header = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0); 5 var unmanagedArray = new UnmanagedArray<T>(header, array.Length);// It's not neecessary to call Dispose() for this unmanaged array. 6 AtomicCounterBuffer buffer = GetIndependentBuffer(unmanagedArray, IndependentBufferTarget.AtomicCounterBuffer, usage) as AtomicCounterBuffer; 7 pinned.Free(); 8 9 return buffer;10 }
T -> 自定義Buffer
思路同上。這個(gè)方式還是比較常見(jiàn)的一種用法。
1 public static AtomicCounterBuffer GetAtomicCounterBuffer<T>(this T data, BufferUsage usage) where T : struct2 {3 var array = new T[] { data };4 return GetAtomicCounterBuffer(array, usage);5 }
如何使用
實(shí)現(xiàn)了上面那些看起來(lái)比較啰嗦的功能,現(xiàn)在來(lái)看看使用的時(shí)候是什么情形。
-> VertexBuffer
最基本的功能是通過(guò)數(shù)組UnmanagedArrayBase或T[]創(chuàng)建一個(gè)VBO,我們首先使用這個(gè)功能??梢?jiàn)只需一行代碼即可實(shí)現(xiàn),且調(diào)用方式也相同。
1 vec3 position = GetPositions();2 VertexBuffer buffer = position.GetVertexBuffer(VBOConfig.Vec3, varNameInShader, BufferUsage.StaticDraw);3 //4 vec3[] positions = GetPositions();5 VertexBuffer buffer = positions.GetVertexBuffer(VBOConfig.Vec3, varNameInShader, BufferUsage.StaticDraw);6 //7 UnmanagedArray<vec3> positions = GetPositions();8 VertexBuffer buffer = positions.GetVertexBuffer(VBOConfig.Vec3, varNameInShader, BufferUsage.StaticDraw);
-> OneIndexBuffer
同上,不解釋。
1 uint position = GetIndexes();2 VertexBuffer buffer = position.GetOneIndexBuffer(DrawMode.Triangles, BufferUsage.StaticDraw);3 //4 uint[] positions = GetIndexes();5 VertexBuffer buffer = positions.GetOneIndexBuffer(DrawMode.Triangles, BufferUsage.StaticDraw);6 //7 UnmanagedArray<uint> positions = GetIndexes();8 VertexBuffer buffer = positions.GetOneIndexBuffer(DrawMode.Triangles, BufferUsage.StaticDraw);
-> 自定義Buffer
同上,不解釋。
1 SomeStruct data = GetIndexes();2 VertexBuffer buffer = position.GetUniformBuffer(BufferUsage.StaticDraw);3 //4 SomeStruct[] data = GetIndexes();5 VertexBuffer buffer = data.GetUniformBuffer(BufferUsage.StaticDraw);6 //7 UnmanagedArray<SomeStruct> data = GetIndexes();8 VertexBuffer buffer = data.GetUniformBuffer(BufferUsage.StaticDraw);
總結(jié)
業(yè)務(wù)數(shù)據(jù)是核心,其他參數(shù)輔助,按照這一思路,就實(shí)現(xiàn)了現(xiàn)在的一行創(chuàng)建VBO的功能。
CSharpGL已經(jīng)有點(diǎn)深度,所以筆記很難寫(xiě)出讓人直接就能眼前一亮的感覺(jué)了。
目前CSharpGL中已經(jīng)涵蓋了我所知的所有OpenGL知識(shí)點(diǎn)。下一步就是精心讀書(shū),繼續(xù)深挖。