原文: WPF 3D model - Sphere, Cone, and Cylinder
Extending Visual3D - Sphere, Cone, and Cylinder
http://blogs.msdn.com/b/danlehen/archive/2005/10/16/481597.aspxHe put together a short sample which derives a Sphere, a Cone, and a Cylinder primitive from ModelVisual3D. (Source available
here.)
You will notice that these primitives are directly usable from Xaml:
<Viewport3D Camera=”{Camera}”
>
<my:Sphere3D Transform=”{XForm1}” Material=”{Azul}” />
<my:Cone3D Transform=”{XForm2}” Material=”{Tulips}” />
<my:Cylinder3D Transform=”{XForm3}” Material=”{Autumn}” />
</Viewport3D>
Perspective : Easy 3D programming with WPF
http://www.odewit.net/ArticleContent.aspx?id=Wpf3DIntro&lang=en&format=xaml[Lieo原創]圓柱體的三角形剖分——使用WPF 3D繪制圓柱體
上個學期在做某個軟體時需要使用WPF繪制一些基本的三維物體。找了很多資料,發現大多是介紹球體的,因為這些圖形的繪制原理基本類似。
但是在繪制圓柱體時遇到了一個問題,就是不知道空間中任意一個圓的參數方程。憑大學裡學的高等數學知識僅能推導出底面與坐标平面平行的圓的參數方程,如果圓與坐标面成任意夾角就無法解決。使用與坐标面平行的圓繪制出的“類圓柱體”已經可以滿足我的程式的需求了,但是繪制出來的畢竟不是一個真正的圓柱。向
同學請教了參數方程後後,終于解決了這個問題。
【鋪墊:使用WPF 3D繪制三維物體的方法概述】
與Direct 3D類似,在WPF中,任何三維物體都是由三角形組成的。MeshGeometry3D對象定義組成圖像的各個三角形頂點和這些頂點的連接配接方式。該對象的Positions屬性是一個Point3D類型的集合,使用者記錄三角形的各頂點坐标,TriangleIndices屬性則描述這些頂點的連結方式。
GeometryModel3D對象的Material和BackMaterial屬性可以定義三維物體的表面和背面材質。在本程式中,材質隻用到了漫反射材質DiffuseMaterial。Geometry屬性指定該三維物體的MeshGeometry3D對象。為此,我們可以為每一個原子、鍵和平面生成各自的GeometryModel3D對象,這樣就可以為每一個元素分别進行着色。
每個GeometryModel3D被承載在一個ModelVisual3D對象中。整個三維場景都被定義在一個Viewport3D對象中。該對象的Children屬性是ModelVisual3D的集合。将預先設定的三維物體的ModelVisual3D對象添加到集合中,便可以在Viewport3D視圖中顯示。
【引子:先了解如何繪制一個球面】
一個球體可以表示成由經線和緯線組成的網格,我們可以将每個網格近似地看成由兩個三角形組成的平面,如下圖所示。

