拾取是什么
我们玩游戏中,选中目标啊,鼠标按下按钮之类的。这些都属于图象拾取范畴。
对于UI来说,因为是2D,很简单,只需要根据鼠标坐标来找出最上层的UI就可以了。
对于3D空间来说,因为透视原理,就不能简单的根据鼠标坐标来判断了。
3D拾取的原理
- 根据鼠标所点的位置,生成一根射线,射线与3D空间中物体相交,选取深度最小的(离摄像机最近的)作为拾取目标
- 读取鼠标坐标
- 根据屏幕长宽比来计算出拾取射线(此时射线已经在观察空间了)
- 把拾取射线从观察空间转到世界空间,再转到检测目标的模型空间
- 判断该射线是否与该目标的AABB盒相交
- 如果相交,检测是否与该目标的某个面相交
原先代码建议改造
稍微修改了下以前的代码。
- 添加一个透明混合渲染(用于渲染出被选中目标的轮廓)
1)VS中,顶点沿着法线方向做一个偏移
2)PS中,设置一个带透明度的颜色
3)把被选中的目标采用这个PSO额外进行一次绘制 - 从wndProc中接收鼠标移动的消息(存储鼠标在游戏窗口的坐标)
1)GameInput没有直接做这个的支持。自己计算比较麻烦,不如直接接受这个消息。
2)未来可能要修改GameInput代码,对一些常用的要做到方便的支持。目前的接口不是很人性化。。。 - 因为升级到了C++17,所以调整了FileUtility的部分代码
拾取代码解释
在这里没有采用龙书的例子。我依旧是跟16章一样,渲染很多的skull,然后选中的skull高亮显示。
很多代码MiniEngine中并没有封装,所以直接调用了dx中的API,未来会稍微封装一下。
void GameApp::checkPick()
{
// 鼠标按下一次触发一次
if (!GameInput::IsFirstPressed(GameInput::kMouse0))
return;
auto [x, y] = GameInput::GetCurPos();
// 获取长宽的比例
float HCot = m_Camera.GetProjMatrix().GetX().GetX();
float VCot = m_Camera.GetProjMatrix().GetY().GetY();
float vx = (+2.0f * x / Graphics::g_DisplayWidth - 1.0f) / HCot;
float vy = (-2.0f * y / Graphics::g_DisplayHeight + 1.0f) / VCot;
// 计算射线向量,此时射线已经在观察空间(view)
Math::Vector4 rayOriginBase = { 0.0f, 0.0f, 0.0f, 1.0f };
Math::Vector4 rayDirBase = { vx, vy, 1.0f, 0.0f };
// 计算世界转view的逆矩阵
auto inView = Math::Invert(m_Camera.GetViewMatrix());
// 遍历场景中的物体
float tmin = FLT_MAX;
for (auto& e : m_vecAll)
{
e->selectedCount = 0;
// 渲染目标的AABB盒
BoundingBox bounds;
XMStoreFloat3(&bounds.Center, 0.5f * (e->vMin + e->vMax));
XMStoreFloat3(&bounds.Extents, 0.5f * (e->vMax - e->vMin));
// 只检查经过视锥体剪裁后的渲染目标即可
for (int idx = 0; idx < e->visibileCount; ++idx)
{
auto& item = e->vObjsData[e->vDrawObjs[idx].x];
// 取出该渲染目标的实际转换矩阵的逆矩阵
auto inObjWorld = Math::Invert(Math::Transpose(item.World));
// 注意这里的乘法是反向的(待以后修改)
auto inViewWorld = inObjWorld * inView;
// 把射线转到渲染目标的模型坐标系(待以后封装下列API)
auto rayOrigin = Math::Vector4(XMVector3TransformCoord(rayOriginBase, inViewWorld));
auto rayDir = Math::Vector4(XMVector3TransformNormal(rayDirBase, inViewWorld));
// Make the ray direction unit length for the intersection tests.
rayDir = Math::Vector4(XMVector3Normalize(rayDir));
// 先检测射线是否相交于物体的AABB盒
float tTemp = 0.0f;
if (bounds.Intersects(rayOrigin, rayDir, tTemp))
{
// 如果已经有相交的渲染目标,则判断AABB盒的深度
if (tTemp > tmin) continue;
auto& vertices = e->geo->vecVertex;
auto& indices = e->geo->vecIndex;
UINT triCount = e->IndexCount / 3;
// 依次判断三角形面
for (UINT i = 0; i < triCount; ++i)
{
// Indices for this triangle.
UINT i0 = indices[i * 3 + 0];
UINT i1 = indices[i * 3 + 1];
UINT i2 = indices[i * 3 + 2];
// Vertices for this triangle.
XMVECTOR v0 = XMLoadFloat3(&vertices[i0].Pos);
XMVECTOR v1 = XMLoadFloat3(&vertices[i1].Pos);
XMVECTOR v2 = XMLoadFloat3(&vertices[i2].Pos);
float t = 0.0f;
if (TriangleTests::Intersects(rayOrigin, rayDir, v0, v1, v2, t))
{
// 命中任意面即认为命中了该渲染目标,记录深度,继续判断下一个渲染目标
tmin = tTemp;
e->selectedCount = 1;
e->vDrawOutLineObjs[0].x = e->vDrawObjs[idx].x;
break;
}
}
}
}
}
}
debug模式下“跳帧”?
在debug模式下。边移动边选择目标,有时会发现,摄像机的行动速度突然蹿了一下。
这个是由于debug效率很低,选中这个计算量很大,会导致update有些慢,而CameraController中的操作是跟deltaTime强相关的,所以会有“跳帧”的现象。
这个CameraController的设计太反知觉了,所有按键啥的都在update中用奇怪的操作处理。。
后期想想怎么修改比较好。暂时先这样