CSharpGL(18)分别处理glDrawArrays()和glDrawElements()两种方式下的拾取(ColorCodedPicking)
我在(Modern OpenGL用Shader拾取VBO内单一图元的思路和实现)记录了基于Color-Coded-Picking的拾取方法。
最近在整理CSharpGL时发现了一个问题:我只解决了用glDrawArrays();渲染时的拾取问题。如果是用glDrawElements();进行渲染,就会得到错误的图元。
本文就分别解决这两种情况下的拾取的问题。
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
两种Index Buffer
ZeroIndexBuffer
用 glDrawArrays(uint mode, int first, int count); 进行渲染时,本质上是用这样一个(特殊索引+ glDrawElements(uint mode, int count, uint type, void* indices); )进行渲染:
uint[] index = { 0, 1, 2, 3, 4, 5, 6, 7, 8, … }
这个特殊索引的特点就是(i == index[i])且(index buffer的长度==position buffer的长度)。
所以我们可以把这个索引看做一个经过优化的VertexBufferObject(VBO)。优化的效果就是:此VBO占用的GPU内存空间(几乎)为零。所以我把这种索引buffer命名为ZeroIndexBuffer。
在之前的文章里,我拾取到了图元的最后一个顶点在position buffer里的索引值。由于index的特殊性质,position buffer前方(左侧)的连续几个顶点就属于拾取到的图元。所以glDrawArrays方式下的拾取问题就解决了。
像下面这个BigDipper的模型,是用glDrawArrays方式渲染的。其拾取功能完全正常。
OneIndexBuffer
我把用glDrawElements进行渲染的index buffer命名为OneIndexBuffer。(因为实在想不出合适的名字了,就模仿一下编译原理里的0型文法、1型文法的命名方式)
lastVertexID
为便于说明,以下面的模型为例:
此模型描述了一个立方体,每个面都由4个顶点组成,共24个顶点。其索引(index buffer)用GL_TRIANGLES方式渲染,索引内容如上图如下:
index = { 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23 };
此index buffer的长度为36个uint(36*sizeof(uint)个字节)。
这个模型的position buffer长度(24)不等于index buffer的长度(36)。
所以,继续用上面的拾取方式,只能拾取到图元的最后一个顶点(此例为三角形的第3个顶点)在position buffer中的索引值。
假设拾取到的是第二个三角形,如下图所示,那么拾取到的图元的最后一个顶点在position buffer的索引值就是3。(此图只渲染了前2个三角形)
如果像之前那样,连续向前(向左)取3个顶点,就会得到position[1],position[2],position[3]。但是,如图所见,正确的3个顶点应该是position[0],position[2],position[3]。
就是说,由于index buffer内容是任意的,导致描述一个图元的各个顶点在position buffer中并非连续排列。
lastVertexID -> lastIndexIDList
继续这个例子,现在已经找到了lastVertexID为3。为了找到这个三角形所有的顶点,我们先在index buffer里找到内容为3的索引。
index = { 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23 };
只需遍历一下就会发现index[5] = 3。
所以,拾取到的三角形的三个顶点就是position[ index[5 – 2] ],position[ index[5 – 1] ],position[ index[5 – 0] ]。(index buffer中描述同一个图元的索引值是紧挨着排列的)
PrimitiveRecognizer
这个例子里,要识别的是三角形。实际上可能会识别点(Points)、线段(Lines、LineStrip、LineLoop)、四边形(Quads、QuadStrip)、多边形(Polygon)。所以我需用一个简单的工厂来提供各种PrimitiveRecognizer。
用于识别三角形的TriangleRecognizer如下:
1 class TrianglesRecognizer : PrimitiveRecognizer 2 { 3 public override List<RecognizedPrimitiveIndex> Recognize( 4 uint lastVertexID, IntPtr pointer, int length) 5 { 6 var lastIndexIDList = new List<RecognizedPrimitiveIndex>(); 7 unsafe 8 { 9 var array = (uint*)pointer.ToPointer(); 10 for (uint i = 2; i < length; i += 3) 11 { 12 if (array[i] == lastVertexID) 13 { 14 var item = new RecognizedPrimitiveIndex(lastVertexID); 15 item.IndexIDList.Add(array[i - 2]); 16 item.IndexIDList.Add(array[i - 1]); 17 item.IndexIDList.Add(array[i - 0]); 18 lastIndexIDList.Add(item); 19 } 20 } 21 } 22 23 return lastIndexIDList; 24 } 25 } 26 27 class RecognizedPrimitiveIndex 28 { 29 public RecognizedPrimitiveIndex(uint lastIndexID, params uint[] indexIDs) 30 { 31 this.LastIndexID = lastIndexID; 32 this.IndexIDList = new List<uint>(); 33 this.IndexIDList.AddRange(indexIDs); 34 } 35 36 public uint LastIndexID { get; set; } 37 38 public List<uint> IndexIDList { get; set; } 39 }