Coin3D三維可視化教程7

使用燈光和照相機

在前面的章節中,我們介紹了組、屬性、和形體等節點,並且演示瞭如何使用這些節點來創建場景。現在,我們將要學習可以影響到 3D 圖形場景外觀的兩個節點類:燈光和照相機。在 Inventor 中,如同在現實世界裏那樣,燈光提供照明以便我們觀察物體。如果在一個場景中沒有包含任何燈光,並且當前的光照模型是缺省的 Phong lighting( 一種特定的光照計算公式,是 Bui Tuong Phong 於 1973 年發明的算法。譯者注一種特定的光照計算公式,是 Bui Tuong Phong 於 1973 年發明的算法。譯者注 ),那麼場景中的物體都將處於黑暗中並且變得不可見。就像在現實世界中有多種光照類型那樣-電燈泡、太陽、舞臺燈-Inventor同樣也提供了不同類型的燈光供我們在場景中使用。
照相機是我們觀察場景的“眼睛”。Inventor 提供了一個和人類眼睛具有相同透視方式的照相機類。同時,Inventor 還另外提供另外一種用於產生場景 2D“快照”的照相機類,這種照相機使用另外一種類型的透視方式。本章將首先討論照相機,並且假設在場景的最頂
端處至少存在一個燈光節點。

提示:觀察器組件(Viewer components)將自動創建自己的照相機和燈光。更多內容請閱讀第 16 章

照相機

照相機節點可以對場景中所有位於它之後的節點“拍攝”一張照片。因爲照相機必須位於我們想要觀察的物體之前,所以,通常要將照相機放在靠近場景最頂端的位置上。一個場景在同一時刻只能有一個激活的照相機。當前幾何座標變換將會影響照相機的空間位置。

SoCamera
所有照相機節點類都是從抽象基類 SoCamera 派生出來的。(見圖 4-1)

SoCamera 具有下列域:

viewportMapping
(SoSFEnum)
用於處理當照相機鏡頭的橫縱比與視口窗口的橫縱比不同的情況下,如
何協調它們之間的映射關係.(詳見“將照相機鏡頭的橫縱比映射變換到
視口窗口的橫縱比上”)
position
(SoSFVec3f)
用於定位照相機的視點位置。當前幾何變換可以修改這個位置。
orientation
(SoSFRotation) )
照相機的觀察方向。這個域用來描述照相機是如何相對於缺省方向進行
旋轉的。缺省情況下,照相機的觀察方向是從(0.0, 0.0, 1.0)的位置指向
座標系原點,照相機的上方向(up direction)是(0.0,1.0, 0.0)。 這
個域連同當前幾何變換可以指定照相機在世界座標系下(又稱全局座標
系)的觀察方向。
aspectRatio
(SoSFFloat)
照相機視口的寬高比.這個值必須大於 0 .在 SoCamera.h 中預定義了一
些寬高比數值:
SO_ASPECT_SQUARE (1/1)
SO_ASPECT_VIDEO (4/3)
SO_ASPECT_HDTV (16/9)
nearDistance
(SoSFFloat)
照相機的視點到近剪裁面的距離
farDistance
(SoSFFloat)
照相機的視點到遠剪裁面的距離
focalDistance
(SoSFFloat)
照相機的視點到焦點的距離(通常用於 examiner 觀察器)

當進行渲染遍歷時,如果遇到的是一個照相機節點,Inventor 將執行下列步驟:
1. 在執行渲染動作期間,首先在場景中定位照相機。(根據照相機的 position 和orientation 域指定照相機的位置和方向。同時修改當前的幾何座標變換也可以影響照相機的位置和方向)。
2. 照相機根據遠近剪裁平面、橫縱比、高度和高度角(依賴於照相機的類型)創建一個取景裁剪體(view volume)。取景裁剪體通常也叫做視圖截錐(viewingfrustum),它是一個用來包圍所要觀察物體的六面錐臺體。所有在取景裁剪體之外物體都將被剪裁丟棄。(本節的最後,有圖表將演示不同類型的照相機是如何創建取景裁剪體的)。
3. 下一步,將 3D 取景裁剪體中的物體壓縮映射到一張 2D 照片上,這個過程類似於使用光學照相機對真實世界進行拍攝的過程。然後將 2D 照片簡單地映射到屏幕的 2D 窗口中 (見“將照相機的橫縱比映射到視口上”)。
4. 接下來,使用照相機創建出來的投影矩陣來渲染圖形場景的其餘部分。

