在組成 Microsoft Windows Presentation Foundation 的類中,System.Windows.Media.Media3D 命 名空間中的那些類很突出。這些類的用途是使主流 Windows® 應用程序能夠顯示三維圖形。與 Windows Presentation Foundation 2D 圖形一樣,通常可以用可擴展應用程序標記語言 (XAML) 非常方 便地訪問 3D 圖形,但二者的相似性非常少。3D 圖形編程涉及非常不同的概念和約定。其中,3D 和 2D 相同的部分是畫筆的區域:您始終要用 2D 畫筆來覆蓋 3D 可視區的表面。
圖 1 顯示了 Hello3D,這是一個 3D 版的傳統“Hello, World”程序。如果您運行的是 Windows Vista™ 或者是安裝了 Microsoft® .NET Framework 3.0 運行庫的 Windows XP,則 只需使用 Internet Explorer® 即可啟動產生該圖形的 XAML 代碼,從而可以看到圖像(參見圖 2) 。
圖 1 Hello3D 圖像
3D 視區
在 3D 圖形編程中,沒有線條、Bezier 樣條曲線、矩形或橢圓。每個 3D 物體都是三 維坐標空間中的三角形的集合。三角形是 3D 編程的基本單位,這是因為每個單獨的三角形總是能定義一 個平面,而三角形集合可以模仿立體物體,甚至可以模擬曲面。隨著您深入了解 3D 編程,您將會用三角 形看待生活中的所有事物。
正如 Hello3D.xaml 所示,3D 視圖由 Viewport3D 元素組成。3D 場 景需要一個或多個 GeometryModel3D 類型的物體、一個或多個光源、以及一個用於控制 3D 物體如何投 射到 2D 表面從而控制觀看者如何看到圖像的攝像機。
GeometryModel3D 元素有三個重要屬性: Geometry、Material 和 BackMaterial。Geometry 屬性被設置為 MeshGeometry3D 元素,用於根據坐標 點和三角形描述可視物體。Material 和 BackMaterial 屬性說明物體的前面和背面如何著色。在 Hello3D.xaml 中,這兩個屬性被設置為 DiffuseMaterial 類型的對象。Material 屬性是 VisualBrush ,由包含文字“Hello, World”的 TextBlock 組成。BackMaterial 屬性只是紅色畫筆。(如 果要看到物體的背面,請將攝像機 Position 屬性更改為“0 0 -5”,並將 LookDirection 更改為“0 0 1”。)
解析網格幾何體
在本專欄中,我將著重介紹此 Viewport3D 聚合的一個特別重要的部分 — MeshGeometry3D 類,該類用於定義 3D 物體的實際幾 何表示形式。該類有四個重要屬性:Positions、TriangleIndices、TextureCoordinates 和 Normals。 Positions 屬性是 Point3D 對象的集合,這些對象通過 X、Y 和 Z 坐標定義位置。在此坐標系統中,X 坐標向右增加,Y 坐標沿屏幕向上增加,Z 坐標向屏幕外增加。(這稱為右手坐標系統;如果用右手食指 指向 X 值增加方向,並用中指指向 Y 值增加方向,則拇指指向 Z 值增加方向。Windows Presentation Foundation 3D 系統還實現了右手旋轉定則:如果右手拇指指向任何軸的增加值方向,則其他手指的曲線 顯示圍繞該軸的正向旋轉軸的方向。)
Hello3D.xaml 示例中的 Positions 屬性有六個以逗號分 隔的 Point3D 對象:
Positions="-2 1 -1, 0 2 0, 2 1 -1, -2 -1 -1, 0 -2 0, 2 -1 -1"
前面三個從左到右是物體頂部的坐標,最後三個是對應的底部 坐標。注意,中心的 Z 坐標是 0,但在左右邊緣是 -1,因此中心是左右邊緣的前景。
Positions 屬性表示物體的所有頂點。這些頂點在定義物體時肯定是有作用的,但它們不能描述所有信息。任何一組 三個頂點都可以組合成一個三角形,這就是 TriangleIndices 集合所說明的內容。TriangleIndices 是 三個一組排列的整數集合。每一組的三個整數都定義了一個三角形。整數值是 Positions 集合中的序數 。例如,第一個三聯數是 0 3 1,它是指 3D 點 (-2, 1, -1)、(-2, -1,-1) 和 (0, 2, 0)。這是位於物 體左上角的三角形。
TriangleIndices="0 3 1, 1 3 4, 1 5 2, 1 4 5"
TriangleIndices 集合實際上驅動物體的呈現。TriangleIndices 未引用的任何 Positions 元素都會忽略(如果沒有 TriangleIndices,則 Positions 集合中的每個 Point3D 三聯數都 將解釋為一個三角形)。
每個三角形都應有正面和背面。查看三角形的正面時,三聯數以反時針方向表示頂點。如果將第一個 三聯數更改為 0 1 3,將看到左上三角形以紅色著色,這是因為查看的是三角形的背面,而不是它的正面 。
如果 3D 物體以實線畫筆著色,則 Positions 和 TriangleIndices 屬性就已足夠。對於其他類型的 畫筆(漸變畫筆或並列畫筆),還需要 TextureCoordinates。畫筆是像熱塑包裝一樣覆蓋 3D 物體的 2D 表面。TextureCoordinates 集合表示 3D 物體的頂點和 2D 畫筆的坐標之間的對應關系。此集合包含與 Positions 中的每個 3D 點對應的一個 2D 點。這些 2D 點是以 Y 軸向下為增加方向的相對坐標(0 和 1 之間)。點 (0, 0) 表示畫筆的左上角,(1, 1) 是右下角。在 Hello3D.xaml 中,六個 2D 點表示 VisualBrush 的坐標,這些坐標被定義為 GeometryModel3D 元素的 Material 屬性:
TextureCoordinates="0 0, 0.5 0, 1 0, 0 1, 0.5 1, 1 1"
實際上,3D 物體的每個三角形均由畫筆的三角形覆蓋,這些三角形可能需要拉伸或收縮以適合具體大 小。
Normals 屬性是按與 Positions 集合的一對一對應關系得到的向量的集合。每個頂點均被視為面向特 定方向,該方向以該頂點的 Normals 向量表示。每個三角形內的每個點基於在其三個頂點上的向量的內 插值,以不同方式反射光線。如果不提供 Normals 集合,則會基於在網格規范中共享的每個頂點上會合 的三角形的 Normals 的平均值計算一個該集合。
算法網格幾何體
System.Windows.Media Media3D 命名空間中的類沒有提供超越 MeshGeometry3D 的任何更高級別的接 口。(看完本文後,您可能會理解為什麼。)對於諸如金字塔體和立方體這樣具有平滑側面的簡單物體來 說,可以手工編寫 MeshGeometry3D 對象。對於更復雜的圖元(特別是那些有曲面的圖元)來說,您可能 會選擇使用符合 .NET 的語言通過算法生成頂點和系數。的確,如果您要求更進一步,則可能想到建立一 個由從 MeshGeometry3D 派生的網格幾何體組成的完整庫。
但您會立即發現這樣行不通。MeshGeometry3D 是密封的;它無法被繼承。此外,MeshGeometry3D 本 身派生於抽象類 Geometry3D,並且無法從 Geometry3D 派生,因為它有獨立的內部構造函數。
如果不考慮從 MeshGeometry3D 派生類,則替代的途徑是直接定義一個能夠生成 MeshGeometry3D 對 象的類,並將該對象作為屬性公開。然後,可以在 XAML 文件中將此類定義為資源,並通過綁定引用 MeshGeometry3D。
圖 3 顯示了一個用 C# 編寫的名為 SimpleCylinderGenerator 的類。(在這裡,我將僅限於圓柱體 。)在此專欄的可下載代碼中,SimpleCylinderGenerator 支持名為 Petzold.MeshGeometries 的 DLL。
SimpleCylinderGenerator 類有兩個屬性:使用 Slices 屬性可以定義用多少三角形模擬柱狀體的曲 面。(我從 Direct3D 類庫中的靜態 Mesh.Cylinder 方法的文檔中選用術語“slices”。)沿柱狀體長 度方向分布的三角形的個數實際上是 Slices 屬性的兩倍。柱狀體頂部和底部各自還需要很多等於 Slices 的三角形。
MeshGeometry 屬性基於 Slices 屬性創建 MeshGeometry3D 對象。對算法進行硬編碼,以創建以一個 單位的半徑沿正向 Y 軸擴展一個單位的柱狀體。注意,可以在隨後調整此物體的大小,並使用轉換將它 移動到任何所需位置。
SimpleCylinderDemo 程序顯示了如何使用此 SimpleCylinderGenerator 類。此程序的主體是 SimpleCylinderDemo.xaml 文件,如圖 4 所示。根元素包含定義 SimpleCylinderGenerator 類並通過前 綴 pmg(表示“Petzold 網格幾何體”)與它關聯的命名空間和 DLL 的 XML 命名空間聲明。
Resources 部分包括鍵名稱為“cylinder”並且 Slices 值為 36 的 SimpleCylinderGenerator 類型 的對象。GeometryModel3D 元素將它的 Geometry 屬性賦給此資源的綁定及其 MeshGeometry 屬性。轉換 將更改柱狀體的大小。在 DiffuseMaterial 上添加了一些有向光線和 SpecularMaterial 對象後,結果 如圖 5 所示。通過使用應用於該物體的任意轉換,可以使此物體運動。
圖 5 柱狀體
顯然,SimpleCylinderGenerator 技術是有效的,但我感到它有很多缺點和不足。找出這些問題並解 決它們將使您不僅能從幾個方面特別地洞察網格幾何體,而且還能洞察 Windows Presentation Foundation 編程的某些常規方面。
適應使用者
當我剛開始為諸如柱狀體等物體定義網格幾何體時,我首先努力實現對稱性, SimpleCylinderGenerator 反映了我的偏好:所有三角形都是等腰三角形。但對於沿柱狀體縱向分布的三 角形來說,實際上這一點就成了問題。
通常,當 Slices 屬性很小時,有利的一點在於網格幾何算法會產生某些有用的結果。請嘗試將 SimpleCylinderGenerator 的 Slices 屬性設置為 4,並在圖 6 的左側顯示物體。(為了說明目的,邊 緣已增強。)這很有趣,但不如右側的物體有用。右側的物體要求縱向三角形不是等腰三角形,而是直角 三角形,以便每一對直角三角形形成一個矩形。
圖 6 對柱狀體進行切片
SimpleCylinderGenerator 不生成 TextureCoordinates 屬性,如果將只用 SolidColorBrush 覆蓋柱 狀體,則需要該屬性。另一方面,如果想使用任何類型的 GradientBrush 或任何類型的 TileBrush,則 需要一個與 Positions 屬性一起智能地生成的 TextureCoordinates 屬性。
用 2D 畫筆覆蓋柱狀體時,您可能希望畫筆環繞柱狀體,以便畫筆的左右邊緣在我將其稱之為“接縫 ”的線條上會合。此接縫應當沿柱狀體的縱向延伸。之所以使用直角三角形而不是等腰三角形來定義網格 ,還有另一個原因。
SimpleCylinderGenerator 還反映了我的另一個偏好,經濟性:Positions 集合中的所有 Point3D 對 象都是唯一的。在生成 TriangleIndices 集合的 for 循環中,使用了模運算符,以便某些隨後的三角形 使用 Positions 集合中前期存在的點。對我來說,似乎為了“閉合”物體必須要這樣做,但這完全是錯 誤的。同樣,如果想用畫筆覆蓋柱狀體,需要在接縫處有重復的 Point3D 對象,以便 3D 空間中的相同 位置同時映射到畫筆的左側和右側。
通過使柱狀體有一個單位的長度和半徑,SimpleCylinderGenerator 采取了在定義 Positions 集合時 可以想像的最容易的途徑。然後,它依賴於類的使用者(可能是您或另一個程序員)來實現轉換,以正確 調整柱狀體大小和位置。通過使用轉換操作,可以相當容易地將柱狀體的底部轉換到另一個位置,但如果 要將柱狀體的頂部也放到特定位置呢?您將需要為該操作計算旋轉轉換。
請讓使用者休息一會兒!如果讓柱狀體生成程序具有屬性 Point1 和 Point2 以表示柱狀體兩端的中 心坐標位置,將會更有意義。可能還要考慮 Radius1 和 Radius2 屬性,以便單獨設置兩端的半徑。具有 單獨的半徑屬性會帶來額外的好處:如果一個半徑是 0,則算法將生成一個錐體。定義網格幾何算法時, “獎勵”圖元始終是受歡迎的。
迄今為止,對 SimpleGeometryGenerator 的改進已經形成一個很長的列表,但我要使該列表更長。
與 XAML 集成
若要使用 SimpleCylinderGenerator,必須將該類定義為資源,然後用綁定訪問它。如果有一個用 XAML 實例化並直接集成到 Viewport3D 標記中的類,則會是更好的選擇。但如何實現?它無法從 MeshGeometry3D 或 Geometry3D 派生。
幸運的是,有另一個方式,但它需要熟悉往往使人糊塗的 Media3D 命名空間和某些在類名稱中涉及的 術語。首先談一談可視效果和模型之間的差異。
可視效果是可以在屏幕上呈現自身的項目。在 Media3D 命名空間中,這是抽象的 Visual3D 類。 Visual3D 對象是 Viewport3D 的子對象。
相比之下,模型是對可視效果可以顯示的項目的描述。在 3D 編程中,模型包括 3D 物體自身,還包 括照射它們的光線。多個可視效果可以共享相同模型。例如,在 Media3D 命名空間中,模型由抽象 Model3D 類表示。從 Model3D 派生的類包括 GeometryModel3D、Light(它是抽象的)和 Model3DGroup 。
將可視效果與模型聯系在一起的類是 ModelVisual3D。ModelVisual3D 派生自 Visual3D,因此它一定 是可視效果,但它有 Model3D 類型的 Content 屬性。模型是可視效果的內容,這一點很像文本或位圖是 按鈕的內容。
令人吃驚的是,ModelVisual3D 是在整個 Media3D 命名空間中唯一既不密封也不抽象的類,這意味著 它可以用於繼承而不會有明顯的問題(有關詳細信息,請參閱在 blogs.msdn.com/478923.aspx 的 Daniel Lehenbauer 的博客內容,以及在 blogs.msdn.com/479924.aspx 的 Karsten Januszewski 的博 客內容)。
假設您編寫了一個從 ModelVisual3D 派生的 Cylinder 類。然後,可以指定諸如 Viewport3D 的子項 這樣的元素:
<Viewport3D> <pmg:Cylinder Point1="0 1 5" Point2="3 2 -4" Radius1="0.25" Radius2="0.125" /> ... </Viewport3D>
這似乎很方便,但它並不像此標記隱含的那樣簡單。如果 Cylinder 類派生自 ModelVisual3D,那麼 它繼承了類型 Model3D(GeometryModel3D 從中派生而來)的 Content 屬性。Cylinder 必須創建一個類 型為 GeometryModel3D 的對象,以便設置為它繼承的 Content 屬性。GeometryModel3D 定義了 Geometry 屬性,因此 Cylinder 類也會創建 MeshGeometry3D 對象以便設置為此 Geometry 屬性。
迄今為止,都還不壞,但還有問題。GeometryModel3D 還有 Material 和 BackMaterial 屬性。這是 材料如何與網格幾何體關聯的問題。若要使此方案正確工作,Cylinder 類必須重新定義 Material 和 BackMaterial 屬性,因此標記將類似於如下所示:
<Viewport3D> <pmg:Cylinder Point1="0 1 5" Point2="3 2 -4" Radius1="0.25" Radius2="0.125"> <pmg:Cylinder.Material> ... </pmg:Cylinder.Material> <pmg:Cylinder.BackMaterial> ... </pmg:Cylinder.BackMaterial> </pmg:Cylinder> ... </Viewport3D>
現在,大問題來了:我已經提到過,Cylinder 類有名為 Point1、Point2、Radius1 和 Radius2 的屬 性。您是否想讓這些屬性潛在地成為數據綁定的目標?我想您希望如此。您是否希望它們可以啟用?我確 信您希望如此。在這兩種情況下,這些屬性都必須被定義為依賴性屬性。您可能聽說過,Windows Presentation Foundation 中的所有東西都是可啟用的(但只有當它是依賴性屬性時)。
依賴性屬性
依賴性屬性已作為 Windows Presentation Foundation 的最重要的創新之一出現。在 Windows Presentation Foundation 中,可以按多種方式設置屬性。可以在代碼或 XAML 中直接在對象上設置屬性 。此外,可以通過樣式和數據綁定來設置屬性。某些屬性可以通過父子關系繼承,並且屬性可以啟用。如 果根本不設置屬性,則它會擁有默認值。依賴性屬性試圖使所有這樣的多樣性能夠以可預測的優先級正確 工作。
依賴性屬性需要您的類中有一些涉及向系統注冊屬性的非常大的開銷,但完成之後,可以幾乎不必考 慮如何設置該屬性。重要的是您的類會在屬性已經更改時獲得通知,因而它可以對這些更改做出反應。
本文附帶的可下載代碼中的 Cylinder 類定義了九個依賴性屬性。我沒有直接從 ModelVisual3D 派生 Cylinder,而是創建了一個可能是其他類(例如 Sphere、Tube 和 BunnyRabbit)的父類的中間類,名為 ModelVisualBase。ModelVisualBase 定義了 Geometry、Material 和 BackMaterial 依賴性屬性,它們 向在 GeometryModel3D 中定義的相同屬性添加所有者。
作為如何在代碼中實現依賴性屬性的示例,讓我們看一看 Cylinder 的 Radius1 屬性。注冊依賴性屬 性的過程涉及定義一個 DependencyProperty 類型的公用靜態只讀字段,名為 Radius1Property,它是屬 性名加上單詞 Property:
public static readonly DependencyProperty Radius1Property = DependencyProperty.Register( "Radius1", typeof(double), typeof(Cylinder), new PropertyMetadata(1.0, PositionsChanged), ValidateNonNegative);
注意,PropertyMetadata 構造函數的參數(用於表示默認值)、Cylinder 類中當值發生更改時要調 用的方法以及用於驗證值的方法。
還包括引用此靜態只讀字段的傳統屬性定義(又稱為“CLR 屬性”):
public double Radius1 { get { return (double)GetValue(Radius1Property); } set { SetValue(Radius1Property, value); } }
GetValue 和 SetValue 方法是從 DependencyObject 繼承的。任何實現依賴性屬性的類都必須派生自 DependencyObject。
請務必意識到對 Radius1 屬性的更改不總是通過 CLR 屬性進行的。例如,當啟用 Radius1 時,會訪 問 Radius1Property 字段,而不是 Radius1 屬性。因此,除了調用 GetValue 和 SetValue 外,應當避 免在 Radius1 屬性中進行其他任何操作。
Radius1Property 的定義包括對 Cylinder 類中兩個方法的引用。因為這些方法是由靜態字段引用的 ,因此方法自身也必須是靜態的。ValidateNonNegative 方法只是檢查 Radius1 是否未設置為負數值:
static bool ValidateNonNegative(object value)
{
return (double)value >= 0;
}
如果某些代碼將 Radius1 設置為負數值,將發生異常,但這不是您的類的責任。
其他方法用於通知類:屬性已更改。由於可以在不訪問 CLR 屬性的情況下更改屬性,因此該通知方法 是對此屬性更改做出反應的唯一機會。下面是我稱為 PositionsChanged 的方法,當 Radius1 屬性已更 改時將調用該方法:
static void PositionsChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
Cylinder cyl = (Cylinder)obj;
cyl.GeneratePositions();
}
該方法是靜態的,但第一個參數是其屬性發生改變的實際 Cylinder 對象。
DependencyPropertyChangedEventArgs 對象具有關於發生更改的特定屬性、它的舊值和新值的相關信 息,但 Cylinder 將忽略這些信息,而直接調用實例方法:GeneratePositions。此方法負責基於新的半 徑生成 MeshGeometry3D 對象的 Positions 和 Normals 屬性。
正確處理集合
為了定義 MeshGeometry3D 對象的四個集合,Cylinder 類定義了三個單獨的方法。 GeneratePositions 負責 Positions 和 Normals 集合;其他兩個方法名為 GenerateTriangleIndices 和 GenerateTextureCoordinates。Cylinder 的構造函數將調用所有三個方法以初始化對象;此後,將在 響應依賴性屬性的更改時調用它們。對於大多數屬性(Point1、Point2、Radius1、Radius2),只有 Positions 和 Normals 集合需要重新計算。但對於 Slices 屬性,所有四個集合都需要重做。類似於 Slices 屬性的是另一個名為 Stacks 的屬性,該屬性控制柱狀體縱向的細分。在很多情況下,可以將 Stacks 設置為等於 1,這是默認值。但如果使用 PointLight 或 SpotLight 照射柱狀體,則需要生成小 很多的三角形來使光線的照射正確。
當諸如 Point1 和 Radius1 這樣的屬性啟用時,可以非常頻繁和盡可能快速地調用像 GeneratePositions 這樣的方法。下面是一些潛在的問題和解決方案。
特別是,方法應當避免分配內存。您希望的最後一件事是讓 CLR 垃圾收集器處理您的動畫,清理您留 下的垃圾。如果全都有可能,則任何新的表達式都應當引用結構,而不是類。例如,在 Cylinder 類中, 我讓構造函數創建了 RotateTransform3D 和 AxisAngleRotation3D 類型的對象,它們作為字段進行存儲 並且可在每次調用 GeneratePositions 時重用。
您設置為 MeshGeometry3D 屬性的集合屬於 Point3DCollection、Vector3DCollection、 Int32Collection 和 PointCollection 類型。所有集合都派生自 Freezable,並且包括在集合的任何元 素更改時所觸發的 Changed 事件。這提供了功能強大的工具:例如,這些集合的單個成員可以啟用,以 創建 3D 物體的變體。但如果您全部重新計算整個集合,則只是想讓通知在您完成時才發生。因此,應當 將集合與 MeshGeometry3D 對象分離,並在您完成時再重新連接它。下面顯示了 GeneratePositions 如 何開始和結束:
void GeneratePositions()
{
MeshGeometry3D mesh = (MeshGeometry3D)Geometry;
Point3DCollection points = mesh.Positions;
mesh.Positions = null;
points.Clear();
...
mesh.Positions = points;
}
第一個語句訪問由 ModelVisualBase 定義並由 Cylinder 繼承的 Geometry 屬性。注意,該方法重用 Point3DCollection,而沒有重新分配一個新的。四個集合均由 Cylinder 的構造函數創建,創建時將使 用它們將基於默認 Slices 屬性而包含的元素的個數。如果 Slices 增加,則集合大小可能不足,並且必 將發生內存分配,但內存分配的發生不應非常頻繁。
另一方面,如果需要用大量元素填充集合,則應當用表示元素個數的構造函數創建該集合。通過分配 所需內存,一開始會有助於在向集合添加元素時避免頻繁的內存分配。感謝 Tim Cahill 在 2006 年 8 月 31 日的博客和 Daniel Lehenbauer 在 2006 年 11 月 19 日的總結所提供的這些提示。
接縫和結束
在本專欄前面,我討論過讓畫筆左端與右端會合的接縫。此接縫應當在哪裡?我決定它應當在柱狀體 的“背面”,在通常情況下它指向負向 Z 軸。此邏輯接縫的唯一位置位於柱狀體的 Z 軸,在此情況下 Cylinder 類將它放在底部。
可以讓 ImageBrush 環繞柱狀體,如果柱狀體的長度和周長與位圖的長寬之比相等,則不會有任何扭 曲。但有一個問題涉及柱狀體的兩端。柱狀體的兩端應當怎樣?我決定一定要讓圖像的組成部分環繞頂端 和底端。圖像環繞的比例受其他兩個屬性控制,它們名為 Fold1 和 Fold2,其默認值分別是 0.1 和 0.9 。它們表示沿柱狀體縱向向兩端折疊圖像的相對畫筆坐標。
柱狀體的兩端與共享中心中公用點的三角形圓環非常接近。如果“展開”定義柱狀體的三角形,並利 用矩形畫筆展開它們,可以讓網格幾何體與畫筆之間的對應關系可視化。圖 7 顯示了 12 片柱狀體和漸 變畫筆的這種對應關系。此對應關系似乎是合理的,如果正確設置畫筆維數、柱狀體和“折疊”設置,則 畫筆可能根本無法拉伸。
圖 7 映射到柱狀體的畫筆
但對於位圖,您可能希望有盡可能少的接縫。您已經有一個讓位圖的兩端會合的接縫。通過使用圖 8 所示的布局,可能避免在柱狀體的兩端出現其他接縫。當然,這將會涉及一些拉伸。(在這兩種情況下, 不會在柱狀體上使用所有畫筆,但除非創建更細的三角形,否則這是不可避免的。)
圖 8 更好的畫筆映射
我無法決定最想要什麼,所以我二者都實現。第一個選項似乎適合於 DrawingBrush,而第二個適合於 ImageBrush,因此我定義了 TextureType 屬性和有三個成員的 TextureType 枚舉:Drawing、Image 和 None。None 選項將完全阻止生成 TextureCoordinates。
展示柱狀體
現在是展示 Cylinder 類的時間。ImageOnCylinder 項目用 XAML 定義了一個柱狀物體,並在它的表 面上放置了一個位圖:
<pmg:Cylinder x:Name="cyl" Slices="32"
TextureType="Image" Fold1="0.25" Fold2="0.75">
<pmg:Cylinder.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush
ImageSource="http://www.charlespetzold.com/PetzoldTattoo.jpg"
/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</pmg:Cylinder.Material>
...
ImageOnCylinder.xaml 文件啟用 Point1、Radius1 和 Radius2 屬性,並且柱狀體還會圍繞它的軸緩 慢旋轉。圖 9 顯示了一張快照。程序還提供了一個滾動條,用於繞 X 軸旋轉攝像機。(運行任何這些示 例時,如果無法看到整個圖像,請嘗試使窗口變窄。)PolkaDottedCylinder 項目定義了 DrawingBrush ,它的尺寸經過仔細計算,以避免扭曲,如圖 10 所示。
圖 9 ImageOnCylinder
圖 10 Polka 點
最後示例回到 SolidColorBrush,但總共有 10 個柱狀體,並顯示了精確定義兩端位置的便捷性。當 兩個 3D 物體會合時,它們合並得非常好,如圖 11 所示。提供的兩個滾動條可以從不同角度查看坐椅( 但還不嘗試坐在它上面)。
圖 11 SteelChair
記錄第一個
提供一個編寫軟件的老建議:在您完成之後,請記錄下您已完成和重新開始的工作,以便將編寫第一 個版本時學到的所有東西合並起來。當然,這個方法不是始終實用的,但它對我很有用。我編寫的 Cylinder 類是我編寫生成網格幾何體的類的第五次或第六次嘗試,現在我覺得我已經可以轉到球體上了 。
將您想詢問的問題和提出的意見發送至:[email protected] [email protected].
本文配套源碼:http://www.bianceng.net/dotnet/201212/784.htm