鼠标拾取
Last updated
Last updated
可以用物理或者pixel方法。
对OpenGL有glReadBuffer方法。
对dx11可以参考:
https://stackoverflow.com/questions/13479259/read-pixel-data-from-render-target-in-d3d11
https://www.gamedev.net/forums/topic/594722-reading-one-pixel-from-texture-to-cpu-in-dx11/
对dx来说,我们必须弄一张 D3D11_USAGE_STAGING 格式的纹理,然后用 ID3D11DeviceContext::Map() 方法。因为只有这种格式cpu才可读:
有几个缺陷:
必须增加一个绘制id的pass,不过可以用mrt的手段,例如对于glsl可以指定两个输出(下面的用于绘制id),因此我认为问题不大:
强行增加回读阶段。我们知道gpu一般落后cpu两三帧,强行从gpu搬数据回读给cpu是消耗巨大的。对应ogl有方法glReadPixels
优点是简单方便,并且绝对准确。但是因为上面的原因效率低下,尤其是顶点缓冲区可能会影响最终发布(前两点可以用宏轻易绕开),因此目前几乎所有引擎都是做的物理的拾取方法。
物理方法一般是使用射线检测,一般做法是和物理引擎绑定起来。但是我还没决定allin physx or bullet,因此打算换用其他的库。这里我选择的是 embree 这个库,对于embree而言,要想对一个geometry使用transform,必须要用instance才行。逻辑是把一个 geometry 的数据(这里我选择的是三角面数据)RTCGeometry geom = rtcNewGeometry(gEmbreeDevice, RTC_GEOMETRY_TYPE_TRIANGLE)
这个geom需要绑定到一个他专门的 instance scene 中,同时再用 rtcNewGeometry(gEmbreeDevice, RTC_GEOMETRY_TYPE_INSTANCE)
来创建一个 instance 的geom,这个instance的geom需要和全局的scene绑定,并且调用 rtcSetGeometryInstancedScene 方法和 instance scene 绑定起来。
因此逻辑是对每个mesh可以弄个instance scene,实例化优化的时候就也要在这个 instance scene 里头创建多个这样的 mesh。
最后射线检测的时候记得要 rayhit.ray.mask = -1; 才行。
射线检测的原点即摄像机位置,射线方向我们可以根据鼠标点击在屏幕空间的位置,在ndc空间构造一个向量,然后通过逆变换反推出世界空间的向量当作射线方向,可以参考:
顶点缓冲区由于带了id的信息,必须每帧更新,也就导致必须每帧从cpu端重传(因为id是从cpu端传给gpu端的)。这就导致对于dx原本可以用default的缓冲区(至多用UpdateSubresource更新)现在变成不得不用dynamic每次map unmap来更新了。 来自https://directx11.tech/#/part1/02