场景图形管理 - (1)

本章主要介绍在场景图形中如何管理场景数据及交互过程,这在实际开发中非常重要。

    1. 视图与相机

        什么是视图?在《OpenGL编程指南》中有下面的比喻,从笔者开始学习图形学就影响深刻,相信对读者学习场景管理也会非常有帮助。

        产生目标场景视图的变换过程类似于用相机进行拍照,主要有如下的步骤:

  1. 把相机在脚架上,让对对准场景(视图变换)。
  2. 对场景进行安排,使照片中各物体位于读者所希望的位置(模型变换)
  3. 选择照相机镜头,并调整放大倍数《投影变换)
  4. 确定最终照片的大小,如放大(视口变换)。

当完成这些步骤以后,它就可以绘制场景了。视图可以简单理解为一个场景拍照。通过上面一个非常形象而生动的比喻,读者应该了解了三维图像的显示流程,如图 8-1 所示

场景图形管理 - (1)_第1张图片

图8-1 三维图像的显示流程

        将世界坐标系中的三维物体经过一系列几何变化(包括平移、旋转和伸缩等),为了使显示的物体能以合适的位置、大小和方向显示出来,必须要通过投影,然后定义一个三维视景体,对物体进行裁剪,只使投影在视景体内的部分显示出来,然后在屏幕窗口内定义一个矩形,称为视口(Viewport),视景体投影后的图形就在视口内显示,最后作一些适当变换,使图形在屏幕坐标系下显示。

     在OSG中,包含 SimpleViewerViewerCompositeViewer3大视口类。设计SimpleViewer 的目的很简单,主要是为了满足用户的需求,使一个已有的程序移植到OSG中,实现与现有框架匹配的简单视口类。对于Viewer和 CompositeViewer类,后面几节中将有专门的讲解。

      1. osg::Camera类

        osg::Camera类继承自 osg::Transformosg:CullSettings 类,用来管理OSG中的模型-视图矩阵,其继承关系图如图8-2所示。

        相机的管理主要是通过各种变换来实现的,在 OpenGL 中如此,在OSG中同样也是如此。在前面已经说到OSG中的图形的显示流程,下面详细说明各种变换。

  1. 视点变换

视点变换就是设置视点的方向和位置。默认情况下,视点定位为坐标原点,指向Y正方向。在Camera中的视点变换函数如下:

void setViewMatrixAsLookAt(const osg:Vec3d &eye, const osg::Vec3d ¢er, const osg::Vec3d &up)

void getViewMatrixAsLookAt(osg.:Vec3d &eye, osg::Vec3d ¢er, osg::Vec3d &up, double lookDistance=1.0)

void getViewMatrixAsLookAt(osg.:Vec3f &eye, osg::Vec3f ¢er, osg::Vec3f &up, float lookDistance=1.0)

第一个参数是视点的位置,第二个参数是参考点的位置,该点通常为场景中的中心轴上的点,第三个参数是视点向上的方向向量的方向

场景图形管理 - (1)_第2张图片

图8-2 osg::Camcra 的继承关系图

  1. 模型变换

模型变换就是对模型的位置、大小及角度的变换。在前面已经讲到了通过两个基本的节点-位置变换节点(osg::PositionAttitudeTransform)和矩阵变换节点(osg:MatrixTransfom)来实现模型变换这里不再重述。

  1. 投影变换

投影变换在显示流程中是一个非常重要的环节。经过模型视景的转换后,场景中的物体放在了所希望的位置上,但由于显示器只能用二维图像显示三维物体,因此就要靠投影来降低维数。

事实上,投影变换的目的就是定义一个视景体,使视景体外多余的部分被裁剪掉,最终进入图像的只是视景体内的有关部分。投影包括透视投影(Perspective Projection)正视投影(OrthographicProjection)两种

透视投影比较好理解,符合正常的视觉现象。离视点越近,物体越大;离视点越远,物体越小当远到极点时,物体消失,变成灭点。

在OSG中,有两种透视投影,即透视视景体和对称透视视景体。

  • 透视视景体的函数如下:

