天天看點

WPF繪制深度不同顔色的3D模型填充圖和線框圖

原文: WPF繪制深度不同顔色的3D模型填充圖和線框圖

在機械測量過程中,測量的資料需要進行軟體處理。通常測量一個零件之後,需要重建零件的3D模型,便于觀察測量結果是否與所測工件一緻。

重建的3D模型需要以填充圖和線框圖兩種方式切換顯示,其中填充圖的材質需要根據不同深度進行着色,線框圖需要消隐(不能透視)。以圓柱為例,如下圖:

WPF繪制深度不同顔色的3D模型填充圖和線框圖
WPF繪制深度不同顔色的3D模型填充圖和線框圖

由于WPF對DirectX進行了封裝,并建構出一套簡單的3D繪圖架構,是以我們可以快速的建立所需要的3D模型,便于像我這樣的對三維計算機圖形學不太了解的人進行開發。

關于WPF 3D繪圖的基礎,大家可以參考《Practical WPF Charts and Graphics》一書,裡面也講到一些3D顯示的計算機圖形學和數學基礎。

下面我們開始3D模型的建構。

在WPF中,我們可以按如下步驟進行操作:

1、建立一個Viewport3D對象來host 三維模型;

2、繼承ModelVisual3D或者ModelUIElement3D來定義3D對象;

3、指定3D場景中的光源,在WPF中光源也是一種ModelVisual3D;

4、建立一個相機來進行3D圖形到2D顯示器的映射。

根據工程中的經驗,我們考慮傳感器采集的資料是圓柱體工件的五個截面(實際可能更多或者更少),每個截面包含8192(或者更多)個點,并且是極坐标格式資料。

這裡我們使用ModelVisual3D來定義三維物體。其中重要的是三角剖分和材質貼圖的部分。由于每一層資料點個數都是一緻的,是以我們可以将圓柱體分成三個部分,上下底面和側面。參考《Practical WPF Charts and Graphics》,我們可以把圓柱的側面分成多個四邊形,每個四邊形分成兩個三角形,并将上下底面分成多個三角形(類似扇形)。

剖分方式大緻如下圖:

WPF繪制深度不同顔色的3D模型填充圖和線框圖
WPF繪制深度不同顔色的3D模型填充圖和線框圖
由于資料點坐标是極坐标,每一層有不同的高度(即三維坐标系中的Y),是以需要将極坐标轉化為三維坐标系中的坐标,即Point3D結構,坐标系如下圖:
WPF繪制深度不同顔色的3D模型填充圖和線框圖
需要注意的是,我們繪制的3D模型是三個曲面構成,每個曲面沒有封閉,這樣看上去會在首尾相接處出現缺口,是以需要多加一些三角形将首尾進行縫合。未縫合的模型如下:
WPF繪制深度不同顔色的3D模型填充圖和線框圖

在三維模型建立結束之後,需要貼上材質,由于工程中需要展現出不同深度(即不同半徑 )的差别,需要使用漸變色進行呈現,類似Matlab中的效果

