vulkanscenegraph显示倾斜模型(5.4)-相机操纵器

前言

在VSG(Vulkan Scene Graph)中,系统支持用户通过鼠标或触摸输入与三维场景进行交互,从而动态控制相机的位置和姿态,实现与三维场景的交互。VSG提供了多种相机操纵器,其中Trackball是一种常见的相机操作器,模拟了一个虚拟的轨迹球,用户可拖动鼠标来旋转、平移和缩放场景。机操纵器的本质是通过用户交互动态修改视图矩阵,从而改变模型在视口中的显示效果。本章探讨对事件的封装vsg::UIEvent,以此作为基础,将深入探讨vsg::trackball的实现原理,重点剖析旋转、平移和缩放三大操作的内部逻辑。


目录

  • 1 vsg::UIEvent的封装
  • 2 vsg::Trackball实现原理

本章为参照用例(vulkanscenegraph显示倾斜模型-CSDN博客)中的如下代码进行深入探讨。

cpp 复制代码
		vsg_viewer->addEventHandler(vsg::Trackball::create(vsg_camera));

1 vsg::Event的封装

vsg::UIEvent继承自vsg::Object,其下共有三种类型的子类:vsg::FrameEvent、vsg::TerminateEvent和vsg::WindowEvent,分别对应帧循环消息、程序或渲染终止消息以及窗口事件。

在5.1章中,介绍了窗口的创建,在windows系统下,创建的窗口对象为vsg::Win32_Window。vsg::Window_Window通过函数handleWin32Messages处理系统消息,并将消息封装为vsg::UIEvent对象。

handleWin32Messages函数在vsg::Window_Window对象构造的过程中通过外部的函数Win32WindowProc与WNDCLASSEX::lpfnWndProc绑定,如下代码所示:

cpp 复制代码
    LRESULT CALLBACK Win32WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        Win32_Window* win = reinterpret_cast<Win32_Window*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
        if (win != nullptr)
        {
#if 1
            // return if vsg::Win32_Window is able to handle it, otherwise fallback to the ::DefWindowProc(..) below
            if (win->handleWin32Messages(msg, wParam, lParam)) return 0;
#else
            win->handleWin32Messages(msg, wParam, lParam);
#endif
        }
        return ::DefWindowProc(hwnd, msg, wParam, lParam);
    }
cpp 复制代码
        WNDCLASSEX wc;
        wc.cbSize = sizeof(WNDCLASSEX);
        wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
        wc.lpfnWndProc = Win32WindowProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = ::GetModuleHandle(NULL);
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = 0;
        wc.lpszMenuName = 0;
        wc.lpszClassName = windowClass.c_str();
        wc.hIconSm = 0;

在vsg的渲染帧循环中(后续章节会深入探究),调用vsg::Window_Window对象的pollEvents函数取出所有的事件,并在视景器vsg::Viewer的handleEvents函数中处理所有事件。

cpp 复制代码
void Viewer::handleEvents()
{
    CPU_INSTRUMENTATION_L1_NC(instrumentation, "Viewer handle events", COLOR_UPDATE);

    for (auto& vsg_event : _events)
    {
        for (auto& handler : _eventHandlers)
        {
            vsg_event->accept(*handler);
        }
    }
}

2 vsg::Trackball实现原理

vsg::Trackball继承自vsg::Visitor,通过重写apply(ButtonPressEvent& buttonPress)、apply(ButtonReleaseEvent& buttonRelease)、apply(MoveEvent& moveEvent)函数,实现对通过鼠标交互实现旋转、平移和缩放三大操作的处理。默认鼠标中键平移、鼠标滚轮缩放、鼠标左键拖动实现旋转,本章重点探讨旋转操作。