void setProjectionMatrixAsFrustum(double left, double right,double bottom,double top,double zNear,double zFar);

bool getProjectionMatrixAsFrustum(double &left,double &right,double &bottom,double &top,double &zNear,double &zFar);

*创建或者得到一个透视视图平截体的矩阵,并把它与当前矩阵相乘。第1-4 个参数分别是近裁剪面的参数信息,第五个参数表示从视点到近裁面的距离:第六个参数表示从视点到远裁剪面的距离,注意,zNear和zFar 必须指定为正数,如图8-3所示*/

场景图形管理 - (1)_第3张图片

图8-3透视投影

  • 对称透视视景体的函数如下:

void setProjectionMatrixAsPerspective(double fovy, double aspectRatio, double zNear, double zFar);

bool getProjectionMatrixAsPerspective(double &fovy, double &aspectRatio, double &zNcar, double &zFar);

/*创建或者得到一个对称透视视图的平截头体的矩阵,并与当前矩阵相乘。第一个参数表示z平面视野角度,第二个参数表示平截头的纵横比:第三个参数表示视点与近裁面的距离,第四个参数表示视点与远裁剪面的矩阵,注意,必须为正数,如图8-4所示*/

场景图形管理 - (1)_第4张图片

图8-4 对称透视投影

使用对称透视视景体时需要注意视野角度,如果调整不好,会导致图形的变形,就是后面会讲到的宽屏变形问题,后面会提出一种解决方案,就是通过设置对称透视视景体实现的。

正射投影也包括两种,即正射投影

  • 正射投影函数如下

void setProjcctionMatrixAsOrtho(double left, double right, double bottom, double top, double zNear, double zFar);

 bool getProjectionMatrixAsOrtho(double &lef, double &right, double &bottom, double &top, double &zNear, double&zFar);

/*创建或者得到一个平行正射投影矩阵,并与当前矩阵相乘。第1~4个参数分别是近裁剪平面的参数信息,第二个参数表示视点距近裁剪面的距离:第三个参数表示视点距远裁剪面的距离,注意,必须为正数且为不同的值,如图 8-5所示/

  • 特殊正射投影函数如下

void setProjectionMatrixAsOrtho2D(double left double right double bottom, double top)

/*创建一个表示把二维投影坐标投影到屏幕上的矩阵,并与当前矩阵相乘,第1-4个参数表示矩阵的信息*/

使用正射投影矩阵把二维图像投影到二维屏幕时,需要注意的是,要指定Near和Far分别为-1.0和 1.0当然,此时采用特殊的正射投影矩阵也可以完成这种渲染效果。

场景图形管理 - (1)_第5张图片

  1. 视口变换

视口变换就是将视景体内投影的物体显示在二维的视口平面上。在计算机图形学中,它的定义是将经过几何变换投影变换裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视口。

视口变换函数如下:

void setViewport(osg::Viewport  viewport);

void setViewport(int x, int y,int width, int height);

const Viewport *getViewport() const;

Vicwport *getViewport();

/*创建或者得到一个视口。

第1-2个参数表示屏幕左下角的坐标:第三个参数表示屏幕宽度,第四个参数表示屏幕高度,默认情况下,表示整个屏幕大小*/

  1. 裁剪变换

在OSG中,默认了6个裁剪平面以去除没有必要显示的物体。读者还可以定义其他的裁剪平面来确定裁剪。定义裁剪平面的方法有很多,如osg::Scissorosg::ClipPlane等。

osg::ClipPlane类继承自osg::StateAttribute类,封装了OpenGL中的glClipPlane()函数,继承关系图如图8-6所示。

场景图形管理 - (1)_第6张图片

图8-6 osg::ClipPlane 的继承关系图

在类的成员函数中,设置裁剪平面的有下面几个函数:

void setClipPlane(const Plane &plane);

void setClipPlane(double a, double b, double c, double d);

void setClipPlane(const Vec4d &plane);

/*参数都指向一个裁剪平面,裁平面可以用 Ax+By+Cz+D-0方程表示。因此,A,B,C,D,4个数依次表示裁剪平面方程的4个参数*/

