欢迎光临
我们一直在努力

记录:场景图形管理(一)

1. 世界坐标系->屏幕坐标系:模型变换(世界坐标系中的三维物体调整物体位置/姿态)->视图变换(调整相机位置/朝向)->投影变换(定义视锥体(透视/正交))->三维裁剪(裁掉视锥体外的物体)->视口变换(映射到屏幕指定区域)->屏幕坐标系中的图形显示;
(1)模型变换:osg::PositionAttitudeTransform直接设置位置、姿态(四元数)、osg::MatrixTransform通过4×4矩阵实现任意变换;
(2)裁剪变换:OSG默认使用6个裁剪平面(左右上下近远),对应视锥体边界,可以自定义裁剪面:
        ① OSG渲染流水线中,先进行视锥体裁剪裁掉视锥体外的所有内容,再自定义裁剪对”已通过视锥体的内容”做二次筛选;
        ② 两种裁剪方式:
                1. osg::ClipPlane,基于平面方程(Ax+By+Cz+D=0)在三维世界空间中裁剪,主要用途是实现模型剖切(比如看三维建筑内部)、局部区域隐藏(比如裁掉场景左侧的物体);
                        【ClipPlane的平面方程会自动被模型-视图矩阵变换,实际生效在视图坐标系,则相机移动后,裁剪平面会跟着相机视角走】

// 创建自定义裁剪平面:x=5(方程1.0*x + 0*y + 0*z -5=0),第一个参数是表示这是第几个自定义裁剪平面
osg::ref_ptr<osg::ClipPlane> clipPlane = new osg::ClipPlane(0, 1.0, 0.0, 0.0, -5.0);
// 启用裁剪平面(添加到场景状态集)
osg::StateSet* ss = root->getOrCreateStateSet();
ss->setAttributeAndModes(clipPlane, osg::StateAttribute::ON);

                2.  osg::Scissor,在视口变换后的窗口坐标系,只显示指定矩形区域内的内容,核心用途是分屏渲染(比如左半屏显示3D场景,右半屏显示UI)、小地图、局部预览;
(3)裁剪流水线和4个渲染阶段关联起来:

2. osg::Camera类
(1)是OSG中管理所有变换的核心类,继承自osg::Transform(负责模型-视图矩阵的变换)和osg::CullSettings(负责裁剪和视锥体设置),控制如何观察三维场景;
(2)还有其它功能:
        1. 清除背景色:setClearColor,设置渲染前清空屏幕的颜色;
        2. 清除缓存:控制深度缓冲、颜色缓冲等的清除行为;
        3. 多相机渲染:通过多个Camera实现多视角、分屏、后处理效果(如延迟渲染、屏幕空间反射);
        4. RTT,渲染到纹理,把Camera的渲染结果输出到纹理而非屏幕;

3. 从程序运行到画面显示,举例:创建一个800×600的窗口,显示3D游戏场景(主视角)+右上角150×100的小地图:
        1. 初始化Viewer:osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer,其中Viewer是整个渲染系统的“总指挥”,负责管理View、驱动渲染循环、处理全局事件(比如窗口关闭),如果不手动配置,Viewer会自动创建“默认View+默认GraphicsContext
+默认Window”;
        2. 创建View:定义渲染单元,需要填充场景数据和Camera,Viewer可以管理多个View(比如多窗口分屏);

osg::ref_ptr<osgViewer::View> view = new osgViewer::View;
viewer->addView(view); // 将View交给Viewer管理

        3. 加载场景数据:给View提供“渲染素材”,存储全局共享的场景数据,后续所有的Camera都基于这个场景渲染,View是场景数据的“唯一持有者”;