為了依次生成球面上離散的各點坐标,需要将球面寫成參數形式。設球心在坐标原點,球體半徑為R,則其參數方程為:
使用參數方程,用雙重循環即可生成球面的點。代碼如下:
'将球面進行三角形拆分。設球面的參數方程為:
' x = - r * cosφ * sinθ
' y = r * sinφ
' z = - r * cos φ * cosθ
'其中,-π/2≤φ≤π/2,-2π≤θ≤2π
Dim mesh As New MeshGeometry3D
Dim x, y, z, theta, phi As Double
Dim normal As Vector3D '組成球面的某個三角形的法向量
For i As Integer = 0 To Stacks '将球面拆分成Stacks個等距離的薄片(緯度,自上向下)
phi = Math.PI / 2 - i * Math.PI / Stacks '計算該緯度位置的phi角
y = R * Math.Sin(phi) '計算該緯度位置的y軸坐标(假設球心在原點)
For j As Integer = 0 To Slices '将球面縱向切成Slices份(經度方向,自西向東)
theta = j * 2 * Math.PI / Slices '計算該經度位置的theta角
'按照參數方程計算出另外兩個坐标值
x = -R * Math.Cos(phi) * Math.Sin(theta)
z = -R * Math.Cos(phi) * Math.Cos(theta)
normal = New Vector3D(x, y, z) '當球心在原點時,某點的法向量就是(x,y,z)
mesh.Positions.Add(normal + center) '某點的坐标:指定的球心+目前計算出的坐标
'計算材質對應的二維坐标,點(j / Slices, i / Stacks)是該點在二維平面展開後對應的坐标
mesh.TextureCoordinates.Add(New Windows.Point(j / Slices, i / Stacks))
Next j
Next i
其中Stacks表示緯度剖分數,Slices為經度拆分數。每計算一個頂點的坐标,便将其添加到Positions集合中。mesh.Normals.Add方法由于指定該點的法向量。該點的法向量方向是球心與該點連線的向量方向,即
,而該點的坐标為
,其中c是球心的坐标。
接下來需要考慮的問題是如何為球面着色。設想球面按球面軸展開,就成了如下圖所示的二維平面。
與頂點集合對應的TextureCoordinates集合指定了填充三維對象表面時頂點與二維圖像之間的關系。WPF定義與三維物體表面對應的二維填充平面的左上頂點和右下頂點分别為(0,0)和(1,1),如上圖所示的(i,j),其對應的二維向量為
。mesh.TextureCoordinates.Add(i/stacks,j/slices)就是指定該頂點與二維填充平面的對應關系。
頂點和相關集設定好後,需要指定這些頂點如何構成三角形。具體實作方式請參考Lieo3DModel類中GenerateSphereMesh方法。
【進入正題:圓柱體側面的三角形剖分】
與球體類似,圓柱體按照其軸展開後也是一個矩形。
可以想象,設n表示從底面圓心指向頂面圓心的向量,将一個底面圓上的按照圓的參數方程等間隔地生成,那麼頂面相對應的頂點的坐标可按照向量n平移得到。将這樣的兩個點連起來,連線與軸平行。按照這個思路,隻需要生成底面圓上各點的坐标,即可按照圖将圓柱剖分成三角形。
空間中任意一個圓的參數方程為:
其中
為圓的法向量。為了計算這兩個與法向量互相正交的向量,可以使用向量的叉積運算。
設M是空間中任意一個不與圓的法向量共線的向量,令
,這樣得到的
就是互相垂直的。将向量歸一化後,便得到了參數方程所需要的a,b。
參照球面生成的代碼,生成圓柱的代碼能夠很容易寫出。具體請下列代碼:
''' <summary>
''' 将兩底面圓心在 p1、p2 位置,底面半徑為 R 的圓柱體進行三角形剖分。
''' </summary>
''' <param name="p1">圓柱上底面圓心的坐标。</param>
''' <param name="p2">圓柱下底面圓心的坐标。</param>
''' <param name="R">圓柱的底面半徑。</param>
''' <returns>傳回值:代表該圓柱面的MeshGeometry3D對象。</returns>
Public Shared Function GenerateCylinderMesh(ByVal p1 As Point3D, ByVal p2 As Point3D, ByVal R As Double) As MeshGeometry3D
'将圓柱體進行三角形剖分
'空間中圓的參數方程:(x,y,z) = r*(A*cosθ+B*sinθ)+(x0,y0,z0) 【0≤θ≤2π】
' 其中 a、b 是機關向量,且滿足 A⊥B⊥n(圓的法向量)
Dim CircleVector As Vector3D = p2 - p1 '從p1點到p2點的3D向量
Dim M As New Vector3D(1, 1, 1)
If Vector3D.AngleBetween(M, CircleVector) < 0.1 Then
M = New Vector3D(1, 0, 0)
End If
Dim A As Vector3D = Vector3D.CrossProduct(CircleVector, M)
Dim B As Vector3D = Vector3D.CrossProduct(CircleVector, A)
A.Normalize()
B.Normalize()
Dim theta As Double
Dim Pos1, Pos2 As Point3D
'設圓與 xz 軸平行
For i As Integer = 0 To Stacks
'計算剖分三角形頂點在參數方程中對應的角度
theta = i / Stacks * Math.PI * 2
Pos1 = R * (A * Math.Cos(theta) + B * Math.Sin(theta)) + p1
Pos2 = R * (A * Math.Cos(theta) + B * Math.Sin(theta)) + p2
mesh.Positions.Add(Pos1)
mesh.Positions.Add(Pos2)
mesh.TextureCoordinates.Add(New Windows.Point(i / Stacks, 0))
mesh.TextureCoordinates.Add(New Windows.Point(i / Stacks, 1))
Next
For i As Integer = 0 To Stacks - 1
mesh.TriangleIndices.Add(i * 2)
mesh.TriangleIndices.Add(i * 2 + 1)
mesh.TriangleIndices.Add(i * 2 + 3)
mesh.TriangleIndices.Add(i * 2 + 2)
Return mesh
End Function