作者/來源:未知
VRML創作工具很多是“所見即所得”式的,通過圖形界面可以方便地創作虛擬境界,但VRML不僅僅是普通的三維設計,盡管這些工具很容易上手,卻往往屏蔽掉了VRML標準的具體細節,因為如果想深入掌握VRML,還需要全面了解節點、域、檢測器等技術細節,而達成此目的的最好方法就是用編寫文本文件的方式創作VRML境界。本教程提供了六個典型例子,這些例子并不復雜,也不精彩,但涵蓋了VRML的關鍵內容。
在開始創作之前,應作好下面的準備。
文本編輯器 隨便你喜歡的文本編輯器,如Win95下的NotePad,Dos下的Edit等等。
VRML瀏覽器 若用的Web瀏覽器是Netscape4.0一下版本,可下載CosmoPlayer(http://cosmo.sgi.com);若用的是Netscape4.0或更高版本,則已內置CosmoPlayer2.0,只是安裝Netscape時請注意是否選中了相應選項;若用的是Internet Explore4.0,則有可能已經內置了VRML2.0瀏覽器,判斷是否內置的方法很簡單,就是看它能否打開VRML文件(*.wrl,*.wrz),如果不行,可以從http://www.microsoft.com/vrml/下載VRML瀏覽器插件,對于IE3.x,還需要下載一些輔助插件。當然在開始之前應基本熟悉VRML瀏覽器的操作方法。
硬件 VRML和硬件平臺無關,只要能提供VRML瀏覽器。在下面的教程中,我們假定硬件平臺是微機,輸出設備是圖形窗口,輸入設備為鼠標器和鍵盤。當然,如果有更先進的虛擬現實設備和支持它的VRML瀏覽軟件效果會更好。對于我們將要創作的境界,微機就足夠了。
資料 本站就是最全面的資料,遇到新概念時可查閱本站相關資料。
第一節 \"Hello,World!\"
按照慣例,我們以\"Hello,World!\"作為我們的第一個虛擬境界,它由立方體、圓錐和球體組成,你可能已經注意到,VRML的標志正是由這三個幾何形狀構成的。輸入的第一行文字是:
#VRML V2.0 utf8
這是VRML文件的標志,所有2.0版本的VRML文件都以這行文字打頭,VRML97是由VRML2.0版修訂而成的,符合VRML97規范的VRML文件也以這行文字打頭。其中“#”表示這是一個注釋。而utf8表示此文件采用的是utf8編碼方案,這在標準中有詳細說明。
先加入一個Group節點(組節點):
Group {
組節點的花括號之內的所有內容視為一個整體,利用組節點可以把虛擬場景組織成條理清晰的樹形分支結構。下面定義組節點的children域(孩子域):
children [
在children后的方括號內定義Group節點的所有孩子對象,第一個孩子是一個Shape節點(形態節點),它描述一個幾何形狀及其顏色等特征:
Shape {
在Shape 節點內定義一個幾何體Box(方盒節點):
geometry Box {}
注意我們沒有為Box定義任何域,這意味著它的尺寸和坐標位置等特性取缺省值(單位立方體)。隨后補齊各右括號:
}
]
}
至此,我們已經成功地制作了第一個虛擬境界,把它保存為Hello World.wrl,下面是完整的文件:
#VRML V2.0 utf8
Group {
children [
Shape {
geometry Box {}
}
]
}
用瀏覽器打開這個文件,你會看到一個灰色的立方體,盡管不太好看,但你還是可以通過改變視點位置從不同方位觀察它,初步體驗“三維交互”的感覺。
下面定義立方體的外觀,這只需改變Shape節點的appearance域(外觀),appearance 域是一個Appearance 節點,此Appearance節點的material域(材質)定義為一個Material 節點:
appearance Appearance {
material Material {}
}
這樣,上面的Shape節點變成了:
Shape {
appearance Appearance {
material Material {}
}
geometry Box {}
}
這是定義幾何造型的基本格式?,F在立方體還是灰色的,這是因為其中的Material節點采用的還是缺省值,下面修改它的diffuseColor域(漫射色),VRML的顏色說明采用的是RGB顏色模型,所以要定義紅色的立方體,漫射色應該是{1 0 0},三個數字依次表示紅色、綠色和藍色,取值范圍都是0到1:
material Material {diffuseColor 1 0 0 }
現在我們生成了第二個場景,完整的代碼是:
#VRML V2.0 utf8
Group {
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
]
}
在這個場景中,紅色的立方體位于屏幕的中心,它的中心坐標為{0 0 0 }。若想把它移動一個位置,可以通過為它外套一個Transform(變換節點)來實現:
Transform {
translation 5 0 0
children [
Shape {
appearance Appearance {
material Material {}
}
geometry Box {}
}
]
}
在VRML中,Transform節點除了可以引進平移、旋轉和縮放變換以外,其作用和Group節點的作用一樣。把Transform 節點的translation域(平移)設置為5 0 0,意味著Transform節點所在的坐標系相對于其上層坐標系向右平移(即x軸方向)5個單位,在其它兩個方向不移動,VRML的距離單位是米,5個單位相當于5米。我們第三個場景的完整代碼是:
#VRML V2.0 utf8
Group {
children [
Transform {
translation 5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
]
}
]
}
接下來我們把方塊所在的Transform節點復制三份,并把各自包含的幾何形狀依次定義為方塊、球體和圓錐:
Group {
children [
Transform {
translation 5 0 0
children [
Shape { .... geometry Box {} }
]
}
Transform {
translation 0 0 0
children [
Shape { ... geometry Sphere {} }
]
}
Transform {
translation -5 0 0
children [
Shape { ... geometry Cone {} }
]
}
]#end of Group children
}
你可能已經感覺到,VRML文件中有許多括號(花括號“{}”和方括號“[]”),所以務請注意括號的配對,建議采用本教程的縮進風格。注意上面的VRML文件中三個Transform節點的平移量是不同的,因而三個幾何體的位置也就不同。另外,還可以修改三個幾何體的顏色:球面Sphere為綠色(0 1 0),圓錐為藍色( 0 1 0 )。最后,為了以后引用方便,分別給這三個Transform 節點指定一個名稱:
DEF box Transform {...}
DEF sphere Transform {...}
DEF cone Transform {...}
這個VRML場景的完整代碼是:
#VRML V2.0 utf8
Group {
children [
DEF box Tranform {
translation 5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
]
}
DEF sphere Transform {
translation 0 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 1 0 }
}
geometry Sphere {}
}
]
}
DEF cone Transform {
translation -5 0 0
children [
Shape {
appearnance Appearance {
material Material { diffuseColor 0 0 1 }
}
geometry Cone { }
}
]
}
]# end of Group children
}
把此文件保存為helloworld.wrl,用VRML瀏覽器打開這個文件,通過調整視點從多個方位瀏覽自己的作品。
小結:在這一節,我們創建了第一個虛擬境界,涉及到如何用幾何體構建境界,以及如何設定幾何體的顏色與材質。盡管這個由方塊、圓錐和球體組成的場景圖比較簡單,但已經反映了VRML的基本功能。當然,除了可用鼠標改變視點外,這還只是一個靜態世界,在下一節,我們將引進VRML的動態特征。
第二節 增加交互能力
上一節我們學習了用幾何體建立虛擬境界以及為幾何體賦予色彩和材質的方法,這樣建立的虛擬境界是靜態的。這一節我們將使一個幾何體(為了更具一般性,下面我們稱之為對象)能夠根據用戶動作做出反應,即交互能力,這是VRML2.0最突出的特征。
1。檢測器
在VRML中,檢測器(Sensor)節點是交互能力的基礎。檢測器節點共九種。在場景圖中,檢測器節點一般是以其它節點的子節點的身份而存在的,它的父節點稱為可觸發節點,觸發條件和時機由檢測器節點類型確定。
接觸檢測器( TouchSensor)是最常用的檢測器之一,最典型的應用例子是開關。其它檢測器將在后續教程中陸續介紹。這里我們定義一個開關節點lightSwitch(這是一個組節點),并定義一個接觸檢測器作為它的子節點:
DEF lightSwitch Group {
children [
各幾何造型子節點...
DEF touchSensor TouchSensor {}
]
}
這樣開關節點lightSwitch就是一個可觸發節點。當然,檢測器存在的理由是它被觸發時能夠引起某種變化,所以在更深入討論開關節點之前,我們先討論一下場景變化。 2.視點
最常見的變化是視點的變化,在我們的第一個境界中你可能已經體驗到視點變化:當你拖動鼠標或按動箭頭鍵時(按照VRML術語,稱為航行),虛擬境界就會旋轉或縮放,這實際上是在調整你的視點位置或視角。在虛擬場景的重要位置可以定義視點節點(ViewPoint),它們是境界作者給用戶推薦的上佳觀賞方位,在CosmoPlayer瀏覽器中,用戶就可以通過鼠標右鍵選擇作者推薦的各個視點。這里我們定義兩個視點節點:
DEF view1 Viewpoint {
position 0 0 20
description \"View1\"
}
DEF view2 Viewpoint {
position 5 0 20
description \"view2\"
}
我們的潛在目的是使用戶可以通過觸發開關節點來切換視點?,F在先研究一下這兩個視點節點,其中的坐標表示視點在場景中的位置,坐標的單位是米,這在前面已經提到過,視點的名稱將會在瀏覽器菜單中提示出來供用戶選擇。把上述視點說明加入helloworld.wrl中(放在Group節點之前),并把其中的方塊節點修改成可觸發節點:
DEF box Tranform {
children [
Shape { .... Box ...}
DEF touchBox TouchSensor {}
]
}
把修改過的文件另存為“touchme.wrl”。
3。事件傳遞
下面我們把觸發(用鼠標箭頭按動方塊)和場景變化(視點切換)這兩件事情聯系起來,在場景圖中,除節點構成的層次體系外,還有一個“事件體系”,事件體系由相互通訊的節點組成。能夠接收事件的節點都應具有事件入口(eventIn),如果它要接收多種類型的事件(稱為入事件),它就應該具有多個事件入口,也就是說,事件入口象節點的域一樣是有類型的。同樣,發送事件的節點應有事件出口(eventOut),事件出口也是有類型的。例如ViewPoint節點就有一個事件入口set_bind,當向此事件送入一個值“TRUE”(即所謂的入事件)時,該viewpoint節點成為當前視點。又如,接觸檢測器TouchSensor有一個事件出口isActive,當受到用戶觸發后它就從此出口送出一個“TRUE”(即所謂的出事件),補充一句,在下一個事件發送之前,此事件一直保存在事件出口中(作為記錄)。
事件出口和事件入口通過路徑相連,這就是VRML文件中除節點以外的另一基本組成部分:ROUTE 語句。ROUTE語句把事件出口和事件入口聯系在一起,從而構成事件體系。在這里,我們是把接觸檢測器touchBox的事件出口isActive連接到視點節點view2的事件入口set_bind:
ROTUE touchBox.isActive TO view2.set_bind
好了!現在我們得到的VRML文件是:
#VRML V2.0 utf8
DEF view1 Viewpoint {
position 0 0 20
description \"view1\"
}
DEF view2 Viewpoint {
position 5 0 20
description \"view2\"
}
Group {
children [
DEF box Transform {
translation 5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0}
}
geometry Box {}
}
DEF touchBox TouchSensor {}
]
}
DEF sphere Transform {
translation 0 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 1 0}
}
geometry Sphere {}
}
]
}
DEF cone Transform {
translation -5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 0 1 }
}
geometry Cone {}
}
]
}
] #end of Group children
}
ROUTE touchBox.isActive TO view2.set_bind
把這個文件調入瀏覽器,然后把鼠標指向方塊并按下左鈕(先別松開?。?,可以看到視點已經變為view2,內部的機制我們已經很清楚:左鈕按下時方塊節點的接觸檢測器被觸發,接著接觸檢測器從事件出口isActive送出一個事件“TRUE”,這個事件通過路由進入視點節點view2的事件入口set_bind,view2收到“TRUE”后成為當前視點,所以在我們眼前場景發生了變化。
現在松開左鈕,可以看到場景恢復到原來方位,這種功能稱為視點回跳,其原因是松開左鈕后接觸檢測器向view2發送了一個“FASLE”事件,這樣view2當前的地位被解除,原來的視點成為系統視點棧的棧頂節點(即當前視點),詳細說明可參見標準中對視點節點的專門論述。如果我們不想視點回跳,就想停留在view2視點,那該怎么辦呢?這種非系統缺省功能要自己來定義。
4。 利用腳本編寫自定義行為 在VRML中,利用Script節點(腳本節點)定義用戶自定義行為,所謂定義即用腳本描述語言(Scripting Language)編寫腳本的過程。VRML97支持的腳本描述語言目前有兩種:Java和EMCAScript(這是JavaScript標準化后的名稱),關于這兩種語言本身,請參考相應參考書,VRML97標準中定義了它們和VRML的接口方法。應提請注意的是:VRML是基于節點的語言,所以腳本也是封裝在Script這個特殊節點中的。這里我們不過多討論腳本描述語言的細節,主要討論把腳本集成到VRML文件中的方法。
上面我們曾把接觸檢測器touchBox 和視點view2直接通過路徑連接起來,現在要定義我們指定的行為,就需要在二者之間插入一個腳本節點,也就是讓路徑繞個彎: ROUTE touchBox.isActive TO touchScript.touchBoxIsActive
ROUTE touchScript.bindView2 TO view2.set_bind
其中的腳本節點touchScript有一個事件人口touchBoxIsActive和一個事件出口bind_View2,前者接收來自接觸檢測器touchBox的事件,然后經自己的腳本處理后,把結果發送給視點節點view2:
DEF touchScript Script {
eventIn SFBool touchBoxIsActive
eventOut SFBool bindView2
url\"javescript:
function touchBoxIsActive(active) {
bindView2= TRUE;
}\"
}
關于這個Script節點,請注意一下幾點:(1)它的事件入口touchBoxIsActive和事件出口bindView2是自定義的,其它VRML節點的域和事件都是固定的。(2)事件入口touchBoxIsActive(即入事件)和事件出口bindView2(即出事件)的類型都是SFBool(單值布爾型),touchBox的事件出口isActive和view2的事件入口set_bind的類型也是相同的。(3)“url”是腳本節點的一個域,可以直接包含腳本,也可以包含一個或多個用URL地址指示的腳本,若有多個地址,則按照先后次序獲取第一個可得到的腳本。(4)腳本是以函數(function)的形式給出的,函數名touchBoxIsActive 與事件入口的名稱相同,這是和ECMAScript語言的接口約定,表示相應事件入口收到事件后調用此函數進行處理。
5.事件流程與小結
下面我們整理一下事件流程:
(1)用戶在方塊上按下鼠標左鍵。
(2)接觸檢測器發出一個“TRUE”事件。
(3)此事件進入腳本節點touchScript的事件入口touchBoxIsActive.
(4)調用腳本函數touchBoxIsActive(注意函數并沒有判斷入事件的值)。
(5)函數向touchScript的事件出口bindView2發送一個“TRUE”事件(還可以進行其它判斷或執行其它事件)。
(6)view2節點收到“TRUE”事件,成為當前視點。按照VRML約定,“認為”上述事件是同時發生的,也就是這些事件的時間戳相同。
(7)若用戶松開鼠標左鍵,則接觸檢測器發出一個“FALSE”事件,此事件同樣引起腳本函數調用并發送“TRUE”事件,所以view2仍然保持為當前視點。
本節的完整代碼是:
#VRML V2.0 utf8
DEF view1 Viewpoint {
position 0 0 20
description \"view1\"
}
DEF view2 Viewpoint {
position 5 0 20
description \"view2\"
}
Group {
children [
DEF box Transform {
translation 5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
DEF touchBox TouchSensor {}
]
}
DEF sphere Transform {
translation 0 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 1 0}
}
geometry Sphere {}
}
]
}
DEF cone Tranform {
transltion -5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 0 1 }
}
geometry Cone {}
}
]
}
] #end of Group children
}
DEF touchScript Script {
eventIn SFBool touchBoxIsActive
eventOut SFBool bindView2
url \"javascript :
function touchBoxIsActive (active) {
bindView2 = TRUE;
}\"
}
ROUTE touchBox.isActive TO touchScript.touchBoxIsActive
ROUTE touchScript.bindView2 TO view2.set_bind
小結:本節建立的虛擬境界并不復雜,但涉及到了VRML2.0最基礎性的功能和概念:利用檢測器產生事件、利用路由傳遞事件以及利用腳本編寫自定義行為,掌握了這些內容也就掌握了VRML2.0的核心。在后面的幾節中,我們將探索一些專題性的有趣功能,而本節是基礎,因而必須透徹理解。
第三節 鄰近檢測器
本節討論鄰近檢測器(proximitySensor),當用戶進入或離開鄰近檢測器所劃定的區域時就會觸發它。正如你在標準中可以查到的那樣,ProximitySensor節點定義為:
ProximitySensor {
exposedField SFVec3f center 0 0 0
exposedField SFVec3f size 0 0 0
exposedField SFBool enabled TRUE
eventOut SFBool isActive
eventOut SFVec3f position_changed
eventOut SFRotation orientation_changed
eventOut SFTime enterTime
eventOut SFTime exitTime
}
這里稍作介紹。ProximitySensor節點共有三個外露域(exposedField)和五個出事件(eventOut).出事件我們已經熟悉,是節點狀態發生改變時用來通知其它節點的,這里的出事件isActive 用于ProximitySensor通報自己已被激活。enterTime和exitTime通報用戶(代表用戶的用戶化身或指示器)進入和退出ProximitySensor檢測區的時刻。若用戶已在檢測器之內,則當用戶的位置或方位發生變化時,送出position_changed和orientation_changed出事件這五個出事件聯合起來,就定義了鄰近檢測器的功能。外露域則集域(Field)、入事件(eventIn)和出事件(eventOut)三者的功能于一身,也就是說,它既象域一樣描述了節點的當前狀態,又可以作為入事件由其它節點修改這種狀態,并作為出事件把這種改變通知其它節點。這里的enabled外露域是布爾型的,用于ProximitySensor的啟用和停用,center和size定義形為長方體的鄰近檢測區。
我們的出發點是第一節中建造的境界helloworld,它是由方塊、球體和圓柱這三個物體構成的靜態世界,現在在球體周圍增加一個鄰近檢測區:
DEF sphere Transform {
translation 0 0 0
children [
Shape {....}
DEF comeClose ProximitySensor {
center 0 0 0
size 4 4 4
}
]
}
ProximitySensor的名字為comeCloser,鄰近區的中心和球體的球心重合,形狀為正方體,邊長為4米,是球體直徑的兩倍。當用戶走進球體時就會觸發這個鄰近檢測器,檢測器發出isActive事件,我們把這個事件出口通過路由指向Script節點(用來綁定視點2):
DEF comeCloserScript Script {
eventIn SFBool enterProximitySensorIsActive
eventOut SFBool bindView2
url \" javascript :
function enterProximitySensorIsActive (active) {
bindView2=TRUE;
} \"
}
隨后,我們在鄰近檢測器的出事件isActive和腳本節點comeCloserScript的入事件enterProximitySensorIsActive之間建立路由,后者收到事件后執行函數enterProximitySensroIsActive,函數發出bindView2出事件,這個出事件通過路由連接到視點節點View2:
ROUTE comeCloser.isActive TO comeCloserScript.enterProximitySensorIsActive
ROUTE comeCloserScript.bindView2 TO view2.set_bind
也就是說,一旦用戶進入鄰近區,境界的當前視點將轉換成View2.這個由兩個視點、三個物體、一個鄰近檢測器和一個腳本節點組成的境界的完整代碼如下:
#VRML V2.0 utf8
DEF view1 Viewpoint {
position 0 0 20
description \"view1\"
}
DEF view2 Viewpoint {
position 0 0 20
description \"view2\"
}
Group {
children [
DEF box Transform {
translation 5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
]
}
DEF sphere Transform {
translation 0 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 1 0 }
}
geometry Sphere {}
}
DEF comeCloser ProximitrySensor {
center 0 0 0
size 4 4 4
}
]
}
DEF cone Transform {
translation -5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 0 1}
}
geometry Cone {}
}
]
}
]#end of Group children
}
DEF comeCloserScript Script {
eventIn SFBool enterProximitySensorIsActive
eventOut SFBool bindView2
url \"javascript :
function enterProximitySensorIsActive(active) {
bindView2=TRUE;
}\"
}
ROUTE comeCloser.isActive TO comeCloserScript.enterProximitySensorIsActive
ROUTE comeCloserScript.bindView2 TO view2.set_bind
啟動VRML瀏覽器進入境界,面向球體一直走過去,當你剛剛感到靠近球體時,會突然感到自己后退了一大步(或者說物體跳到前方更遠的地方),這表明鄰近檢測器已經檢測到你的靠近,它把這件事通知腳本節點,腳本節點把視點View2綁定成當前視點,從而使你感到視點突然改變。
再稍稍修改一下鄰近檢測器,把它的中心位置向右移了2米:
DEF comeCloser ProximitySensor {
center 2 0 0
size 4 4 4
}
這樣你就可以從左邊(方塊那一邊)走進球體(視點不跳),但不能從右邊(圓錐那一邊)走近它(視點跳轉)。
總之,ProximitySensor能夠檢測用戶是否進入或離開檢測器指定的空間區域,典型用法是當用戶走進房間時開啟燈光,當用戶離開時關閉燈光,從而建立功能豐富的“智能”空間。
第四節 連續動畫
在第二節中我們已經使用過接觸檢測器,當我們把鼠標指針放到方塊(這個幾何節點包含接觸檢測器)上面時,指針形狀發生變化,這意味著我們已經進入檢測區,如果按下鼠標左鈕,則按照我們的定義,當前視點會發生變化。
這一節仍然制作這樣一個對接觸有反應的方塊,只是接觸后它會連續不斷地轉動,動畫行為可以用時間檢測器(TimeSensor)驅動,而不斷變化的旋轉值可用腳本節點或朝向插補器(orientationInterpolator)給出。
1。接觸檢測器
作為開始的基本代碼是:
#VRML V2.0 utf8
DEF cube Transform {
rotation 1 1 1 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
DEF TouchS TouchSensor {}
]
}
DEF revolver Script {
eventIn SFBool startRevolving
eventOut SFRotation revolve
field SFFloat angle 0
url \"javascript :
function startRevolving () {
revolve[0]=1;
revolve[1]=1;
revolve[2]=1;
revolve[3]=angle;
angle+=0.1;
}\"
}
ROUTE TouchS.isOver TO revolver.startRevolving
ROUTE revolver.revolve TO cube.set_rotation
其中,方塊cube包含兩個子節點,前者定義了它的形態(紅色的單位立方體),后者把它定義成接觸檢測器。注意,cube的類型是Transform節點,它的rotation 域是外露域,指定本組相對于上層坐標系的旋轉值,這里指定的初始值是“1 1 1 0 ”,其中前三個數值定義旋轉軸,最后一個值定義旋轉角。由于它是外露域,因而可以通過入事件(名為set_rotation)進行修改,下面定義的動態行為就是這樣實現的。
Script節點revolver的核心是內聯的ECMAScript腳本函數。它給定一個不斷變化的旋轉值。當鼠標指針移動到方塊之上時,接觸檢測器發出isOver,和第一節中采用的isActive事件不同,isOver只有在鼠標左鈕按下時才會發出。isOver事件通過路由傳遞給腳本節點的事件入口startRevolving,從而啟動函數startRevolving,函數將一個新的旋轉值發往事件出口revolve,這個旋轉值通過路由進入cube的外露域rotation,修改了方塊的旋轉角,引起它的朝向變化。鼠標指針在cube上面的每次方位變化都引起isOver事件發送一次,從而導致方塊旋轉一次。
2。時間檢測器
為了使方塊能夠連續旋轉,需要引進等間隔連續發送的時間序列,這正是時間檢測器的用武之地。時間檢測器隨著時間推移不斷產生事件,可用于多種目的,包括: a. 驅動連續性的仿真和動畫
b. 控制周期性的活動(如每分鐘一次)
c. 初始化單獨事件,如報警鐘
下面是我們要用的時間檢測器和修改后的路由關系:
DEF ticker TimeSensor {
cleInterval 0.1
loop TRUE
enabled FALSE
}
ROUTE TouchS.isOver TO ticker.set_enabled
ROUTE ticker.cycleTime TO revolver.startRevolving
ROUTE revolver.revolve TO cube.set_rotation
enabled用于啟用和停用時間檢測器,開始時它處于停用狀態,以后由接觸檢測器的isOver事件修改這一狀態。啟用的時間檢測器每隔0.1秒送出一個cycleTime事件,并用它來觸發revolver的startRevolving事件,注意,cycleTime事件的類型為SFTime,而路由兩端事件的類型必須匹配,所以盡管這里我們不關心這個事件表示的具體時刻,還是把revolver的startRevolving事件類型也改為SFTime.這樣,revolver的函數startRevolving()就會每0.1秒調用一次,從而驅動方塊連續旋轉。完整的代碼是:
#VRML V2.0 utf8
DEF cube Transform {
rotation 1 1 1 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geoemtry Box {}
}
DEF TouchS TouchSensor {}
]
}
DEF revolver Script {
eventIn SFTime startRevolving
eventOut SFRotation revolve
field SFFloat angle 0
url \"vrmlscript :
function startRevolving () {
revolve[0]=1;
revolve[1]=1;
revolve[2]=1;
revolve[3]=angle;
angle+=0.1;
}\"
}
DEF ticker TimeSensor {
cycleInterval 0.1
loop TRUE
enabled FALSE
}
ROUTE TouchS.isOver TO ticker.set_enabled
ROUTE ticker.cycleTime TO revolver.startRevolving
ROUTE revolver.revolve TO cube.set_rotation
上述腳本節點的功能比較簡單,只是不斷送出調整的旋轉值,它是關鍵幀動畫的一種。由于關鍵幀動畫十分常用,故VRML專門定義了插補器節點來實現它。
3。 朝向插補器
插補器節點可認為是VRML內置的腳本節點,它們執行簡單的動態計算,通常和時間檢測器或者能夠使對象產生動作的節點結合在一起使用,生成線性關鍵幀動畫。插補器節點實際上是一個由關鍵點和對應關鍵值定義的分段線形函數。根據插值類型的不同,VRML共定義六個插補器節點:ColorInterpolator(顏色插補器)、CoordinateInterpolator(坐標插補器)、NormalInterpolator(法線插補器)、OrientationInterpolator(朝向插補器)、positionInterpolator(位置插補器)、ScalarInterpolator(標量插補器)。
所有插補器的域和事件都是類似的:
eventIn SFFloat set_fruction
exposedField MFFloat key [...]
exposedField MF keyValue [.....]
eventOut [S|M]F value_changed
關鍵值域keyValue的類型決定了插補器的類型(例如,OrientationInterpolator的keyValue域的類型是MFFloat).入事件set_fraction接收SFFloat型的事件,插補器隨即根據它進行插值,并通過出事件value_changed送出插值結果。
這里我們把時間檢測器的fraction_changed事件作為插補器的輸入,這個事件是一個[0,1]區間的值,每個時間步都送出一次,表示當前周期內已過去的時間相對于整個周期的比例,是插補器常用的輸入源之一。與此對應,我們把插補器關鍵幀的取值也定義在[0,1]范圍內。與0和1這兩個關鍵幀對應的關鍵值的旋轉軸是相同的,只是旋轉角度不同(0,3.14159),這樣方位插補器輸出的旋轉值的旋轉軸固定不變,旋轉角從0遞增到3.14159,然后不斷重復。
#VRML V2.0 utf8
DEF cube Transform {
rotation 1 1 1 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
DEF TouchS TouchSensor {}
]
}
DEF revolver OrientationInterpolator {
key [0,1]
keyValue [ 0.5 0.5 0.5 0,0.5 0.5 0.5 3.14149]
}
DEF ticker TimeSensor {
cycleInterval 2
loop TRUE
enabled FALSE
}
ROUTE TouchS.isOver TO ticker.set_enabled
ROUTE ticker.fraction_changed TO revolver.set_fraction
ROUTE revolver.value_changed TO cube.set_rotation
小結:本節實現連續動畫,動畫由接觸檢測器啟動,由時間檢測器驅動,動畫本身比較簡單,就是不斷地旋轉。產生不斷變化的旋轉值的方法有兩種:自己編寫腳本,或者利用插補器節點。
第五節 動態修改場景圖
場景圖是描述境界結構的基本概念,節點是構成場景圖的基本單元。組節點是能夠包含字節點的節點,組節點本身還可作為其它組節點的子節點,從而形成層次性體系結構。VRML中的組節點包含Anchor(錨)、 Billboard(布告牌)、 Collision(碰撞)、Group (組)、Inline (內聯)、LOD(細節層次)、 Switch(開關)、Transform(變換)共八種,除Inline、LOD、Switch這幾個具有特殊功能外,它們都定義了入事件addChildren 和removeChildren ,前者用于向組節點的子節點域children 中增加新的子節點,后者用于從中刪除子節點,這樣就可以動態修改場景圖的結構。
下面是我們這一節要建立的境界,開始的時候球體位于左邊紅色方塊的內部,在按動底部的綠色方塊后,球體進入右邊藍色方塊之內。
首先定義三個方塊:
#VRML V2.0 utf8
Viewpoint { position 0 0 15 }
DEF leftBox Transform {
translation -5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
]
}
DEF rightBox Transform {
translation 5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 0 1 }
}
geometry Box {}
}
]
}
DEF onoff Transform {
translation 0 -5 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 0 1 0 }
}
geometry Box {}
}
]
}
其中左邊的方塊為紅色,右邊的方塊為藍色,下邊的方塊為綠色,都是Transform類型,三者都位于場景圖的最高層,都是場景圖的根節點,都包含一個Box幾何體作為子節點。下面為紅方塊增加一個球體子節點:
DEF leftBox Transform {
translation -5 0 0
children [
Shape {
appearance Appearance {
material Material { diffuseColor 1 0 0 }
}
geometry Box {}
}
DEF SphereChild Shape {
appearance Appearance {
material Material { diffuseColor 1 0 1 }
}
geometry Sphere { radius 1.2 }
}
]
}
為了以后引用方便,這里還為球體子節點起了名字:SphereChild .為了讓用戶能夠增刪這個兒子,把綠方塊定義成接觸檢測器:
DEF onoff Transform {
translation 0 -5 0
children [
Shape {
appearance Appearance {
material Material {diffuseColor 0 1 0 }
}
geometry Box {}
}
DEF TS TouchSensor {}
]
}
子節點增刪的具體任務由Script節點來完成:
DEF S Script {
eventIn SFBool isActive
eventOut MFNode child
field MFNode testNode USE SphereChild
url\"javascript :
function isActive (value) {
if (value)child = testNode;
}\"
}
注意它的出事件child的類型是MFNode,也就是說通過這個事件送出的是節點。節點S的testNode域是對球體SphereChild引用(USE語句),引用不復制該節點,而是把同一節點再次插入場景圖,從而導致SphereChild擁有多個父親,所以場景圖僅僅是層次結構,而不是樹形結構。加上下面的路由語句,建立事件聯系:
ROUTE TS.isActive TO S.isActive
ROUTE S.child TO leftBox.removeChildren
ROUTE S.child TO rightBox.addChildren
接觸檢測器TS的激活事件isActive連接到腳本節點S的isActive,這樣用戶一旦按動綠方塊,就會啟動腳本節點的事件處理函數isActive(),此函數把testNode節點(即球體節點SphereChild )送至出事件S.child.根據路由,左邊紅方塊的事件入口leftBox.removeChildren 收到此事件,按照removeChildren的語義,球體節點SphereChild從leftBox的子節點列表中刪除。與此同時,右邊藍方塊的事件入口rightBox.addChildren也收到S.child出事件,根據addChildren的語義,球體節點SphereChild加入 rightBox的子節點列表。通過這個過程,球體節點SphereChild的父節點從leftBox更換成rightBox.
第六節 擴充節點類型
VRML提供了54種節點類型,稱為內部節點類型。然而實際應用種可能要求新的節點類型,原型(prototype)是VRML實現節點類型擴充的基本機制。新節點類型是根據已定義的(內部的或原型的)節點類型定義的,一旦定義,原型節點類型就可以象內部節點類型一樣在場景圖中實例化。原型可以在當前文件中定義并使用,也可以在其它文件中定義,即外部原型,外部原型提供了一種使節點類型能夠跨越網絡的機制。本節的原型例子取自VRML97標準,它定義的是一個桌子類型,這個原型為:
#VRML V2.0 utf8
PROTO TwoColor Table [ field SFColor legColor 0.8 0.4 0.7
field SFColor topColor 0.6 0.6 0.1 ]
}
Transform {
children [
Transform {
translation 0.0 0.6 0.0
children [
Transform {
appearance Appearance {
material Material { diffuseColor IS topColor }
}
geometry Box { size 1.2 0.2 1.2 }
}
}
Transform {
translation -0.5 0 -0.5
children [
DEF Leg Shape {
appearance Appearance { diffuseColor IS legColor }
}
geometry Cylinder { height 1 radius 0.1 }
}
]
}
Transform { #另一條桌腿
translation 0.5 0 -0.5
children USE Leg
}
Transform { #另一條桌腿
translation -0.5 0 0.5
children USE Leg
}
Transform { #另一條桌腿
translation 0.5 0 0.5
children USE Leg
}
]#根節點Transform的兒子結束
}#根Transform 結束
}#原型結束
原型語句PROTO分為原型接口聲明和原型定義兩部分、接口聲明包括原型的入事件和出事件的類型和名稱,以及原型的域的類型、名稱和缺省值。這里的接口聲明為:
PROTO TwoColorTable [ field SFColor legColor 0.8 0.4 0.7
field SFColor topColor 0.6 0.6 0.1 ]
這個原型的類型名稱為“TwoColorTable\"(雙色桌),它有兩個域:legColor(桌腿色)和topColor(桌面色)。作為節點類型,TwoColorTable的用法和其它內部節點類型一樣,例如下面的語句定義一個TwoColorTable類型的節點,它的桌腿為紅色,桌面為綠色:
TwoColor Table {
legColor 1 0 0 topColor 0 1 0
}
接口聲明之后是原型的主體,稱為原型定義。原型定義實際上是一個場景圖,由一個或多個根節點、嵌入的PROTO語句和ROUTE語句構成,其中的第一個節點類型確定原型實例在VRML文件中的使用方法。例如,如果原型定義中的第一個節點是Material節點,則只要可以使用Material節點的地方,原型實例都可以使用。原型定義中定義的其它節點及其附帶的場景圖都不進入原型實例所在的變換層系,但可以被原型定義中的ROUTE語句或Script節點引用。TwoColorTable原型中的第一個節點是Transform組節點,它決定了TwoColorTable型節點在場景圖中的方法,在場景圖中添加一個TwoColorTable型節點,相當于增加Transform.
原型定義中節點的域、入事件、出事件可以通過IS語句和接口聲明中的域、入事件、出事件建立關聯,關聯實際上相當于把原型定義中的這些域和事件公開作為原型的域和事件。關聯的基本規則是域和域、入事件和入事件、出事件和出事件對應關聯,原型定義中的外露域可以和接口聲明中的域、入事件、出事件或外露域關聯。本例中的關聯有兩個:桌面diffuseColor 域和接口聲明中的topColor域,桌腿的diffuseColor域和接口聲明中的legColor域之間。也就是說,TwoColorTable型節點中的topColor和legColor值實際上分別確定了桌面和桌腿的漫反射色diffuseColor.
第七節 結束語
本教程創建了六個典型VRML境界,介紹了VRML的主要功能。這些例子的側重點在于VRML的交互式特征,而不是營造境界的造型特征,后者可參見一般的三維圖形工具,把這二者結合起來,可以創建更加精彩的交互式3D境界。本章根據需要介紹了部分VRML節點的基本用法,以后將對VRML節點進行分類和較為全面的評論。
當然,VRML功能十分豐富,要成為VRML專家,一方面需要研讀VRML97標準,以求全面深入的掌握和應用,另一方面,要經常研讀成功的作品,獲取創作靈感。