我們可以使用pointAt()方法來修改照相機orientation的域值。這個方法可以將照相機的方向指向一個特定的目標點。如果可能的話,它將儘可能保持照相機的“上方向”平行於+Y軸方向。否則將保持照相機的“上方向”平行於+Z軸方向。pointAt()方法的語法如下:

void pointAt(const SbVec3f &targetPoint )

SoCamera另外的兩個常用方法是viewAll()和getViewVolume() 。viewAll()方法可以很容易地讓照相機使用當前的方向來觀察整個場景。這個方法需要提供被觀察場景的根節點作爲第一個參數(通常這個根節點要包含本照相機節點),以及需要提供渲染動作所要使用的視口區域(viewport region)作爲第二個參數。slack參數通常用於定位遠近剪裁平面。slack等於1.0(缺省值)將使遠近剪裁平面“最緊密地包圍住”(tightest fit)整個場景。viewAll()的語法如下:

void viewAll(SoNode *sceneRoot , const SbViewportRegion vpRegion, float slack = 1.0)

viewAll()方法會修改照相機的position 、nearDistance、farDistance域值。它不影響照相機的方向值。在“使用不同照相機觀察場景”的章節中有如何使用viewAll()的例子。
getViewVolume()方法返回照相機的取景裁剪體,通常用於和拾取操作有關的功能。

SoCamera 的子類:

SoCamera類包括兩個子類,如圖 4-1 所示:

  • SoPerspectiveCamera
  • SoOrthographicCamera

SoPerspectiveCamera

SoPerspectiveCamera照相機類可以模擬人眼的功能:遠處的物體變小,近處的物體變大。如果想模擬物體是怎樣顯示在人類的眼中,使用透視投影照相機是最自然不過的事情了。
SoPerspectiveCamera節點除了有SoCamera類所定義的所有域外,還另外附帶一個域:heightAngle (SoSFFloat) 指定取景裁剪體的垂直高度角(弧度單位)
SoPerspectiveCamera節點所定義的取景裁剪體是一個如圖 4-2 所示的截棱錐。高度角和橫縱比按照下面的公式來決定寬度角:

widthAngle = heightAngle * aspectRatio

SoOrthographicCamera

相對於透視投影照相機,SoOrthographicCamera 類所代表的是平行投影(parallel projections)照相機。平行投影方式不會因爲距離的原因而使圖像發生變形。對於某些需要確保精度的設計工作,視覺的變形有可能對準確測量產生干擾。所以平行投影照相機對這類工作特別有用。
SoOrthographicCamera節點除了有SoCamera類所定義的所有域外,還另外有一個域:height (SoSFFloat) 指定取景裁剪體的高度。
SoOrthographicCamera 所定義的取景裁剪體是一個如圖 4-3 所示的長方體。高度和橫縱比按照下面的公式來決定矩形的寬度:

width = height * aspectRatio

將照相機的橫縱比映射到視口上

視口(viewport)是窗口用於顯示被渲染場景的矩形區。缺省情況下,視口和窗口(SoXtRenderArea) 的尺寸大小相同。當構造SoGLRenderAction (見第 9 章)對象時,需要指定視口作爲構造函數的其中的一個參數。
SoCamera的viewportMapping域是用來指定當照相機的橫縱比和視口不同時,如何將照相機的投影映射到視口上。前三個選項是通過修改視口來匹配照相機的投影。這三個選項的優點是照相機的橫縱比保持不變 (缺點是視口可能會有空白區(dead space)出現)。

  • CROP_VIEWPORT_FILL_FRAME 調整視口以匹配照相機。使用最適當的橫縱比率來繪製視口。同時在沒有用的地方填充上灰色。
  • CROP_VIEWPORT_LINE_FRAME 調整視口以匹配照相機。使用框線來繪製視口的邊界。
  • CROP_VIEWPORT_NO_FRAME 調整視口以匹配照相機。不繪製視口的邊界.

下面兩個選項是調整照相機來匹配視口:

  • ADJUST_CAMERA 調整照相機以匹配視口。照相機的投影圖像是正常顯示的,沒有產生變形。(實際上是,保存在aspectRatio和height/heightAngle域中的數據是沒有被修改的。如果視口映射需要的話,這些值只是臨時地覆蓋掉)。這個選項是缺省選項。
  • LEAVE_ALONE 不修改任何數據。調整照相機的圖像大小來匹配視口。這將有可能產生一個變形的圖像。
     

使用不同類型的照相機觀察場景

