# 鼠标拾取

可以用物理或者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才可读：

<figure><img src="https://1433329390-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGUKK6YnYBR07zaFrfU1O%2Fuploads%2FhvquEx0InOIFkRhvqpNk%2Fimage.png?alt=media&#x26;token=6cd0632a-8f8b-45d1-8fa9-c796523e4e0b" alt=""><figcaption><p><a href="https://directx11.tech/#/part1/02">https://directx11.tech/#/part1/02</a></p></figcaption></figure>

## pixel方法

有几个缺陷：

1. 必须增加一个绘制id的pass，不过可以用mrt的手段，例如对于glsl可以指定两个输出（下面的用于绘制id），因此我认为问题不大：

   ```
   out vec4 FragColor;
   out int color2;
   ```
2. 强行增加回读阶段。我们知道gpu一般落后cpu两三帧，强行从gpu搬数据回读给cpu是消耗巨大的。对应ogl有方法glReadPixels
3. 顶点缓冲区由于带了id的信息，必须每帧更新，也就导致必须每帧从cpu端重传（因为id是从cpu端传给gpu端的）。这就导致对于dx原本可以用default的缓冲区（至多用UpdateSubresource更新）现在变成不得不用dynamic每次map unmap来更新了。\ <img src="https://1433329390-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGUKK6YnYBR07zaFrfU1O%2Fuploads%2FLk7aZ76OnAYtJHJq886I%2Fimage.png?alt=media&#x26;token=4df174fe-48e1-41c9-b35a-1e8a33f5c9f7" alt="" data-size="original">\
   来自<https://directx11.tech/#/part1/02>

优点是简单方便，并且绝对准确。但是因为上面的原因效率低下，尤其是顶点缓冲区可能会影响最终发布（前两点可以用宏轻易绕开），因此目前几乎所有引擎都是做的物理的拾取方法。

## 物理方法

物理方法一般是使用射线检测，一般做法是和物理引擎绑定起来。但是我还没决定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空间构造一个向量，然后通过逆变换反推出世界空间的向量当作射线方向，可以参考：

<https://antongerdelan.net/opengl/raycasting.html>
