Visual 是Python的一個簡單易用的3D圖形庫,使用它可以快速創建3D場景、動畫。和TVTK相比它更加適合于創建交互式的3D場景,而TVTK則更加適合于數據的3D圖形化顯示。在本節中將通過一個實例簡單的介紹如何使用Visual制作3D動畫。
先來看一個最簡單的例子:
from visual import *
box()
這個程序的運行結果如下圖的左圖所示:
用鼠標旋轉之后,可以看出VPython繪制的立方體
我們先從visual庫中載入所有對象,然后通過box()創建一個box類的實例,創建這個實例的同時將產生一標題為VPython的場景窗口。由于我們沒有給box傳遞參數,所創建的立方體的所有屬性都是缺省配置:
而場景中的照相機缺省從Z軸的上方往下看(俯視圖),縮放比例缺省是正好顯示場景中的所有物體。于是我們在場景中看到的是一個剛好充滿場景窗口的正方形。
照相機
照相機實際上就是我們觀察3D場景的工具,我們通過照相機觀察場景中的物體,照相機本身在場景中是不可見的。縮放比例和旋轉場景其實都是對照相機進行操作,進行這些操作時,場景中的物體并沒有改變,只是我們觀察物體的方位改變了。
在場景窗口中,同時按住鼠標左右按鍵,上下移動鼠標可以進行縮放場景;按住鼠標右鍵移動鼠標可以旋轉場景。右圖是進行適當的旋轉和縮放之后的效果。我們看到box()確實是創建了一個立方體。
為了搞清楚照相機的位置和坐標軸之間的關系,讓我們運行下面這個小程序:
# -*- coding: utf-8 -*-
from visual import *
display(title=u"坐標軸".encode("gb2312"), width=300, height=300)
arrow(pos=(1,0,0), axis=(1,0,0), color=(1,0,0))
arrow(pos=(0,1,0), axis=(0,1,0), color=(0,1,0))
arrow(pos=(0,0,1), axis=(0,0,1), color=(0,0,1))
VPython照相機的缺省位置,紅綠藍分別表示X,Y,Z軸
這段程序中,我們通過調用display()創建一個場景窗口,并且指定了窗口的標題、寬度和高度。標題必須使用Windows系統缺省的編碼,因此為了顯示中文,需要將unicode轉換為gb2312編碼。
調用3次arrow()創建了三個箭頭物體,我們通過幾個關鍵字參數配置箭頭的屬性:
通過觀察圖中的三個箭頭的位置,我們可以知道:
因此此時的照相機位于z軸正方向上的某點,方向沿著z軸負方向俯視。
下面讓我們來看看如何用visual創建一個簡單的3D動畫,先看一下完整的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # -*- coding: utf-8 -*-
from visual import *
display(title=u"簡單動畫".encode("gb2312"), width=500, height=300)
ball = sphere(pos=(-5,0,0), radius=0.5, color=color.red)
wall_right = box(pos=(6,0,0), size=(0.1, 4, 4), color=color.green)
wall_left = box(pos=(-6,0,0), size=(0.1, 4, 4), color=color.green)
dt = 0.05
ball.velocity = vector(6, 0, 0)
while True:
rate(1/dt)
ball.pos = ball.pos + ball.velocity*dt
if ball.x > wall_right.x-ball.radius or ball.x < wall_left.x+ball.radius:
ball.velocity.x *= -1
|
球在板子之間反復運動的簡單動畫
運行這段程序會出現一個有兩塊綠色板子和一個紅球的窗口,紅球在兩塊板子之間反復運動。
第6-8行創建了場景中的三個物體:兩塊綠色的板子(box)和一個紅色的球(sphere)。sphere可以通過radius屬性設置其半徑,而box可以通過size屬性設置其x, y, z軸方向的長度。前面提到過axis屬性也可以改變box的大小,這兩個屬性是互相影響的,在用戶手冊中我們會詳細討論這個問題。
第10行定義了一個變量dt,我們用它來表示動畫中每幀之間的時間間隔。第11行我們給ball添加一個velocity屬性,它是一個3D矢量表示球體的速度。請注意velocity不是sphere類固有的屬性,是我們為ball物體動態添加的屬性。
第13行開始一個死循環,在這個循環中不斷地更新ball的pos屬性以實現動畫效果,為了控制動畫的播放速度,在循環中先調用rate函數。由于dt為0.05秒,因此我們動畫速度為每秒20幀。rate函數會讓程序等待足夠長的時間使得動畫播放的幀數接近指定的幀數。
第15行修改ball的pos屬性,加上在dt時間段中ball的位移量。第16, 17行處理和板子的碰撞,因為pos為球的中心坐標,而碰撞點在球的表面,因此處理碰撞時還需要考慮球的半徑。確定碰撞之后,只需要將球的速度反轉即可。
由于球的速度為6,而兩板之間的間隔為12,因此球從左板移動到右板需要2秒鐘時間。
下面讓我們來看一個完整的反彈動畫程序。在場景中放置6個半透明的墻面,形成一個正方體,球體的在正方體內部運動反彈,我們可以調整重力加速度(Z方向的加速度)和反彈系數,同時還顯示球的速度矢量和運動軌跡。下面是完整的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | # -*- coding: utf-8 -*-
from visual import *
display(title=u"簡單動畫".encode("gb2312"), width=500, height=500)
# 創建球體和6個墻面,墻面設置為半透明,以觀察球體的運動軌跡
ball = sphere(pos=(-5,0,0), radius=0.5, color=color.red)
wall_right = box(pos=(6,0,0), size=(0.1, 12, 12), color=color.green, opacity = 0.2)
wall_left = box(pos=(-6,0,0), size=(0.1, 12, 12), color=color.green, opacity = 0.2)
wall_front = box(pos=(0,-6,0), size=(12, 0.1, 12), color=color.green, opacity = 0.2)
wall_back = box(pos=(0,6,0), size=(12, 0.1, 12), color=color.green, opacity = 0.2)
wall_bottom = box(pos=(0,0,-6), size=(12, 12, 0.1), color=color.green, opacity = 0.2)
wall_top = box(pos=(0,0,6), size=(12, 12, 0.1), color=color.green, opacity = 0.2)
dt = 0.05
g = 9.8 # 重力加速度
f = 0.9 # 反彈能量保持系數,1.0表示完全反彈
ball.velocity = vector(8, 6, 12)
bv = arrow(pos = ball.pos, axis=ball.velocity*0.2, color=color.yellow)
ball.trail = curve(color=ball.color)
trail_color = 0 # 軌跡的顏色
while True:
rate(1/dt)
# 重力加速度改變z軸方向的速度,不存在反彈時修改速度
ball.velocity.z -= g * dt
# 根據速度修改球體的位置
ball.pos += ball.velocity * dt
## 速度為正時判斷正方向的墻,速度為負時判斷負方向的墻
## 處理反彈時需要修正球的位置,使它正好和墻面接觸
# 處理左右墻的反彈
if ball.velocity.x > 0 and ball.x >= wall_right.x - ball.radius:
ball.x = wall_right.x - ball.radius
ball.velocity.x *= -f
if ball.velocity.x < 0 and ball.x <= wall_left.x + ball.radius:
ball.x = wall_left.x + ball.radius
ball.velocity.x *= -f
# 處理前后墻的反彈
if ball.velocity.y > 0 and ball.y >= wall_back.y - ball.radius:
ball.y = wall_back.y - ball.radius
ball.velocity.y *= -f
if ball.velocity.y < 0 and ball.y <= wall_front.y + ball.radius:
ball.y = wall_front.y + ball.radius
ball.velocity.y *= -f
# 處理上下墻的反彈
if ball.velocity.z > 0 and ball.z >= wall_top.z - ball.radius:
ball.z = wall_top.z - ball.radius
ball.velocity.z *= -f
elif ball.velocity.z < 0 and ball.z <= wall_bottom.z + ball.radius:
ball.z = wall_bottom.z + ball.radius
ball.velocity.z *= -f
# 更新速度箭頭的位置和方向
bv.pos = ball.pos
bv.axis = ball.velocity*0.2
# 添加球的軌跡點
ball.trail.append( pos = ball.pos, color = (trail_color, 0, 0))
trail_color += 1.0/30.0*dt # 30秒后顏色變為全紅
if trail_color > 1.0: trail_color = 1.0
|
球在封閉的盒子中反彈的動畫
第8-13行創建上下左右前后六個墻面,通過設置其opacity屬性,設置其不透明度為0.2。opacity=0.0表示完全透明,opacity=1.0表示完全不透明。
第19行用arrow()創建了一個箭頭物體,它的起始點位置為球體的中心,方向和球體的速度方向相同:
bv = arrow(pos = ball.pos, axis=ball.velocity*0.1, color=color.yellow)
第20行用cureve()創建一個曲線物體,并賦值給球體的trail屬性:
ball.trail = curve(color=ball.color)
第27行使用加速度更新球體的速度,第30行使用速度更新球的體位移。
第35-56行,處理球體和墻壁的碰撞,x, y, z三個方向的碰撞處理方式相同,這里以x方向為例簡要說明一下碰撞處理。
當球體的x軸方向的速度為正時,判斷球體是否和正方向的墻壁(右墻)相撞,如果相撞的話則將其x軸方向的速度反向,并且乘以碰撞系數模擬能量損失,同時修改球體的x軸坐標,使得其正好和右墻相接觸。球體的x軸方向速度為負時,和左墻進行碰撞檢測:
if ball.velocity.x > 0 and ball.x >= wall_right.x - ball.radius:
ball.x = wall_right.x - ball.radius
ball.velocity.x *= -f
if ball.velocity.x < 0 and ball.x <= wall_left.x + ball.radius:
ball.x = wall_left.x + ball.radius
ball.velocity.x *= -f
第59,60行更新箭頭物體的位置和方向以表示球體的速度。第62行將現在的球體的位置添加進球體的軌跡曲線物體。第63,64行更新軌跡的顏色,這樣顏色按照隨著時間逐漸變紅,從黑變紅一共需要30秒時間。