說明:本系列基本上是《WPF揭秘》的讀書筆記。在結構安排與文章內容上參照《WPF揭秘》的編排,對內容進行了總結并加入一些個人理解。
?
WPF將易用性的理念帶入了3D世界,WPF中3D也工作在一種保留模式下,這意味著系統會負責刷新與重繪。WPF中2D圖形與3D圖形系統有著很緊密的融合,首先在繪圖系統基礎及2D圖形篇所介紹的概念對3D圖形是適用的。2D媒體,如Video,Drawing和Visual,可以顯示在3D模型表面。而Viewport3D中的場景也可以融合到程序中其它UI元素,也可以放入ItemsControl中。
?
3D圖形入門
3D圖形系統的目的是使3D模型作為2D圖形輸出到屏幕等設備上。對3D系統的第一印象,來看這段XAML,其中定義了一個3D的小房子:
1 <Page Background="Black" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 4 <Viewport3D> 5 <Viewport3D.Camera> 6 <OrthographicCamera Position="5,5,5" LookDirection="-1,-1,-1" Width="5"/> 7 </Viewport3D.Camera> 8 <Viewport3D.Children> 9 <ModelVisual3D x:Name="Light"> 10 <ModelVisual3D.Content> 11 <AmbientLight/> 12 </ModelVisual3D.Content> 13 </ModelVisual3D> 14 <ModelVisual3D> 15 <ModelVisual3D.Content> 16 <Model3DGroup x:Name="House"> 17 <GeometryModel3D x:Name="Roof"> 18 <GeometryModel3D.Material> 19 <DiffuseMaterial Brush="Blue"/> 20 </GeometryModel3D.Material> 21 <GeometryModel3D.Geometry> 22 <MeshGeometry3D Positions="-1,1,1 0,2,1 0,2,-1 -1,1,-1 0,2,1 1,1,1 23 1,1,-1 0,2,-1" 24 TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7"/> 25 </GeometryModel3D.Geometry> 26 </GeometryModel3D> 27 <GeometryModel3D x:Name="Sides"> 28 <GeometryModel3D.Material> 29 <DiffuseMaterial Brush="Green"/> 30 </GeometryModel3D.Material> 31 <GeometryModel3D.Geometry> 32 <MeshGeometry3D Positions="-1,1,1 -1,1,-1 -1,-1,-1 -1,-1,1 1,1,-1 33 1,1,1 1,-1,1 1,-1,-1" 34 TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7"/> 35 </GeometryModel3D.Geometry> 36 </GeometryModel3D> 37 <GeometryModel3D x:Name="Ends"> 38 <GeometryModel3D.Material> 39 <DiffuseMaterial Brush="Red"/> 40 </GeometryModel3D.Material> 41 <GeometryModel3D.Geometry> 42 <MeshGeometry3D 43 Positions="-0.25,0,1 -1,1,1 -1,-1,1 -0.25,-1,1 -0.25,0,1 44 -1,-1,1 0.25,0,1 1,-1,1 1,1,1 0.25,0,1 0.25,-1,1 1,-1,1 45 1,1,1 0,2,1 -1,1,1 -1,1,1 -0.25,0,1 0.25,0,1 1,1,1 1,1,-1 46 1,-1,-1 -1,-1,-1 -1,1,-1 1,1,-1 -1,1,-1 0,2,-1" 47 TriangleIndices="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 15 48 17 18 19 20 21 19 21 22 23 24 25"/> 49 </GeometryModel3D.Geometry> 50 </GeometryModel3D> 51 </Model3DGroup> 52 </ModelVisual3D.Content> 53 </ModelVisual3D> 54 </Viewport3D.Children> 55 </Viewport3D> 56 </Page>
隨著下文介紹的深入我們將了解其中這些元素的作用。
?
我們通過與2D圖形系統中的類對比來對3D圖形系統有個大致的認識。
2D類型 | 3D類型 | 描述 |
Drawing | Model3D | Drawing表示一片2D內容,如Geometry可以由一個Visual對象渲染 Model3D表示一個3D模型,可以由一個Visual3D渲染 |
Geometry | Geometry3D | Geometry表示一個2D形狀Geometry,能夠回答像邊界和交叉點這樣的問題。Geometry自身不能被渲染。GeometryDrawing結合了一個Geometry和一個Brush來呈現它的外觀 Geometry3D表示3D表面。為了渲染Geometry3D,其使用GeometryModel3D將它和一個Material結合起來 |
Visual | Visual3D | Visual是渲染2D內容的元素的基類。其中包括所有的DrawingVisual和所有的FrameworkElement,后者如Control和Shape Visual3D是渲染3D內容的元素的基類,ModelVisual3D是其一個子類,用于渲染Model3D的3D內容 |
Transform | Transform3D | Transform(的子類)用于進行2D Drawing和Visual的變換,如平移,旋轉和拉伸 類似于Transform,在3D世界中,Transform3D用于對Model3D和Visual3D執行變換操作 |
除了表格中列出的擴展自2D圖形的類, 3D世界中還有如下兩個獨有的概念。
- Camera:場景中放置的虛擬相機,在特定位置,以特定的角度產生3D模型的圖形。
- Material與Light:在Brush填充過的表面上,進一步添加光照效果。
?
坐標系統
WPF中,2D,3D分別使用如下坐標系統
3D使用的坐標系統除了比2D坐標系統多z軸外,y軸的正方向是向上而非向下。另外2D圖形系統中很少用負數坐標,但3D中負數坐標很普遍。所以2D圖形中一般把左上角作為圖像的中心,而3D中把原點作為空間的中心。
提示:WPF中使用右手坐標系統
左手坐標系統與右手坐標系統的不同在于z軸與x軸,y軸關系的區別,下面的圖很好的說明了這個問題:
你只需要這樣記:右手法則中,z軸是靠近我們,而左手法則中z軸是遠離我們的,基本上就不會弄錯了。
?
Camera
????在3D應用設計中,最常打交道的兩個Camera分別是OrthographicCamera和PerspectiveCamera,通過這兩個類提供的屬性可以設置Camera在3D坐標系中的位置和方向,下文將詳細說明其中的屬性及其控制效果。
- Position屬性
????該屬性控制Camera在空間中的位置。改變此屬性即可移動Camera,從而建立場景的不同視圖。該屬性為Point3D類型,其中包括了x,y,z坐標來定義此點在坐標系中的位置。如下XAML(前文示例的一部分),我們在OrthographicCamera中設置了Position屬性:
1 <Viewport3D> 2 <Viewport3D.Camera> 3 <OrthographicCamera Position="5,5,5" LookDirection="-1,-1,-1" Width="5"/> 4 </Viewport3D.Camera> 5 </Viewport3D>
我們指定的屬性值"5,5,5"表示我們將Camera放置在x,y,z軸正方向上各5個像素的一點,如圖:
上面XAML中用到了我們接下來要介紹的一個屬性。
- LookDirection屬性
????該屬性定義了Camera的朝向。其是Vector3D類型,該類型也包含x,y,z三個屬性,但它們的作用是定義方向與幅度(Length)。以上文XAML中該屬性設置的值"-1,-1,-1"來說,假設我們以上北下南前后來描述右手規則的坐標系,"-1,-1,-1"定義的方向為西北后方(見下圖)。
而幅度通過這個公式得到。
提示:
一般情況下,對于Vector3D,系統只會確定其方向而不會立即計算出Length。Length主要用在如我們將Vector3D與Point3D進行相加(這樣會得到一個新的Point3D對象)等計算,這時系統會自動計算Length值。
?
????注意,Point3D與LookDirection的設置一定要搭配,如還是前面的例子,我們把Position改成(-5,-5,-5),而不改變LookDirection的話,目標就會在鏡頭中消失。得到LookDirection一個簡單的方法是,在坐標系中找到一個想要看到的點,用其x,y,z依次與Position的x,y,z值相減得到的結果就可作為LookDirection的x,y,z值。
????可以使用如下的代碼進行計算,以用于Camara移動的場景下LookDirection的變化。
1 camera.LookDirection = lookAtPoint - camera.Position;
?
- UpDirection屬性
????通過LookDirection確定相機的朝向后,我們還需要通過UpDirection屬性確定鏡頭不會繞中心點旋轉,就像一個真實的相機,我們要固定其是橫放還是豎放。UpDirection屬性默認值是<0,1,0>(對于真實相機,這是最常見的橫放的方法),而如<1,0,0>就是將相機"豎起來"。
????如果是在場景中放置靜態Camera,最好的方法就是直接設置上面介紹的這三個屬性 – Position, UpDirection和LookDirection。而如果需要移動和旋轉相機應該使用Camera的Transform屬性。使用這個屬性可以很容易的使Camera與目標對象保持一致變化,如使相機跟隨目標對象移動,只需將Camera的Transform設置的與目標對象的Transform一致即可。
注意:要及時變換UpDirection
正如改變Position時,需要及時調整LookDirection來使目標一直出現在鏡頭中,在LookDirection發生某些改變時,UpDirection也要隨之進行改變。例如,鏡頭從目標的一面移動到另一面,LookDirection也進行了相應改變,從望向一面轉到另一面。這時UpDirection務必跟隨LookDirection更新。否則當在一面時,Camera拍出來的照是正的,到了另一面Camera中目標會倒過來。
?
- NearPlaneDistance屬性與FarPlaneDistance屬性
????這兩個屬性用于避免這樣的問題,即當Camera離目標對象過近或過遠時會出現渲染問題(這是一種深度沖突問題)。NearPlaneDistance默認值為0.125(這個值一般不需要自行調整)。與Camera距離小于NearPlaneDistance定義的值目標對象的部分都會被裁減掉。而由于極遠物體深度沖突不常發生,所以FarPlaneDistance默認值被設置為無限遠。
?
平行投影與透視投影
????回到兩種Camera – OrthographicCamera與PerspectiveCamera,PerspectiveCamera呈現的圖像接近人眼觀看現實世界的效果,即離Camera越遠的對象看起來越小,而OrthographicCamera中的目標對象無論距Camera遠或近都會以實際大小呈現,可以用于精確測量分析。
????由空間中的3D模型到Camera呈現出的2D畫面,完成了一個投影的過程。OrthographicCamera與PerspectiveCamera分別使用了平行投影與透視投影。
????在平行投影模式下投影到的平面與可視空間的大小是一致的(一個一對一映射的過程)。通過OrthographicCamera的Width屬性可以指定可視區域的大小,而高度由Viewport3D自動計算以保證寬高比。下面的代碼展示了Width的設置:
1 <Viewport3D> 2 <Viewport3D.Camera> 3 <OrthographicCamera Position="5,5,5" LookDirection="-1,-1,-1" Width="5"/> 4 </Viewport3D.Camera> 5 </Viewport3D>
在透視投影模式下,即使用PerspectiveCamera時,可視區域寬度隨著與Camera的距離的增大而增大。由于離Camera越遠可視范圍區域越大,距離遠的對象在投影中會更小。通過PerspectiveCamera的FieldofView屬性可以控制視野擴張的水平角度。理論上這個角度越大,可視區域的范圍越大,遠處的物體看起來會越小。FieldOfView可以理解為真實相機變焦的作用。
????PerspectiveCamera的Width屬性的作用與OrthographicCamera的Width屬性作用類似。Width與FieldOfView搭配使用,當這兩個值較小時會對目標對象的某一部分進行放大(如使用相機時,我們常說的拉近鏡頭的效果),反之當這兩個屬性取較大值時可以展示更多的場景。
?
MatrixCamera
WPF中提供的MatrixCamera是一種高級相機,其讓用戶可以通過Matrix3D定義視圖及投影變換。如PerspectiveCamera與OrthographicCamera支持的投影模式都是一種預定義的變換矩陣。當需要獲得高級效果時需要使用MatrixCamera。另外MatrixCamera使用的變換矩陣模型與Direct3D完全一致,可以很容易的實現應用的移植。
?
Transform3D
Transform3D用于Model3D,ModelVisual3D和Camera的Transform熟悉國內,來對3D對象執行移動,旋轉或拉伸。
Transform3D有如下5種子類,用于執行3D變換:
- TranslateTransform3D
- ScaleTransform3D
- RotateTransform3D
- MatrixTransform3D
- Transform3DGroup:用于包含一組Transform3D集合,以將多個變化應用到3D對象上。
下文將詳細介紹這些類及其使用。
?
TranslateTransform3D
????TranslateTransform3D將對象相對容器進行偏移,其OffsetX,OffsetY和OffsetZ三個屬性用來指定各個方向上的偏移量。如下面的代碼將位于坐標系原點的模型移動到(3,2,1)這個位置。
1 <ModelVisual3D> 2 <ModelVisual3D.Transform> 3 <TranslateTransform3D OffsetX="3" OffsetY="2" OffsetZ="1" /> 4 </ModelVisual3D.Transform> 5 </ModelVisual3D>
?
ScaleTransform3D
ScaleTransform3D用于改變3D對象的大小,ScaleX,ScaleY和ScaleZ屬性分別控制每個方向上的縮放比例。要在對3D對象執行縮放時維持比例需要將這三個屬性設置成相同的值,當然這三個值可以獨立設置(甚至只設置其中的1到2個)。
另外還有三個屬性CenterX,CenterY和CenterZ屬性用來設置縮放的中心點。默認縮放的中心點是坐標系的原點。若3D對象內沒有一點與原點重合,縮放就會導致3D對象移位,只需要將縮放中心點設置為3D對象中的任一點就可以實現原地縮放。另一種解決這個問題的方法是縮放后平移,平移的值就是坐標原點(即默認縮放中心)與3D對象上要以此為縮放固定點進行原地縮放的點之間的坐標距離。
另外,當縮放值設置為0時(所有方向上),對象會被縮成一個點(注意,需要等比例縮放時,應該講縮放比例設成1),而如果縮放值設置成負數,則會在被設為負值的方向的反方向上產生鏡像效果,并且其也按負值的大小進行了比例縮放。
提示:在底層對于通過CenterX,CenterY和CenterZ指定縮放中心的變化的處理上使用的方法是先將指定的縮放中心移動到坐標原點,等縮放完成后再平移回去。
?
RotateTransform3D
????RotateTransform3D用于在空間中旋轉3D對象。旋轉的定義通過Rotation3D對象描述,Rotation3D是一個抽象類,其有兩個字類:
AxisAngleRotation3D – 沿指定軸將對象旋轉Angle屬性指定的度數。這是實現旋轉最簡單的方法。
QuaternionRotation3D – 使用Quaternion來定義一個旋轉,Quaternion對Axis/Angle(軸/角度)旋轉編碼方法被許多其它3D工具使用。使用這種方式可以很容易將旋轉變化進行導入導出。
????通過RotationTransform3D定義的旋轉,會使坐標系按指定的度數旋轉,在右手坐標系中一個正值的角度會使坐標系逆時針旋轉。這種旋轉變化存在著與縮放變化同樣的問題即當旋轉軸不在3D對象上時,旋轉坐標系后對象位置會發生移動。兩種解決方法也同前面介紹,一是在旋轉之后通過平移調整位置。而是通過CenterX,CenterY和CenterZ屬性改變旋轉中心。注意Axis屬性指定的旋轉軸不能確定旋轉中心,如我們通過Axis指定繞Y軸旋轉,還需要指定在X-Z平面上具體哪一點繞Y軸旋轉。
?
Transform3DGroup
????如上文中所提到的那樣,在3D變化中,多種變化往往需要同時進行,如先縮放再平移或先旋轉再平移,通過Transform3DGroup可以很方便的把1個以上的變換組合成一個變換,其使用與2D中TransformGroup很類似這里不再給出代碼示例。
?
MatrixTransform3D
????這是一種很復雜的變換,用于定義其他幾種變換所無法實現的效果,或者將其它基于矩陣表示變換的程序移植到WPF中。另外,前面介紹的包括Transform3DGroup在內的變換都可以通過其Value屬性的得到Matrix3D的表示。
?
Model3D
????Model3D作用正如其名,為3D場景構建模型。通常把多個Model3D組合在一起來生成單個3D模型。Model3D在一些特性及使用上類似2D中Drawing。
Model3D有3個子類完成具體功能。
- Light – Light提供的一些子類用于向場景中投射光線。在實際使用中常把Light結合到后文要介紹的Model3DGroup中來實現如車燈照射等效果。
- GeometryModel3D – 與給定的Material結合使用來渲染表面。GeometryModel3D類似于2D中的GeometryDrawing。
- Model3DGroup – 用于包含一組Model3D,如組合多個GeometryModel3D,組合Light來照射3D模型。
下面將詳細介紹Model3D這三個組成部分。
?
Light
????光照(Light)也是WPF 3D中獨有的概念,其基本作用就是根據場景中3D對象與光源的遠近動態計算3D對象的明暗。光照效果的出現也是通過三個部分的結合。Light對象用于把光線發射到場景中;Material用于把光線反射給Camera;模型幾何體確定入射及反射光線的角度。這一部分中先重點討論Light對象,從WPF支持的不同種類的Light開始:
- DirectionalLight:從無限遠處的光源發射平行光到場景,其可以模擬太陽光照射的效果。
- PointLight:從場景中一個點向各個方向均勻發射光線,光線強度隨著距光源距離增加而減弱。其用于提供一種沒有聚焦的光源效果,現實生活中的燈泡就類似這種效果。
- SpotLight:從場景中一點發射逐漸擴散的錐形光源,光線強度同樣是隨著距離增加而減弱。如手電筒所發的光線就是這種光源的效果。
- AmbientLight:均勻照射模型的每一個表面。如果是白色的AmbientLight由于缺乏明暗變化會使目標的視覺感很"平"。而較暗的AmbientLight會在場景表面產生漫反射的效果。
- 下面我們詳細了解每種光源的特性及使用。
?
- DirectionalLight
由于光源距離近乎無限遠,從而光線接近平行。DirectalLight中,Direction屬性控制光線照射場景的方向,而Transform屬性(繼承自Model3D基類)可以影響光照方向。Color屬性用來控制Light的顏色。
提示:Color屬性可以反應光照強度,#FFFFFF是完全強度的白光,而其值的一半#808080是半強度的白光。而alpha通道的設置對光照無影響,另外光照效果可以重疊。如:同方向的兩個半強光疊加可以產生一個完全強度的光照,而方向不同的光也會交叉重疊。下面這行XAML展示了一個簡單的例子:
1 <ModelVisual3D> 2 <ModelVisual3D.Content> 3 <DirectionalLight Direction="1,-1,-0.5" Color="White" /> 4 </ModelVisual3D.Content> 5 </ModelVisual3D>?
單一的使用Direction所產生的效果不是很自然,可以結合AmbientLight使效果更自然。
?
- PointLight
PointLight與DirecitonalLight不同的一點在于前者光線強度隨著距離的增加而減弱。PointLight中,Position屬性用于指定光源的位置,ConstantAttenuation,LinearAttenuation和QuadraticAttenuation三個屬性一起控制光線隨距離增加的衰減比。我們把三個屬性分別簡寫為C,L,Q,另外用d表示光源與被照射點的位置。則衰減率公式為:
?
????從公式可以看出,將C,L,Q分別設置為1,0,0,就可以得到距離無關,等強度的PointLight。
????PointLight還有一個Range屬性,用于定義光源的范圍(以光源為中心,半徑為指定值的范圍),在這個范圍之外光線不再有效。其默認值為無窮大最后給出一段XAML展示PointLight的使用:
1 <ModelVisual3D> 2 <ModelVisual3D.Content> 3 <PointLight Color="White" Position="2,2,2" 4 ConstantAttenuation="0" LinearAttenuation="0" 5 QuadraticAttenuation="0.125" /> 6 </ModelVisual3D.Content> 7 </ModelVisual3D>
?
- SpotLight
現實世界中,使用凹透鏡或反射鏡會出現SpotLight的效果。WPF實現中,通過將PointLight發射的光限制在一個椎體上來模擬(角度范圍限制)。SpotLight中,Direction屬性指定了椎體的方向,而OuterConeAngle和InnerConeAngle屬性控制者椎體的形狀(椎體張開的角度)。InnerConeAngle與OuterConeAngle之間的區域是一個發散區間。光照強度會由指定值一直衰減到無。在InnerConeAngle區間內光照在同一半徑上會保持指定強度。改變InnerConeAngle與OuterConeAngle的區間可以調整發散區域的大小。當OuterConeAngle小于InnerConeAngle時就沒有發散區域了。?
- AmbientLight
AmbientLight通常用來在多個表面上產生漫反射的效果。Ambient最重要的屬性是Color,其控制光線的強度與顏色;另外Transform屬性對AmbientLight沒有效果。
前面提到在AmbientLight中使用強度過高的顏色,會使目標看起來白茫茫一片,最好的產生自然光效果的方法是僅使用一個AmbientLight,且使用低于三分之一的白色(#555555或更小)。
?
GeometryModel3D
????GeometryModel3D對象用來定制3D幾何體,3D幾何體構成了可視對象的形狀。而Geometry3D定義的3D幾何體本身是無法展示出來的,為了可以看到3D幾何體的表面,需要將其與Material一起使用。而GeemotryModel3D這個Model3D的作用就是將Geometry3D與Material屬性相結合,而Model3D是可以最終被渲染的對象。
????首先來看一個GeometryModel3D的具體例子,其中Material部分定義了一個藍色的DiffuseMaterial,Geometry部分使用MeshGeometry3D描述了一個矩形。
1 <ModelVisual3D> 2 <ModelVisual3D.Content> 3 <GeometryModel3D> 4 <GeometryModel3D.Material> 5 <DiffuseMaterial Brush="Blue" /> 6 </GeometryModel3D.Material> 7 <GeometryModel3D.Geometry> 8 <MeshGeometry3D Positions="-1,1,0 -1,-1,0 1,-1,0 1,1,0" 9 TriangleIndices="0 1 2, 0 2 3" /> 10 </GeometryModel3D.Geometry> 11 </GeometryModel3D> 12 </ModelVisual3D.Content> 13 </ModelVisual3D>
?
首先我們先詳細介紹下例子中的第一部分 – Material。
Material
????如前所述,Light對象的屬性決定場景中光線的方向和顏色。Material對象的屬性會最終確定反射到觀察者的光線,即我們最終看到的圖像。
????我們先來介紹下色彩的原理,在現實中,比如一個蘋果看起來是紅色的,是因為果皮反射了紅色的光,同時吸收了其他波長的光。Material對象的屬性正是決定了反射回哪些顏色到Camera從而建立圖像。
WPF內置的Material有如下幾類:
- DiffuseMaterial – 從所有角度散射抵達表面的光線,效果如新聞紙,平臺但不光滑。
- SpecularMaterial – 對入射光線以相同的角度反射,用于產生像塑料或金屬等光滑表面的高光效果。
- EmissiveMaterial – 近似于會發出光線的表面(但這個光不會照亮其他對象),所以無論場景中是否有光照對象,EmissiveMaterial看上去總是亮的,其用于創建總以完全亮度顯示的圖像,以及不需要陰影的圖像。
- MaterialGroup – 組合使用多個Material其中最后的Material顯示在最前面,這樣依次排列。
下面將詳細介紹這些Material。
?
- DiffuseMaterial
????DiffuseMaterial是最常用的一種Material。散射的強度與光線與被照射面的角度有關。光線直射時反射強度最大。所有對于一個球形物體,正對光源的部分看起來較亮,而兩側較暗。另外Camera的角度對反射沒有影響。
????DiffuseMaterial的Brush屬性定義了其反射的顏色。如給一個物體定義的Material使用了紅色的Brush(以SolidColorBrush為例),當我們向其投射白光(白光是所有色光在一起的光),其會表現為紅色。當然我們還可以通過使用漸變畫刷,甚至是ImageBrush,使反射光呈現漸變效果或更多自定的效果。這里需要注意,當使用SolidColorBrush之外的Brush時,需要使用TextureCoordinate來控制Brush的某一部分如何對應3D對象的某一部分。如果不指定TextureCoordinate模型不會被渲染,(這時WPF不能知道Brush顏色與模型表面點的映射關系,而對于SolidColorBrush模型上每個點映射到相同的顏色,所以可以不設置TextureCoordinate屬性)。使用Brush來指定Material的另一大好處是靈活的數據綁定,不但可以通過數據綁定控制3D模型,還可以將2D中的DrawingVideo等作為對象表面的紋理。
????DiffuseMaterial的Color屬性用于過濾Light的顏色,只有其指定的顏色才可以被反射,其默認值為白色也就是不進行過濾。
????最后要說的一個屬性是AmbientColor,通過這個屬性設置的顏色只會對來自AmbientLight中的顏色起過濾作用,其主要作用也就是控制表面對環境光的反射程度。
提示:WPF處理重疊表面的方式
對于層疊的對象有兩種處理方式:一,將所有的對象排序,然后自后先前渲染。二是,深度緩沖,這是WPF使用的方式,這種方式下最靠近Camera的表面會最后渲染,這種方式比前一種快很多,當然副作用是遠處的目標不再被渲染,當最前面的對象是透明時會有很大問題。對此的解決方向是將透明的DiffuseMaterial放在集合最后,來保證透明對象后的對象先被渲染。或者可以使用EmissiveMaterial,其不使用深度緩沖方式被混合 ,且其也可以創建類似透明的效果。
- SpecularMaterial
????這種Material會反射光線,當Camera與光源夾角較小時,目標會像鏡子般反射光線到Camera,且僅當Camera靠近反射光線時SpecularMaterial的效果才能被觀察到。SpecularMaterial常與DiffuseMaterial結合使用,給硬的、閃亮的表面添加高亮效果。如下面這段XAML所示:
1 <GeometryModel3D.Material> 2 <MaterialGroup> 3 <DiffuseMaterial Color="Red" /> 4 <SpecularMaterial Color="White" SpecularPower="40" /> 5 </MaterialGroup> 6 </GeometryModel3D.Material>
其中SpecularPower屬性控制高亮反射的聚焦程度,值越大,聚焦程度越大高亮效果越強。
提示:組合Material得到常見的效果
如將明亮的DiffuseMaterial和白色的SpecularMaterial結合在一起,表面上看起來就像塑料。使用暗的DiffuseMaterial和具有相同色調且明亮的SpecularMaterial會得到金屬效果的表面。
?
????同DiffuseMaterial,最后反射到Camera的顏色也是由Light的Color屬性,SpecularMaterial的Brush和Color屬性共同決定的。且SpecularMaterial的Brush屬性也可以指定為各類Brush對象,從而使反射光線呈現各種變化效果。另外注意,SpecularMaterial沒有提供AmbientLight屬性,因為這種光線是沒有方向的。
?
- EmissiveMaterial
????EmissiveMaterial總是向Camera發出可見光,其光線不會像Light那樣被反射。
????EmissiveMaterial向圖像添加光照效果的原理是混合入圖像,通俗表述就是使物體透亮,如我們在燈籠里點一根蠟燭,則燈籠表面就是透亮物。另外這種混合沒有阻止后方物體反射的光線穿過自身。為了防止這種被穿透可以通過MaterialGroup將EmissiveMaterial與DiffuseMaterial結合使用,參見如下XAML:
1 <MaterialGroup> 2 <DiffuseMaterial Brush="Black" /> 3 <SpecularMaterial Brush="Green" /> 4 </MaterialGroup>
?
- MaterialGroup
????通過MaterialGroup可以在一個表面應用多個材質。MaterialGroup中材質的渲染順序正是安其定義的順序。在上面介紹其它材質時也提到了一些組合使用的技巧。
?
Geometry3D
????接下來重點了解一下構成GeometryModel3D的另一個重要組成部分Geometry3D,當前Geometry3D只有一個子類MeshGeometry3D來實現功能。
????MeshGeometry的作用是將一組指定的3D表面表示為三角形序列。MeshGeometry3D的主要屬性如下:
- Positions:定義了目標中包含的三角形的頂點。
- TriangleIndices:定義了 Positions中三角頂點的連接關系,如果沒有指定該屬性,則表示按點在Positions中出現的順序連接:0,1,2然后3,4,5依此類推。
- Normals:該屬性用來調整網格的光照
- TextureCoordinates:該屬性前文有提到,用于為Material使用的表面指定3D到2D的映射方法。
下面重點介紹這些屬性及相關概念。
- Position
網格中的三角形的頂點通過3D坐標定義。默認情況下Position集合中3個Point3D組成一個三角形。如下面這段XML定義了兩個三角形:
1 <GeometryModel3D> 2 <GeometryModel3D.Geometry> 3 <MeshGeometry3D Positions="-1,1,0 -1,-1,0 1,-1,0 -1,1,0 1,-1,0 1,1,0" /> 4 </GeometryModel3D.Geometry> 5 </GeometryModel3D>
?
- TriangleIndices
所有模型均使用三角形組合而成,包括一些彎曲的表面也是用小三角形拼接來達到近似的效果。小三角形拼接時會有很多共同邊,這就需要通過自定義點連接來實現。這是使用TriangleIndices的默認值所做不到的。如下面的例子我們使用四個點構造了一個正方形(拼接兩個三角形,上面例子中小房子的一面墻)
1 <GeometryModel3D> 2 <GeometryModel3D.Geometry> 3 <MeshGeometry3D Positions="-1,1,0 -1,-1,0 1,-1,0 1,1,0" 4 TriangleIndices="0 1 2, 0 2 3" /> 5 </GeometryModel3D.Geometry> 6 </GeometryModel3D>
如例子所示,通過TrangleIndices使三角形可以共享點的位置,此時三角形被認為是連續表面的一部分。而當不共享點位置時,三角形是分開的臨接表面,有著不同的Normal和TextureCoordinates定義。
提示:WPF中的MeshGeometry3D會確保共享點的三角形間的相鄰邊被無縫渲染。而如果通過Transform3D使兩個MeshGeometry3D重疊則可能由于誤差產生小縫隙,而避免方法是使變換程度盡量大到產生微量疊加。
??
Normals
????Normals屬性表述一個幾何學中法線的概念。法線是經過某一點垂直于表面的直線。在WPF 3D中通過指定經過各頂點的法線來通知系統三角形是表示平的表面還是表示近似的曲面。WPF中Normals集合中每個變量的類型都是Vector3D對象,且這些對象的個數與Position對象相同,來一對一的表示Position中每個點發現的方向。
????我們以如下圖形為例講述法線的作用。
如在一個3D空間中我們有兩個相鄰但不在一個平面上的三角形。假設我們不為其顯式指定法線,且兩個三角形之間沒有共享點,那么法線就是通過各項點垂直于面的線(面法線)。當我們由Y軸正方向向負方向垂直看去,三角形兩條邊及法線(藍色標識)方向如下圖所示:
則這時兩個三角形的面看起來都是平的,交接處會有很明顯的棱角的效果。
????而如果兩個三角形共享頂點,且沒有顯式指定法線。系統會根據共享點原本的兩條面法線,取一個中間值作為法線,如下圖:
這時,由于法線不與面垂直,其產生的陰影會被平滑的內插在三角形的表面,兩個三角形相鄰處也看不到任何明顯的折痕。
對于一個平面(如單一一個三角形),三條法線也是平行且垂直于平面,這時平面看起來也是平坦,要想讓其看起來有近似彎曲的表面。只需調整其中一到兩條法線的方向即可。
?
TextureCoordinates
????在2D圖形中,當向一個GeometryDrawing應用Brush時,系統會很自然的把Brush應用到2D圖形邊界范圍之內。在3D圖形中需要使用TextureCoordinates手動指明映射,這個屬性是一個集合屬性,每一項均為Brush范圍內的2D點。通過這些點將3D空間的三角映射到Brush空間的三角。
?
Model3DGroup
????Model3DGroup派生自Model3D,用于將Model3D的對象集合組合為一個單獨的模型,如將多個GeometryModel3D對象組合在一起建立一個使用不同Material的模型(一個GeometryModel3D中只能組合一套Material與Geometry3D)的模型。
?
Visual3D
????如同可以被呈現到屏幕的2D元素都繼承自Visual基類,Visual3D也是可以被呈現的3D內容的根結點。另外Visual3D同樣支持命中測試等特性,且也是通過VisualTreeHelper來使用。下面將詳細介紹Visual3D的具體子類ModelVisual3D
?
ModelVisual3D
????ModelVisual3D類似于2D圖形系統中的DrawingVisual。ModelVisual3D的Content屬性用來設置內容,其接受并呈現的內容為Model3D的對象。而且同一個Model3D對象可以在多個ModelVisual3D間重用,甚至用于處于嵌套層次中 的ModelVisual3D。另外,ModelVisual3D有一個集合類型的Children屬性用于嵌套其他ModelVisual3D對象,從而將多個Visual3D組合在一起。特別注意,不同于許多其它以Content作為內容屬性的類,ModelVisual3D將Children作為內容屬性,所以可以使用最自然的方式來將ModelVisual3D嵌套起來(但同時注意,設置Content時,XAML中一定要添加Content元素)。
提示:關于Model3DGroup和ModelVisual3D的選擇
這兩個類都具有組合子元素的作用,在它們使用的選擇上也很簡單,舉一個例子就能明白,比如一個場景中有車,有房。則使用Model3DGroup來組合車的輪子,架子和玻璃,而ModelVisual3D用來將房子和車組合在一起。
?
提示:ModelVisual3D可以被擴展,建立一個增加了自定義行為的、可重用的Visual3D類。
?
3D命中測試
????Visual3D的可視命中測試同樣是用于找到哪一個可視對象(Visual)在指針點擊處,在3D中做命中測試最簡單的方法是監聽Viewport3D元素的鼠標事件,參見下面的代碼:
1 <Viewport3D MouseDown="Viewport3D_MouseDown">
1 private void Viewport3D_MouseDown(object sender, MouseButtonEventArgs e) 2 { 3 base.OnMouseLeftButtonDown(e); 4 5 Viewport3D viewport3D = (Viewport3D) sender; 6 Point location = e.GetPosition(viewport3D); 7 8 HitTestResult result = VisualTreeHelper.HitTest(viewport3D, location); 9 10 if(result != null && result.VisualHit is Visual3D) 11 { 12 MessageBox.Show("點擊了3D對象"); 13 } 14 }
當然在3D中,使用委托來反饋結果的HitTest函數的重載也是被支持的。如同在2D中一樣,結果以由后到前的順序返回。HitTest還有一個重載接受一個Visual3D和一個HitTestParameters3D作為參數。
提示:在命中測試中,可以根據命中目標的不同將HitTestResult轉化為PointHitTestResult或GeometryHitTestResult等類型。比較特殊的,當命中目標是一個3D網格時,可以將HitTestResult強制轉化為一個RayMeshGeometry3DHitTestResult對象,其中會包含大量交叉點的詳細信息。
?
Viewport3D元素
????Viewport3D相當于2D中的FrameworkElement。Viewport3D的父元素是像Window或者Grid這樣的2D元素。Viewport3D的子元素是Visual3D。Visual3D的子元素描述的3D場景是在Viewport3D矩形布局框中渲染。Viewport3D的Camera屬性控制了從Viewport3D中看到的3D場景的視圖。
提示:需要顯示指定Viewport3D的Width和Height,Viewport3D 不能像Button那樣根據其中的內容自動調整大小,也就說Viewport3D不知道其中的Visual3D有多大而默認會將Width和Height置0,從而無法顯示內容。
?
????作為一個FrameworkElement對象,Viewport3D支持系統的布局功能。這樣可以將3D元素集成到幾乎任何地方。如同過Sytle或ControlTemplate屬性將控制外觀換成可以交互的3D內容。
提示:在內部Viewport3D是通過Viewport3DVisual將3D可視樹關聯到2D可視樹上2DVisual對象的。Viewport3D在Viewport3DVisual的基礎上增加了Viewport屬性,用來實現Framework層擁有而Visual層沒有的布局的概念,以設置3D場景的顯式范圍。
?
本文完
?
參考:
《WPF揭秘》