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); )进行渲染:

  1. uint[] index = { , , , , , , , , , }
 

这个特殊索引的特点就是(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方式渲染,索引内容如上图如下:

  1. index = { , , , 0, 2, 3, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , };
 

此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的索引。

  1. index = { , , , , , 3, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , };

只需遍历一下就会发现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 = ; i < length; i += )
  11. {
  12. if (array[i] == lastVertexID)
  13. {
  14. var item = new RecognizedPrimitiveIndex(lastVertexID);
  15. item.IndexIDList.Add(array[i - ]);
  16. item.IndexIDList.Add(array[i - ]);
  17. item.IndexIDList.Add(array[i - ]);
  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. }

TrianglesRecognizer

lastIndexIDList -> lastIndexID

这个例子里,只有一个index[5]=3。实际上可能会有多个index[i]=索引值

所以要想办法从这些候选图元中找到真正拾取到的那个。

那么,什么时候会出现多个候选图元?就是这几个图元共享最后一个顶点的时候。例如下面的例子:在鼠标所在位置执行拾取时,会找到[0 1 3]、[0 2 3]和[1 2 3]这三组lastIndexID。

那么如何分辨出我们拾取到的是[0 1 3]而不是另2个?

我想到的方法是,将共享点前移,然后重新渲染、拾取。在这个例子里,就是把[0 1 3]和[0 2 3]变成[3 0 1]和[3 0 2],然后渲染[3 0 1 3 0 2]这个小小的index buffer(即仅渲染这2个图元)。这样是能够拾取到[3 0 1]的,这就排除了[3 0 2]。然后继续用同样的方法排除[1 2 3]。这就找到了[0 1 3]这个正确的目标。

  1. /// <summary>
  2. /// 在所有可能的图元(<see cref="lastVertexId"/>匹配)中,
  3. /// 逐个测试,找到最接近摄像机的那个图元,
  4. /// 返回此图元的最后一个索引在<see cref="indexBufferPtr"/>中的索引(位置)。
  5. /// </summary>
  6. /// <param name="lastIndexIdList"></param>
  7. /// <returns></returns>
  8. private RecognizedPrimitiveIndex GetLastIndexId(
  9. ICamera camera,
  10. List<RecognizedPrimitiveIndex> lastIndexIdList,
  11. int x, int y, int canvasWidth, int canvasHeight)
  12. {
  13. if (lastIndexIdList.Count == ) { throw new ArgumentException(); }
  14.  
  15. int current = ;
  16. foreach (var item in lastIndexIdList[].IndexIdList)
  17. {
  18. if (item == uint.MaxValue) { throw new Exception(); }
  19. }
  20. for (int i = ; i < lastIndexIdList.Count; i++)
  21. {
  22. foreach (var item in lastIndexIdList[i].IndexIdList)
  23. {
  24. if (item == uint.MaxValue) { throw new Exception(); }
  25. }
  26. OneIndexBufferPtr twoPrimitivesIndexBufferPtr;
  27. uint lastIndex0, lastIndex1;
  28. AssembleIndexBuffer(
  29. lastIndexIdList[current], lastIndexIdList[i], this.indexBufferPtr.Mode,
  30. out twoPrimitivesIndexBufferPtr, out lastIndex0, out lastIndex1);
  31. uint pickedIndex = Pick(camera, twoPrimitivesIndexBufferPtr, x, y, canvasWidth, canvasHeight);
  32. if (pickedIndex == lastIndex1)
  33. { current = i; }
  34. else if (pickedIndex == lastIndex0)
  35. { /* nothing to do */}
  36. else if (pickedIndex == uint.MaxValue)
  37. { /* nothing to do */}
  38. else
  39. { throw new Exception("This should not happen!"); }
  40. }
  41.  
  42. return lastIndexIdList[current];
  43. }

GetLastIndexId

lastIndexID -> PickedGeometry

现在得到了图元的所有顶点在position buffer中的索引(上面的例子中,是[0 1 3]),只需一步就可以找到顶点了。(上面的例子中,是position[ index[0] ],position[ index[1] ],position[ index[3] ])

  1. private PickedGeometry GetGeometry(RecognizedPrimitiveIndex lastIndexId, uint stageVertexId)
  2. {
  3. var pickedGeometry = new PickedGeometry();
  4. pickedGeometry.GeometryType = this.indexBufferPtr.Mode.ToPrimitiveMode().ToGeometryType();
  5. pickedGeometry.StageVertexId = stageVertexId;
  6. pickedGeometry.From = this;
  7. pickedGeometry.Indexes = lastIndexId.IndexIdList.ToArray();
  8. GL.BindBuffer(BufferTarget.ArrayBuffer, this.positionBufferPtr.BufferId);
  9. IntPtr pointer = GL.MapBuffer(BufferTarget.ArrayBuffer, MapBufferAccess.ReadOnly);
  10. unsafe
  11. {
  12. var array = (vec3*)pointer.ToPointer();
  13. List<vec3> list = new List<vec3>();
  14. for (int i = ; i < lastIndexId.IndexIdList.Count; i++)
  15. {
  16. list.Add(array[lastIndexId.IndexIdList[i]]);
  17. }
  18. pickedGeometry.Positions = list.ToArray();
  19. }
  20. GL.UnmapBuffer(BufferTarget.ArrayBuffer);
  21. GL.BindBuffer(BufferTarget.ArrayBuffer, );
  22.  
  23. return pickedGeometry;
  24. }

GetGeometry

测试用例

ZeroIndexBuffer

这个情况属于早就解决了的,可以在(CSharpGL(17)重构CSharpGL)中查看。

OneIndexBuffer

Cube

下图中的Cube模型就可以用来测试OneIndexBuffer的拾取功能。

下面12个测试用例测试了拾取CubeModel的12个三角形的情况。结果显示完全符合对Cube的定义。

最后一个面在背面,所以需要旋转过来。

Sphere

当然,Cube是不足以完全测试OneIndexBuffer的拾取的。因为Cube里不存在共享最后一个顶点的情况。

Sphere里就有。

Teapot

Teapot的顶点组织方式我没有查看,权且充个数吧。

2016-04-26

为了严格测试OneIndexBuffer时存在“多个图元共享同一个最后的顶点”的情况,我制作了下面这个四边形模型。这验证了下图的情况。

Tetrahedron

根据上图,我设计了这样的模型数据:

首先看一下这个四边形的结构。通过设置GLSwitch里的PolygonModeSwtich为Lines,就可以看到这确实是个四边形。

(白色部分是geometry shader制造的法线,不必理会)

然后恢复到Filled模式下开始测试。下图中右边标明了各个顶点的索引(白色的0 1 2 3)。

可以看到这正是本文示例中描述的情况。结果完全符合预期。

然后我们在其他位置都试试看。

其余位置就不贴图了。

总结

解决拾取问题的过程也是整理ModernRenderer的过程。由于两种渲染方式的巨大差异,我设计了对应的ModernRenderer(即ZeroIndexModernRenderer和OneIndexModernRenderer)。再配合工厂模式,既封装了细节,实现了功能,又易于使用。

原CSharpGL的其他功能(UI、3ds解析器、TTF2Bmp、CSSL等),我将逐步加入新CSharpGL。

欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL

CSharpGL(18)分别处理glDrawArrays()和glDrawElements()两种方式下的拾取(ColorCodedPicking)的更多相关文章

  1. 多线程实现Thread.Start()与ThreadPool.QueueUserWorkItem两种方式对比

    Thread.Start(),ThreadPool.QueueUserWorkItem都是在实现多线程并行编程时常用的方法.两种方式有何异同点,而又该如何取舍? 写一个Demo,分别用两种方式实现.观 ...

  2. System.Web.Http.Cors配置跨域访问的两种方式

    System.Web.Http.Cors配置跨域访问的两种方式 使用System.Web.Http.Cors配置跨域访问,众多大神已经发布了很多文章,我就不在详细描述了,作为小白我只说一下自己的使用心 ...

  3. java中实现同步的两种方式:syschronized和lock的区别和联系

    Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...

  4. Code First02---CodeFirst配置实体与数据库映射的两种方式

    Code First有两种配置数据库映射的方式,一种是使用数据属性DataAnnotation,另一种是Fluent API. 这两种方式分别是什么呢?下面进行一一解释: DataAnnotation ...

  5. 不停止MySQL服务增加从库的两种方式

    不停止MySQL服务增加从库的两种方式 转载自:http://lizhenliang.blog.51cto.com/7876557/1669829 现在生产环境MySQL数据库是一主一从,由于业务量访 ...

  6. jquery ajax提交表单数据的两种方式

    http://www.kwstu.com/ArticleView/kwstu_201331316441313 貌似AJAX越来越火了,作为一个WEB程序开发者要是不会这个感觉就要落伍,甚至有可能在求职 ...

  7. JSP连接数据库的两种方式:Jdbc-Odbc桥和Jdbc直连(转)

    学JSP的同学都要知道怎么连数据库,网上的示例各有各的做法,弄得都不知道用谁的好.其实方法千变万化,本质上就两种:Jdbc-Odbc桥和Jdbc直连. 下面先以MySQL为例说说这两种方式各是怎么连的 ...

  8. strus2中获取表单数据 两种方式 属性驱动 和模型驱动

    strus2中获取表单数据 两种方式 属性驱动 和模型驱动 属性驱动 /** * 当前请求的action在栈顶,ss是栈顶的元素,所以可以利用setValue方法赋值 * 如果一个属性在对象栈,在页面 ...

  9. 云服务器 ECS Linux 服务器修改时区的两种方式

    在云服务器 ECS Linux 系统中,以 Centos6.5 为例,可以通过如下两种方式,修改系统时区: 可以使用命令 tzselect,修改时区.操作示例: [root@localhost ~]# ...

随机推荐

  1. 闰秒导致MySQL服务器的CPU sys过高

    今天,有个哥们碰到一个问题,他有一个从库,只要是启动MySQL,CPU使用率就非常高,其中sys占比也比较高,具体可见下图. 注意:他的生产环境是物理机,单个CPU,4个Core. 于是,他抓取了CP ...

  2. 06.SQLServer性能优化之---数据库级日记监控

    汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 之前说了一下数据库怎么发邮件:http://www.cnblogs.com/duniti ...

  3. 红黑树——算法导论(15)

    1. 什么是红黑树 (1) 简介     上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...

  4. inline-block元素间距问题的几种解决方案

    不知道大家有没有碰到过设置了display:inline-block;的几个相邻元素之间有几px间距的问题,这里提供几种简单实用的解决方法,希望能够帮到大家!   方法1. 将<li>标签 ...

  5. .Net语言 APP开发平台——Smobiler学习日志:手机应用的TextTabBar快速实现方式

    参考页面: http://www.yuanjiaocheng.net/webapi/create-crud-api-1-put.html http://www.yuanjiaocheng.net/we ...

  6. Kooboo CMS技术文档之四:Kooboo CMS的站点组成部分

    Kooboo CMS本着功能独立分离的原则,将站点分为三部分组成:用户管理,站点管理和内容数据库管理.各个功能之间既可独立使用,也可以容易组成在一起形成一个完整的系统. 用户管理 管理整个系统内的用户 ...

  7. CSS margin详解

    以下的分享是本人最近几天学习了margin知识后,大有启发,感觉以前对margin的了解简直太浅薄.所以写成以下文章,一是供自己整理思路:二是把知识分享出来,避免各位对margin属性的误解.内容可能 ...

  8. 如何区别char与varchar?

    1.varchar与char两个数据类型用于存储字符串长度小于255的字符,MySQL5.0之前是varchar支持最大255.比如向一个长度为40个字符的字段中输入一个为10个字符的数据.使用var ...

  9. IIS8 使用FastCGI配置PHP环境支持 过程详解

    平时帮朋友们配置过一些PHP环境的服务器,但是一直使用的都是Apache HTTP+PHP,今天呢,我吧IIS+PHP配置方式给大家发一下下~呵呵. 在这里,我使用的是FastCGI模块映射的方式配置 ...

  10. windows 7(32/64位)GHO安装指南(U盘引导篇)~

    上一篇我们说了怎么制作U盘启动盘,那么这一篇让我们来看看如何进行正确的U盘引导启动. 现在的个人计算机一般分为台式机和笔记本,由于各厂商的喜好不同(开玩笑的啦),所以对于主板的BIOS设置各所不同.进 ...