开始

总的来说,OpenGL应用开发者会遇到为如下三种数据创建Vertex Buffer Object的情形:

  1. 任意一个struct类型T data;

  2. 任意一个元素类型为struct的数组T[] array;

  3. 任意一个非托管数组UnmanagedArray<T> array;

而可创建的Vertex Buffer Object也分为如下的类别:

  1. 描述顶点属性(位置、颜色、法线等)的VertexBuffer;

  2. 描述索引的IndexBuffer;

  3. 描述其他自定义内容的各种Buffer;

本文介绍用C#如何实现上述功能。

回到顶部(go to top)

非托管数组->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         }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

回到顶部(go to top)

非托管数组->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的情形了。

回到顶部(go to top)

非托管数组->自定义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         }

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

回到顶部(go to top)

如何使用

实现了上面那些看起来比较啰嗦的功能,现在来看看使用的时候是什么情形。

 -> 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);

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

回到顶部(go to top)

总结

业务数据是核心,其他参数辅助,按照这一思路,就实现了现在的一行创建VBO的功能。

CSharpGL已经有点深度,所以笔记很难写出让人直接就能眼前一亮的感觉了。

目前CSharpGL中已经涵盖了我所知的所有OpenGL知识点。下一步就是精心读书,继续深挖。

 


延伸阅读

告别“老顽固”-Java培训,做最负责任的教育,学习改变命运,软件学习,再就业,大学生如何就业,帮大学生找到好工作,lphotoshop培训,电脑培训,电脑维修培训,移动软件开发培训,网站设计培训,网站建设培训告别“老顽固”