开始
总的来说,OpenGL应用开发者会遇到为如下三种数据创建Vertex Buffer Object的情形:
任意一个struct类型T data;
任意一个元素类型为struct的数组T[] array;
任意一个非托管数组UnmanagedArray<T> array;
而可创建的Vertex Buffer Object也分为如下的类别:
描述顶点属性(位置、颜色、法线等)的VertexBuffer;
描述索引的IndexBuffer;
描述其他自定义内容的各种Buffer;
本文介绍用C#如何实现上述功能。
非托管数组->VertexBuffer
最基本的功能是通过非托管数组UnmanagedArrayBase创建一个VBO,我们首先实现这个功能。
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
很多时候,大家都是在用习惯了的托管数组(int[]、Point[]、vec3[]等)。那么能不能直接用托管数组创建VBO呢?当然可以。虽然是托管数组,但是在内存中毕竟也还是连续存放的一块内存。我们只需找到它的地址就可以了。找地址这件事通过 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
那么单独的一个struct变量,如何为之创建VBO?只需用一个 var array = new T[1]{ data }; 将其封装起来,就可以用上面的方法了。
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 }
非托管数组->IndexBuffer
非托管数组->OneIndexBuffer
从非托管数组到OneIndexBuffer的思路和上面一致。要注意的是,OneIndexBuffer能接受的元素类型只能是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个元素的索引数组,比较奇葩,不过也是可以实现的。
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
这事一个特殊的Buffer,因为实际上在OpenGL的server端并没有真正创建一个Buffer。但是逻辑上把它也视作一个Buffer是方便合理的。既然没有真正创建Buffer,那么也就不存在用非托管数组创建ZeroIndexBuffer的情形了。
非托管数组->自定义Buffer
自定义Buffer都有哪些
所谓自定义Buffer,是那些用途各异的特殊Buffer,目前CSharpGL包含了:
1 AtomicCounterBuffer2 PixelPackBuffer3 PixelUnpackBuffer4 ShaderStorageBuffer5 TextureBuffer6 UniformBuffer
下面以 AtomicCounterBuffer 为例,其他雷同。
非托管数组->自定义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
思路同上。这个方式还是比较常见的一种用法。
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 }
如何使用
实现了上面那些看起来比较啰嗦的功能,现在来看看使用的时候是什么情形。
-> VertexBuffer
最基本的功能是通过数组UnmanagedArrayBase或T[]创建一个VBO,我们首先使用这个功能。可见只需一行代码即可实现,且调用方式也相同。
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);
总结
业务数据是核心,其他参数辅助,按照这一思路,就实现了现在的一行创建VBO的功能。
CSharpGL已经有点深度,所以笔记很难写出让人直接就能眼前一亮的感觉了。
目前CSharpGL中已经涵盖了我所知的所有OpenGL知识点。下一步就是精心读书,继续深挖。