在場景中創建兩個視口。其中一個用於從坦克駕駛員的視角觀察場景。該視口將被渲染於屏幕的上半部分。第二個視口由缺省的osgViewer::Viewer類接口(軌跡球,飛行等控制器)控制。它將被渲染於屏幕的中下部分。
概述:
OSG向開發人員提供了各種的抽象層次接口。前面的教程討論的主要是一些較高層級的接口應用:例如使用Viewer類來控制視點,場景,交互設備和 窗口系統。OSG的優勢之一,就是可以允許開發者在使用高層次的接口的同時,訪問較低層次的抽象接口。本章將使用一些低抽象層級的功能,對視點進行控制, 並使用相應的類渲染場景。
代碼:
為了創建兩個視口,我們需要提供兩個獨立可控的攝像機。與OSG 1.2版本中所述不同的是,本例中將不再使用Prodecer::CameraConfig類,而是將多個不同的視口添加到組合視口 CompositeViewer類當中。下面的函數即用於實現添加視口並設置其中的攝像機位置。
void createView (osgViewer::CompositeViewer *viewer,//查看器,一個相框
osg::ref_ptr<osg::Group> scene,//場景
osg::ref_ptr<osg::GraphicsContext> gc,//顯示設置定義相框的大小,View和Viewr在屏幕上的大小,位置
osgGA::TrackballManipulator* Tman,//放置相機的位置
int x, int y, int width, int height)//視口的大小,從多大的窗口看場景
{
double left,right,top,bottom,near,far, aspectratio;
double frusht, fruswid, fudge;
bool gotfrustum = true;
// 向最終的組合視口添加一個新的視口,並設置其操控方式。
//View是一個顯示窗口
osgViewer::View* view = new osgViewer::View;
//在查看器中加入顯示窗口
viewer->addView(view);
//顯示窗口設定自身相機的位置
view->setCameraManipulator(Tman);
// 設置視口的場景數據,並設置攝像機的截錐坐標。
//指定場景
view->setSceneData(scene.get());
//指定視口的大小,通過個窗口的大小來查看場景,也就是相片的大小
view->getCamera()->setViewport(new osg::Viewport(x,y, width,height));
//指定相機的焦距
view->getCamera()-> getProjectionMatrixAsFrustum(left,right,
bottom,top,
near,far);
if (gotfrustum)
{
aspectratio = (double) width/ (double) height;
frusht = top - bottom;
fruswid = right - left;
fudge = frusht*aspectratio/fruswid;
right = right*fudge;
left = left*fudge;
view->getCamera()-> setProjectionMatrixAsFrustum(left,right,
bottom,top,
near,far);
}
//指定相機中的圖像在屏幕上的顯示方法,與系統相關
view->getCamera()->setGraphicsContext(gc.get());
// 添加渲染狀態控制器
//這個控制器,控制圖的光照,材質等
osg::ref_ptr<osgGA::StateSetManipulator> statesetManipulator = new osgGA::StateSetManipulator;
statesetManipulator->setStateSet(view->getCamera()->getOrCreateStateSet());
view->addEventHandler( statesetManipulator.get() );
}
現在我們已經有了設置攝像機的函數,在仿真的其余部分中,我們將不再贅述有關基本場景建立(包括一個地形模型以及在其上運動的坦克)的內容。相關的代碼可 以從源程序中獲取。我們需要對坦克模型添加一個位移變換節點。這樣我們就可以將攝像機的位置置於坦克的后上訪,以便進行觀察。
int main( int argc, char **argv )
{
// 場景根節點和坦克模型節點指針。
osg::ref_ptr<osg::Group> rootNode;
osg::ref_ptr<osg::Group> ownTank;
osgGA::TrackballManipulator *Tman1 = new osgGA::TrackballManipulator();
osgGA::TrackballManipulator *Tman2 = new osgGA::TrackballManipulator();
// 建立場景和坦克。
if (!setupScene(rootNode, ownTank))
{
std::cout<< "problem setting up scene" << std::endl;
return -1;
}
// 聲明一個位於坦克偏后上方的位移變換節點。將其添加到坦克節點。
osg::PositionAttitudeTransform * followerOffset =
new osg::PositionAttitudeTransform();
followerOffset->setPosition( osg::Vec3(0.0,-25.0,10) );
followerOffset->setAttitude(
osg::Quat( osg::DegreesToRadians(-15.0), osg::Vec3(1,0,0) ) );
ownTank.get()->addChild(followerOffset);
// 聲明一個自定義的位移累加器類,以便放置相機。將其關聯給上面的變換節點。
transformAccumulator* tankFollowerWorldCoords = new transformAccumulator();
tankFollowerWorldCoords->attachToGroup(followerOffset);
// 構建視口類,以及與其相關的圖形設備類。
osgViewer::CompositeViewer viewer;
//得到屏幕接口
osg::GraphicsContext::WindowingSystemInterface* wsi =
osg::GraphicsContext::getWindowingSystemInterface();
if (!wsi)
{
osg::notify(osg::NOTICE)
<<"Error, no WindowSystemInterface available, cannot create windows."<<std::endl;
return 1;
}
unsigned int width, height;
//屏幕的分辨率
wsi->getScreenResolution(osg::GraphicsContext::ScreenIdentifier(0), width, height);
//設置圖形上下文的屬性
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
//x,y定義了viewer的起始位置,width和height定義了Viewer(顯示相框)的大小
traits->x = 100;
traits->y = 100;
traits->width = width;
traits->height = height;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
if (gc.valid())
{
osg::notify(osg::INFO)<<" GraphicsWindow has been created successfully."<<std::endl;
gc->setClearColor(osg::Vec4f(0.2f,0.2f,0.6f,1.0f));
gc->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
else
{
osg::notify(osg::NOTICE)<<" GraphicsWindow has not been created successfully."<<std::endl;
}
// 第一個視口
createView (&viewer,rootNode,gc,Tman1,0, 0, traits->width/2, traits->height);
// 第二個視口
createView (&viewer,rootNode,gc,Tman2,traits->width/2, 0, traits->width/2, traits->height);
viewer.setThreadingModel(osgViewer::CompositeViewer::SingleThreaded);
現在我們已經基本建立了仿真的代碼。它與前述的程序有少許不同。在每次更新場景圖形節點之后,我們都會手動重置控制器的位置,從而設置攝像機的方向和跟隨效果。另一個視口的攝像機則保持缺省的位置和接口不變。代碼如下所示:
while( !viewer.done() )
{
// 獲取跟蹤攝像機的句柄。使用相應的方法設置坦克跟蹤相機的世界坐標位置矩陣。
// 注意該矩陣需要從Y軸向上旋轉到Z軸向上的坐標系。
Tman2->setByInverseMatrix(tankFollowerWorldCoords->getMatrix()
*osg::Matrix::rotate( -M_PI/2.0, 1, 0, 0 ));
viewer.frame();
}
return 0;
}
Camera::setViewPort決定了相片也就是圖像的大小。
osg::GraphicsContext::Traits的Width,Height 定義了在屏幕上顯示的相框的大小。
一般ViewPort和相框會一樣大,如果ViewPort大於相框,則在相框中只顯示一部分的場景。
如果ViewPort小於相框,相框中會一部分的空白。
在OSG中,使用Camera得到圖像,並通過設置其中的GraphicsContext顯示到屏幕上
。在View和Viewer中都有一個或多個Camera,實現對場景的顯示和對GUI事件的
反應。
關於其中的概念解釋:
摘自OpenGL 基礎圖形編程-OpenGL變換8
相機模型:
在真實世界里,所有的物體都是三維的。但是,這些三維物體在計算機世界中卻必須以二維平面物體的形式表現出來。
那么,這些物體是怎樣從三維變換到二維的呢?下面我們采用相機(Camera)模擬的方式來講述這個概念,
如下圖所示:

實際上,從三維空間到二維平面,就如同用相機拍照一樣,通常都要經歷以下幾個步驟 (括號內表示的是相應的圖形學概念):
第一步,將相機置於三角架上,讓它對准三維景物(視點變換,Viewing Transformation)。
第二步,將三維物體放在適當的位置(模型變換,Modeling Transformation)。
第三步,選擇相機鏡頭並調焦,使三維物體投影在二維膠片上(投影變換,Projection Transformation)。
第四步,決定二維像片的大小(視口變換,Viewport Transformation)。
這樣,一個三維空間里的物體就可以用相應的二維平面物體表示了,也就能在二維的電腦屏幕上正確顯示了。
三維圖形顯示流程
運用相機模擬的方式比較通俗地講解了三維圖形顯示的基本過程,但在具體應用OpenGL函數庫編程時,還必須了解三維圖形世界中的幾個特殊坐標系的概念,
以及用這些概念表達的三維圖形顯示流程。
計算機本身只能處理數字,圖形在計算機內也是以數字的形式進行加工和處理的。大家都知道,坐標建立了圖形和數字之間的聯系。
為了使被顯示的物體數字化,要在被顯示的物體所在的空間中定義一個坐標系。
這個坐標系的長度單位和坐標軸的方向要適合對被顯示物體的描述,這個坐標系稱為世界坐標系。
計算機對數字化的顯示物體作了加工處理后,要在圖形顯示器上顯示,這就要在圖形顯示器屏幕上定義一個二維直角坐標系,這個坐標系稱為屏幕坐標系。
這個坐標系坐標軸的方向通常取成平行於屏幕的邊緣,
坐標原點取在左下角,長度單位常取成一個象素的長度,大小可以是整型數。
為了使顯示的物體能以合適的位置、大小和方向顯示出來,必須要通過投影。投影的方法有兩種,即正射投影和透視投影。
有時為了突出圖形的一部分,只把圖形的某一部分顯示出來,這時可以定義一個三維視景體(Viewing Volume)。
正射投影時一般是一個長方體的視景體,透視投影時一般是一個棱台似的視景體。
只有視景體內的物體能被投影在顯示平面上,其他部分則不能。
在屏幕窗口內可以定義一個矩形,稱為視口(Viewport),視景體投影后的圖形就在視口內顯示。
為了適應物理設備坐標和視口所在坐標的差別,還要作一適應物理坐標的變換。這個坐標系稱為物理設備坐標系。
根據上面所述,三維圖形的顯示流程應如圖8-2所示。

1)視點變換。視點變換是在視點坐標系中進行的。相當於放置相機的位置。
對應MatrixManipulator類
2)模型變換。模型變換是在世界坐標系中進行的。相當於布景。在這個坐標系中,
可以對物體實施平移glTranslatef()、旋轉glRotatef()和放大縮小glScalef()。
對應PositionAttitudeTransform類 和 MatrixTransform類,
3)投影變換。投影變換類似於選擇相機的鏡頭。本例中調用了一個透視投影函數glFrustum(),
在調用它之前先要用glMatrixMode()說明當前矩陣方式是投影GL_PROJECTION。
這個投影函數一共有六個參數,由它們可以定義一個棱台似的視景體。即視景體內的部分可見,
視景體外的部分不可見,這也就包含了三維裁剪變換。
3.1正射投影(Orthographic Projection)
正射投影,又叫平行投影。這種投影的視景體是一個矩形的平行管道,也就是一個長方體,
如下圖所示。正射投影的最大一個特點是無論物體距離相機多遠,投影后的物體大小尺寸不變。

