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对象 2 using (var buffer = new VertexAttributeBuffer( 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 上传到GPU,获得我们需要的buffer对象。 15 positionBufferPtr = buffer.GetBufferPtr(); 16 }// using结束,释放VertexAttributeBuffer申请的非托管内存。 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 ///2 /// Creates a object(actually an array) directly in server side(GPU) without initializing its value. 3 /// 4 /// element's type of this 'array'. 5 /// How many elements are there? 6 /// mapping to vertex shader's 'in' type. 7 /// 8 /// mapping to vertex shader's 'in' name. 9 /// 10 /// 11 /// 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
索引buffer
OneIndexBufferPtr
对于使用 glDrawElements() 的索引对象,创建索引buffer就与上面雷同了。
1 ///2 /// Creates a object directly in server side(GPU) without initializing its value. 3 /// 4 /// 5 /// 6 /// 7 /// 8 /// 9 /// 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 ///2 /// Creates a object directly in server side(GPU) without initializing its value. 3 /// 4 /// 5 /// 6 /// 7 /// 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 ///public static IndependentBufferPtr Create(IndependentBufferTarget target, int byteLength, BufferUsage usage, int length)2 /// Creates a sub-type of object directly in server side(GPU) without initializing its value. 3 /// 4 /// 5 /// 6 /// 7 /// 8 /// 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 }
总结
现在CSharpGL已经有点深度,所以笔记很难写出让人直接就能眼前一亮的感觉了。
目前CSharpGL中已经涵盖了我所知的所有OpenGL知识点。下一步就是精心读书,继续深挖。