// 加载游戏场景(地形、玩家、建筑)
osg::ref_ptr<osg::Node> scene = osgDB::readNodeFile("game_scene.osgb");
view->setSceneData(scene); // 场景数据绑定到View,所有Camera共享

        4. 创建两个Camera:每个Camera定义“从哪看、怎么看、画到窗口的哪个位置”;主相机用view->getCarera()获取(View默认创建),从相机用addSlave()加入;两个Camera共享View的scene数据,但各自的ViewMatrix、ProjectionMatrix、Viewport完全独立;View负责管理所有Camera的生命周期和数据共享;【当窗口宽高比不是4"3时,默认的透视投影矩阵aspectRatio与窗口比例不匹配,会导致物体拉伸变形,解决方法是:获取窗口当前宽高比,重新设置透视投影矩阵的宽高比。】

// 相机1:主场景相机(全屏)
osg::Camera* mainCamera = view->getCamera(); // View默认自带一个主相机
mainCamera->setViewMatrixAsLookAt(
osg::Vec3(5,5,5), // 相机位置(玩家视角)
osg::Vec3(0,0,0), // 看向场景中心
osg::Vec3(0,0,1) // 头顶方向
);
mainCamera->setProjectionMatrixAsPerspective(30, 800.0/600.0, 0.1, 1000); // 透视投影
mainCamera->setViewport(0, 0, 800, 600); // 视口:整个窗口

// 相机2:小地图相机(右上角)
osg::ref_ptr<osg::Camera> miniMapCamera = new osg::Camera;
view->addSlave(miniMapCamera); // 把小地图相机加入View(从相机)
miniMapCamera->setViewMatrixAsLookAt(
osg::Vec3(0,0,50), // 相机位置(场景正上方)
osg::Vec3(0,0,0), // 看向场景中心
osg::Vec3(1,0,0) // 头顶方向
);
miniMapCamera->setProjectionMatrixAsOrtho2D(-50,50,-50,50); // 正交投影(小地图无近大远小)
miniMapCamera->setViewport(600, 500, 150, 100); // 视口:右上角

        5. 创建GraphicsContext和Window:GraphicsContext包含OpenGL上下文,是所有渲染指令的“执行环境”;Window是GraphicsContext绑定的物理显示区域,由OSG根据Traits自动创建(跨平台封装);GraphicsContext和Window是”绑定关系“,一个Window必须绑定一个GraphicsContext,没有它,Camera的渲染指令无法执行;

// 1. 配置窗口/上下文参数(跨平台)
osg::GraphicsContext::Traits* traits = new osg::GraphicsContext::Traits;
traits->x = 100; traits->y = 100; traits->width = 800; traits->height = 600;
traits->windowName = "游戏场景"; traits->doubleBuffer = true;

// 2. 创建GraphicsContext(渲染上下文=OpenGL上下文+帧缓冲区)
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits);

// 3. 此时gc内部已经自动创建了对应的操作系统窗口(Windows的HWND/Linux的XWindow)

         6. 将Camera绑定到GraphicsContext:告诉每个Camera”要在这个GraphicsContext里执行渲染指令,最终画到它绑定的Window上“;如果有多个Window,也可以给不同Camera绑定不同的GraphicsContext(比如主场景和小地图分两个窗口显示);