這種投影通常用在建築藍圖繪制和計算機輔助設計等方面,這些行業要求投影后的物體尺寸
及相互間的角度不變,以便施工或制造時物體比例大小正確。
OpenGL正射投影函數共有兩個,這在前面幾個例子中已用過。一個函數是:
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top,
GLdouble near,GLdouble far)
在OSG中:
void osg::Camera::setProjectionMatrixAsOrtho
(double left, double right, double bottom, double top, double zNear, double zFar)
它創建一個平行視景體。實際上這個函數的操作是創建一個正射投影矩陣,並且用這個
矩陣乘以當前矩陣。其中近裁剪平面是一個矩形,矩形左下角點三維空間坐標是(left,bottom,-near),
右上角點是(right,top,-near);遠裁剪平面也是一個矩形,左下角點空間坐標是(left,bottom,-far
),右上角點是(right,top,-far)。所有的near和far值同時為正或同時為負。
如果沒有其他變換,正射投影的方向平行於Z軸,且視點朝向Z負軸。
這意味着物體在視點前面時far和near都為負值,物體在視點后面時far和near都為正值。
另一個函數是:
void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)
在OSG中
void setProjectionMatrixAsOrtho2D
(double left, double right, double bottom, double top)
它是一個特殊的正射投影函數,主要用於二維圖像到二維屏幕上的投影。它的near和far缺省值
分別為-1.0和1.0,所有二維物體的Z坐標都為0.0。
因此它的裁剪面是一個左下角點為(left,bottom)、右上角點為(right,top)的矩形。
3.2透視投影(Perspective Projection)
透視投影符合人們心理習慣,即離視點近的物體大,離視點遠的物體小,遠到極點即為消失,成為滅點。
它的視景體類似於一個頂部和底部都被切除掉的棱椎,也就是棱台。
這個投影通常用於動畫、視覺仿真以及其它許多具有真實性反映的方面。
OpenGL透視投影函數也有兩個,其中函數glFrustum()在8.1.3節中提到過,它所形成的視景體如圖8-10所示。

