CSharpGL(37)创建和使用VBO的最佳方式

CSharpGL(37)创建和使用VBO的最佳方式

开始

近日在OpenGL红宝书上看到这样的讲解。

 

其核心意思是,在创建VBO时用

glBufferData(GL_ARRAY_BUFFER, length, NULL, GL_STATIC_DRAW);

来初始化buffer占用的内存(此内存在GPU端),其中的 NULL 表示并未初始化数据(即此buffer中的数据是随机值,类似在C语言中刚刚创建一个数组 int x[10]; 的情况)。

这样,就不必在CPU端申请大量内存了。接下来需要初始化buffer数据时,用

1     IntPtr pointer = buffer.MapBuffer(MapBufferAccess.WriteOnly);
2     var array = (vec3*)pointer.ToPointer();
3     for (int i = 0; i < length; i++)
4     {
5         array[i] = this.model.positions[i];
6     }
7     ptr.UnmapBuffer();

来直接操作GPU上的数据即可。

使用这种方式,省去了CPU端创建大规模非托管数组并上传到GPU的步骤,直接在GPU端创建了buffer,且所需代码更少,可以说是目前我找到的最佳方式。

因此我在CSharpGL中集成并使用了这种方式。

顶点属性buffer

CSharpGL中,用于描述顶点属性数组(Vertex Buffer Object)的类型是 VertexAttributeBufferPtr 。以前,我们可以通过如下的方式创建 VertexAttributeBufferPtr 。

 1     // 先在CPU端创建非托管数组VertexAttributeBuffer<vec3>对象
 2 using (var buffer = new VertexAttributeBuffer<vec3>(
 3         varNameInShader, VertexAttributeConfig.Vec3, BufferUsage.StaticDraw))
 4     {
 5         buffer.Alloc(this.model.positions.Length);// 在CPU端申请内存
 6         unsafe// 使用指针快速初始化
 7         {
 8             var array = (vec3*)buffer.Header.ToPointer();
 9             for (int i = 0; i < this.model.positions.Length; i++)
10             {
11                 array[i] = this.model.positions[i];
12             }
13         }
14         // 将CPU端的VertexAttributeBuffer<vec3>上传到GPU,获得我们需要的buffer对象。
15         positionBufferPtr = buffer.GetBufferPtr();
16     }// using结束,释放VertexAttributeBuffer<vec3>申请的非托管内存。
17     return positionBufferPtr;

可见,这种方式实际上是按下面的步骤创建VBO的。注意其中的data不是 NULL 。

1     uint[] buffers = new uint[1];
2     glGenBuffers(1, buffers);
3     const uint target = OpenGL.GL_ARRAY_BUFFER;
4     glBindBuffer(target, buffers[0]);
5     glBufferData(target, length, data, usage);
6     glBindBuffer(target, 0);

这种方式需要先在CPU端申请一块内存,初始化数据,之后才能上传到GPU端。现在我们用新的方式创建buffer,就不需要在CPU端申请内存了。

下面是创建buffer的方法。

 1     /// <summary>
 2     /// Creates a <see cref="VertexAttributeBufferPtr"/> object(actually an array) directly in server side(GPU) without initializing its value.
 3     /// </summary>
 4     /// <param name="elementType">element's type of this 'array'.</param>
 5     /// <param name="length">How many elements are there?</param>
 6     /// <param name="config">mapping to vertex shader's 'in' type.</param>
 7     /// <param name="usage"></param>
 8     /// <param name="varNameInVertexShader">mapping to vertex shader's 'in' name.</param>
 9     /// <param name="instanceDivisor"></param>
10     /// <param name="patchVertexes"></param>
11     /// <returns></returns>
12     public static VertexAttributeBufferPtr Create(Type elementType, int length, VertexAttributeConfig config, BufferUsage usage, string varNameInVertexShader, uint instanceDivisor = 0, int patchVertexes = 0)
13     {
14         if (!elementType.IsValueType) { throw new ArgumentException(string.Format("{0} must be a value type!", elementType)); }
15 
16         int byteLength = Marshal.SizeOf(elementType) * length;
17         uint[] buffers = new uint[1];
18         glGenBuffers(1, buffers);
19         const uint target = OpenGL.GL_ARRAY_BUFFER;
20         glBindBuffer(target, buffers[0]);
21         glBufferData(target, byteLength, IntPtr.Zero, (uint)usage);
22         glBindBuffer(target, 0);
23 
24         var bufferPtr = new VertexAttributeBufferPtr(
25             varNameInVertexShader, buffers[0], config, length, byteLength, instanceDivisor, patchVertexes);
26 
27         return bufferPtr;
28     }

使用这样的方式创建了buffer,之后在初始化数据时,就得用glMapBuffer/glUnmapBuffer了。

 1     int length = this.model.positions.Length;
 2     // 创建buffer
 3     VertexAttributeBufferPtr buffer = VertexAttributeBufferPtr.Create(typeof(vec3), length, VertexAttributeConfig.Vec3, BufferUsage.StaticDraw, varNameInShader);
 4     unsafe
 5     {
 6         IntPtr pointer = buffer.MapBuffer(MapBufferAccess.WriteOnly);// 获取指针
 7         var array = (vec3*)pointer.ToPointer();// 强制类型转换
 8         // 初始化数据
 9         for (int i = 0; i < length; i++)
10         {
11             array[i] = this.model.positions[i];
12         }
13         buffer.UnmapBuffer();// 完成,上传到GPU端。
14     }

对比来看,在内存上,新的方式省略了在CPU端申请非托管数组的开销;在代码上,新的方式也省去了对 VertexAttributeBuffer<vec3> 对象的使用( VertexAttributeBuffer<vec3> 类型完全可以不用了)。

