本主題概述 Windows Presentation Foundation (WPF) 圖形系統中的三維功能。通過 WPF 三維實現,開發人員可使用與該平臺所提供給二維圖形的相同的功能,對標記和過程代碼中的三維圖形進行繪制、轉換和動畫處理。 開發人員可以合并二維和三維圖形來創建豐富的控件,提供復雜的數據圖解,或者增強用戶對應用程序界面的體驗。WPF 支持三維的設計宗旨不是提供功能齊全的游戲開發平臺。
二維容器中的三維
WPF 中的三維圖形內容封裝在 Viewport3D 元素中,該元素可以參與二維元素結構。與 WPF 中的許多其他內容一樣,圖形系統將 Viewport3D 視為二維可視化元素。Viewport3D 充當三維場景中的一個窗口(視區)。更準確地說,它是三維場景所投影到的圖面。
在傳統的二維應用程序中,當您需要使用 Grid 或 Canvas 之類的另一個容器元素時,可以使用 Viewport3D。 盡管您可以將 Viewport3D 與同一個場景圖中的其他二維繪圖對象結合使用,但是您不能在 Viewport3D 內部滲透二維和三維對象。 本主題重點講述如何在 Viewport3D 內部繪制三維圖形。
三維坐標空間
二維圖形的 WPF 坐標系將原點定位在呈現區域(通常是屏幕)的左上角。在二維系統中,x 軸上的正值朝右,y 軸上的正值朝下。 但是,在三維坐標系中,原點位于呈現區域的中心,x 軸上的正值朝右,但是 y 軸上的正值朝上,z 軸上的正值從原點向外朝向觀察者。
傳統的二維和三維坐標系表示形式
由這些軸定義的空間是三維對象在 WPF 中的固定參考框架。當您在該空間中生成模型并創建光源和照相機以查看這些模型時,一定要在向每個模型應用變換時,將固定參考框架或“全局空間”與您為該模型創建的局部參考框架區分開。另請記住,根據光源和照相機設置,全局空間中的對象可能會看上去完全不同或者根本不可見,但是照相機的位置不會改變對象在全局空間中的位置。
照相機和投影
處理二維對象的開發人員習慣于將繪圖基元置于二維屏幕上。當您創建三維場景時,一定要記住您實際上是要創建三維對象的二維表示形式。由于三維場景的外觀會因觀察者的觀察位置不同而異,因此您必須指定觀察位置。可以使用 Camera 類來為三維場景指定觀察位置。
了解三維場景如何在二維圖面上表示的另一種方法就是將場景描述為到觀察表面上的投影。使用 ProjectionCamera,可以指定不同的投影及其屬性以更改觀察者查看三維模型的方式。PerspectiveCamera 指定用來對場景進行透視收縮的投影。 換言之,PerspectiveCamera 提供消失點透視。 您可以指定照相機在場景坐標系中的位置、照相機的方向和視野以及用來定義場景中“向上”方向的向量。下圖闡釋 PerspectiveCamera 的投影。
ProjectionCamera 的 NearPlaneDistance 和 FarPlaneDistance 屬性限制照相機的投影范圍。由于照相機可以位于場景中的任何位置,因此照相機實際上可能會位于模型內部或者緊靠模型,這使得很難正確區分對象。使用 NearPlaneDistance,可以指定一個距離照相機的最小距離,即,在超過該距離后將不繪制對象。 相反,使用 FarPlaneDistance,可以指定一個距離照相機的距離(即,在超過該距離后將不繪制對象),從而確保因距離太遠而無法識別的對象將不包括在場景中。
照相機位置
OrthographicCamera 指定三維模型到二維可視化圖面上的正投影。與其他照相機一樣,它指定位置、觀察方向和“向上”方向。但是,與 PerspectiveCamera 不同的是,OrthographicCamera 描述了不包括透視收縮的投影。換言之,OrthographicCamera 描述了一個側面平行的取景框,而不是側面匯集在場景中一點的取景框。下圖演示使用 PerspectiveCamera 和 OrthographicCamera 查看同一模型時的情況。
透視投影和正投影
模型和網格基元
Model3D 是表示泛型三維對象的抽象基類。 若要生成三維場景,需要一些要查看的對象,而且構成場景圖的對象必須派生自 Model3D。 目前,WPF 支持用 GeometryModel3D 對幾何形狀進行建模。此模型的 Geometry 屬性采用網格基元。
若要生成模型,請首先生成一個基元或網格。 三維基元是一系列構成單個三維實體的頂點。 大多數三維系統都提供在最簡單的閉合圖(由三個頂點定義的三角形)上建模的基元。 由于三角形的三個點在一個平面上,因此您可以繼續添加三角形,以便對網格這樣較為復雜的形狀建模。
WPF 三維系統目前提供 MeshGeometry3D 類,使用該類,可以指定任何幾何形狀;它目前不支持預定義的三維基元(如球體和立方體)。首先通過將三角形頂點的列表指定為它的 Positions 屬性來創建 MeshGeometry3D。每個頂點都指定為 Point3D。 (在可擴展應用程序標記語言 (XAML) 中,將該屬性指定為三個一組的數字列表,每組中的三個數字表示每個頂點的坐標)。 根據網格的幾何形狀,網格可能會由多個三角形組成,其中的一些三角形共用相同的角(頂點)。 若要正確地繪制網格,WPF 需要有關哪些頂點由哪些三角形共用的信息。 可以通過指定具有 TriangleIndices 屬性的三角形索引列表來提供此信息。此列表指定在 Positions 列表中指定的點將按哪種順序確定三角形。
在上面的示例中,Positions 列表指定用八個頂點來定義立方體形狀的網格。 TriangleIndices 屬性指定了一個包含十二個組的列表,每組由三個索引組成。 列表中的每個數字都指向與 Positions 列表的偏移量。 例如,由 Positions 列表指定的第一組(三個頂點)是 (1,1,0)、(0,1,0) 和 (0,0,0)。由 TriangleIndices 列表指定的第一組(三個索引)是 0、2 和 1,這與 Positions 列表中的第一個、第三個和第二個點相對應。 因此,構成立方體模型的第一個三角形將按照從 (1,1,0) 到 (0,1,0) 再到 (0,0,0) 的順序組合而成,其余的十一個三角形將按照類似方式確定。
您可以通過為 Normals 和 TextureCoordinates 屬性指定值來繼續定義模型。 為了呈現模型的圖面,圖形系統需要有關曲面在任何給定三角形上的朝向信息。圖形系統使用此信息來針對該模型進行照明計算:直接朝向光源的圖面比偏離光源的圖面顯得更亮。盡管 WPF 可以使用位置坐標來確定默認的法向量,但是您還可以指定不同的法向量來近似計算曲面的外觀。
TextureCoordinates 屬性指定 Point 集合,該集合可通知圖形系統如何將用來確定紋理繪制方式的坐標映射到網格的頂點。TextureCoordinates 可指定為 0 和 1(包括 0 和 1)之間的值。 如同 Normals 屬性一樣,圖形系統可以計算默認紋理坐標,但是您可以選擇設置不同的紋理坐標來控制對包括重復圖案一部分的紋理的映射。
下面的示例演示如何在過程代碼中創建立方體模型的一面。請注意,您可以將整個立方體繪制為單個 GeometryModel3D;此示例將該立方體的各個面繪制為一個不同的模型,以便在以后向每個面應用不同的紋理。
向模型應用 Material
為了使網格看上去像三維對象,必須向其應用紋理,以便覆蓋由頂點和三角形定義的圖面,從而使其可以由照相機照明和投影。在二維中,可以使用 Brush 類來向屏幕中的區域應用顏色、圖案、漸變或其他可視化內容。 但是,三維對象的外觀是照明模型的功能,而不只是應用于它們的顏色或圖案。實際對象的圖面質量不同,它們反射光的方式也會有所不同:光亮的圖面與粗糙或不光滑的圖面看上去不同,某些對象似乎可以吸收光,而某些對象似乎能夠發光。您可以向三維對象應用與應用于二維對象的完全相同的畫筆,但是您不能直接應用它們。
WPF 使用 Material 抽象類來定義模型圖面的特征。Material 的具體子類用來確定模型圖面的某些外觀特征,每個子類還提供一個可以向其傳遞 SolidColorBrush、TileBrush 或 VisualBrush 的 Brush 屬性。
DiffuseMaterial 指定將向模型應用畫筆,就好像模型是使用漫射光來照亮的一樣。使用 DiffuseMaterial 與直接針對二維模型使用畫筆非常相似;模型表面不反射光,就好像是自發光一樣。
SpecularMaterial 指定將向模型應用畫筆,就好像模型的表面堅硬或者光亮,能夠反射光線一樣。可以通過為 SpecularPower 屬性指定一個值來設置系統將為紋理的反射特質(或“發光”)建議的度數。
使用 EmissiveMaterial 可以指定將應用紋理,就好像模型所發出的光與畫筆的顏色相同。這不會使模型成為光源;但是,它參與陰影設置的方式將不同于用 DiffuseMaterial 或 SpecularMaterial 設置紋理時的情況。
為進一步提高性能,可以從場景中精選 GeometryModel3D 的背面(由于它們相對于照相機位于模型的背面,因此您將看不到這些面)。 若要指定要應用于模型(如飛機)背面的 Material,請設置模型的 BackMaterial 屬性。
為了實現某些圖面質量(如發光或發射效果),您可能希望向模型連續應用幾個不同的畫筆。您可以使用 MaterialGroup 類來應用和重用多個 Material。MaterialGroup 的子級在多個呈現過程中按照從頭到尾的順序來應用。
下面的代碼示例演示如何將純色和繪圖以畫筆形式應用于三維模型。
照亮場景
與實際的光一樣,三維圖形中的光能夠使圖面可見。更確切地說,光確定了場景的哪個部分將包括在投影中。WPF 中的光對象創建了各種光和陰影效果,而且是按照各種實際光的行為建模的。您必須至少在場景中包括一個光,否則模型將不可見。
下面的光派生自基類 Light:
AmbientLight:它所提供的環境光以一致的方式照亮所有的對象,而與對象的位置或方向無關。
DirectionalLight:像遠處的光源那樣照亮。 將方向光的 Direction 指定為 Vector3D,但是沒有為方向光指定位置。
PointLight:像近處的光源那樣照亮。PointLight 具有一個位置并從該位置投射光。場景中的對象是根據對象相對于光源的位置和距離而照亮的。PointLightBase 公開了一個 Range 屬性,該屬性確定一個距離,在超過該距離后模型將無法由光源照亮。PointLight 還公開了多個衰減屬性,這些屬性確定光源的亮度如何隨距離的增加而減小。您可以為光源的衰減指定恒定、線性或二次內插算法。
SpotLight:繼承自 PointLight。Spotlight 的照亮方式與 PointLight 類似,但是它既具有位置又具有方向。它們在 InnerConeAngle 和 OuterConeAngle 屬性所設置的錐形區域(以度為單位指定)中投射光。
光源是 Model3D 對象,因此您可以轉換光源對象并對光源屬性(包括位置、顏色、方向和范圍)進行動畫處理。
變換模型
當您創建模型時,它們在場景中具有特定的位置。為了在場景中移動、旋轉這些模型或者更改這些模型的大小而更改用來定義模型本身的頂點是不切實際的。 相反,正如在二維中一樣,您可以向模型應用轉換。
每個模型對象都有一個可用來對模型進行移動、重定向或調整大小的 Transform 屬性。 當您應用轉換時,實際上是按照由轉換功能指定的向量或值(以適用者為準)來有效地偏移模型的所有點。換言之,您已經變換了在其中定義模型的坐標空間(“模型空間”),但是,您尚未更改在整個場景的坐標系(“全局空間”)中構成模型幾何形狀的值。
對模型進行動畫處理
WPF 三維實現與二維圖形參與同一個計時和動畫系統。換言之,若要對三維場景進行動畫處理,需要對其模型的屬性進行動畫處理。 可以直接對基元的屬性進行動畫處理,但是通常很容易對用來更改模型位置或外觀的變換進行動畫處理。 由于可以向 Model3DGroup 對象及其各個模型應用轉換,因此可以向 Model3DGroup 的子級應用一組動畫,向一組子對象應用另一組動畫。 還可以通過對場景的照明屬性進行動畫處理來實現各種可視化效果。 最后,您可以選擇通過對照相機的位置或視野進行動畫處理來對投影本身進行動畫處理。
若要對 WPF 中的對象進行動畫處理,可以創建時間線、定義動畫(實際上是隨著時間的推移而更改某個屬性值)并指定要向其應用動畫的屬性。 由于三維場景中的所有對象都是 Viewport3D 的子級,因此要應用于場景的任何動畫所面向的屬性都是 Viewport3D 的屬性。
假設您希望實現模型看上去是在原地搖擺的效果, 您可以選擇向模型應用 RotateTransform3D,并對模型從一個向量旋轉到另一個向量時所圍繞的軸進行動畫處理。下面的代碼示例演示如何將 Vector3DAnimation 應用于該轉換的 Rotation3D 的 Axis 屬性,并假設 RotateTransform3D 是應用于具有 TransformGroup 的模型的幾個轉換之一。
向窗口中添加三維內容
若要呈現場景,請向 Model3DGroup 中添加模型和光源,然后將 Model3DGroup 設置為 ModelVisual3D 的 Content。將 ModelVisual3D 添加到 Viewport3D 的 Children 集合中。 通過設置 Viewport3D 的 Camera 屬性來向其添加照相機。
最后,請向該窗口中添加 Viewport3D。在將 Viewport3D 作為布局元素(如 Canvas)的內容來包含時,可以通過設置 Viewport3D 的 Height 和 Width 屬性(繼承自 FrameworkElement)來指定 Viewport3D 的大小。