這個函數原型為:
void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,
GLdouble near,GLdouble far);
在OSG中為:
void osg::Camera::setProjectionMatrixAsFrustum
(double left, double right, double bottom, double top, double zNear, double zFar)
它創建一個透視視景體。其操作是創建一個透視投影矩陣,並且用這個矩陣乘以當前矩陣。這個函數的參數
只定義近裁剪平面的左下角點和右上角點的三維空間坐標,即(left,bottom,-near)和(right,top,-near);
最后一個參數far是遠裁剪平面的Z負值,其左下角點和右上角點空間坐標由函數根據透視投影原理自動生成。
near和far表示離視點的遠近,它們總為正值。
另一個函數是:
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);
在OSG中
void setProjectionMatrixAsPerspective
(double fovy, double aspectRatio, double zNear, double zFar)
它也創建一個對稱透視視景體,但它的參數定義於前面的不同,如圖8-11所示。其操作是創建一個對稱的透視投影矩陣,
並且用這個矩陣乘以當前矩陣。
參數fovy定義視野在X-Z平面的角度,范圍是[0.0, 180.0];參數aspect是投影平面寬度與高度的比率;參數zNear和Far分別
是遠近裁剪面沿Z負軸到視點的距離,它們總為正值。
以上兩個函數缺省時,視點都在原點,視線沿Z軸指向負方向。
4)視口變換。視口變換就是將視景體內投影的物體顯示在二維的視口平面上。通常,都調用函數glViewport()來定義一個視口,
這個過程類似於將照片放大或縮小。
用法。運用相機模擬方式,我們很容易理解視口變換就是類似於照片的放大與縮小。在計算機圖形學中,它的定義是將經過幾何變換
、投影變換和裁剪變換后的物體顯示於屏幕窗口內指定的區域內,
這個區域通常為矩形,稱為視口。OpenGL中相關函數是:
glViewport(GLint x,GLint y,GLsizei width, GLsizei height);
在OSG中
void setViewport (int x, int y, int width, int height)
這個函數定義一個視口。函數參數(x, y)是視口在屏幕窗口坐標系中的左下角點坐標,參數width和height分別是
視口的寬度和高度。缺省時,參數值即(0, 0, winWidth, winHeight) 指的是屏幕窗口的實際尺寸大小。
所有這些值都是以象素為單位,全為整型數。
注意:在實際應用中,視口的長寬比率總是等於視景體裁剪面的長寬比率。如果兩個比率不相等,那么投影后的圖像顯示於
視口內時會發生變形,如圖8-14所示。另外,屏幕窗口的改變一般不明顯影響視口的大小。
因此,在調用這個函數時,最好實時檢測窗口尺寸,及時修正視口的大小,保證視口內的圖像能隨窗口的變化而變化,且不變形。