cpp 复制代码
        _updateMode = ROTATE;

        moveEvent.handled = true;

        dvec3 xp = cross(normalize(control_tbc), normalize(prev_tbc));
        double xp_len = length(xp);
        if (xp_len > 0.0)
        {
            _rotateAngle = asin(xp_len);
            _rotateAxis = xp / xp_len;

            rotate(_rotateAngle * scale, _rotateAxis);
        }
        else
        {
            _rotateAngle = 0.0;
        }

在Trackball::apply(MoveEvent& moveEvent)方法中,场景旋转的具体实现如下:prev_tbc表示鼠标左键点击时(即旋转操作开始时)对应的TBC(Trackball Coordinate)坐标,而control_tbc则表示当前鼠标位置对应的TBC坐标。通过计算这两个坐标之间的公垂线xp,将其作为旋转轴,同时以公垂线的长度作为旋转角度,从而实现场景的旋转操作。

其中有鼠标所在屏幕坐标计算得到TBC坐标的代码如下,其中ndc函数为取值范围为0,1的归一化屏幕坐标系。

cpp 复制代码
dvec3 Trackball::tbc(const PointerEvent& event)
{
    dvec2 v = ndc(event);

    double l = length(v);
    if (l < 1.0f)
    {
        double h = 0.5 + cos(l * PI) * 0.5;
        return dvec3(v.x, -v.y, h);
    }
    else
    {
        return dvec3(v.x, -v.y, 0.0);
    }
}

旋转函数rotate具体实现如下:

cpp 复制代码
void Trackball::rotate(double angle, const dvec3& axis)
{
    dmat4 rotation = vsg::rotate(angle, axis);
    dmat4 lv = lookAt(_lookAt->eye, _lookAt->center, _lookAt->up);
    dvec3 centerEyeSpace = (lv * _lookAt->center);

    dmat4 matrix = inverse(lv) * translate(centerEyeSpace) * rotation * translate(-centerEyeSpace) * lv;

    _lookAt->up = normalize(matrix * (_lookAt->eye + _lookAt->up) - matrix * _lookAt->eye);
    _lookAt->center = matrix * _lookAt->center;
    _lookAt->eye = matrix * _lookAt->eye;

    clampToGlobe();
}

通过给定的旋转角度(angle)和旋转轴(axis)对相机上方(up)、观察点(center)、相机点(eye),实现视图矩阵的更新。

文末:本章在上一篇文章的基础上,进一步深入相机操作器,机操纵器的本质是通过用户交互动态修改视图矩阵,从而改变模型在视口中的显示效果。重点分析了vsg中针对事件vsg::UIEvent的封装、vsg::Trackball中轨迹球旋转的具体实现。下章将分析视景器准备过程中的CommandGraph的创建过程。

相关推荐
玖玥拾7 小时前
C/C++ 基础笔记(十四)多态与模板编程
c语言·c++·多态·模板
Roann_seo%7 小时前
C++文件操作完全指南:从文本读写到二进制文件处理
开发语言·c++
坚果派·白晓明8 小时前
【鸿蒙PC】SDL3 适配:AtomCode + Skills 快速集成 NAPI 测试工具
c++·华为·ai编程·harmonyos·atomcode
凡人叶枫9 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
凡人叶枫10 小时前
Effective C++ 条款16:成对使用 new 和 delete 时要采取相同形式
开发语言·c++·effective c++
不吃土豆的马铃薯11 小时前
C++ 高性能网络缓冲区 Buffer 源码解析
linux·服务器·开发语言·网络·c++
.千余11 小时前
【C++】C++继承入门(下):友元、静态成员与菱形继承的底层逻辑
开发语言·c++·笔记·学习·其他
初中就开始混世的大魔王11 小时前
6 Fast DDS-传输层
开发语言·c++·中间件·信息与通信
代码中介商14 小时前
C++ 智能指针完全指南(三):weak_ptr 与循环引用
开发语言·c++
BestOrNothing_201514 小时前
ROS2 C++ 小车控制完整实战(二):自定义 msg 消息发布与订阅保姆级教程
c++·ros2·subscriber·publisher·msg·topic通信·自定义接口