例 4-1 演示了在不同的位置上使用一個平行投影照相機和兩個透視投影照相機來觀察場景的代碼。例子使用了一個頻閃節點(blinker node)(見 13 章描述)來切換這三個照相機節點。場景(一個公園長椅)是從一個文件中讀取的。圖 4-5 顯示了例子中圖形場景的結構。

#include <Inventor/SbLinear.h>
#include <Inventor/SoDB.h>
#include <Inventor/SoInput.h>
#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/SoQtRenderArea.h>
#include <Inventor/nodes/SoBlinker.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoOrthographicCamera.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoTransform.h>

int main(int argc, char* argv[]) {
	// Initialize Inventor and Qt
	QWidget *myWindow = SoQt::init(argv[0]);
	if (myWindow ==NULL)
	{
		exit(1);
	}
	SoSeparator* root = new SoSeparator;
	root->ref();
	// Create a blinker node and put it in the scene. A blinker
	// switches between its children at timed intervals.
	SoBlinker* myBlinker = new SoBlinker;
	root->addChild(myBlinker);

	// Create three cameras.Their positions will be set later.
	// This is because the viewAll method depends on the size
	// of the render area, which has not been created yet.
	SoOrthographicCamera * orthoViewAll = new SoOrthographicCamera;
	SoPerspectiveCamera* perspViewAll = new SoPerspectiveCamera;
	SoPerspectiveCamera* perspOffCenter = new SoPerspectiveCamera;
	myBlinker->addChild(orthoViewAll);
	myBlinker->addChild(perspViewAll);
	myBlinker->addChild(perspOffCenter);

	// Create a light
	root->addChild(new SoDirectionalLight);
	// Read the object from a file and add to the scene
	SoInput myInput;
	if (!myInput.openFile("parkbench.iv"))
		return 1;
	SoSeparator* fileContents = SoDB::readAll(&myInput);

	if (fileContents == NULL)
		return 1;
	SoMaterial* myMaterial = new SoMaterial;
	myMaterial->diffuseColor.setValue(0.8, 0.23, 0.03);
	root->addChild(myMaterial);
	root->addChild(fileContents);
	SoQtRenderArea* myRenderArea = new SoQtRenderArea(myWindow);
	// Establish camera positions.
	// First do a viewAll() on all three cameras.
	// Then modify the position of the off-center camera.
	SbViewportRegion myRegion(myRenderArea->getSize());
	orthoViewAll->viewAll(root, myRegion);
	perspViewAll->viewAll(root, myRegion);
	perspOffCenter->viewAll(root, myRegion);
	SbVec3f initialPos;
	initialPos = perspOffCenter->position.getValue();
	float x, y, z;
	initialPos.getValue(x, y, z);
	perspOffCenter->position.setValue(x + x / 2., y + y / 2., z + z / 4.);
	myRenderArea->setSceneGraph(root);
	myRenderArea->setTitle("Cameras");
	myRenderArea->show();
	SoQt::show(myWindow);
	SoQt::mainLoop();

	return EXIT_SUCCESS;
}

parkbench with different camera

燈光

當使用缺省光照模式時(Phong 模式),在可以觀察物體之前,場景中必須包含至少一個燈光節點。在執行渲染動作期間,如果遍歷遇到了場景中的燈光節點,渲染將開啓這個燈光節點(During a rendering action, traversing a light node in the scene graph turns that light on.)。場景中燈光節點的位置可以決定兩件事情:

  • ? 燈光將照亮那些物體?- 在圖形場景中,燈光節點可以照亮跟隨它之後任何物體。(燈光參數是遍歷狀態的一部分(見第 3 章中的描述)。使用SoSeparator節點可以將一個特定燈光節點所產生的效果與圖形場景中其它部分隔離開來 (即燈光節點如果是包含在一個即燈光節點如果是包含在一個SoSeparator 節點內, 則這個燈光節點只會照亮SoSeparator 節點內的物體。對於SoSeparator 節點外的物體將不會產生影響。譯者注) )
  • ? 燈光在 3D空間中位於何處? - 有些燈光節點(例如,SoPointLight)有一個location域。燈光的位置是受到當前幾何座標變換影響的。另外一些光源節點有一個direction域(例如,SoDirectionalLight),同樣,燈光的方向也是受到當前幾何座標變換影響的。

關於所有光源節點的另一個重要事實是:燈光效果是累積的。每當向圖形場景中增加一個燈光節點時,場景就會變得亮一些。Open Inventor 能開啓的最大燈光數依賴於系統當前OpenGL 的具體實現。

SoLight

