使用SharpGL三維建模技術生成3D井眼軌迹圖
前面的文章裡寫過使用sharpGL三維建模生産3D井眼軌迹,這篇文章主要是說一下在WPF中如何進行3d圖繪制。
(一)、先介紹一下3D繪圖基本概念三維坐标系由于我們要将三維模型顯示在二維顯示器上,所以我們創建場景時,實際上是要創建三維對象的二維表現形式。前面的文章已經講過,WPF中二維圖形坐标系原點在屏幕左上角,x軸正方向朝右,y軸正方向朝下。但是在三維坐标系中原點位于呈現中心的中間,x軸正方向朝右,y軸正方向朝上,z軸正方向朝外。這點和OpenGL類似,三維坐标系統使用的也是右手坐标系。
二維坐标系統與三維坐标系統
在WPF中使用右手坐标系統
WPF三維坐标系統
相機和投影
當我們創建三維場景時,實際上是要創建三維對象的在顯示屏幕上二維表示形式。由于三維場景的外觀會因觀察者的觀察位置不同而異,因此我們必須設置觀察位置。可以使用相機來為三維場景指定觀察位置。 了解三維場景如何在二維圖面上表示的另一種方法就是将場景描述為到觀察表面上的投影。“投影”這個詞聽起來比較抽象,生活中的物品都是三維的,但人的眼睛隻能看到正面,不能看到被遮擋的背面。我們眼睛看到三維幾何體就像看到被相機拍攝的二維相片。三維空間體轉化為二維圖的過程就叫投影,例如在現實世界中攝像機拍攝物體,是由物體表面反射的光線經過凸透鏡聚到感光元件CCD單元上産生的。投影就是把三維空間投影到二維空間的過程。而不同的投影方式投影尺寸的算法不同。針對于不同的三維場景通常使用不同的投影方式,比如工業設計通常使用正投影(平行投影),而各種遊戲場景則通常采用透視投影。
正投影和透視投影在三維圖中的效果:
透視投影相機代碼示例
// Defines the camera used to view the 3D object. In order to view the 3D object,
// the camera must be positioned and pointed such that the object is within view
// of the camera.
PerspectiveCamera myPCamera = new PerspectiveCamera();
// Specify where in the 3D scene the camera is.
myPCamera.Position = new Point3D(0, 0, 2);
// Specify the direction that the camera is pointing.
myPCamera.LookDirection = new Vector3D(0, 0, -1);
// Define camera's horizontal field of view in degrees.
myPCamera.FieldOfView = 60;
// Asign the camera to the viewport
myViewport3D.Camera = myPCamera;
ProjectionCamera,可以指定不同的投影及其屬性以更改觀察者查看三維模型的方式。PerspectiveCamera 指定用來對場景進行透視收縮的投影。 換言之,PerspectiveCamera 提供消失點透視。 您可以指定照相機在場景坐标系中的位置、照相機的方向和視野以及用來定義場景中“向上”方向的向量。下圖闡釋 PerspectiveCamera 的投影。 ProjectionCamera 的 NearPlaneDistance 和 FarPlaneDistance 屬性限制照相機的投影範圍。由于照相機可以位于場景中的任何位置,因此照相機實際上可能會位于模型内部或者緊靠模型,這使得很難正确區分對象。使用 NearPlaneDistance,可以指定一個距離照相機的最小距離,即,在超過該距離後将不繪制對象。 相反,使用 FarPlaneDistance,可以指定一個距離照相機的距離(即,在超過該距離後将不繪制對象),從而确保因距離太遠而無法識别的對象将不包括在場景中。
正投影:
OrthographicCamera 指定三維模型到二維可視化圖面上的正投影。與其他照相機一樣,它指定位置、觀察方向和“向上”方向。但是,與 PerspectiveCamera 不同的是,OrthographicCamera 描述了不包括透視收縮的投影。換言之,OrthographicCamera 描述了一個側面平行的取景框,而不是側面彙集在場景中一點的取景框。
Point3D position = new Point3D(1.5, 2, 3);
Vector3D lookDirection = new Vector3D(
-position.X, -position.Y, -position.Z);
Vector3D upDirection = new Vector3D(0, 1, 0);
double width = 4;
OrthographicCamera camera =
new OrthographicCamera(position, lookDirection, upDirection, width);
viewport.Camera = camera;
3D 圖形是由3D網格構成的,3D網格也被稱為模型,一個3D圖形通常是由一些小的基本元素(頂點,邊,面,多邊形)構成。頂點是3D建模時用到的最小構成元素,頂點定義為兩條或是多條邊交會的地方,是一個具有x、y、z坐标的空間位置。通過連接多個頂點形成多邊形,而面特指一個三角形,由三個頂點和三條邊構成。根據網格的幾何形狀,網格可能會由多個三角形組成,其中的一些三角形共用相同的角(頂點)。三個點才能構成一個平面,而且僅有三個面才能保證面是平的,多一個點不能保證面是平的,少一個點不能構成一個平面,所以不多不少正好是三個。
三維模型是若幹3D點(Point3D)的集合,每3個3D點按一定環繞方向組成1個三角形,WPF采用逆時針的環繞方向,符合所謂“右手法則”,即垂直豎起右手的大拇指,彎曲其餘4指,其餘4指指向正是三角形的環繞方向,大拇指的指向是三角形的正面,反向是其背面,如下圖所示,正是這些三角形構成了WPF中的三維造型世界。
在3D模型中,通常面數越多(也就是三角形的數量),模型越精細,反之則越粗糙(根據面數多少也就有了高模與低模之分)
WPF 三維系統目前提供 MeshGeometry3D 類,使用該類,可以指定任何幾何形狀。 首先通過将三角形頂點的列表指定為它的Positions 屬性來創建 MeshGeometry3D。 每個頂點都指定為 Point3D。 根據網格的幾何形狀,網格可能會由多個三角形組成,其中的一些三角形共用相同的角(頂點)。 若要正确地繪制網格,WPF 需要有關哪些頂點由哪些三角形共用的信息。 可以通過指定具有 TriangleIndices 屬性的三角形索引列表來提供此信息。 此列表指定在 Positions 列表中指定的點将按哪種順序确定三角形。
MeshGeometry3D 常用的有四個屬性(Positions、TriangleIndices 、TextureCoordinates、Normals)。其中Positions、TriangleIndices兩個最重要,需要手動賦值,而其它兩個屬性,不寫的話,會自動判斷來給出缺省值。
這張簡單描述了一個三位坐标系,裡面有四個坐标點,也就是頂點位置,都已标出,也就組成了集合(Positions),它所标示的是一個正方形。
舉個例子:
TriangleIndices="0 1 2 2 3 0"
它所表示的是什麼。每個數字什麼意思。
先講一下概念,字面意思是三角形索引的集合。為什麼要用到三角形呢,因為在3D圖形的世界裡,所有物體都可以被描述成為一系列三角形的集合。
比如我們現在畫的這個正方形,可以有兩個三角形組成。
那麼TriangleIndices="0 1 2 2 3 0" 按照圖片顯示的可以翻譯成 “P0 P1 P2,P2 P3 P0”,或者 0 對應 (-1,1,0),1 對應 (-1,-1,0),以此類推。
這裡面的每個數字對應着圖片裡的每個點。可是為什麼這樣對應呢。
這關系到三角形呈現的是有正反面區分的,可以看出上面每三個點組成的一個三角形都是逆時針順序的,這是因為WPF采用逆時針的環繞方式來顯示正面,
到這裡基本就搞清了TriangleIndices 和 Positions 的關系。
TextureCoordinates:紋理坐标用于确定将 Material 映射到構成網格的三角形的頂點的方式。
TextureCoordinates="0,0 0,1 1,1 1,0"
一般材質的的正常坐标按照上圖來說順序依次是 P0,P3,P2,P1。也就是說 0,0 0,1 1,1 1,0 這是一個正常順序,是按照本來畫面顯示的。
但如果換成TextureCoordinates="1 0, 0 0, 0 1, 1 1",你會發現顯示的畫面向左倒了。
這也和你定義的坐标集合有關系。
Normals:法向量是與定義網格的每個三角形的面垂直的向量。 法向量用于确定是否亮顯給定三角形面。如果指定了三角形索引,則将考慮相鄰面來生成法向量。
光源光源與實際的光一樣,三維圖形中的光能夠使圖面可見。 更确切地說,光确定了場景的哪個部分将包括在投影中。 WPF 中的光對象創建了各種光和陰影效果,而且是按照各種實際光的行為建模的。 您必須至少在場景中包括一個光,否則模型将不可見。
WPF中支持不同類型的光源,如下:
AmbientLight (環境光):它所提供的環境光以一緻的方式照亮所有的對象,而與對象的位置或方向無關。這個燈光會照亮場景裡全部的物體(前提是被光照的物體肯定是可以接受燈光),這種燈沒有方向所有無法産生陰影。
DirectionalLight (平行光):像遠處的光源那樣照亮。 将方向光的 Direction 指定為 Vector3D,但是沒有為方向光指定位置。
PointLight(點光源) :像近處的光源那樣照亮。 PointLight 具有一個位置并從該位置投射光。 場景中的對象是根據對象相對于光源的位置和距離而被照亮的。PointLightBase 公開 Range 屬性,該屬性确定一個距離,超過該距離後模型将無法由光源照亮。 PointLight 還公開了多個衰減屬性,這些屬性确定光源的亮度如何随距離的增加而減小。 您可以為光源的衰減指定恒定、線性或二次内插算法。
SpotLight(聚光燈) :從 PointLight 繼承, Spotlight 的照亮方式與 PointLight 類似,但是它既具有位置又具有方向。 它們在 InnerConeAngle 和 OuterConeAngle 屬性所設置的錐形區域(以度為單位指定)中投射光。
光源是 Model3D 對象,因此您可以轉換光源對象并對光源屬性(包括位置、顔色、方向和範圍)進行動畫處理。
不同光源場景區别如下圖:
材質、紋理
為了讓一個三維模型看起來像一個三維物體,它必須有一個應用的紋理來覆蓋由頂點和三角形定義的表面,這樣它才能被攝像機照亮和投射。在2D中,您使用畫筆類将顔色、模式、漸變或其他視覺内容應用于屏幕區域。然而,3D對象的外觀是照明模型的功能,而不僅僅是應用于它們的顔色或圖案。實際對象的圖面質量不同,他們反射光的方式也會有所不同,你可以将同樣的筆刷應用到3D對象上,就像你可以應用到2D對象上一樣,但是你不能直接應用它們。
為了定義模型表面的特征,WPF使用Material abstract類。Material的具體子類決定了模型表面的一些外觀特征,每個子類還提供了一個Brush屬性,您可以将SolidColorBrush、TileBrush或VisualBrush傳遞給它。
在3D世界中,模型是骨架,紋理為皮膚,二者缺一不可。
示例代碼:構造一個材質對象,這裡就用一個簡單的畫刷作為材質的紋理。然後用這個材質和上面構造的網格構造一個3D模型,然後設置燈光。
DiffuseMaterial dm = new DiffuseMaterial();
dm.Brush = Brushes.Cyan;
GeometryModel3D gm = new GeometryModel3D();
gm.Geometry = meshg;
gm.Material = dm;
DirectionalLight dl = new DirectionalLight ( );
dl.Color = Colors.Blue;
dl.Direction = new Vector3D ( 0, 0, -1 );
頂點在縮放、旋轉、平移等變換中的坐标轉換矩陣。當您創建模型時,它們在場景中具有固定的位置。為了在場景中移動、旋轉這些模型或者更改這些模型的大小而更改用來定義模型本身的頂點是不切實際的。
相反,您可以像在二維模型一樣應用轉換。 每個模型對象都有一個可用來對模型進行移動、重定向或調整大小的 Transform 屬性。 當您應用轉換時,實際上是按照由Transform 屬性指定的向量或值來偏移模型的所有點。
也就是說變換了定義模型的坐标系(“模型空間”)而模型所在的整個場景的坐标系(“全局空間”)卻沒有改變,從而實現了3D模型的變換。
(二)、三維井眼軌迹實現首先在窗體中引入Viewport3D容器,然後定義及設置相機屬性,設置燈光材質等。
<Viewport3D Grid.Row="0" Grid.Column="0" Name="MainViewport" />
TheCamera = new PerspectiveCamera();
TheCamera.FieldOfView = 60;
MainViewport.Camera = TheCamera;
PositionCamera();
DefineLights(MainModel3Dgroup);
最後定義3d模型,進行展示。
坐标軸、網格線及井眼軌迹繪制
為坐标和網格線,創建材質對象
NormalMaterial = new DiffuseMaterial(Brushes.White);
SelectedMaterial = new DiffuseMaterial(Brushes.Yellow);
var faceMaterial = new DiffuseMaterial(Brushes.DarkGray);
畫線段方法:
public static void AddSegment(MeshGeometry3D mesh,
Point3D point1, Point3D point2, Vector3D up, double thickness,)
{
Vector3D v = point2 - point1;
Vector3D n1 = up.Scale(thickness / 2.0);
Vector3D n2 = Vector3D.CrossProduct(v, n1);
n2 = n2.Scale(thickness / 2.0);
Point3D p1pp = point1 n1 n2;
Point3D p1mp = point1 - n1 n2;
Point3D p1pm = point1 n1 - n2;
Point3D p1mm = point1 - n1 - n2;
Point3D p2pp = point2 n1 n2;
Point3D p2mp = point2 - n1 n2;
Point3D p2pm = point2 n1 - n2;
Point3D p2mm = point2 - n1 - n2;
AddTriangle(mesh, p1pp, p1mp, p2mp);
AddTriangle(mesh, p1pp, p2mp, p2pp);
AddTriangle(mesh, p1pp, p2pp, p2pm);
AddTriangle(mesh, p1pp, p2pm, p1pm);
AddTriangle(mesh, p1pm, p2pm, p2mm);
AddTriangle(mesh, p1pm, p2mm, p1mm);
AddTriangle(mesh, p1mm, p2mm, p2mp);
AddTriangle(mesh, p1mm, p2mp, p1mp);
AddTriangle(mesh, p1pp, p1pm, p1mm);
AddTriangle(mesh, p1pp, p1mm, p1mp);
AddTriangle(mesh, p2pp, p2mp, p2mm);
AddTriangle(mesh, p2pp, p2mm, p2pm);
}
調用此方法畫出x,y,z坐标軸刻度,然後再畫出網格線。
定義添加面的方法,在坐标系 左側和後側畫出背景框。
public static void AddFace(this MeshGeometry3D mesh,
float x, float y, float z, float dx, float dy, float dz)
{
// Right //outside
AddTriangle(mesh,
new Point3D(x dx, y, z),
new Point3D(x dx, y dy, z),
new Point3D(x dx, y dy, z dz));
AddTriangle(mesh,
new Point3D(x dx, y, z),
new Point3D(x dx, y dy, z dz),
new Point3D(x dx, y, z dz));
// Right//inside
AddTriangle(mesh,
new Point3D(x dx, y dy, z dz),
new Point3D(x dx, y dy, z),
new Point3D(x dx, y, z) );
AddTriangle(mesh,
new Point3D(x dx, y, z dz),
new Point3D(x dx, y dy, z dz),
new Point3D(x dx, y, z));
// Back //outside
AddTriangle(mesh,
new Point3D(x, y, z),
new Point3D(x, y dy, z),
new Point3D(x dx, y dy, z));
AddTriangle(mesh,
new Point3D(x, y, z),
new Point3D(x dx, y dy, z),
new Point3D(x dx, y, z));
// Back //inside
AddTriangle(mesh,
new Point3D(x dx, y dy, z),
new Point3D(x, y dy, z),
new Point3D(x, y, z));
AddTriangle(mesh,
new Point3D(x dx, y, z),
new Point3D(x dx, y dy, z),
new Point3D(x, y, z));
}
這裡每個面由兩個相鄰的三角形組成,因為内外都需要可見,每個面裡外總共用了四個三角形。
,
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!