C# winform用sharpGL(OpenGl)解析讀取3D模型obj
原文作者:aircraft
原文連結:https://www.cnblogs.com/DOMLX/p/11783026.html
自己寫了個簡單的類讀取解析obj模型,使用匯入類,然後new個物件,在讀取obj模型,然後呼叫顯示列表顯示就可以了。至於其他什麼旋轉移動的你們自己加起來應該很容易的,因為我有看過c#下別人寫的obj模型解析的程式碼專案,加了很多東西,我都找不到自己要用的程式碼在哪裡,而我只需要讀取解析obj模型這塊程式碼而已,氣的我自己寫了個類自己解析,所以我怕我程式碼寫多了,
你們反而看起來不好理解hhhhhh
在c++下用OpenGL解析的話可以看我其他部落格
執行環境:vs2017,需要配置的庫為:sharpGL
一.讀取3D模型
在3d圖形處理中,一個模型(model)通常由一個或者多個Mesh(網格)組成,一個Mesh是可繪製的獨立實體。例如複雜的人物模型,可以分別劃分為頭部,四肢,服飾,武器等各個部分來建模,這些Mesh組合在一起最終形成人物模型。
Mesh由頂點、邊、面Faces組成的,它包含繪製所需的資料,例如頂點位置、紋理座標、法向量,材質屬性等內容,它是OpenGL用來繪製的最小實體。Mesh的概念示意如下圖所示(來自:What is a mesh in OpenGL?):
Mesh
Mesh可以包含多個Face,一個Face是Mesh中一個可繪製的基本圖元,例如三角形,多邊形,點。要想模型更加逼真,一般需要增加更多圖元使Mesh更加精細,當然這也會受到硬體處理能力的限制,例如PC遊戲的處理能力要強於移動裝置。由於多邊形都可以劃分為三角形,而三角形是圖形處理器中都支援的基本圖元,因此使用得較多的就是三角形網格來建模。例如下面的圖(來自:What
is a mesh in OpenGL?)表達了使用越來越複雜的Mesh建模一隻兔子的過程:
Mesh2
隨著增加三角形個數,兔子模型變得越來越真實。
讀取3d模型有很多種方法,但是最常用的無非就是呼叫別人寫好的庫,比如(openmesh),其次呢就是自己讀取解析3d模型檔案裡面的一個個座標資料,什麼v vf vn之類的。
那麼現在就是講一下第二種方法,就是直接解析讀取3d模型檔案,提取裡面我們所需的資料。下面是一個obj的模型檔案,我們可以使用記事本開啟看看裡面是什麼:
# Blender3D v249 OBJ File: untitled.blend # www.blender3d.org mtllib cube.mtl v 1.000000 -1.000000 -1.000000 v 1.000000 -1.000000 1.000000 v -1.000000 -1.000000 1.000000 v -1.000000 -1.000000 -1.000000 v 1.000000 1.000000 -1.000000 v 0.999999 1.000000 1.000001 v -1.000000 1.000000 1.000000 v -1.000000 1.000000 -1.000000 vt 0.748573 0.750412 vt 0.749279 0.501284 vt 0.999110 0.501077 vt 0.999455 0.750380 vt 0.250471 0.500702 vt 0.249682 0.749677 vt 0.001085 0.750380 vt 0.001517 0.499994 vt 0.499422 0.500239 vt 0.500149 0.750166 vt 0.748355 0.998230 vt 0.500193 0.998728 vt 0.498993 0.250415 vt 0.748953 0.250920 vn 0.000000 0.000000 -1.000000 vn -1.000000 -0.000000 -0.000000 vn -0.000000 -0.000000 1.000000 vn -0.000001 0.000000 1.000000 vn 1.000000 -0.000000 0.000000 vn 1.000000 0.000000 0.000001 vn 0.000000 1.000000 -0.000000 vn -0.000000 -1.000000 0.000000 usemtl Material_ray.png s off f 5/1/1 1/2/1 4/3/1 f 5/1/1 4/3/1 8/4/1 f 3/5/2 7/6/2 8/7/2 f 3/5/2 8/7/2 4/8/2 f 2/9/3 6/10/3 3/5/3 f 6/10/4 7/6/4 3/5/4 f 1/2/5 5/1/5 2/9/5 f 5/1/6 6/10/6 2/9/6 f 5/1/7 8/11/7 6/10/7 f 8/11/7 7/12/7 6/10/7 f 1/2/8 2/9/8 3/13/8 f 1/2/8 3/13/8 4/14/8
對這個文字格式做一個簡要說明:
- 以#開始的行為註釋行
- usemtl和mtllib表示的材質相關資料,解析材質資料稍微繁瑣,本節我們只是為了說明載入模型的原理,不做討論。
- o 引入一個新的object
- v 表示頂點位置
- vt 表示頂點紋理座標
- vn 表示頂點法向量
- f 表示一個面,面使用1/2/8這樣格式,表示頂點位置/紋理座標/法向量的索引,這裡索引的是前面用v,vt,vn定義的資料 注意這裡Obj的索引是從1開始的,而不是0
那麼我們只要拿到這些資料,按照opengl的繪製的規則,不就可以把他們都繪製出來了嗎?
讀取資料obj的類myReadobj.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Collections; using SharpGL; namespace WindowsFormsApp5 { class myReadObj { public myReadObj() { } public class POINT3 { public double X; public double Y; public double Z; }; public class WenLi { public double TU; public double TV; }; public class FaXiangLiang { public double NX; public double NY; public double NZ; }; public class Mian { public int[] V = new int[3]; public int[] T = new int[3]; public int[] N = new int[3]; }; public class Model { public List<POINT3> V = new List<POINT3>();//V:代表頂點。格式為V X Y Z,V後面的X Y Z表示三個頂點座標。浮點型 public List<WenLi> VT = new List<WenLi>();//表示紋理座標。格式為VT TU TV。浮點型 public List<FaXiangLiang> VN = new List<FaXiangLiang>();//VN:法向量。每個三角形的三個頂點都要指定一個法向量。格式為VN NX NY NZ。浮點型 public List<Mian> F = new List<Mian>();//F:面。面後面跟著的整型值分別是屬於這個面的頂點、紋理座標、法向量的索引。 //面的格式為:f Vertex1/Texture1/Normal1 Vertex2/Texture2/Normal2 Vertex3/Texture3/Normal3 } public Model mesh = new Model(); public float movX; public float movY; public float movZ; public float xRotate; public float yRotate; public float x; public float y; //放縮引數 public static float scale; //顯示列表 public uint showFaceList; public int YU = 1; public void loadFile(String fileName) { // Mian[] f; //POINT3[] v; //FaXiangLiang[] vn; //WenLi[] vt; StreamReader objReader = new StreamReader(fileName); ArrayList al = new ArrayList(); string texLineTem = ""; while (objReader.Peek() != -1) { texLineTem = objReader.ReadLine(); if (texLineTem.Length < 2) continue; if (texLineTem.IndexOf("v") == 0) { if (texLineTem.IndexOf("t") == 1)//vt 0.581151 0.979929 紋理 { string[] tempArray = texLineTem.Split(' '); WenLi vt = new WenLi(); vt.TU = double.Parse(tempArray[1]); vt.TV = double.Parse(tempArray[2]); mesh.VT.Add(vt); } else if (texLineTem.IndexOf("n") == 1)//vn 0.637005 -0.0421857 0.769705 法向量 { string[] tempArray = texLineTem.Split(new char[] { '/', ' ' }, System.StringSplitOptions.RemoveEmptyEntries); FaXiangLiang vn = new FaXiangLiang(); vn.NX = double.Parse(tempArray[1]); vn.NY = double.Parse(tempArray[2]); if (tempArray[3] == "\\") { texLineTem = objReader.ReadLine(); vn.NZ = double.Parse(texLineTem); } else vn.NZ = double.Parse(tempArray[3]); mesh.VN.Add(vn); } else {//v -53.0413 158.84 -135.806 點 string[] tempArray = texLineTem.Split(' '); POINT3 v = new POINT3(); v.X = double.Parse(tempArray[1]); v.Y = double.Parse(tempArray[2]); v.Z = double.Parse(tempArray[3]); mesh.V.Add(v); } } else if (texLineTem.IndexOf("f") == 0) { //f 2443//2656 2442//2656 2444//2656 面 string[] tempArray = texLineTem.Split(new char[] { '/', ' ' }, System.StringSplitOptions.RemoveEmptyEntries); Mian f = new Mian(); int i = 0; int k = 1; while (i < 3) { if (mesh.V.Count() != 0) { f.V[i] = int.Parse(tempArray[k]) - 1; k++; } if (mesh.VT.Count() != 0) { f.T[i] = int.Parse(tempArray[k]) - 1; k++; } if (mesh.VN.Count() != 0) { f.N[i] = int.Parse(tempArray[k]) - 1; k++; } i++; } mesh.F.Add(f); } } } public uint createListFace(ref SharpGL.OpenGL gl) { gl.NewList(showFaceList, OpenGL.GL_COMPILE); if(mesh.V.Count() == 0) return 119; for (int i = 0; i < mesh.F.Count(); i++) { gl.Begin(OpenGL.GL_TRIANGLES); // 繪製三角形 if (mesh.VT.Count() != 0) gl.TexCoord(mesh.VT[mesh.F[i].T[0]].TU, mesh.VT[mesh.F[i].T[0]].TV); //紋理 if (mesh.VN.Count() != 0) gl.Normal(mesh.VN[mesh.F[i].N[0]].NX, mesh.VN[mesh.F[i].N[0]].NY, mesh.VN[mesh.F[i].N[0]].NZ);//法向量 gl.Vertex(mesh.V[mesh.F[i].V[0]].X / YU, mesh.V[mesh.F[i].V[0]].Y / YU, mesh.V[mesh.F[i].V[0]].Z / YU); // 上頂點 if (mesh.VT.Count() != 0) gl.TexCoord(mesh.VT[mesh.F[i].T[1]].TU, mesh.VT[mesh.F[i].T[1]].TV); //紋理 if (mesh.VN.Count() != 0) gl.Normal(mesh.VN[mesh.F[i].N[1]].NX, mesh.VN[mesh.F[i].N[1]].NY, mesh.VN[mesh.F[i].N[1]].NZ);//法向量 gl.Vertex(mesh.V[mesh.F[i].V[1]].X / YU, mesh.V[mesh.F[i].V[1]].Y / YU, mesh.V[mesh.F[i].V[1]].Z / YU); // 左下 if (mesh.VT.Count() != 0) gl.TexCoord(mesh.VT[mesh.F[i].T[2]].TU, mesh.VT[mesh.F[i].T[2]].TV); //紋理 if (mesh.VN.Count() != 0) gl.Normal(mesh.VN[mesh.F[i].N[2]].NX, mesh.VN[mesh.F[i].N[2]].NY, mesh.VN[mesh.F[i].N[2]].NZ);//法向量 gl.Vertex(mesh.V[mesh.F[i].V[2]].X / YU, mesh.V[mesh.F[i].V[2]].Y / YU, mesh.V[mesh.F[i].V[2]].Z / YU); // 右下 gl.End(); // 三角形繪製結束 } gl.EndList(); return showFaceList; } } }
自己拿來用的話改一下這句 為你們的名稱空間名字就行了 namespace WindowsFormsApp5
因為前天才開始學c#,所以還是不太懂得c#的一些語法,寫法風格也偏向我經常寫的c++,大家將就著看吧。。。。。等我多學幾天,學點c#的程式碼規範再重新改吧。。。
呼叫方法也很簡單,在winform下的話,在openglControl控制元件的draw事件中加下面的程式碼:
第一步:New一個物件
第二步:讀取自己路徑下的obj模型檔案
第三步:呼叫顯示列表繪製圖案
private void openGLControl1_OpenGLDraw(object sender, SharpGL.RenderEventArgs args) { // 建立一個GL物件 SharpGL.OpenGL gl = this.openGLControl1.OpenGL; gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT); // 清空螢幕 gl.LoadIdentity(); // 重置 gl.Translate(0.0f, 0.0f, -6.0f); // 設定座標,距離螢幕距離為6 gl.Rotate(0, 1.0f, 0.0f, 0.0f); // 繞X軸旋轉 gl.Rotate(y, 0.0f, 1.0f, 0.0f); // 繞Y軸旋轉 gl.Rotate(0, 0.0f, 0.0f, 1.0f); // 繞Z軸旋轉 gl.Scale(0.003, 0.003, 0.003); myReadObj obj = new myReadObj(); obj.loadFile("bunny.obj"); obj.createListFace(ref gl); gl.CallList(obj.showFaceList); }
對了如果用opengl讀取模型解析3d模型後看起來像個2d的樣子,無非就是你的光照問題,或者模型檔案裡面沒有頂點法線vt存在。。這時候可以藉助一些3d模型操作軟體 匯入重新生成儲存。
執行結果:
專案程式碼百度雲連結關注下面公眾號,新增小編微信,傳送文章標題加原始碼獲取。。。。。
若有興趣交流分享技術,可關注本人公眾號,裡面會不定期的分享各種程式設計教程,和共享原始碼,諸如研究分享關於c/c++,python,前端,後端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎程式設計,影象處理和機器視覺開發的知識
&n