优缺点及其应用场景:

Framebuffer Picker :

  1. 消耗性能低;
  2. 对于射线碰撞,没有精度问题;
  3. 但是只能拾取Mesh这种粒度;

raycaster:

  1. 除了拾取Mesh还可以拾取图元,比如三角形;
  2. 依靠物理系统,消耗性能比较高;
  3. 会有精度问题,比如图纸放大缩小上百倍很难拾取(本人因放大缩小图纸感受到了精度问题);

射线检测raycaster:

射线包围盒是一种常用的方法,在 CPU 中进行拾取,性能较好,但是精度较差。当拾取频率不高时,可以考虑使用像素级精度的帧缓冲拾取Framebuffer Picker.射线投射涉及将射线投射到场景中并检查对象和射线之间的碰撞。这样做有一些缺点;如果您有多个具有许多三角形的复杂网格形状,则计算碰撞可能会很昂贵,可以使用包围盒来判断,但是因为包围盒比较简单,拾取边缘误差较大。此外,您需要编写算法或使用第三方库来遍历您的场景并计算碰撞。这为小型纯图形项目增加了不必要的工作量。

射线拾取的原理就是坐标变换,相信熟悉图形流水线的都非常清楚:
物体坐标系->(模型矩阵)世界坐标系->(视角矩阵)view/camera坐标系->(投影矩阵)裁剪坐标系->(透视除法)NDC坐标系->(窗口变换)窗口坐标系;
所以射线拾取就是反过来:窗口坐标系->NDC坐标系 ->世界坐标系.

射线数学表达: Ray = o+kv (o原点,)