// 所有Camera都绑定到同一个GraphicsContext
mainCamera->setGraphicsContext(gc);
miniMapCamera->setGraphicsContext(gc);

        7. 启动渲染循环:viewer->run(),Viewer驱动所有组件工作,会触发以下子步骤(每帧都执行):
                ① 事件处理(用户交互):Windows接收操作系统事件(比如鼠标拖动、键盘按下),通过GraphicsContext转发给View;View根据事件类型处理(比如鼠标拖动->修改主相机的ViewMatrix->改变视角);如果需要调整窗口(比如用户缩放窗口),View会通过GUIActionAdapter发送”调整窗口大小“的请求,GraphicsContext再调用系统API;
                ② 遍历场景:View遍历自己的场景数据,收集所有需要渲染的几何体(三角形、纹理),这个过程对所有Camera共享,只执行一次,避免重复计算;
                        【这里执行一次只是在“更新遍历”阶段,而拣选遍历和绘制遍历是每个相机独立进行】
                ③ Camera执行渲染:Viewer依次调用View下的所有Camera的渲染方法,所有渲染指令都在GraphicsContext的OpenGL上下文中执行,结果先存入帧缓冲区(双缓冲的后台缓冲区);
                        【多个相机依次渲染到同一个后台缓冲区,后续相机的绘制会覆盖先前相机在同一个像素位置的内容(取决于深度测试和绘制顺序),这也是为什么视口不重叠时,多个相机可以合成最终图像。】
                ④ 交换缓冲区(显示画面):GraphicsContext调用系统API,把后台缓冲区的画面交换到前台,Window就会显示出最终的合成画面;
        8. 每帧重复步骤7.1~7.4,直到用户关闭窗口,Viewer检测到窗口关闭事件后,销毁View、Camera、GraphicsContext、Window,释放资源;
        9. 总结上述组件:
                ① Viewer:只做”全局管控“—-启动/停止循环、管理View、检测退出;
                ② View:只做”数据和逻辑管控“—-存场景、管Camera、处理事件;
                ③ Camera:只做“具体渲染”—-用View的场景,按自己的参数画到指定区域;
                ④ GraphicsContext:只做“GPU层执行”—-执行Camera的渲染指令,绑定Window;
                ⑤ Window:只做“物理显示+事件接收”—-展示画面,把用户操作传给View;

4. 多视图和相机
(1)多视图的管理是通过osgViewer::CompositeViewer类来实现的,负责多个视图的管理及同步工作,继承自osgViewer::ViewerBase类,ViewerBase类主要负责线程管理、设置线程模式、启动线程等工作,多视图渲染时,线程管理很麻烦,但是osgViewer::CompositeViewer类中已经实现了;

5. 实践:
(1)当实现单视图多相机时,发现:移动鼠标,它们的显示内容会同时变化,因为所有camera共享同一个View的场景数据和事件处理逻辑。
(2)osg::GraphicsContext::WindowSystemInterface:承担了OSG与底层操作系统窗口系统之间的桥梁角色,不同操作系统的窗口管理API完全不同,它封装了这些差异,提供了统一的接口,让OSG上层代码无需关心底层是哪个平台,就能完成窗口创建、屏幕信息获取等操作;一般情况下,程序中默认调用了该类,但是有些特殊情况需要手动创建调用;
        1. createGraphicsContext是“对外的统一接口”,不直接创建窗口,负责统筹“窗口创建+OpenGL上下文创建”,而WindowSystemInterface才是“真正和操作系统打交道、创建窗口的底层执行者”,封装了不同平台的原生窗口API,是OSG和操作系统窗口系统的直接交互层;
(3)实现多视图时:手动创建osgViewer::view时,需要手动添加相机操作器(CameraManipulator)和事件处理器(EventHandler);
        1. 默认View,由Viewer自动创建时,当使用viewer.setScaneData(),Viewer会自动创建默认View,并配置相机操作器和事件处理器;
        2. OpenGL视口坐标系统的坐标原点(0,0)位于窗口的左下角,y轴向上递增;
        3. 停止旋转后场景仍自动旋转:因为TrackballManipulaotr的默认“惯性(Throw)”效果,当快速拖动鼠标并释放时,操作器会根据最后一次拖动的速度,继续模拟相机旋转,直到速度衰减为零,营造出“甩动”的视觉效果,通过manipulator->setAllowThrow(false)来禁用惯性,如果想保留惯性但调整速度,可以调用manipulator->setThrowScale来设置数值进行调整;
(4)多窗口多视图:主要是每个GraphicsContext与每个View的Camera进行绑定,一个GraphicsContext对应一个操作系统窗口;

【这里面的细节还是很多的,感觉还有很多内容需要继续深入学习,只有实践才会真的了解,后续不断实践并不断优化本文内容】
       

赞(0)
未经允许不得转载:171主机测评 » 记录:场景图形管理(一)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址