索引buffer

OneIndexBufferPtr

对于使用 glDrawElements() 的索引对象,创建索引buffer就与上面雷同了。

 1     /// <summary>
 2     /// Creates a <see cref="OneIndexBufferPtr"/> object directly in server side(GPU) without initializing its value.
 3     /// </summary>
 4     /// <param name="byteLength"></param>
 5     /// <param name="usage"></param>
 6     /// <param name="mode"></param>
 7     /// <param name="type"></param>
 8     /// <param name="length"></param>
 9     /// <returns></returns>
10     public static OneIndexBufferPtr Create(int byteLength, BufferUsage usage, DrawMode mode, IndexElementType type, int length)
11     {
12         uint[] buffers = new uint[1];
13         glGenBuffers(1, buffers);
14         const uint target = OpenGL.GL_ELEMENT_ARRAY_BUFFER;
15         glBindBuffer(target, buffers[0]);
16         glBufferData(target, byteLength, IntPtr.Zero, (uint)usage);
17         glBindBuffer(target, 0);
18 
19         var bufferPtr = new OneIndexBufferPtr(
20              buffers[0], mode, type, length, byteLength);
21 
22         return bufferPtr;
23     }

ZeroIndexBufferPtr

对于使用 glDrawArrays() 的索引,则更简单。

 1     /// <summary>
 2     /// Creates a <see cref="ZeroIndexBufferPtr"/> object directly in server side(GPU) without initializing its value.
 3     /// </summary>
 4     /// <param name="mode"></param>
 5     /// <param name="firstVertex"></param>
 6     /// <param name="vertexCount"></param>
 7     /// <returns></returns>
 8     public static ZeroIndexBufferPtr Create(DrawMode mode, int firstVertex, int vertexCount)
 9     {
10         ZeroIndexBufferPtr bufferPtr = new ZeroIndexBufferPtr(
11          mode, firstVertex, vertexCount);
12 
13         return bufferPtr;
14     }

使用方法也与上面雷同。

独立buffer

对于AtomicCounterBuffer、PixelPackBuffer、PixelUnpackBuffer、ShaderStorageBuffer、TextureBuffer、UniformBuffer这些,我统称为IndependentBuffer。他们当然也可以用新方法创建和使用。

 1     /// <summary>
 2     /// Creates a sub-type of <see cref="IndependentBufferPtr"/> object directly in server side(GPU) without initializing its value.
 3     /// </summary>
 4     /// <param name="target"></param>
 5     /// <param name="byteLength"></param>
 6     /// <param name="usage"></param>
 7     /// <param name="length"></param>
 8     /// <returns></returns>
 9     public static IndependentBufferPtr Create(IndependentBufferTarget target, int byteLength, BufferUsage usage, int length)
10     {
11         uint bufferTarget = 0;
12         switch (target)
13         {
14             case IndependentBufferTarget.AtomicCounterBuffer:
15                 bufferTarget = OpenGL.GL_ATOMIC_COUNTER_BUFFER;
16                 break;
17 
18             case IndependentBufferTarget.PixelPackBuffer:
19                 bufferTarget = OpenGL.GL_PIXEL_PACK_BUFFER;
20                 break;
21 
22             case IndependentBufferTarget.PixelUnpackBuffer:
23                 bufferTarget = OpenGL.GL_PIXEL_UNPACK_BUFFER;
24                 break;
25 
26             case IndependentBufferTarget.ShaderStorageBuffer:
27                 bufferTarget = OpenGL.GL_SHADER_STORAGE_BUFFER;
28                 break;
29 
30             case IndependentBufferTarget.TextureBuffer:
31                 bufferTarget = OpenGL.GL_TEXTURE_BUFFER;
32                 break;
33 
34             case IndependentBufferTarget.UniformBuffer:
35                 bufferTarget = OpenGL.GL_UNIFORM_BUFFER;
36                 break;
37 
38             default:
39                 throw new NotImplementedException();
40         }
41 
42         uint[] buffers = new uint[1];
43         glGenBuffers(1, buffers);
44         glBindBuffer(bufferTarget, buffers[0]);
45         glBufferData(bufferTarget, byteLength, IntPtr.Zero, (uint)usage);
46         glBindBuffer(bufferTarget, 0);
47 
48         IndependentBufferPtr bufferPtr;
49         switch (target)
50         {
51             case IndependentBufferTarget.AtomicCounterBuffer:
52                 bufferPtr = new AtomicCounterBufferPtr(buffers[0], length, byteLength);
53                 break;
54 
55             case IndependentBufferTarget.PixelPackBuffer:
56                 bufferPtr = new PixelPackBufferPtr(buffers[0], length, byteLength);
57                 break;
58 
59             case IndependentBufferTarget.PixelUnpackBuffer:
60                 bufferPtr = new PixelUnpackBufferPtr(buffers[0], length, byteLength);
61                 break;
62 
63             case IndependentBufferTarget.ShaderStorageBuffer:
64                 bufferPtr = new ShaderStorageBufferPtr(buffers[0], length, byteLength);
65                 break;
66 
67             case IndependentBufferTarget.TextureBuffer:
68                 bufferPtr = new TextureBufferPtr(buffers[0], length, byteLength);
69                 break;
70 
71             case IndependentBufferTarget.UniformBuffer:
72                 bufferPtr = new UniformBufferPtr(buffers[0], length, byteLength);
73                 break;
74 
75             default:
76                 throw new NotImplementedException();
77         }
78 
79         return bufferPtr;
80     }
public static IndependentBufferPtr Create(IndependentBufferTarget target, int byteLength, BufferUsage usage, int length)

总结

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

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

原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-37-best-practice-creating-VBO.html