/*** 射线拾取函数* 选中的网格模型变为半透明效果*/function ray() {var Sx = event.clientX;//鼠标单击位置横坐标var Sy = event.clientY;//鼠标单击位置纵坐标//屏幕坐标转标准设备坐标var x = ( Sx / window.innerWidth ) * 2 - 1;//标准设备横坐标var y = -( Sy / window.innerHeight ) * 2 + 1;//标准设备纵坐标var standardVector  = new THREE.Vector3(x, y, 0.5);//标准设备坐标//标准设备坐标转世界坐标var worldVector = standardVector.unproject(camera);//射线投射方向单位向量(worldVector坐标减相机位置坐标)var ray = worldVector.sub(camera.position).normalize();//创建射线投射器对象var raycaster = new THREE.Raycaster(camera.position, ray);//返回射线选中的对象var intersects = raycaster.intersectObjects([boxMesh,sphereMesh,cylinderMesh]);if (intersects.length > 0) {intersects[0].object.material.transparent = true;intersects[0].object.material.opacity = 0.6;}}

unproject是Vector3对象的方法,作用是把标准设备坐标转化为世界坐标,方法的参数是相机对象camera,该方法的使用可以参考Vector3对象的project方法, 这两个方法的作用都是进行坐标变换,只不过方向是反过来的。

拾取Mesh上的图元比如三角形或者顶点;

blender里面就是用来GPU与ray的拾取方式:

颜色/GPU 拾取(Framebuffer Picker)

在将场景渲染到屏幕上时,GPU已经在进行该mesh所有必要的计算,以确定每个像素应该具有什么颜色。深度测试会丢弃看不见的片元。当点击位于a(x,y)的像素,这个像素的颜色实际上是特定场景中的Object。这种技术的想法是为在渲染每个对象时通过推送常量为它们提供唯一标识符,然后在填充颜色缓冲区的同时将其渲染到额外的帧缓冲区目标 glFramebufferRenderbuffer。渲染完成后,将纹理读回主机,并使用鼠标坐标查找对象标识符。OpenGL 提供了一个 glReadPixels 函数它是利用颜色的6位16进制表示,以颜色作为ID,在后台渲染出纹理后,根据鼠标坐标下的纹理颜色,进行ID的查询进行拾取操作.。当然,你不想把它渲染到屏幕上。像素信息将在缓冲区中可用,但不会显示在屏幕上。该技术的问题在于,需要再次将场景渲染为图像(FBO的RTT)。但是,对象拾取仅在用户单击鼠标按钮时完成,因此该性能损失仅在该时间发生!在绘图中,这是带有几个对象的原始场景。每个对象都将指定一种唯一的颜色。threejs渲染的图像将如下所示:

有几种方法可以进行对象拾取。 如果有现有的物理引擎,则可以从鼠标位置投射光线。 不幸的是,我们小引擎还没有物理引擎,所以我将讨论另一种方法,即使用 GPU。 其实说白了缓冲区拾取就是用空间(多一份数据)换时间(拾取快),另外由于缓冲区拾取不需要遍历模型,所以模型是可以做合批的。

  • 准备好两份数据,一份渲染输出到屏幕,一份渲染到FBO,同时把每个物体的信息存起来。
  • 创建一个webglRenderTarget()(FBO,不直接输出到屏幕)。
  • 渲染FBO,通过获取到的颜色位换算回ID值,判断点击了那个物体。
  • 通过ID值获取到点击的物体的信息,在生成一个正方体套在点击物体外面,表示高亮。
  • 最后正常渲染场景,输出到颜色缓冲区(屏幕)。

Vulkan GPU拾取对象实现

GPU的picking优化降低显存消耗:

GPU的RTT Pick有一些小技巧,比如把RTT的尺寸设置为4个像素,降低渲染一帧的负担之类的,makeFrustum可以设置一下视窗的位置和尺寸,调整一下尺寸。相应的代码也得改一下,主要是像素位置要对应上。具体实现:就是调节framebuffer的尺寸,最后你要返回一个Image来解译ID。这个Image可以很小。我记得是2x2(至于为什么最小是22,因为在GPU中的帧缓存或者说纹理的数据结构就是平铺多维数组,就是俗称的分块,最小是22);回归正题,在设置RTT的时候,要设置一下Framebuffer object的大小,这个尺寸可以通过camera的投影矩阵来设置,RTT要attach一个纹理,这个纹理尺寸就是可以是2x2。然后通过设置makeFrustrum,来设置RTT Camera的透视投影为你想要的像素位置,然后将针对该像素左下2x2这个大小区域进行绘制。当然具体makeFrustrum的参数不是2x2,而是反算到你的NDC Space下的,大概是的。你绘制的场景不变,只是绘制的投影矩阵变了,拾取只关心鼠标周围的像素ID,不关心距离很远的那些像素,这样就不用消耗过多的显存。特别注意!是渲染的场景不变,不是渲染的窗口不变。渲染的窗口通过设置投影矩阵来聚焦到鼠标周围,然后绘制的纹理要设置到一个小尺寸下,绘制出来后,图像的00点就是你鼠标位置处的那个像素。如果你设置了100x100的纹理,你就会看到从鼠标位置开始,左下100x100那么大的图像。

这个思想在分屏渲染里用的很多实际上就是假设这一个屏幕是一个2x2的窗口了,另外用的是RTT技术而已。想象一下你现在有一个10x10个屏幕,然后你想渲染1000x1000这么大的图像。那么每个图像就是100x100。你怎么去渲染这单个图像呢?其实就是你每次移动就渲染其他新的,但是永远也不可能说能看完一整张,也不用看完一整张图!本人最近也在看GAMES104游戏引擎架构,当你要实现一个小引擎或者工作实操上有很多小trick,比如可以延迟查询来降低回传同步造成的CPU卡顿,比如交互操作用不着实时渲染查询,每一秒渲染一次也就够了,这就可以降低大量的数据传输卡顿了。读到这相信你能跟我一样理解什么事工业界什么事学术界的区别~

第三种方法~ 深度值拾取:

第三种方法前辈大佬说08年测绘院就有写过,看来我还是在玩泥巴哈哈哈哈~~~鼠标位置可以转换到gl_FragCoord.xy,深度值是FragCoord.z,这就是Projection后的[-1,1]区间坐标值(opengl是-1到1,而DX是0到1),然后invVP得到世界坐标的值。这就是一个普通的转换,如果是从gbuffer返回来,那就说的高级一点就是coordinate regeneration但是换汤不换药。

该方法的工作原理如下:沿鼠标射线获取整个深度范围,并将其存储为有限的固定大小。 我选择 DEPTH_ARRAY_SCALE 32用于演示目的,但实际上应该在 1000 左右。 在片段着色器中,我们将实体 ID 写入相应的深度桶 bucket中。为了创建存储bucket.,使用绑定到片元着色器的写到存储缓冲区。

#define DEPTH_ARRAY_SCALE 32layout(set=0, binding = 3) buffer writeonly s_Write_t
{uint data[DEPTH_ARRAY_SCALE];
} s_Write;

我们通过 push_constant 以 uniform 的形式将UNIQUE_ID和MOUSE_POS传递给片段着色器

layout(push_constant) uniform PushConsts
{vec2 MOUSE_POS;uint UNIQUE_ID;
} pushC;

然后在片段着色器中,我们得到顶点着色器使用 gl_FragCoord.z 计算的当前深度值。 这个值应该在 0 和 1 之间。我们通过将它乘以数组的长度 (DEPTH_ARRAY_SCALE) 来放大它。 这给了我们深度桶 bucket的索引。 如果我们当前着色的像素接近当前鼠标坐标,我们将唯一 ID 写入该索引位置。

// get the depth and scale it up by
// the total number of buckets in depth array
uint zIndex = uint(gl_FragCoord.z * DEPTH_ARRAY_SCALE);if( length( pushC.MOUSE_POS - gl_FragCoord.xy) < 1)
{s_Write.data[zIndex] = pushC.UNIQUE_ID;
}

以上是片元着色器~
在主机端,你可以做两件事之一,要么使用 HOST_VISIBLE 存储缓冲区,并保持它的持久映射。 或者,使用 DEVICE_LOCAL 存储缓冲区,并在写入片段后执行 bufferCopy。 我选择了前者,因为它更容易。
您现在在主机上拥有的是一个数组,其中数组中的每个索引都代表鼠标射线上的某个深度。 我们遍历数组并找到最接近的非零值。

auto * v = ... get mapped memory ...
auto u = static_cast<uint32_t*>(v);uint32_t SELECTED_OBJECT_ID = 0;for(size_t i=0;i<DEPTH_ARRAY_SCALE;i++)
{if( u[i] != 0){SELECTED_OBJECT_ID = u[i];break;}
}
// we have to zero out the memory each frame
std::memset(v, 0, DEPTH_ARRAY_SCALE * sizeof(uint32_t));

效果图如下:

总结:

打通了引擎Runtime和编辑器开发的桥梁,通过物体的拾取就可以挂载其他辅助的组件,例如Gizmos,进而编辑场景。或者通过脚本来调用raycast对场景的物体进行射线检测或者动画拾取。不会言拾取,必称射线检测,不同的方法有不同的适用范围,可以按需选择,甚至两者混用。

参考资料:

图形学中拾取的几种思路以及坑
Vulkan实现GPU拾取
渲染和编辑器之间的边界——第 2 部分:拾取
Vulkan 鼠标拾取使用storage buffers
threejs中的拾取交互

图形学的三种拾取实现与比较相关推荐

  1. 计算机图形学(三种画线算法)

    第二章:光栅图形学算法 1.光栅显示器:光栅扫描式图形显示器简称光栅显示器,是画点设备,可看作是一个点阵单元发生器,并可控制每个点阵单元的亮度 2.由来:随着光栅显示器的出现,为了在计算机上处理.显示 ...

  2. 计算机图形学画图形,计算机图形学(三种画线算法)(示例代码)

    第二章:光栅图形学算法 1.光栅显示器:光栅扫描式图形显示器简称光栅显示器,是画点设备,可看作是一个点阵单元发生器,并可控制每个点阵单元的亮度 2.由来:随着光栅显示器的出现,为了在计算机上处理.显示 ...

  3. VTK: 拾取方式的三种实现

    拾取操作是可视化应用程序中常见的一种功能.拾取主要是用于选择数据和Actor或者获取底层的数据值.在显示位置(以像素为坐标值)中拾取时,就会调用vtkAbstractPicker的Pick()方法.依 ...

  4. 独家 | 将时间信息编码用于机器学习模型的三种编码时间信息作为特征的三种方法...

    作者:Eryk Lewinson 翻译:汪桉旭 校对:zrx本文约4400字,建议阅读5分钟 本文研究了三种使用日期相关的信息如何创造有意义特征的方法. 标签:时间帧,机器学习,Python,技术演示 ...

  5. linux查看日志的几种命令,Linux查看日志三种命令(转载)

    第一种:查看实时变化的日志(比较吃内存) 最常用的: tail -f filename (默认最后10行,相当于增加参数 -n 10) Ctrl+c 是退出tail命令 其他情况: tail -n 2 ...

  6. robotstudio仿真搬运编程_敲黑板 | 机器人是怎么完成任务的?这三种编程方式的区别你造吗...

    9012年了,机器人对我们来说已经很熟悉.在许多领域,它都发挥着卓有成效的作用,那么,这些机器人究竟是怎么在各个领域完成任务的呢? 想知道这个问题的答案,我们首先要知道机器人编程. 机器人编程[rob ...

  7. 跟我一起学Redis之看完这篇比常人多会三种类型实战(又搞了几个小时)

    前言 对于Redis而言,很多小伙伴只关注其关键的五大基础类型:string.hash.list.set.sorted set(有序集合),其实还有三种特殊类型在很多应用场景也比较适合使用,分别是:b ...

  8. 三维坐标系带偏航角俯仰角_浅谈三维旋转的三种方法及差异

    概述 在3D图形学中,几何变换大致分为三种:平移变换(Translation).缩放变换(Scaling).旋转变换(Rotation),而其中又以旋转变换(Rotation)最为复杂,通常旋转变换( ...

  9. 3DMAX渲染AO(白膜)图的三种方法

    使用Mental Ray渲染AO 1. 我为这个演示制作了一个非常简单的场景.该场景包含一个茶壶.一个盒子和一个球体.我还应用了一些材质,并将渲染引擎设置为Mental Ray. 2. 我还在场景中添 ...

最新文章

  1. DevOps和容器:本地or云端,如何选择?
  2. Java虚拟机:类加载机制详解
  3. java如何设置圆角边框_巧妙实现带圆角的渐变边框
  4. python2中可以使用print()函数吗_在Python2.x中使用print()(函数版本)
  5. 2017-05-12-Linux文件操作
  6. python安装运行时提示不是内部或外部命令怎么办_如何解决cmd运行python提示不是内部命令...
  7. 常见8种机器学习算法总结
  8. vue 点击li 中的img 怎么不冒泡_Vue全解
  9. 写入缓存 还是 直接 写入 json 方便,
  10. 4.3 现在可用的客体类有哪些呢
  11. 深度克隆对象【前端每日一题-19】
  12. matlab相机标定
  13. 白日门传奇手游源码端
  14. CAD复制,如何自由复制CAD图形?
  15. 计算机英语四六级考试时间2015,2015年四级考试时间安排(官方版)
  16. Svn修改自己已经提交的备注
  17. 【VS消除警告】VS消除特定警告/安全函数警告C4996 strncpy unsafe……
  18. 使用ULIB+Altium Designer绘制元件原理图及封装
  19. 玄魂工作室--咪噜妹
  20. 大伙帮我一起来看看这个数据库的题目

热门文章

  1. python协程爬取斗鱼美女图片
  2. android里面通过scp上传文件
  3. Windows自带mstsc远程无法关机重启小技巧
  4. 在线BASE64加密解密、UrlEncode编码/解码、native/ascii在线转换工具 -toolfk程序员工具网
  5. 了解眩光与星芒,夜景灯光拍摄翻倍美
  6. 2019全国大众点评数据更新
  7. TeamViewer和向日葵远控软件的个人使用感觉
  8. domino 调用java_java调用domino
  9. Selenium-针对alert弹窗无法获取,弹出no such alert的解决方法
  10. 三十.什么是vm和vc?