为什么80%的码农都做不了架构师?>>>
OpenCASCADE Camera
Abstract. OpenCASCADE introduce a new class Graphic3d_Camera for the Visualization module. The camera class provides object-oriented approach to setting up projection and orientation properties of 3D view. The paper use GLUT to test the function of the new Camera class.
Key Words. OpenCASCADE, Camera, OpenGL, GLUT, Transformations
1. Introduction
在三维场景中变换的过程与用相机拍照的过程很相似,如下图所示,拍照的过程大概如下:
Figure 1.1 The Camera Analogy (From the Red Book)
v 放好三角架并使相机对准要拍照的场景(viewing transformation);
v 布置好场景到理想的位置,以便拍照(modeling transformation);
v 选择镜头或调整变焦(projection transformation);
v 确定最终的照片要有多大尺寸,例如你可能需要放大一些的照片(viewport transformation);
OpenCASCADE在6.8.0版本中引入了一个Graphic3d_Camera类来对视图的变换进行管理,通过对camera的调整,方便对场景中的模型/视图(Model/View)进行变换。
Figure 1.2 Camera in the Scene
如下图所示,对象的空间坐标经过一系列的变换,最终得到显示屏幕上的二维坐标。
Figure 1.3 Stages of Vertex Transformation (From the Red Book)
为了指定viewing, modeling和projection变换,可以构造一个4x4的矩阵与顶点的齐次坐标相乘来实现这些变换。
2.Coordinate Transforms
在计算机图形学中,造型MODELING就是定义一个可渲染物体的数字表示。对于OpenGL而言,通常就是用多边形来近似表示一个物体。用多边形表示物体至少需要每个多边形顶点的坐标和连接关系的信息。附加的信息包含顶点颜色、法向量或贴图坐标等。
三维对象的属性如顶点位置及面的法向量,为了造型的方便,通常定义在对象空间OBJECT SPACE中,即在局部坐标系中建模。对象空间通常也叫作模型空间MODEL SPACE或造型坐标系统MODELING COORDINATE SYSTEM。
在 一个包含大量三维对象的场景中,每一个对象都是在其对象空间中建模的,这是我们就需要一个统一的坐标系统COMMON COORDINATE SYSTEM,通常称之为世界空间WORLD SPACE或世界坐标系WORLD COORDINATE SYSTEM,或绝对坐标系。
定 义了世界坐标系统后,场景中所有模型对象必须从其局部坐标系变换到世界坐标系中来。从对象空间变换到世界空间的变换称之为模型变换MODELING TRANSFORMATION。若模型对象造型坐标系中的单位是feet,但是世界坐标系中是inches,对象坐标必须缩小12倍以得到世界坐标系。若 对象在其对象空间中朝前的,但是在世界场景中需要其朝后,那么需要对模型对象进行一个旋转变换。当要将模型对象放到场景中的合适位置时,那么就需要进行一 个移动变换。所有这些单个变换也可以合成一个矩阵,即模型变换矩阵MODEL TRANSFORMATION MATRIX,就是表示将模型对象从对象坐标系变换到世界坐标系中。
当场景确定好后,视图viewing parameters的参数就需要确定了。视图的参数之一就是视点vantage point(eye or camera position),即在哪里对模型进行观察。视图的参数还包括焦点focus point(also called the lookat point or the direction in which the camera is pointed)和向上的方向(the camera may be held sideways or upside down)。视图的参数定义了视图变换VIEWING TRANSFORMATION,它们也可以合成一个矩阵,称作视图矩阵VIEWING MATRIX。一个坐标乘以这个矩阵就可以将其从世界空间变换到观察空间EYE SPACE,也称为EYE COORDINATE SYSTEM。由定义可知,此坐标系的原点位于视点处。
尽管有些3D图形API允许分别指定模型变换矩阵modeling matrix和视图变换矩阵viewing matrix,但是在OpenGL中将这两者合成为一个矩阵,称为模型/视图矩阵MODELVIEW MATRIX。这个矩阵定义了坐标从对象空间变换到观察空间的变换。
Figure 2.1 Coordinate spaces and transforms in OpenGL(From OpenGL Shading Language)
在 OpenGL中,可以调用函数glMatrixMode来选择modelview matrix或其他的矩阵,然后可以调用函数glLoadIdentity将当前矩阵设置为单位矩阵,或调用函数glLoadMatrix将当前矩阵替换 为任意一个矩阵。替换时你需要清楚地知道你要进行的变换操作,否则容易产生完全不可理解的视图。还可以调用函数glMultMatrix来将当前矩阵与任 意矩阵相乘。可以通过调用函数gluLookAt来设置modelview变换矩阵。
当坐标变换到观察空间EYE SPACE后,下一步需要定义一个观察体VIEWING VOLUME,即这个区域内的三维场景将出现在最终的图像上。将模型对象放进观察体即裁剪空间CLIP SPACE(也称作裁剪坐标系CLIPPING COORDINATE SYSTEM)的变换称为投影变换PROJECTION TRANSFORMATION。在OpenGL中,可以通过glMatrixModel来选择需要设置的变换矩阵。也可以调用函数glOrtho、 glFrustum、gluPerspective来设置。
通过投影变换将观察空间内的三维模型投影到了二维空间,下一步变换就是顶点坐 标的perspective divide。这个操作将观察体内所有坐标点的各个分量除以齐次坐标分量w。得到的x, y, z的取值范围都是[-1, 1],而齐次坐标值w为1,所以就不再需要w了。换句话说所有可见的图元graphics primitives都变换到一个在点(-1,-1,-1)和点(1,1,1)之间的立方体区域,这就是规范化设备坐标空间NORMALIZED DEVICE COORDINATE SPACE,他是个允许将观察区域viewing area映射到任意尺寸及深度的视口的中间空间。
窗 口显示的像素点pixel不能指向-1到1的浮点数,通常指向窗口坐标系WINDOW COORDINATE SYSTEM中的坐标,x取值范围从0到窗口宽度减1,y取值范围从0到窗口高度减1,因此还需要进行一步变换处理。视口变换指定了从规范设备坐标系到窗 口坐标系的变换VIEWPORT TRANSFORMATION。在OpenGL通过函数glViewport来指定视口变换。
以上内容 主要来自《OpenGL Shading Language》一书中的第一章“Review of OpenGL Basics-1.9 Coordinate Transforms”,详细内容可参考原书。也可利用下面的程序来直观地学习这些变换及投影的知识:
Figure 2.2 OpenGL Tutors-Projection (by Nate Robins)
3.Camera of OpenCASCADE
OpenCASCADE6.8.0 版本中引入一个类Graphic3d_Camera来简化对三维视图的变换操作,即可以对视图进行投影和模型视图的相关设置。如通过设置视点Eye position和中心点可以计算得到观察矩阵,类似gluLookAt的功能,相关代码如下所示:
// function : UpdateOrientation
// purpose :
// =======================================================================
template <typename Elem_t>
Graphic3d_Camera::TransformMatrices<Elem_t>&
Graphic3d_Camera::UpdateOrientation (TransformMatrices<Elem_t>& theMatrices) const
{
if (theMatrices.IsOrientationValid())
{
return theMatrices; // for inline accessors
}
theMatrices.InitOrientation();
NCollection_Vec3<Elem_t> anEye (static_cast<Elem_t> (myEye.X()),
static_cast<Elem_t> (myEye.Y()),
static_cast<Elem_t> (myEye.Z()));
NCollection_Vec3<Elem_t> aCenter (static_cast<Elem_t> (myCenter.X()),
static_cast<Elem_t> (myCenter.Y()),
static_cast<Elem_t> (myCenter.Z()));
NCollection_Vec3<Elem_t> anUp (static_cast<Elem_t> (myUp.X()),
static_cast<Elem_t> (myUp.Y()),
static_cast<Elem_t> (myUp.Z()));
NCollection_Vec3<Elem_t> anAxialScale (static_cast<Elem_t> (myAxialScale.X()),
static_cast<Elem_t> (myAxialScale.Y()),
static_cast<Elem_t> (myAxialScale.Z()));
LookOrientation (anEye, aCenter, anUp, anAxialScale, *theMatrices.Orientation);
return theMatrices; // for inline accessors
}
通过调用这个函数得到将世界坐标系变换到观察坐标系的变换矩阵。其实现步骤如下:
v 平移观察坐标原点到世界坐标系原点;
v 进行旋转,分别让观察坐标系的Vx, Vy, Vz轴对应到世界坐标的Wx,Wy,Wz轴。
OpenCASCADE中的实现代码如下所示:
// function : LookOrientation
// purpose :
// =======================================================================
template <typename Elem_t>
void Graphic3d_Camera::LookOrientation (const NCollection_Vec3<Elem_t>& theEye,
const NCollection_Vec3<Elem_t>& theLookAt,
const NCollection_Vec3<Elem_t>& theUpDir,
const NCollection_Vec3<Elem_t>& theAxialScale,
NCollection_Mat4<Elem_t>& theOutMx)
{
NCollection_Vec3<Elem_t> aForward = theLookAt - theEye;
aForward.Normalize();
// side = forward x up
NCollection_Vec3<Elem_t> aSide = NCollection_Vec3<Elem_t>::Cross (aForward, theUpDir);
aSide.Normalize();
// recompute up as: up = side x forward
NCollection_Vec3<Elem_t> anUp = NCollection_Vec3<Elem_t>::Cross (aSide, aForward);
NCollection_Mat4<Elem_t> aLookMx;
aLookMx.SetRow (0, aSide);
aLookMx.SetRow (1, anUp);
aLookMx.SetRow (2, -aForward);
theOutMx.InitIdentity();
theOutMx.Multiply (aLookMx);
theOutMx.Translate (-theEye);
NCollection_Mat4<Elem_t> anAxialScaleMx;
anAxialScaleMx.ChangeValue (0, 0) = theAxialScale.x();
anAxialScaleMx.ChangeValue (1, 1) = theAxialScale.y();
anAxialScaleMx.ChangeValue (2, 2) = theAxialScale.z();
theOutMx.Multiply (anAxialScaleMx);
}
当指定投影变换类型后,可以得到投影变换矩阵,可以指定的类型如下:
//! - Projection_Orthographic : orthographic projection.
//! - Projection_Perspective : perspective projection.
//! - Projection_Stere : stereographic projection.
//! - Projection_MonoLeftEye : mono projection for stereo left eye.
//! - Projection_MonoRightEye : mono projection for stereo right eye.
enum Projection
{
Projection_Orthographic,
Projection_Perspective,
Projection_Stereo,
Projection_MonoLeftEye,
Projection_MonoRightEye
};
当 指定为Projection_Orthographic时即为正投影变换。使用从坐标位置到观察平面的正投影变换,任意一点(x, y, z)的投影位置是(x, y)。因此在建立观察体的范围后,该矩形平行管道内部的坐标描述即为投影坐标,它们不需要另外的投影处理就可直接映射到规范化观察体normalized view volume。有些图形软件包使用单位立方体作为规范化观察体,其x,y,z坐标规范成0到1之间,另外的规范化变换方法使用坐标范围从-1到1的对称立 方体。
由于屏幕坐标系经常指定为左手系,因此规范化观察体也常指定为左手系统。正投影观察体的规范化变换矩阵是:详细推导请参考《Computer Graphics with OpenGL》。
对应OpenCASCADE中实现代码如下所示:
// function : OrthoProj
// purpose :
// =======================================================================
template <typename Elem_t>
void Graphic3d_Camera::OrthoProj (const Elem_t theLeft,
const Elem_t theRight,
const Elem_t theBottom,
const Elem_t theTop,
const Elem_t theNear,
const Elem_t theFar,
NCollection_Mat4<Elem_t>& theOutMx)
{
// row 0
theOutMx.ChangeValue (0, 0) = Elem_t (2.0) / (theRight - theLeft);
theOutMx.ChangeValue (0, 1) = Elem_t (0.0);
theOutMx.ChangeValue (0, 2) = Elem_t (0.0);
theOutMx.ChangeValue (0, 3) = - (theRight + theLeft) / (theRight - theLeft);
// row 1
theOutMx.ChangeValue (1, 0) = Elem_t (0.0);
theOutMx.ChangeValue (1, 1) = Elem_t (2.0) / (theTop - theBottom);
theOutMx.ChangeValue (1, 2) = Elem_t (0.0);
theOutMx.ChangeValue (1, 3) = - (theTop + theBottom) / (theTop - theBottom);
// row 2
theOutMx.ChangeValue (2, 0) = Elem_t (0.0);
theOutMx.ChangeValue (2, 1) = Elem_t (0.0);
theOutMx.ChangeValue (2, 2) = Elem_t (-2.0) / (theFar - theNear);
theOutMx.ChangeValue (2, 3) = - (theFar + theNear) / (theFar - theNear);
// row 3
theOutMx.ChangeValue (3, 0) = Elem_t (0.0);
theOutMx.ChangeValue (3, 1) = Elem_t (0.0);
theOutMx.ChangeValue (3, 2) = Elem_t (0.0); theOutMx.ChangeValue (3, 3) = Elem_t (1.0);}
平 行投影主要用于设计制图CAD,而透视投影更有真实感觉,即近大远小,所以在一些三维浏览或仿真程序中主要使用透视投影。当指定投影类型为 Projection_Perspective时,即指定为场景的投影变换为透视变换。一般的透视投影规范化变换矩阵如下所示:详细推导请参考 《Computer Graphics with OpenGL》。
OpenCASCADE中相关的实现代码如下所示:
// function : PerspectiveProj
// purpose :
// =======================================================================
template <typename Elem_t>
void Graphic3d_Camera::PerspectiveProj (const Elem_t theLeft,
const Elem_t theRight,
const Elem_t theBottom,
const Elem_t theTop,
const Elem_t theNear,
const Elem_t theFar,
NCollection_Mat4<Elem_t>& theOutMx)
{
// column 0
theOutMx.ChangeValue (0, 0) = (Elem_t (2.0) * theNear) / (theRight - theLeft);
theOutMx.ChangeValue (1, 0) = Elem_t (0.0);
theOutMx.ChangeValue (2, 0) = Elem_t (0.0);
theOutMx.ChangeValue (3, 0) = Elem_t (0.0);
// column 1
theOutMx.ChangeValue (0, 1) = Elem_t (0.0);
theOutMx.ChangeValue (1, 1) = (Elem_t (2.0) * theNear) / (theTop - theBottom);
theOutMx.ChangeValue (2, 1) = Elem_t (0.0);
theOutMx.ChangeValue (3, 1) = Elem_t (0.0);
// column 2
theOutMx.ChangeValue (0, 2) = (theRight + theLeft) / (theRight - theLeft);
theOutMx.ChangeValue (1, 2) = (theTop + theBottom) / (theTop - theBottom);
theOutMx.ChangeValue (2, 2) = -(theFar + theNear) / (theFar - theNear);
theOutMx.ChangeValue (3, 2) = Elem_t (-1.0);
// column 3
theOutMx.ChangeValue (0, 3) = Elem_t (0.0);
theOutMx.ChangeValue (1, 3) = Elem_t (0.0);
theOutMx.ChangeValue (2, 3) = -(Elem_t (2.0) * theFar * theNear) / (theFar - theNear); theOutMx.ChangeValue (3, 3) = Elem_t (0.0);}
其中投影方式Projection_Stereo为立体投影,在网上搜了一下,这种投影可以应用到3D影片的播放。目前来看,相关应用还是很火爆的,可以用来在电视中播放立体影片。更多介绍可参考:
http://www.nvidia.com/content/gtc-2010/pdfs/2010_gtc2010.pdf
http://www.gali-3d.com/archive/articles/StereoOpenGL/StereoscopicOpenGLTutorial.php
4.Test the Camera in GLUT
OpenGL实用函数工具包(OpenGL Utility Toolkit, GLUT)提供了与任意屏幕窗口系统进行交互的函数库,可以用来快速演示OpenGL中的相关概念。其中设置投影变换矩阵的方法如下代码所示:
glMatrixMode(GL_PROJECTION);
glLoadMatrixd(theCamera->ProjectionMatrix().GetData());
设置模型/视图变换矩阵代码如下所示:
glLoadMatrixd(theCamera->OrientationMatrix().GetData());
glutPostRedisplay();
通过键盘按键o和p用来在平行投影和透视投影之间进行切换,切换效果如下图所示:
Figure 4.1 Orthographic and Perspective projection test
如 上图所示,透视投影比平行投影更具立体感,但是在工程设计CAD或机械制图CAD软件中,平行投影更有利于设计人员使用。当处于平行投影模式时,视点的远 近对模型在视图中的显示没有影响;当处于透视投影模式时,移动视点对视图是有影响的:当视点离模型较近时,模型在视图中显示较大,反之较小。
按下鼠标中键可以用来测试四元数Quaternion插值来对场景绕Y轴进行旋转,其实主要用来测试OpenCASCADE的quaternion插值类的插值效果。代码及效果图如下所示:
static gp_Quaternion aEndQuat(gp_Vec(0.0, 1.0, 0.0), M_PI);
static gp_QuaternionSLerp aSlerp(aStartQuat, aEndQuat);
gp_Quaternion aResultQuat;
aSlerp.Interpolate(T, aResultQuat);
Figure 4.2 Test Quaternion SLERP
程序源代码见文后附件,可下载编译后自己修改一些参数来测试效果。
5.Conclusion
OpenGL中的坐标变换涉及到对象空间,模型空间,世界空间,观察空间及相关的坐标变换,而所有的变换操作都可以统一为一个4X4的齐次矩阵的数学运算,如矩阵加法、矩阵乘法、求逆矩阵等。
OpenCASCADE 用面向对象的思想封装了Grpahic3d_Camera用来方便对视图的变换控制,通过设置视点及投影类型等相关参数,即可得到相关的变换矩阵,进而可 对模型/视图方便地进行变换。利用Camera的类,可以方便对视图进行缩放、旋转等变换操作。相关实现可参考OpenCASCADE中视图类中的代码。
最后,还测试了一下gp_Quaternion插值的算法。
6. References
1. Dave Shreiner. OpenGL Programming Guide(7th Edition). Addison-Wesley. 2010
2. Randi J. Rost. OpenGL Shading Language(2nd Edition). Addison-Wesley. 2006
3. Donald Hearn, M. Pauline Baker. Computer Graphics with OpenGL. 2004
4. Nate Robins. OpenGL Tutors. www.cs.utah.edu/~narobins/opengl.html
5. Song Ho Ahn. OpenGL Transformation. http://www.songho.ca/opengl/gl_transform.html
6. 孙家广等. 计算机图形学. 清华大学出版社. 2000
PDF Version and Source Code: OpenCASCADE Camera