上面的方法只是设置一个裁剪平面的参数,如果想要调用该裁剪平面,还需要在属性中开启该裁剪平面,如下面的代码:

root->getOrCreateStateSet()->setAttributeAndModes(cc, osg::StateAttribute ::ON )

如果在应用程序中要指定多个裁剪平面,此时需要注意指定平面的索引,否则前面先指定的裁剪平面会被覆盖。可以使用下面的函数:

void setClipPlaneNum(unsigned int num);

unsigned int getClipPlaneNum() const;

/* 得到一个裁剪平面的索引或者指定一个平面的索引 */

osg::Scissor 类继承自 osg:StateAttribute 类装了 OpenGL 中 glScissor()函数,继承关系图如图8-7所示。

场景图形管理 - (1)_第7张图片

该类主要用于设置一个视口裁剪平面矩形。设置裁剪平面矩形的函数如下:

void setScissor(int x, int y, int width, int height);// 参数表示一个平面矩形的信息(左下角坐标、宽度和高度)。

上面讲了几种变换,理解这几种变换对学习后面的内容是非常有帮助的。在osgCamera类中除了定义一些基本变换以外,还添加了其他的操作,如清除背景色和清除各种缓存信息等。

清除背景色可以用下面的函数:

void setClearColor(const osg::Vec4 &color)

const osg::Vec4 & getClearColor()const

清除各种缓存信息可以用下面的函数:

void setColorMask(osg::ColorMask *colorMask):

void setColorMask(bool red, bool green, bool blue, bool alpha);

const ColorMask * getColorMask()const;

ColorMask *getColorMask0;

例如,“viewer->getCamera->setClearMask(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);”向函数中传入OpenGL缓存标示,表示清除颜色深度和模板缓存。

      1. 裁平面示例(一)

裁剪平面(osg::ClipPlane)示例(一)的代码如程序清单8-1所示

void clipPlane_8_1(const string strDataFolder)

{

    osg::ref_ptr viewer = new osgViewer::Viewer();

    osg::ref_ptr traits = new osg::GraphicsContext::Traits;

    traits->x = 40;

    traits->y = 40;

    traits->width = 600;

    traits->height = 480;

    traits->windowDecoration = true;

    traits->doubleBuffer = true;

    traits->sharedContext = 0;

    osg::ref_ptr gc = osg::GraphicsContext::createGraphicsContext(traits.get());

    osg::ref_ptr camera = new osg::Camera;

    camera->setGraphicsContext(gc.get());

    camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));

    GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;

    camera->setDrawBuffer(buffer);

    camera->setReadBuffer(buffer);

    viewer->addSlave(camera.get());

    // 创建一个裁剪平面;

    osg::ref_ptr cp1 = new osg::ClipPlane();

    // 设置裁剪平面

    cp1->setClipPlane(0, 1, 1, 1);

    // 指定平面索引

    cp1->setClipPlaneNum(0);

    // 创建一个裁剪平面

    osg::ref_ptr cp2 = new osg::ClipPlane();

    // 设置裁剪平面

    cp2->setClipPlane(1, 0, 0, 1);

    // 指定平面索引

    cp2->setClipPlaneNum(1);

    osg::ref_ptr root = new osg::Group();

    // 读取模型

    string strDataPath = strDataFolder + "cow.osg";

    osg::ref_ptr node = osgDB::readNodeFile(strDataPath);

    root->addChild(node.get());

    // 启用裁剪平面

    root->getOrCreateStateSet()->setAttributeAndModes(cp1.get(), osg::StateAttribute::ON);

    root->getOrCreateStateSet()->setAttributeAndModes(cp2.get(), osg::StateAttribute::ON);

    // 优化场景数据

    osgUtil::Optimizer optimizer;

    optimizer.optimize(root.get());

    viewer->setSceneData(root.get());

    viewer->realize();

    viewer->run();

}

运行程序,截图如图8-8所示

场景图形管理 - (1)_第8张图片

图8-8裁平面示例(一)截图

你可能感兴趣的:(OSG,c++,图形渲染,3d)