參考我轉載的一篇文章(http://blog.csdn.net/congduan/article/details/21092341),就可以得出基本思路:在計算模型的時候,根據不同的R值,生成一張RGB color的映射表,并将其作為材質應用在3D模型上。

參考Codeproject上http://www.codeproject.com/KB/WPF/WPFChart3D.aspx這篇文章的源碼,其中的TextureMapping.cs檔案便是進行材質顔色映射的類,可以根據需要将多種顔色改寫成2種顔色等等,做出适合自己的定制。當然,這個類使用了C#指針(unsafe關鍵字),需要在項目屬性生成一項中開啟不安全代碼支援。以下是我修改例子,做出的兩種顔色的漸變圖:

WPF繪制深度不同顔色的3D模型填充圖和線框圖
以下便是參考該文例子寫出的本文圓柱的代碼:

for (int i = 0; i < drawPointCount; i++)
{ 
for (int j = 0; j < PointCollectionList.Count; j++)
{
//從極坐标構造笛卡爾坐标系資料 
points[i, j] = MathHelper.GetPosition(baseRadius +
ZoomContourValue * (PointCollectionList[j][i * interval].Y - fittedCircles[j].Radius) / (maxR - minR),
PointCollectionList[j][i * interval].X,
(j - halfPointCollectionCount) * h,
(Vector3D)Center); 

//根據表面半徑內插補點,生成RGB顔色紋理坐标
double u = (PointCollectionList[j][i * interval].Y - minR) / (maxR - minR);
Color color = TextureMapping.PseudoColor(u);
Point mapPt = m_mapping.GetMappingPosition(color);
texcoords[i, j] = new Point(mapPt.X, mapPt.Y);
}
}           

注意其中半徑都是歸一化之後才生成顔色值的。

最後将TextureMapping生成的材質貼到模型上。

//填充表面材質
surfaceModel = new GeometryModel3D(surfaceMeshBuilder.ToMesh(), m_mapping.m_material);           

======================================================================

對于線框圖(WireFrame),WPF支援的不好,因為WPF中沒有3D線段的類,好在微軟的人意識到了這一點,開發出開源的3D Tools for WPF(http://3dtools.codeplex.com/)作為補充,其中就有ScreenSpaceLines3D類用于3D線段繪制。這樣前面的填充圖也可以加上輪廓了。

但是,問題又來了。繪制出的線框圖是下面這樣的:

WPF繪制深度不同顔色的3D模型填充圖和線框圖

由于沒有消隐,很多線段交叉重疊在一起,影響判斷。《Practical WPF Charts and Graphics》一文中的例子是這樣處理的,在前面填充圖的基礎上加上線框,但是填充的材質改成純白色,并将線框線條加粗。效果還是不錯的,如下:

WPF繪制深度不同顔色的3D模型填充圖和線框圖

但是我這樣試過之後,發現一些問題,偶爾線條會被填充白色的三角形遮擋住,為了解決這個問題,我找到OpenGL的C#開源封裝庫,比如OpenTK和SharpGL,其中SharpGL中做如下處理之後會得到較好的效果:

//突出邊框,設定多邊形偏移
gl.Enable(OpenGL.GL_POLYGON_OFFSET_FILL);
gl.PolygonOffset(1.0f, 1.0f);
//開啟反走樣
gl.Enable(OpenGL.GL_BLEND);
gl.Enable(OpenGL.GL_LINE_SMOOTH);
gl.Enable(OpenGL.GL_POLYGON_SMOOTH);
gl.Hint(OpenGL.GL_POINT_SMOOTH_HINT, OpenGL.GL_NICEST); // Make round points, not square points
gl.Hint(OpenGL.GL_LINE_SMOOTH_HINT, OpenGL.GL_NICEST); // Antialias the lines
gl.BlendFunc(OpenGL.GL_SRC_ALPHA, OpenGL.GL_ONE_MINUS_SRC_ALPHA);           

效果:

WPF繪制深度不同顔色的3D模型填充圖和線框圖

可以SharpGL未能很好地利用GPU,将資料全部拷貝到記憶體中,導緻記憶體暴漲,甚至有時候記憶體洩露。是以最後放棄了這個庫。而OpenTK并沒有WPF的控件,隻能使用WindowsFormsHost來host WinForm的控件,不是很友善。

後來使用Helix 3D Toolkit,它将WPF和SharpDX進行了再次封裝,并提供了一些比較好的3D算法。并使用其中的MeshBuilder更友善地建立三角形和3D線段,最終出現本文開始的效果。

最後,重點提一下線框圖中的線段分布的算法。

顯示線框圖的時候,會繪制一部分豎線,開始考慮的是均勻間隔繪制,但是豎線繪制多少,是一個需要仔細思考的問題。線段多了,損耗性能,線段少了,輪廓的還原度不會搞,尤其是一些凹槽和凸出的部分會因為過于稀疏的線段無法顯示出來,如下圖的部分:

WPF繪制深度不同顔色的3D模型填充圖和線框圖

思考了很久,想到一種比較滿意的算法:計算每一層所有資料點的陡變程度,提取前100個(可以根據不同疏密需要的情況進行選取)陡變最大的點繪制豎線。

由于資料點是離散的,可以使用離散曲率刻畫點的陡變程度,離散曲率的計算公式如下:

WPF繪制深度不同顔色的3D模型填充圖和線框圖
WPF繪制深度不同顔色的3D模型填充圖和線框圖

參考論文《一種度量圖像像素陡變程度的方法》可以知道,這種連續曲線曲率直接離散化的結果并不是很好,并且該論文提出了一種更好的算法,具體算法大家可以去閱讀論文原文。公式如下:

WPF繪制深度不同顔色的3D模型填充圖和線框圖

有了這樣的評判标準,實作之後變成功将需要的輪廓比較完整地顯示了出來。由于資料點很多,計算公式也比較複雜,最好是放入背景線程異步處理,避免UI線程阻塞。

最後,注意大量資料點會造成三角形過多(這裡會達到20W個左右),導緻建構性能問題突出,最好在保證3D模型還原度高的情況下,進行稀疏處理,減少三角形數量。

繼續閱讀