CSharpGL(38)带初始数据创建Vertex Buffer Object的情形汇总
开始
总的来说,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 AtomicCounterBuffer 2 PixelPackBuffer 3 PixelUnpackBuffer 4 ShaderStorageBuffer 5 TextureBuffer 6 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 : struct 2 { 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知识点。下一步就是精心读书,继续深挖。