所有的燈光類都是從抽象基類SoLight派生出來的。SoLight是從SoNode派生出來的,它沒有增加新的函數。SoLight有下列的域:

  • on (SoSFBool) 燈光是否開啓
  • intensity (SoSFFloat) 燈光的亮度.數值的範圍從 0.0(無光)到 1.0(最大亮度)
  • color (SoSFColor) 燈光的顏色

SoLight 的子類

SoLight類有三個子類,如圖 4-7:

  • ? SoPointLight

  • ? SoDirectionalLight

  • ? SoSpotLight

圖 4-8 演示了不同類型的燈光效果。左邊的圖表示的是光線的方向。右邊的圖顯示了在相同場景下不同燈光類型渲染的效果。
提示:射燈(Directional lights)通常要比點光源(point lights)渲染的要快。它們倆同時又都比聚光燈(spotlights)渲染的要快。所以,如果想增加渲染速度的話,就要儘量使用少量、簡單的燈光。

SoPointLight

SoPointLight燈光類,就像一顆星星那樣,在給定的 3D空間位置上,均勻地向四周放射光線。SoPointLight節點有一個附加的域:location (SoSFVec3f) 點光源的 3D空間位置(這個位置是受到當前幾何變換影響的)

SoDirectionalLight

SoDirectionalLight燈光類只是均勻地按照一個方向放射光線。因爲它的光照距離是無限的,所以它不需要指定 3D空間位置。SoDirectionalLight節點有一個附加的域:direction (SoSFVec3f) 指定射燈發出光線的方向。(這個方向是受到當前幾何變換影響的)。

提示:如果一個平面只是由一個多邊形組成的(例如是一個大的矩形),並且這個多邊形每個頂點的法向向量都相同,那麼 Inventor 將不會顯示出點光源的任何效果.這是因爲燈光計算(使用 OpenGL)只是針對於每個頂點而言的。只有複雜的表面才能顯示理想的效果.( 這裏所說的點光源的效果,就是指當點光源照射一個由多個多邊形組成的複雜物體時,在物體的表面會有一處是最亮的,在這個最亮處的周圍會逐漸暗下來.而如果點光源照射的是隻是一個多邊形的話,受到 OpenGL 的限制,在這個多邊形上就不會出現上述的效果,多邊形依照與光源位置的相對關係,要麼整個都是明的,要麼整個都是暗的,不會出現中間過渡的部分.譯者注)

使用多個燈光

現在我們可以試驗向場景中增加不同類型的燈光。例 4-2 中包含有兩個光源:一個固定位置的紅色射燈源和一個綠色的點光源。綠色的點光源被 SoShuttle 節點(見 13 章)所控制,前後來回往復運動。圖 4-10 給出了這個例子的整個場景圖。

#include <Inventor/SoDB.h>
#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPointLight.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoShuttle.h>
#include <Inventor/nodes/SoTransformSeparator.h>

int main(int argc, char* argv[]) {
	// Initialize Inventor and Qt
	QWidget *myWindow = SoQt::init(argv[0]);
	if (myWindow == NULL)
		exit(1);
	SoSeparator* root = new SoSeparator;
	root->ref();
	// Add a directional light
	SoDirectionalLight* myDirLight = new SoDirectionalLight;
	myDirLight->direction.setValue(0, -1, -1);
	myDirLight->color.setValue(1, 0, 0);
	root->addChild(myDirLight);
	// Put the shuttle and the light below a transform separator.
	// A transform separator pushes and pops the transformation
	// just like a separator node, but other aspects of the state
	// are not pushed and popped. So the shuttle's translation
	// will affect only the light. But the light will shine on
	// the rest of the scene.
	SoTransformSeparator* myTransformSeparator =
		new SoTransformSeparator;
	root->addChild(myTransformSeparator);
	
	// A shuttle node translates back and forth between the two
	// fields translation0 and translation1.
	// This moves the light.
	SoShuttle* myShuttle = new SoShuttle;
	myTransformSeparator->addChild(myShuttle);
	myShuttle->translation0.setValue(-2, -1, 3);
	myShuttle->translation1.setValue(1, 2, -3);
	
	// Add the point light below the transformSeparator
	SoPointLight* myPointLight = new SoPointLight;
	myTransformSeparator->addChild(myPointLight);
	myPointLight->color.setValue(0, 1, 0);
	root->addChild(new SoCone);
	SoQtExaminerViewer* myViewer =
		new SoQtExaminerViewer(myWindow);
	myViewer->setSceneGraph(root);
	myViewer->setTitle("Lights");
	myViewer->setHeadlight(FALSE);
	myViewer->show();
	SoQt::show(myWindow);
	SoQt::mainLoop();
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章