一、前言
最近需要在項目的軟體中增加一個功能,根據連續測斜數據展示三維的井眼軌跡圖,由於購買的廠商的圖件效果不理想,所以研究自己寫代碼實現類似的功能。
井眼軌跡的計算內容繁雜涉及了大量的鑽井工程專業專業知識。由於筆者是信息技術專業出身,所以只能依照自己的行業經驗研究井眼軌跡的三維技術實現。其中涉及到鑽井工程細節部分可能不準確或錯誤,本文內容僅供技術實現方面參考。
支持3D繪圖方面的技術框架很多,本文介紹使用SharpGL這個開源項目來實現三維井眼軌跡圖。
一、開源SharpGL項目介紹
SharpGL 可以讓你在 Windows Forms 或者 WPF 應用中輕鬆的使用 OpenGL 開發圖形應用。從核心內容來說,SharpGL是一個OpenGL API的封裝。一般來說,OpenGL API可以直接用於C/C++應用的開發,但是使用起來比較複雜, SharpGL直接提供了OpenGL全部的功能和擴展。SharpGL將所有的函數和一組豐富的對象,以及高級功能的對象集合放到一個包裝器中,你可以使用SharpGL執行opengl繪圖。不過SharpGL也包括一些不屬於OpenGL的內容,針對WinForms和WPF的用戶控制項用戶控制項提供了OpenGL渲染界面和用於處理類似於shader和紋理等複雜問題能力。甚至提供了一個高級類SceneGraph可以更好的使用面向對象的思想創建各類場景。
SharpGL背後的原理是OpenGL in .NET, SharpGL並不是一個需要重新學習的新框架,它只不過是封裝的OpenGL。
為什麼不直接使用OpenGL,而是使用SharpGL呢?
首先是我喜歡做.Net開發,可以使用託管代碼輕鬆調用C API,Dlllmport可以方便的調用這些API。但是必須要為所有的函數創建籤名。如果發生錯誤,整個錯誤將是非常龐大的,並且很難分析錯誤。
另外一個使用SharpGL的原因是SharpGL可以作為標準平臺調用來調用多數OpenGL函數,而不用創建外部方法的籤名。OpenGL擴展函數在運行時被夾在-這就沒有一個固定的進入點進入DLL,這樣就增加了工作量。
在OpenGL中獲得一個RD是比較困難的,底層的Win32代碼有大量的函數獲得像素格式,這些工作是大量重複和痛苦的,好在這一切SharpGL幫我做了。
最後一個選擇使用SharpGL的原因是,在OpenGL中大量的很痛苦的重複的工作如加載信息等,但是這些工作在.NET中是非常容易處理的。
OpenGL內容很多,而且非常成熟,相應的SharpGL內容也很多, 我不會在本文中寫出太多細節,我們只是用了其中很簡單(小)的一部分內容,在寫代碼過程中發現網上SharpGL的中文資料很有限。谷歌的原因英文資料找起來也很費勁,SharpGL缺少一個官方技術社區,值得一說的是官方提供了比較詳細的示例代碼,說實話其中很多代碼都是參考官方示例寫的。
SharpGL中的主要對象介紹:
SharpGL - 包含主OpenGL對象- 這個對象包裝所有的OpenGL函數,枚舉和擴展。
SharpGL.SceneGraph 包含OpenGL對象和場景元素光。材質。紋理。NURBs。著色器和其他對象的所有包裝。
SharpGL.WinForms - 包含應用程式的Windows 窗體控制項。
SharpGL.WPF - 包含用於你的應用程式的WPF控制項。
SharpGL.Serialization - 包含用於從 3D Studio Max文件。謹慎obj文件和trueSpace文件加載幾何圖形和數據的類。
三、井眼軌跡參數介紹
實際中井連續測斜數據如下:
測量井深(斜深):指井口至測點的井眼長度。
井斜角:井身軸線上某點的切線與垂線之間的夾角。
方位角:井身軸線上某點的切線的投影與正北方向的夾角。
依靠這三個參數可以繪製井眼軌跡,具體做法是通過這三個參數計算垂深、東西位移、南北位移。分別映射到SharpGL三維模型中世界坐標的Y軸坐標、X軸坐標、Z軸坐標。井眼軌跡參數計算本文不作介紹,感興趣的朋友可以去查找鑽井工程計算相關知識,我們這裡只介紹軟體實現方面內容。
四、三維井眼軌跡實現
4.1 三維繪圖中坐標系簡單介紹
二維繪圖:笛卡爾坐標有一個X軸和一個Y軸組成,X軸為水平方向,Y軸為垂直方向,X和Y相互垂直
三維繪圖:笛卡爾坐標多了一個Z軸,Z軸同時垂直於X和Y軸。Z軸的實際意義代表著三維物體的深度
為了描述3D世界,首先要設計一些三維模型出來。
設計三維模型的時候用的坐標系就是Model Coordinate System。
Model Space只負責描述一個模型。在Model Space設計模型的時候,要注意使模型的包圍盒的中心位於原點(0, 0,0)。
包圍盒就是能夠把模型包圍的最小的長方體。為什麼要圍繞原點?因為這樣才能在下文所述的WorldSpace裡"正常地"旋轉、縮放和平移模型。
世界坐標系它是一個特殊的坐標系,它建立了描述其他坐標系所需要的參考系。也就是說,可以用世界坐標系去描述其他所有坐標系或者物體的位置。所以有很多人定義世界坐標系是「我們所關心的最大坐標系」,通過這個坐標系可以去描述和刻畫所有想刻畫的實體。
世界坐標系又稱全局坐標系或者宇宙坐標系。
5種比較重要的坐標系系統
局部空間(Local Space,或者稱為物體空間(Object Space))
世界空間(World Space)
觀察空間(View Space,或者稱為視覺空間(Eye Space))
裁剪空間(Clip Space)
屏幕空間(Screen Space)
將頂點從一個坐標系轉換到另一個坐標系需要用到幾個變換矩陣,其中幾個比較重要的是模型(Model)、觀察(View)、投影(Projection)三個矩陣。
物體頂點的起始坐標按序經過上述5個坐標系系統最終轉換為屏幕坐標。
坐標系及坐標轉換的內容感興趣的朋友請自行baidu。
項目開始
啟動VS,建立一個Windows桌面程序,引入如下Dlls:
在這裡我們使用SharpGL.WinForms命名空間中的OpenGLControl 控制項。
把控制項拖拽到當前窗體中,這裡主要用到下面三個方法:
void openGLControl1_OpenGLInitialized(object sender, System.EventArgs e)
用於執行任何OpenGL初始化。
void openGLControl1_Resize(object sender, System.EventArgs e)
Resize方法:控制項大小變化是邏輯處理。
privatevoid openGLControl1_OpenGLDraw(object sender, RenderEventArgs e)
繪製方法:用於做OpenGL渲染。
繪製後背景面/左側背景面
如圖所示:灰色的兩個面就是左背景面和後背景面
首先需要在openGLControl1_OpenGLDraw方法中獲取SharpGL繪製對象
SharpGL.OpenGL gl = this.openGLControl1.OpenGL;
然後開始繪製背景面。
為了測試我們使用兩種方式分別繪製後背景面和左側背景面。
後背景面使用一個圖片來渲染,而左側背景面之間用顏色來繪製,這兩種方式顯示的效果是相同的。
後背景面:
Texture texture = new Texture();
texture.Create(gl, "20190919154917.png");
加載一個圖片當作材質。
繪製後背景面:
gl.Begin(OpenGL.GL_QUADS);
gl.TexCoord(1.0f, 0.0f); /
gl.Vertex(-2.0f, 0.0f, -2.0f); // 右下
gl.TexCoord(1.0f, 1.0f);
gl.Vertex(-2.0f, 4.0f, -2.0f); // 右上
gl.TexCoord(0.0f,1.0f);
gl.Vertex(2.0f, 4.0f, -2.0f);// 左上
gl.TexCoord(0.0f, 0.0f);
gl.Vertex(2.0f, 0.0f, -2.0f);// 左下
然後繪製右側背景面:
設置灰色背景色
gl.Color(0.8f,0.8f,0.8f);
接著繪製四個頂點
gl.Vertex(-2.0f,0.0f, -2.0f);
gl.Vertex(-2.0f, 0.0f, 2.0f);
gl.Vertex(-2.0f, 4.0f, 2.0f);
gl.Vertex(-2.0f, 4.0f, -2.0f);
gl.End();
gl.Flush();
繪製底部網格
設置OpenGL.GL_LINES類型,繪製網格線。
在X,Z平面上繪製網格
for (float i = -2; i <= 2; i+= 0.5f)
{
//設置類型為繪製線
gl.Begin(OpenGL.GL_LINES);
//X軸方向
gl.Vertex(-2f, 0f, i);
gl.Vertex(2f, 0f, i);
//Z軸方向
gl.Vertex(i, 0f, -2f);
gl.Vertex(i, 0f, 2f);
gl.End();
}
}
繪製東西軸線/南北軸線/深度軸線
使用gl.Begin(OpenGL.GL_LINE_STRIP);來繪製坐標軸線
設置線寬,使用比網格粗一點的線。
gl.LineWidth(2f);
//Y軸方向
gl.Vertex(0.0f, 0.0f, 0.0f);
gl.Vertex(0.0f, 4.0f, 0.0f);
//X軸方向
gl.Vertex(-2.0f, 4.0f, 0.0f);
gl.Vertex(0.0f, 4.0f, 0.0f);
//Z軸方向
gl.Vertex(0.0f, 0.0f, -2.0f);
gl.Vertex(0.0f, 0.0f, 2.0f);
繪製井口橫縱投影線
井口橫縱投影線有點特殊,用的是虛線。
使用代碼:
//設置顏色
gl.Color(0.8f, 0.8f, 0.8f);
//線寬
gl.LineWidth(1f);
//允許繪製虛線
gl.Enable(OpenGL.GL_LINE_STIPPLE);
//設置虛線的種類,具體類型可搜索OpenGL文檔
gl.LineStipple(1, 0x3F07);
其它的代碼與前面一樣:
gl.Vertex(-2.0f, 4.0f, 0.0f);
gl.Vertex(0.0f, 4.0f, 0.0f);
繪製底部東西/南北刻度線/深度刻度線
接下來,我們在底部面上繪製刻度線。
我們把X軸當作東西軸,Z軸當作南部軸。
我們需要在背景面的底部線上和相鄰的底部面的一個邊上繪製刻度數,比如0米100米200米300米等。
說白了其實就是在不同的屏幕位置繪製文字。
在SharpGL中有兩種類型的繪製文字,立體文字和平面文字
立體文字是在世界坐標系上繪製文字,方法是:
gl.DrawText3D,
平面文字是在二維屏幕上繪製文字,對應的方法是:
gl.DrawText
這裡我們更適合使用平面文字,我們需要把三維的世界坐標轉換成只有x,y的二維屏幕坐標。只有這樣當旋轉三維圖形時候,二維文字一直會顯示在正面。
SharpGL中提供了OpenGLSceneGraphExtensions.Project,可以處理此類問題。
繪製刻度的代碼:
for (float i = 0; i<= 4;i=i+0.5f )
{
gl.Begin(OpenGL.GL_LINE_STRIP);
gl.Vertex(-0.04f, i, 0f);
gl.Vertex(0.04f, i, 0f);
gl.End();
//獲取三維點的二維坐標
var sv =OpenGLSceneGraphExtensions.Project(gl, new Vertex(0.05f, i, 0.0f));
gl.DrawText((int)(sv.X), (int)(sv.Y),1, 1, 1, "宋體",10, (i * 1000).ToString());
}
注意:我們這裡只是示例,實際使用過程中可能需要根據一口井連續測斜數據,找到最大的東西位移和南北位移,然後結合井深來確定坐標刻度的大小。比如:東西位移和南北位移加起來不夠100米,那最大刻度值就設置成100米。又例如:井深10000米,南北和東西位移都比較小,還需要調整深度和底部面的比例尺範圍,讓圖形顯得更正常。而不是去顯示一條特別長,沒有什麼彎度的軌跡線。
繪製深度軸刻度方式與上面的類似。
繪製井眼軌跡線/投影線
井眼軌跡線分真正的井眼軌跡線(黃色),還有在背景面,左側面,和底部面的投影線。
這裡涉及到比例尺換算的問題,我們需要把井的實際井深換算到三維圖裡的世界坐標位置。具體做法如下根據測斜點測量井深和方位角算出該測點的的實際井垂深,根據垂深算出該測斜點的Y坐標值(比如:井深1000米對應三維高度4)。
根據測點方位角,計算出東西位移和南北位移值,然後換算出X坐標和Y坐標。這些內容與軟體技術沒什麼關係,就不細說了。
設置顏色
gl.Color(1.0f, 1.0f, 0f);
gl.LineWidth(1f);
//繪製連續的曲線
gl.Begin(OpenGL.GL_LINE_STRIP);
//獲取井眼軌跡的三維坐標值
var vertexList = LoadWellVertexList()
foreach(var vertex in vertexList)
{
//繪製測點
gl.Vertex(vertex);
}
繪製投影線就更簡單了,把測點對應的投影面的坐標設置為0即可。
繪製水平投影圖,把所有測點的Z坐標設置為0進行繪製。
繪製井底點水平線
查找到最底部的測點,然後繪製一條到Y軸的直線即可。
縮放/旋轉
縮放和旋轉就更簡單了
聲明一個縮放值變量,用滑鼠滾軸進行控制,對圖進行縮放。
gl.Scale(ScaleValue, ScaleValue, ScaleValue);
旋轉使用方法:
gl.Rotate(rtri,0.0f, 1.0f, 0.0f);
感興趣可以去查手冊。
五、後續完善
可以在井口處使用三維工具軟體設計好的模型,加載進來,效果好看很多。
井眼軌跡也可以使用材質渲染出一個實際的井的外觀,現在只畫一條線很醜。
增加光照和陰影效果