d3d12龙书学习之MiniEngine的最小化实现(十三) 龙书第17章 拾取

拾取是什么

我们玩游戏中,选中目标啊,鼠标按下按钮之类的。这些都属于图象拾取范畴。

对于UI来说,因为是2D,很简单,只需要根据鼠标坐标来找出最上层的UI就可以了。
对于3D空间来说,因为透视原理,就不能简单的根据鼠标坐标来判断了。

3D拾取的原理

  1. 根据鼠标所点的位置,生成一根射线,射线与3D空间中物体相交,选取深度最小的(离摄像机最近的)作为拾取目标
  2. 读取鼠标坐标
  3. 根据屏幕长宽比来计算出拾取射线(此时射线已经在观察空间了)
  4. 把拾取射线从观察空间转到世界空间,再转到检测目标的模型空间
  5. 判断该射线是否与该目标的AABB盒相交
  6. 如果相交,检测是否与该目标的某个面相交

原先代码建议改造

稍微修改了下以前的代码。

  1. 添加一个透明混合渲染(用于渲染出被选中目标的轮廓)
    1)VS中,顶点沿着法线方向做一个偏移
    2)PS中,设置一个带透明度的颜色
    3)把被选中的目标采用这个PSO额外进行一次绘制
  2. 从wndProc中接收鼠标移动的消息(存储鼠标在游戏窗口的坐标)
    1)GameInput没有直接做这个的支持。自己计算比较麻烦,不如直接接受这个消息。
    2)未来可能要修改GameInput代码,对一些常用的要做到方便的支持。目前的接口不是很人性化。。。
  3. 因为升级到了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中用奇怪的操作处理。。
后期想想怎么修改比较好。暂时先这样

最终效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值