本章主要介绍在场景图形中如何管理场景数据及交互过程,这在实际开发中非常重要。
什么是视图?在《OpenGL编程指南》中有下面的比喻,从笔者开始学习图形学就影响深刻,相信对读者学习场景管理也会非常有帮助。
产生目标场景视图的变换过程类似于用相机进行拍照,主要有如下的步骤:
当完成这些步骤以后,它就可以绘制场景了。视图可以简单理解为一个场景拍照。通过上面一个非常形象而生动的比喻,读者应该了解了三维图像的显示流程,如图 8-1 所示
图8-1 三维图像的显示流程
将世界坐标系中的三维物体经过一系列几何变化(包括平移、旋转和伸缩等),为了使显示的物体能以合适的位置、大小和方向显示出来,必须要通过投影,然后定义一个三维视景体,对物体进行裁剪,只使投影在视景体内的部分显示出来,然后在屏幕窗口内定义一个矩形,称为视口(Viewport),视景体投影后的图形就在视口内显示,最后作一些适当变换,使图形在屏幕坐标系下显示。
在OSG中,包含 SimpleViewer、Viewer 和CompositeViewer3大视口类。设计SimpleViewer 的目的很简单,主要是为了满足用户的需求,使一个已有的程序移植到OSG中,实现与现有框架匹配的简单视口类。对于Viewer和 CompositeViewer类,后面几节中将有专门的讲解。
osg::Camera类继承自 osg::Transform和osg:CullSettings 类,用来管理OSG中的模型-视图矩阵,其继承关系图如图8-2所示。
相机的管理主要是通过各种变换来实现的,在 OpenGL 中如此,在OSG中同样也是如此。在前面已经说到OSG中的图形的显示流程,下面详细说明各种变换。
视点变换就是设置视点的方向和位置。默认情况下,视点定位为坐标原点,指向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)
第一个参数是视点的位置,第二个参数是参考点的位置,该点通常为场景中的中心轴上的点,第三个参数是视点向上的方向向量的方向
图8-2 osg::Camcra 的继承关系图
模型变换就是对模型的位置、大小及角度的变换。在前面已经讲到了通过两个基本的节点-位置变换节点(osg::PositionAttitudeTransform)和矩阵变换节点(osg:MatrixTransfom)来实现模型变换这里不再重述。
投影变换在显示流程中是一个非常重要的环节。经过模型视景的转换后,场景中的物体放在了所希望的位置上,但由于显示器只能用二维图像显示三维物体,因此就要靠投影来降低维数。
事实上,投影变换的目的就是定义一个视景体,使视景体外多余的部分被裁剪掉,最终进入图像的只是视景体内的有关部分。投影包括透视投影(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所示*/
图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所示*/
图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当然,此时采用特殊的正射投影矩阵也可以完成这种渲染效果。
视口变换就是将视景体内投影的物体显示在二维的视口平面上。在计算机图形学中,它的定义是将经过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视口。
视口变换函数如下:
void setViewport(osg::Viewport viewport);
void setViewport(int x, int y,int width, int height);
const Viewport *getViewport() const;
Vicwport *getViewport();
/*创建或者得到一个视口。
第1-2个参数表示屏幕左下角的坐标:第三个参数表示屏幕宽度,第四个参数表示屏幕高度,默认情况下,表示整个屏幕大小*/
在OSG中,默认了6个裁剪平面以去除没有必要显示的物体。读者还可以定义其他的裁剪平面来确定裁剪。定义裁剪平面的方法有很多,如osg::Scissor和osg::ClipPlane等。
osg::ClipPlane类继承自osg::StateAttribute类,封装了OpenGL中的glClipPlane()函数,继承关系图如图8-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所示。
该类主要用于设置一个视口裁剪平面矩形。设置裁剪平面矩形的函数如下:
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缓存标示,表示清除颜色深度和模板缓存。
裁剪平面(osg::ClipPlane)示例(一)的代码如程序清单8-1所示
void clipPlane_8_1(const string strDataFolder)
{
osg::ref_ptr
osg::ref_ptr
traits->x = 40;
traits->y = 40;
traits->width = 600;
traits->height = 480;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr
osg::ref_ptr
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->setClipPlane(0, 1, 1, 1);
// 指定平面索引
cp1->setClipPlaneNum(0);
// 创建一个裁剪平面
osg::ref_ptr
// 设置裁剪平面
cp2->setClipPlane(1, 0, 0, 1);
// 指定平面索引
cp2->setClipPlaneNum(1);
osg::ref_ptr
// 读取模型
string strDataPath = strDataFolder + "cow.osg";
osg::ref_ptr
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所示
图8-8裁平面示例(一)截图