笔者设想的3D仿真中的元件,是不可能都是“画”出来的。这样就玩复杂了,应该把任务分包出去,让善于制作模型的软件来制作三维模型,我们只需要解析并且显示它即可。
3dsmax制作三维模型的方便,快捷,专业,我想是没有人提反对意见的。它可以把制作好的模型导出为业界通用的3ds格式,如果你愿意的话,3ds格式也可以包含材质和uvw贴图坐标。这样的模型我们在opengl中导入后只用打光和显示,非常省事。
解析3ds格式比较复杂,不过读者可以拿来主义,直接用下面的代码就可以了。
代码已经加入了必要的注释,笔者就不罗嗦了。
源代码: SharpGLForm.cs
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using SharpGL; 10 using Model3D; 11 using System.IO; 12 13 14 namespace SharpGLWinformsApplication1 15 { 16 //原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/ 17 public partial class SharpGLForm : Form 18 { 19 private string configPath = AppDomain.CurrentDomain.BaseDirectory + "config"; 20 private H3DModel h3d; 21 private float rotation = 0.0f; 22 private bool isRotate = false; 23 private bool isLines = false; 24 private bool isFrontView = false; 25 private bool isLeftView = false; 26 private bool isTopView = false; 27 private bool isPerspective = true; 28 private float[] lightPos = new float[] { -1, -3, 1, 1 }; 29 private float[] lightSphereColor = new float[] { 0.2f, 0.5f, 0.8f }; 30 private IList<float[]> lightColor = new List<float[]>(); 31 private double[] lookatValue = { 1, 1, 2, 0, 0, 0, 0, 1, 0 }; 32 33 float[] no_mat = new float[] { 0.0f, 0.0f, 0.0f, 1.0f }; // 无材质颜色 34 float[] mat_ambient = new float[] { 0.7f, 0.7f, 0.7f, 1.0f }; // 环境颜色 35 float[] mat_ambient_color = new float[] { 0.8f, 0.6f, 0.2f, 1.0f }; 36 float[] mat_diffuse = new float[] { 0.2f, 0.5f, 0.8f, 1.0f }; // 散射颜色 37 float[] no_shininess = new float[] { 0.0f }; // 镜面反射指数为0 38 float[] mat_emission = new float[] { 0.3f, 0.2f, 0.3f, 0.0f }; // 发射光颜色 39 float[] high_shininess = new float[] { 100.0f }; // 镜面反射指数为100.0 40 float[] low_shininess = new float[] { 5.0f }; // 镜面反射指数为5.0 41 float[] mat_specular = new float[] { 1.0f, 1.0f, 1.0f, 1.0f }; // 镜面反射颜色 42 43 private IList<double[]> viewDefaultPos = new List<double[]>(); 44 public SharpGLForm() 45 { 46 InitializeComponent(); 47 48 } 49 50 private void openGLControl_OpenGLDraw(object sender, PaintEventArgs e) 51 { 52 OpenGL gl = openGLControl.OpenGL; 53 gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT); 54 gl.LoadIdentity(); 55 gl.Rotate(rotation, 0.0f, 1.0f, 0.0f); 56 drawGrid(gl); 57 draw3DSModel(gl); 58 if (isRotate) 59 rotation += 3.0f; 60 } 61 62 private void draw3DSModel(OpenGL Gl) 63 { 64 Gl.PushMatrix(); 65 { 66 //Gl.PixelStore(OpenGL.GL_UNPACK_ALIGNMENT, 4); 67 //Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, mat_specular); 68 //Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, mat_specular); 69 //Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SPECULAR, no_mat); 70 //Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, no_mat); 71 //Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_EMISSION, no_mat); 72 Gl.Scale(0.05, 0.05, 0.05); 73 Gl.Translate(0, 0, 0); 74 h3d.DrawModel(Gl,isLines); 75 h3d.DrawBorder(Gl); 76 } 77 Gl.PushMatrix(); 78 } 79 80 private void setLightColor(OpenGL gl) 81 { 82 gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_AMBIENT, lightColor[0]); 83 gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_DIFFUSE, lightColor[1]); 84 gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_SPECULAR, lightColor[2]); 85 } 86 87 private void openGLControl_OpenGLInitialized(object sender, EventArgs e) 88 { 89 OpenGL gl = openGLControl.OpenGL; 90 91 //四个视图的缺省位置 92 viewDefaultPos.Add(new double[] { 1, 1, 2, 0, 0, 0, 0, 1, 0 }); //透视 93 viewDefaultPos.Add(new double[] { 0, 0, 2, 0, 0, 0, 0, 1, 0 }); //前视 94 viewDefaultPos.Add(new double[] { 5, 0, 0, 0, 0, 0, 0, 1, 0 }); //左视 95 viewDefaultPos.Add(new double[] { 0, 13, 0, -1, 0, 0, 0, 1, 0 }); //顶视 96 lookatValue =(double[])viewDefaultPos[0].Clone(); 97 98 lightColor.Add(new float[] { 1f, 1f, 1f, 1f }); //环境光(ambient light) 99 lightColor.Add(new float[] { 1f, 1f, 1f, 1f }); //漫射光(diffuse light) 100 lightColor.Add(new float[] { 1f, 1f, 1f, 1f }); //镜面反射光(specular light) 101 102 setLightColor(gl); 103 gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, lightPos); 104 105 gl.Enable(OpenGL.GL_LIGHTING); 106 gl.Enable(OpenGL.GL_LIGHT0); 107 gl.Enable(OpenGL.GL_NORMALIZE); 108 109 110 gl.ClearColor(0, 0, 0, 0); 111 h3d = H3DModel.FromFile(gl, "teport3.3DS"); 112 113 114 loadConfig(); 115 116 } 117 118 119 120 private void openGLControl_Resized(object sender, EventArgs e) 121 { 122 123 OpenGL gl = openGLControl.OpenGL; 124 gl.MatrixMode(OpenGL.GL_PROJECTION); 125 gl.LoadIdentity(); 126 gl.Perspective(40.0f, (double)Width / (double)Height, 0.01, 100.0); 127 128 129 gl.LookAt(lookatValue[0], lookatValue[1], lookatValue[2], 130 lookatValue[3], lookatValue[4], lookatValue[5], 131 lookatValue[6], lookatValue[7], lookatValue[8]); 132 133 gl.MatrixMode(OpenGL.GL_MODELVIEW); 134 updateLabInfo(); 135 } 136 137 138 void drawGrid(OpenGL gl) 139 { 140 //关闭纹理和光照 141 gl.Disable(OpenGL.GL_TEXTURE_2D); 142 gl.Disable(OpenGL.GL_LIGHTING); 143 144 //绘制过程 145 gl.PushAttrib(OpenGL.GL_CURRENT_BIT); //保存当前属性 146 gl.PushMatrix(); //压入堆栈 147 gl.Translate(0f, -2f, 0f); 148 gl.Color(0f, 0f, 1f); 149 150 //在X,Z平面上绘制网格 151 for (float i = -50; i <= 50; i += 1) 152 { 153 //绘制线 154 gl.Begin(OpenGL.GL_LINES); 155 { 156 if (i == 0) 157 gl.Color(0f, 1f, 0f); 158 else 159 gl.Color(0f, 0f, 1f); 160 161 //X轴方向 162 gl.Vertex(-50f, 0f, i); 163 gl.Vertex(50f, 0f, i); 164 //Z轴方向 165 gl.Vertex(i, 0f, -50f); 166 gl.Vertex(i, 0f, 50f); 167 168 } 169 gl.End(); 170 } 171 gl.PopMatrix(); 172 gl.PopAttrib(); 173 gl.Enable(OpenGL.GL_LIGHTING); 174 } 175 176 177 void drawSphere(OpenGL gl,double radius,int segx,int segy,bool isLines) 178 { 179 gl.PushMatrix(); 180 gl.Translate(2f, 1f, 2f); 181 var sphere = gl.NewQuadric(); 182 if (isLines) 183 gl.QuadricDrawStyle(sphere, OpenGL.GL_LINES); 184 else 185 gl.QuadricDrawStyle(sphere, OpenGL.GL_QUADS); 186 gl.QuadricNormals(sphere, OpenGL.GLU_SMOOTH); 187 gl.QuadricOrientation(sphere, (int)OpenGL.GLU_OUTSIDE); 188 gl.QuadricTexture(sphere, (int)OpenGL.GLU_FALSE); 189 gl.Sphere(sphere, radius, segx, segy); 190 gl.DeleteQuadric(sphere); 191 gl.PopMatrix(); 192 } 193 194 private void moveObject(int obj,string keyName) 195 { 196 //obj==0移动视图 197 switch (keyName) 198 { 199 case "btnQ": 200 if (obj == 0) ++lookatValue[1]; //y 201 else 202 ++lightPos[1]; 203 break; 204 case "btnE": 205 if (obj == 0) --lookatValue[1]; 206 else 207 --lightPos[1]; 208 break; 209 case "btnW": 210 if (obj == 0) --lookatValue[2]; //z 211 else 212 --lightPos[2]; 213 break; 214 case "btnS": 215 if (obj == 0) ++lookatValue[2]; 216 else 217 ++lightPos[2]; 218 break; 219 case "btnA": 220 if (obj == 0) --lookatValue[0]; //X 221 else 222 --lightPos[0]; 223 break; 224 case "btnD": 225 if (obj == 0) ++lookatValue[0]; 226 else 227 ++lightPos[0]; 228 break; 229 } 230 } 231 232 private void rbPerspective_CheckedChanged(object sender, EventArgs e) 233 { 234 switch (((RadioButton)sender).Name) 235 { 236 case "rbPerspective": 237 isPerspective = !isPerspective; 238 isFrontView = false; 239 isTopView = false; 240 isLeftView = false; 241 break; 242 case "rbLeft": 243 isLeftView = !isLeftView; 244 isFrontView = false; 245 isPerspective = false; 246 isTopView = false; 247 break; 248 case "rbFront": 249 isFrontView = !isFrontView; 250 isTopView = false; 251 isPerspective = false; 252 isLeftView = false; 253 break; 254 case "rbTop": 255 isTopView = !isTopView; 256 isPerspective = false; 257 isLeftView = false; 258 isFrontView = false; 259 break; 260 default: 261 return; 262 } 263 setViewDefaultValue(); 264 openGLControl_Resized(null, null); 265 } 266 267 private void cbxRotate_CheckedChanged(object sender, EventArgs e) 268 { 269 var cbx=((CheckBox)sender); 270 switch (cbx.Name) 271 { 272 case "cbxRotate": 273 isRotate = cbx.Checked; 274 break; 275 case "cbxLines": 276 isLines = cbx.Checked; 277 break; 278 case "cbxLightOff": 279 if (!cbx.Checked) 280 this.openGLControl.OpenGL.Enable(OpenGL.GL_LIGHT0); 281 else 282 this.openGLControl.OpenGL.Disable(OpenGL.GL_LIGHT0); 283 break; 284 } 285 } 286 287 private void SharpGLForm_Load(object sender, EventArgs e) 288 { 289 this.cbxLightType.SelectedIndex = 0; 290 updateLabInfo(); 291 } 292 293 private void loadConfig() 294 { 295 var ary= File.ReadAllText(configPath).Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); 296 if (ary.Length == 2) 297 { 298 var lightary = ary[0].Split(',').Select(s => { 299 float f1=0; 300 float.TryParse(s, out f1); 301 return f1; 302 }).ToArray(); 303 var lookAtary = ary[1].Split(',').Select(s => 304 { 305 double d1=0; 306 double.TryParse(s,out d1); 307 return d1; 308 }).ToArray(); 309 for (int i = 0; i < lightPos.Length; i++) 310 lightPos[i] = lightary[i]; 311 for (int i = 0; i < lookatValue.Length; i++) 312 lookatValue[i] = lookAtary[i]; 313 } 314 } 315 316 private void saveConfig() 317 { 318 try 319 { 320 File.WriteAllText(configPath, tbLightPos.Text + Environment.NewLine + tbLookAt.Text + Environment.NewLine); 321 } 322 catch (Exception ex) 323 { 324 MessageBox.Show(ex.Message); 325 } 326 } 327 328 private void updateLabInfo() 329 { 330 tbLightPos.Text = string.Format("{0},{1},{2},{3}", lightPos[0], lightPos[1], lightPos[2], lightPos[3]); 331 tbLookAt.Text = string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}", lookatValue[0], lookatValue[1], lookatValue[2], 332 lookatValue[3], lookatValue[4], lookatValue[5], lookatValue[6], lookatValue[7], lookatValue[8]); 333 btnSetPos_Click(null, null); 334 } 335 336 private void rbWhite_CheckedChanged(object sender, EventArgs e) 337 { 338 var rad = ((RadioButton)sender); 339 var lightType = this.cbxLightType.SelectedIndex; 340 if (rad.Checked) 341 { 342 switch (rad.Name) 343 { 344 case "rbBlack": 345 lightColor[lightType][0] = 0f; 346 lightColor[lightType][1] = 0f; 347 lightColor[lightType][2] = 0f; 348 lightColor[lightType][3] = 1f; 349 break; 350 case "rbWhite": 351 lightColor[lightType][0] = 1f; 352 lightColor[lightType][1] = 1f; 353 lightColor[lightType][2] = 1f; 354 lightColor[lightType][3] = 1f; 355 break; 356 case "rbRed": 357 lightColor[lightType][0] = 1f; 358 lightColor[lightType][1] = 0f; 359 lightColor[lightType][2] = 0f; 360 lightColor[lightType][3] = 1f; 361 break; 362 case "rbGreen": 363 lightColor[lightType][0] = 0f; 364 lightColor[lightType][1] = 1f; 365 lightColor[lightType][2] = 0f; 366 lightColor[lightType][3] = 1f; 367 break; 368 case "rbBlue": 369 lightColor[lightType][0] = 0f; 370 lightColor[lightType][1] = 0f; 371 lightColor[lightType][2] = 1f; 372 lightColor[lightType][3] = 1f; 373 break; 374 } 375 setLightColor(openGLControl.OpenGL); 376 } 377 } 378 379 private void cbxLightType_SelectedIndexChanged(object sender, EventArgs e) 380 { 381 var lightType = this.cbxLightType.SelectedIndex; 382 if (lightType >= 0) 383 judgeColor(lightColor[lightType]); 384 } 385 386 private void judgeColor(float[] color) 387 { 388 if (color[0] == 1f && color[1] == 1f && color[2] == 1f && color[3] == 1f) 389 rbWhite.Checked = true; 390 else if (color[0] == 1f && color[1] == 0f && color[2] == 0f && color[3] == 1f) 391 rbRed.Checked = true; 392 else if (color[0] == 0f && color[1] == 1f && color[2] == 0f && color[3] == 1f) 393 rbGreen.Checked = true; 394 else if (color[0] == 0f && color[1] == 0f && color[2] == 1f && color[3] == 1f) 395 rbBlue.Checked = true; 396 else if (color[0] == 0f && color[1] == 0f && color[2] ==0f && color[3] ==1f) 397 rbBlack.Checked = true; 398 } 399 400 private void btnQ_Click(object sender, EventArgs e) 401 { 402 moveObject(radioButton1.Checked ? 0 : 1,((Button)sender).Name); 403 openGLControl_Resized(null, null); 404 openGLControl.OpenGL.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, lightPos); 405 } 406 407 private void setViewDefaultValue() 408 { 409 if (isPerspective) 410 { 411 lookatValue = (double[])viewDefaultPos[0].Clone(); 412 } 413 else if (isFrontView) 414 { 415 lookatValue = (double[])viewDefaultPos[1].Clone(); 416 } 417 else if (isLeftView) 418 { 419 lookatValue = (double[])viewDefaultPos[2].Clone(); 420 } 421 else if (isTopView) 422 { 423 lookatValue = (double[])viewDefaultPos[3].Clone(); 424 } 425 } 426 427 private void btnDefaultPOS_Click(object sender, EventArgs e) 428 { 429 if (radioButton1.Checked) 430 { 431 setViewDefaultValue(); 432 } 433 else 434 { 435 lightPos = new float[] { -1, -3, 1, 1 }; 436 openGLControl.OpenGL.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, lightPos); 437 } 438 openGLControl_Resized(null, null); 439 } 440 441 private void openGLControl_KeyDown(object sender, KeyEventArgs e) 442 { 443 string name = string.Empty; 444 switch (e.KeyCode) 445 { 446 case Keys.W: 447 name = "btnW"; 448 break; 449 case Keys.A: 450 name = "btnA"; 451 break; 452 case Keys.S: 453 name = "btnS"; 454 break; 455 case Keys.D: 456 name = "btnD"; 457 break; 458 case Keys.Q: 459 name = "btnQ"; 460 break; 461 case Keys.E: 462 name = "btnE"; 463 break; 464 } 465 moveObject(radioButton1.Checked ? 0 : 1, name); 466 openGLControl_Resized(null, null); 467 } 468 469 private void btnSetPos_Click(object sender, EventArgs e) 470 { 471 if (radioButton1.Checked) 472 { 473 double[] ary = tbLookAt.Text.Split(',').Select(s => Convert.ToDouble(s)).ToArray(); 474 lookatValue = ary; 475 openGLControl_Resized(null, null); 476 } 477 else 478 { 479 float[] ary = tbLightPos.Text.Split(',').Select(s => Convert.ToSingle(s)).ToArray(); 480 lightPos = ary; 481 openGLControl.OpenGL.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, ary); 482 } 483 484 } 485 486 private void tbLightPos_TextChanged(object sender, EventArgs e) 487 { 488 saveConfig(); 489 } 490 491 492 } 493 }
源代码:Model3D.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.IO; 5 using System.Diagnostics; 6 using System.Drawing; 7 using System.Drawing.Imaging; 8 using SharpGL; 9 //原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/ 10 namespace Model3D 11 { 12 internal class FileHead 13 { 14 //基本块 15 public static UInt32 PRIMARY { get { return 0x4D4D; } set { } } 16 17 //主块 18 public static UInt32 OBJECTINFO { get { return 0x3D3D; } set { } } // 网格对象的版本号 19 public static UInt32 VERSION { get { return 0x0002; } set { } } // .3ds文件的版本 20 public static UInt32 EDITKEYFRAME { get { return 0xB000; } set { } } // 所有关键帧信息的头部 21 22 // 对象的次级定义(包括对象的材质和对象) 23 public static UInt32 MATERIAL { get { return 0xAFFF; } set { } } // 保存纹理信息 24 public static UInt32 OBJECT { get { return 0x4000; } set { } } // 保存对象的面、顶点等信息 25 26 // 材质的次级定义 27 public static UInt32 MATNAME { get { return 0xA000; } set { } } // 保存材质名称 28 public static UInt32 MATDIFFUSE { get { return 0xA020; } set { } } // 对象/材质的颜色 29 public static UInt32 MATMAP { get { return 0xA200; } set { } } // 新材质的头部 30 public static UInt32 MATMAPFILE { get { return 0xA300; } set { } } // 保存纹理的文件名 31 32 public static UInt32 OBJECT_MESH { get { return 0x4100; } set { } } // 新的网格对象 33 34 // OBJECT_MESH的次级定义 35 public static UInt32 OBJECT_VERTICES { get { return 0x4110; } set { } } // 对象顶点 36 public static UInt32 OBJECT_FACES { get { return 0x4120; } set { } } // 对象的面 37 public static UInt32 OBJECT_MATERIAL { get { return 0x4130; } set { } } // 对象的材质 38 public static UInt32 OBJECT_UV { get { return 0x4140; } set { } } // 对象的UV纹理坐标 39 40 //转换字符 41 public static int byte2int(byte[] buffer) { return BitConverter.ToInt32(buffer, 0); } 42 public static float byte2float(byte[] buffer) { return BitConverter.ToSingle(buffer, 0); } 43 } 44 45 // 定义3D点的类,用于保存模型中的顶点 46 public class CVector3 47 { 48 public float x, y, z; 49 } 50 // 定义2D点类,用于保存模型的UV纹理坐标 51 public class CVector2 52 { 53 public float x, y; 54 } 55 // 面的结构定义 56 public class tFace 57 { 58 public int[] vertIndex = new int[3]; //顶点坐标 59 public int[] coordIndex = new int[3]; //纹理坐标索引 60 61 } 62 // 材质信息结构体 63 public class tMaterialInfo 64 { 65 public String strName = ""; //纹理名称 66 public String strFile = ""; //如果存在纹理映射,则表示纹理文件名称 67 public int[] color = new int[3]; //对象的RGB颜色 68 public int texureId; //纹理ID 69 public float uTile; //u重复 70 public float vTile; //v重复 71 public float uOffset; //u纹理偏移 72 public float vOffset; //v纹理偏移 73 } 74 //对象信息结构体 75 public class t3DObject 76 { 77 public int numOfVerts; // 模型中顶点的数目 78 public int numOfFaces; // 模型中面的数目 79 public int numTexVertex; // 模型中纹理坐标的数目 80 public int materialID; // 纹理ID 81 public bool bHasTexture; // 是否具有纹理映射 82 public String strName; // 对象的名称 83 public CVector3[] pVerts; // 对象的顶点 84 public CVector3[] pNormals; // 对象的法向量 85 public CVector2[] pTexVerts; // 纹理UV坐标 86 public tFace[] pFaces; // 对象的面信息 87 } 88 //模型信息结构体 89 public class t3DMdoel 90 { 91 public int numOfObjects; // 模型中对象的数目 92 public int numOfMaterials; // 模型中材质的数目 93 public List<tMaterialInfo> pMaterials = new List<tMaterialInfo>(); // 材质链表信息 94 public List<t3DObject> pObject = new List<t3DObject>(); // 模型中对象链表信息 95 } 96 public class tIndices 97 { 98 public UInt16 a, b, c, bVisible; 99 } 100 // 保存块信息的结构 101 public class tChunk 102 { 103 public UInt32 ID; //块的ID 104 public UInt32 length; //块的长度 105 public UInt32 bytesRead; //需要读的块数据的字节数 106 } 107 108 109 public class CLoad3DS 110 { 111 private tChunk m_CurrentChunk = new tChunk(); 112 private tChunk m_TempChunk = new tChunk(); 113 private FileStream m_FilePointer; 114 115 116 117 public bool Import3DS(t3DMdoel pModel, String strFileName) // 装入3ds文件到模型结构中 118 { 119 if (pModel == null) 120 return false; 121 pModel.numOfMaterials = 0; 122 pModel.numOfObjects = 0; 123 try 124 { 125 this.m_FilePointer = new FileStream(strFileName, FileMode.Open); 126 } 127 catch (Exception ex) 128 { 129 Debug.WriteLine(ex.ToString()); 130 return false; 131 } 132 // 当文件打开之后,首先应该将文件最开始的数据块读出以判断是否是一个3ds文件 133 // 如果是3ds文件的话,第一个块ID应该是PRIMARY 134 135 // 将文件的第一块读出并判断是否是3ds文件 136 ReadChunk(this.m_CurrentChunk); //读出块的id和块的size 137 // 确保是3ds文件 138 if (m_CurrentChunk.ID != FileHead.PRIMARY) 139 { 140 Debug.WriteLine("Unable to load PRIMARY chuck from file: " + strFileName); 141 return false; 142 } 143 // 现在开始读入数据,ProcessNextChunk()是一个递归函数 144 145 // 通过调用下面的递归函数,将对象读出 146 ProcessNextChunk(pModel, m_CurrentChunk); 147 148 // 在读完整个3ds文件之后,计算顶点的法线 149 ComputeNormals(pModel); 150 151 m_FilePointer.Close(); 152 153 return true; 154 } 155 //读出3ds文件的主要部分 156 void ProcessNextChunk(t3DMdoel pModel, tChunk pPreviousChunk) 157 { 158 t3DObject newObject = new t3DObject(); 159 int version = 0; 160 161 m_CurrentChunk = new tChunk(); 162 163 // 下面每读一个新块,都要判断一下块的ID,如果该块是需要的读入的,则继续进行 164 // 如果是不需要读入的块,则略过 165 166 // 继续读入子块,直到达到预定的长度 167 while (pPreviousChunk.bytesRead < pPreviousChunk.length) 168 { 169 //读入下一个块 170 ReadChunk(m_CurrentChunk); 171 172 //判断ID号 173 if (m_CurrentChunk.ID == FileHead.VERSION) 174 { 175 m_CurrentChunk.bytesRead += fread(ref version, m_CurrentChunk.length - m_CurrentChunk.bytesRead, m_FilePointer); 176 177 // 如果文件版本号大于3,给出一个警告信息 178 if (version > 3) 179 Debug.WriteLine("Warning: This 3DS file is over version 3 so it may load incorrectly"); 180 } 181 else if (m_CurrentChunk.ID == FileHead.OBJECTINFO) 182 { 183 //读入下一个块 184 ReadChunk(m_TempChunk); 185 186 //获得网络的版本号 187 m_TempChunk.bytesRead += fread(ref version, m_TempChunk.length - m_TempChunk.bytesRead, m_FilePointer); 188 189 //增加读入的字节数 190 m_CurrentChunk.bytesRead += m_TempChunk.bytesRead; 191 192 //进入下一个块 193 ProcessNextChunk(pModel, m_CurrentChunk); 194 } 195 else if (m_CurrentChunk.ID == FileHead.MATERIAL)//材质信息 196 { 197 //材质的数目递增 198 pModel.numOfMaterials++; 199 //在纹理链表中添加一个空白纹理结构 200 pModel.pMaterials.Add(new tMaterialInfo()); 201 //进入材质装入函数 202 ProcessNextMaterialChunk(pModel, m_CurrentChunk); 203 } 204 else if (m_CurrentChunk.ID == FileHead.OBJECT)//对象的名称 205 { 206 //对象数目递增 207 pModel.numOfObjects++; 208 209 //添加一个新的tObject节点到对象的链表中 210 pModel.pObject.Add(new t3DObject()); 211 212 //获得并保存对象的名称,然后增加读入的字节数 213 m_CurrentChunk.bytesRead += getStr(ref pModel.pObject[pModel.numOfObjects - 1].strName); 214 215 //进入其余对象信息的读入 216 ProcessNextObjectChunk(pModel, pModel.pObject[pModel.numOfObjects - 1], m_CurrentChunk); 217 } 218 else 219 { 220 // 跳过关键帧块的读入,增加需要读入的字节数 EDITKEYFRAME 221 // 跳过所有忽略的块的内容的读入,增加需要读入的字节数 222 while (m_CurrentChunk.bytesRead != m_CurrentChunk.length) 223 { 224 int[] b = new int[1]; 225 m_CurrentChunk.bytesRead += fread(ref b, 1, m_FilePointer); 226 } 227 228 } 229 //添加从最后块中读入的字节数 230 pPreviousChunk.bytesRead += m_CurrentChunk.bytesRead; 231 232 } 233 //当前快设置为前面的块 234 m_CurrentChunk = pPreviousChunk; 235 } 236 //处理所有的文件中的对象信息 237 void ProcessNextObjectChunk(t3DMdoel pModel, t3DObject pObject, tChunk pPreviousChunk) 238 { 239 m_CurrentChunk = new tChunk(); 240 241 //继续读入块的内容直至本子块结束 242 while (pPreviousChunk.bytesRead < pPreviousChunk.length) 243 { 244 ReadChunk(m_CurrentChunk); 245 246 if (m_CurrentChunk.ID == FileHead.OBJECT_MESH)//正读入的是一个新块 247 { 248 //使用递归函数调用,处理该新块 249 ProcessNextObjectChunk(pModel, pObject, m_CurrentChunk); 250 251 } 252 else if (m_CurrentChunk.ID == FileHead.OBJECT_VERTICES)//读入的是对象顶点 253 { 254 ReadVertices(pObject, m_CurrentChunk); 255 } 256 else if (m_CurrentChunk.ID == FileHead.OBJECT_FACES)//读入的是对象的面 257 { 258 ReadVertexIndices(pObject, m_CurrentChunk); 259 } 260 else if (m_CurrentChunk.ID == FileHead.OBJECT_MATERIAL)//读入的是对象的材质名称 261 { 262 //该块保存了对象材质的名称,可能是一个颜色,也可能是一个纹理映射。 263 //同时在该块中也保存了纹理对象所赋予的面 264 265 //下面读入对象的材质名称 266 ReadObjectMaterial(pModel, pObject, m_CurrentChunk); 267 } 268 else if (m_CurrentChunk.ID == FileHead.OBJECT_UV)//读入对象的UV纹理坐标 269 { 270 ReadUVCoordinates(pObject, m_CurrentChunk); 271 } 272 else 273 { 274 //掠过不需要读入的块 275 while (m_CurrentChunk.bytesRead != m_CurrentChunk.length) 276 { 277 int[] b = new int[1]; 278 m_CurrentChunk.bytesRead += fread(ref b, 1, m_FilePointer); 279 } 280 } 281 282 //添加从最后块中读入的字节数 283 pPreviousChunk.bytesRead += m_CurrentChunk.bytesRead; 284 285 } 286 //当前快设置为前面的块 287 m_CurrentChunk = pPreviousChunk; 288 } 289 //处理所有的材质信息 290 void ProcessNextMaterialChunk(t3DMdoel pModel, tChunk pPreviousChunk) 291 { 292 //给当前块分配存储空间 293 m_CurrentChunk = new tChunk(); 294 295 //继续读入这些块,直到该子块结束 296 while (pPreviousChunk.bytesRead < pPreviousChunk.length) 297 { 298 //读入下一块 299 ReadChunk(m_CurrentChunk); 300 301 //判断读入的是什么块 302 if (m_CurrentChunk.ID == FileHead.MATNAME)//材质的名称 303 { 304 //读入材质的名称 305 m_CurrentChunk.bytesRead += fread(ref pModel.pMaterials[pModel.numOfMaterials - 1].strName, m_CurrentChunk.length - m_CurrentChunk.bytesRead, m_FilePointer); 306 } 307 else if (m_CurrentChunk.ID == FileHead.MATDIFFUSE)//对象的RGB颜色 308 { 309 ReadColorChunk(pModel.pMaterials[pModel.numOfMaterials - 1], m_CurrentChunk); 310 } 311 else if (m_CurrentChunk.ID == FileHead.MATMAP)//纹理信息头部 312 { 313 //进入下一个材质块信息 314 ProcessNextMaterialChunk(pModel, m_CurrentChunk); 315 } 316 else if (m_CurrentChunk.ID == FileHead.MATMAPFILE) 317 { 318 //读入材质文件名称 319 m_CurrentChunk.bytesRead += fread(ref pModel.pMaterials[pModel.numOfMaterials - 1].strName, m_CurrentChunk.length - m_CurrentChunk.bytesRead, m_FilePointer); 320 } 321 else 322 { 323 //掠过不需要读入的块 324 while (m_CurrentChunk.bytesRead != m_CurrentChunk.length) 325 { 326 int[] b = new int[1]; 327 m_CurrentChunk.bytesRead += fread(ref b, 1, m_FilePointer); 328 } 329 } 330 //添加从最后块中读入的字节数 331 pPreviousChunk.bytesRead += m_CurrentChunk.bytesRead; 332 } 333 //当前快设置为前面的块 334 m_CurrentChunk = pPreviousChunk; 335 } 336 //读下一个块 337 private void ReadChunk(tChunk pChunk) 338 { 339 //pChunk.bytesRead = fread(ref pChunk.ID, 2, this.m_FilePointer); 340 341 Byte[] id = new Byte[2]; 342 Byte[] length = new Byte[4]; 343 pChunk.bytesRead = (UInt32)this.m_FilePointer.Read(id, 0, 2); 344 pChunk.bytesRead += (UInt32)this.m_FilePointer.Read(length, 0, 4); 345 pChunk.ID = (UInt32)(id[1] * 256 + id[0]); 346 pChunk.length = (UInt32)(((length[3] * 256 + length[2]) * 256 + length[1]) * 256 + length[0]); 347 348 } 349 //读入RGB颜色 350 void ReadColorChunk(tMaterialInfo pMaterial, tChunk pChunk) 351 { 352 //读入颜色块信息 353 ReadChunk(m_TempChunk); 354 355 //读入RGB颜色 356 m_TempChunk.bytesRead += fread(ref pMaterial.color, m_TempChunk.length - m_TempChunk.bytesRead, m_FilePointer); 357 358 //增加读入的字节数 359 pChunk.bytesRead += m_TempChunk.bytesRead; 360 } 361 //读入顶点索引 362 void ReadVertexIndices(t3DObject pObject, tChunk pPreviousChunk) 363 { 364 int index = 0; 365 //读入该对象中面的数目 366 pPreviousChunk.bytesRead += fread(ref pObject.numOfFaces, 2, m_FilePointer); 367 368 //分配所有的储存空间,并初始化结构 369 pObject.pFaces = new tFace[pObject.numOfFaces]; 370 371 //遍历对象中所有的面 372 for (int i = 0; i < pObject.numOfFaces; i++) 373 { 374 pObject.pFaces[i] = new tFace(); 375 for (int j = 0; j < 4; j++) 376 { 377 //读入当前对象的第一个点 378 pPreviousChunk.bytesRead += fread(ref index, 2, m_FilePointer); 379 380 if (j < 3) 381 { 382 pObject.pFaces[i].vertIndex[j] = index; 383 } 384 } 385 } 386 } 387 //读入对象的UV坐标 388 void ReadUVCoordinates(t3DObject pObject, tChunk pPreviousChunk) 389 { 390 //为了读入对象的UV坐标,首先需要读入数量,再读入具体的数据 391 392 //读入UV坐标的数量 393 pPreviousChunk.bytesRead += fread(ref pObject.numTexVertex, 2, m_FilePointer); 394 395 //初始化保存UV坐标的数组 396 pObject.pTexVerts = new CVector2[pObject.numTexVertex]; 397 398 //读入纹理坐标 399 pPreviousChunk.bytesRead += fread(ref pObject.pTexVerts, pPreviousChunk.length - pPreviousChunk.bytesRead, m_FilePointer); 400 } 401 //读入对象的顶点 402 void ReadVertices(t3DObject pObject, tChunk pPreviousChunk) 403 { 404 //在读入实际的顶点之前,首先必须确定需要读入多少个顶点。 405 406 //读入顶点的数目 407 pPreviousChunk.bytesRead += fread(ref pObject.numOfVerts, 2, m_FilePointer); 408 409 //分配顶点的储存空间,然后初始化结构体 410 pObject.pVerts = new CVector3[pObject.numOfVerts]; 411 412 //读入顶点序列 413 pPreviousChunk.bytesRead += fread(ref pObject.pVerts, pPreviousChunk.length - pPreviousChunk.bytesRead, m_FilePointer); 414 415 //因为3DMax的模型Z轴是指向上的,将y轴和z轴翻转——y轴和z轴交换,再把z轴反向 416 417 //遍历所有的顶点 418 for (int i = 0; i < pObject.numOfVerts; i++) 419 { 420 float fTempY = pObject.pVerts[i].y; 421 pObject.pVerts[i].y = pObject.pVerts[i].z; 422 pObject.pVerts[i].z = -1 * fTempY; 423 } 424 } 425 //读入对象的材质名称 426 void ReadObjectMaterial(t3DMdoel pModel, t3DObject pObject, tChunk pPreviousChunk) 427 { 428 String strMaterial = ""; //用来保存对象的材质名称 429 int[] buffer = new int[50000]; //用来读入不需要的数据 430 431 //读入赋予当前对象的材质名称 432 pPreviousChunk.bytesRead += getStr(ref strMaterial); 433 434 //遍历所有的纹理 435 for (int i = 0; i < pModel.numOfMaterials; i++) 436 { 437 //如果读入的纹理与当前纹理名称匹配 438 439 if (true)//strMaterial.Equals(pModel.pMaterials[i].strName)) 440 { 441 //设置材质ID 442 pObject.materialID = i; 443 //判断是否是纹理映射,如果strFile是一个长度大于1的字符串,则是纹理 444 if (pModel.pMaterials[i].strName.Length > 0) //if (pModel.pMaterials[i].strFile.Length > 0) 445 { 446 //设置对象的纹理映射标志 447 pObject.bHasTexture = true; 448 } 449 break; 450 } 451 else 452 { 453 //如果该对象没有材质,则设置ID为-1 454 pObject.materialID = -1; 455 } 456 } 457 pPreviousChunk.bytesRead += fread(ref buffer, pPreviousChunk.length - pPreviousChunk.bytesRead, m_FilePointer); 458 } 459 460 //下面的这些函数主要用来计算顶点的法向量,顶点的法向量主要用来计算光照 461 //计算对象的法向量 462 private void ComputeNormals(t3DMdoel pModel) 463 { 464 CVector3 vVector1, vVector2, vNormal; 465 CVector3[] vPoly; 466 467 vPoly = new CVector3[3]; 468 //如果模型中没有对象,则返回 469 if (pModel.numOfObjects <= 0) 470 return; 471 472 //遍历模型中所有的对象 473 for (int index = 0; index < pModel.numOfObjects; index++) 474 { 475 //获得当前对象 476 t3DObject pObject = pModel.pObject[index]; 477 478 //分配需要的空间 479 CVector3[] pNormals = new CVector3[pObject.numOfFaces]; 480 CVector3[] pTempNormals = new CVector3[pObject.numOfFaces]; 481 pObject.pNormals = new CVector3[pObject.numOfVerts]; 482 483 //遍历对象所有面 484 for (int i = 0; i < pObject.numOfFaces; i++) 485 { 486 vPoly[0] = pObject.pVerts[pObject.pFaces[i].vertIndex[0]]; 487 vPoly[1] = pObject.pVerts[pObject.pFaces[i].vertIndex[1]]; 488 vPoly[2] = pObject.pVerts[pObject.pFaces[i].vertIndex[2]]; 489 490 //计算面的法向量 491 vVector1 = Vector(vPoly[0], vPoly[2]); 492 vVector2 = Vector(vPoly[2], vPoly[1]); 493 494 vNormal = Cross(vVector1, vVector2); 495 pTempNormals[i] = vNormal; 496 vNormal = Normalize(vNormal); 497 pNormals[i] = vNormal; 498 } 499 500 //下面求顶点的法向量 501 CVector3 vSum = new CVector3(); 502 vSum.x = 0; vSum.y = 0; vSum.z = 0; 503 int shared = 0; 504 505 //遍历所有的顶点 506 for (int i = 0; i < pObject.numOfVerts; i++) 507 { 508 for (int j = 0; j < pObject.numOfFaces; j++) 509 { 510 if (pObject.pFaces[j].vertIndex[0] == i || 511 pObject.pFaces[j].vertIndex[1] == i || 512 pObject.pFaces[j].vertIndex[2] == i) 513 { 514 vSum = AddVector(vSum, pTempNormals[j]); 515 shared++; 516 } 517 } 518 pObject.pNormals[i] = DivideVectorByScaler(vSum, (float)(-1 * shared)); 519 520 //规范化最后的顶点法向量 521 pObject.pNormals[i] = Normalize(pObject.pNormals[i]); 522 523 vSum.x = 0; vSum.y = 0; vSum.z = 0; 524 shared = 0; 525 } 526 } 527 } 528 //求两点决定的矢量 529 CVector3 Vector(CVector3 p1, CVector3 p2) 530 { 531 CVector3 v = new CVector3(); 532 v.x = p1.x - p2.x; 533 v.y = p1.y - p2.y; 534 v.z = p1.z - p2.z; 535 return v; 536 } 537 //返回两个矢量的和 538 CVector3 AddVector(CVector3 p1, CVector3 p2) 539 { 540 CVector3 v = new CVector3(); 541 v.x = p1.x + p2.x; 542 v.y = p1.y + p2.y; 543 v.z = p1.z + p2.z; 544 return v; 545 } 546 //返回矢量的缩放 547 CVector3 DivideVectorByScaler(CVector3 v, float Scaler) 548 { 549 CVector3 vr = new CVector3(); 550 vr.x = v.x / Scaler; 551 vr.y = v.y / Scaler; 552 vr.z = v.z / Scaler; 553 return vr; 554 } 555 //返回两个矢量的叉积 556 CVector3 Cross(CVector3 p1, CVector3 p2) 557 { 558 CVector3 c = new CVector3(); 559 c.x = ((p1.y * p2.z) - (p1.z * p2.y)); 560 c.y = ((p1.z * p2.x) - (p1.x * p2.z)); 561 c.z = ((p1.x * p2.y) - (p1.y * p2.x)); 562 return c; 563 } 564 //规范化矢量 565 CVector3 Normalize(CVector3 v) 566 { 567 CVector3 n = new CVector3(); 568 double mag = Mag(v); 569 n.x = v.x / (float)mag; 570 n.y = v.y / (float)mag; 571 n.z = v.z / (float)mag; 572 return n; 573 } 574 //矢量的模 575 double Mag(CVector3 v) 576 { 577 return Math.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z); 578 } 579 580 //读出一个字符串 581 uint getStr(ref String str) 582 { 583 str = ""; 584 char c = (char)m_FilePointer.ReadByte(); 585 while (c != 0) 586 { 587 str += c; 588 c = (char)m_FilePointer.ReadByte(); 589 } 590 591 return (uint)(str.Length + 1); 592 } 593 //读出byte数组 594 public static uint fread(ref int[] buffer, uint length, FileStream f) 595 { 596 for (uint i = 0; i < length; i++) 597 { 598 try 599 { 600 buffer[i] = f.ReadByte(); 601 } 602 catch (Exception ex) 603 { 604 Debug.WriteLine(f.Name + " 读取出错"); 605 Debug.WriteLine(ex.ToString()); 606 return i; 607 } 608 } 609 return length; 610 } 611 //读出2个字节或4个字节的int 612 public static uint fread(ref int buffer, uint length, FileStream f) 613 { 614 if (length == 2) 615 { 616 Byte[] buf = new Byte[2]; 617 uint len = (UInt32)f.Read(buf, 0, 2); 618 buffer = (buf[1] * 256 + buf[0]); 619 return len; 620 } 621 else if (length == 4) 622 { 623 Byte[] buf = new Byte[4]; 624 uint len = (UInt32)f.Read(buf, 0, 4); 625 buffer = (((buf[3] * 256 + buf[2]) * 256 + buf[1]) * 256 + buf[0]); 626 return len; 627 } 628 return 0; 629 } 630 //读出CVector3数组 631 public static uint fread(ref CVector3[] buffer, uint length, FileStream f) 632 { 633 uint l = 0; 634 try 635 { 636 for (uint i = 0; i < length / 12; i++) 637 { 638 buffer[i] = new CVector3(); 639 Byte[] bts = new Byte[4]; 640 l += (uint)f.Read(bts, 0, 4); 641 buffer[i].x = FileHead.byte2float(bts); 642 l += (uint)f.Read(bts, 0, 4); 643 buffer[i].y = FileHead.byte2float(bts); 644 l += (uint)f.Read(bts, 0, 4); 645 buffer[i].z = FileHead.byte2float(bts); 646 } 647 return l; 648 } 649 catch (Exception ex) 650 { 651 Debug.WriteLine(f.Name + " 读取出错"); 652 Debug.WriteLine(ex.ToString()); 653 return l; 654 } 655 } 656 //读出CVector数组 657 public static uint fread(ref CVector2[] buffer, uint length, FileStream f) 658 { 659 uint l = 0; 660 try 661 { 662 for (uint i = 0; i < length / 8; i++) 663 { 664 buffer[i] = new CVector2(); 665 Byte[] bts = new Byte[4]; 666 l += (uint)f.Read(bts, 0, 4); 667 buffer[i].x = FileHead.byte2float(bts); 668 l += (uint)f.Read(bts, 0, 4); 669 buffer[i].y = FileHead.byte2float(bts); 670 } 671 return l; 672 } 673 catch (Exception ex) 674 { 675 Debug.WriteLine(f.Name + " 读取出错"); 676 Debug.WriteLine(ex.ToString()); 677 return l; 678 } 679 } 680 //读出字符串 681 public static uint fread(ref String buffer, uint length, FileStream f) 682 { 683 uint l = 0; 684 buffer = ""; 685 try 686 { 687 for (int i = 0; i < length; i++) 688 { 689 Byte[] b = new Byte[1]; 690 l += (uint)f.Read(b, 0, 1); 691 if (i != length - 1) 692 buffer += (char)(b[0]); 693 } 694 695 return l; 696 } 697 catch (Exception ex) 698 { 699 Debug.WriteLine(f.Name + " 读取出错"); 700 Debug.WriteLine(ex.ToString()); 701 return l; 702 } 703 } 704 } 705 706 public class H3DModel 707 { 708 public const int CHANGE = 1; 709 public const int IGNORE = 2; 710 public const int ADD = 3; 711 t3DMdoel model = null; 712 uint[] g_Texture; 713 CVector3 boxMin, boxMax; 714 715 public H3DModel() 716 { 717 this.model = new t3DMdoel(); 718 } 719 public static H3DModel FromFile(OpenGL gl, string fileName) //从文件中加载3D模型 720 { 721 H3DModel h3d = new H3DModel(); 722 CLoad3DS load = new CLoad3DS(); 723 load.Import3DS(h3d.model, fileName); 724 if (!h3d.LoadTextrue(gl)) 725 return null; 726 h3d.LoadBox(); 727 return h3d; 728 } 729 public t3DMdoel getModelData() //得到3D模型数据 730 { 731 return this.model; 732 } 733 734 protected bool LoadTextrue(OpenGL gl) 735 { 736 this.g_Texture = new uint[100]; 737 for (int i = 0; i < model.numOfMaterials; i++) 738 { 739 if (model.pMaterials[i].strName.Length > 0) //if (model.pMaterials[i].strFile.Length > 0) 740 if (!CreateTexture(gl, ref this.g_Texture, model.pMaterials[i].strName, i)) // if (!CreateTexture(gl, ref this.g_Texture, model.pMaterials[i].strFile, i)) 741 return false; 742 model.pMaterials[i].texureId = i; 743 } 744 return true; 745 } 746 protected void LoadBox() 747 { 748 boxMax = new CVector3(); 749 boxMin = new CVector3(); 750 boxMax.x = float.MinValue; 751 boxMax.y = float.MinValue; 752 boxMax.z = float.MinValue; 753 boxMin.x = float.MaxValue; 754 boxMin.y = float.MaxValue; 755 boxMin.z = float.MaxValue; 756 for (int i = 0; i < model.numOfObjects; i++) 757 { 758 t3DObject pObject = model.pObject[i]; 759 for (int j = 0; j < pObject.numOfVerts; j++) 760 { 761 float x = pObject.pVerts[j].x; 762 float y = pObject.pVerts[j].y; 763 float z = pObject.pVerts[j].z; 764 if (boxMin.x > x) 765 boxMin.x = x; 766 if (boxMin.y > y) 767 boxMin.y = y; 768 if (boxMin.z > z) 769 boxMin.z = z; 770 if (boxMax.x < x) 771 boxMax.x = x; 772 if (boxMax.y < y) 773 boxMax.y = y; 774 if (boxMax.z < z) 775 boxMax.z = z; 776 } 777 } 778 779 } 780 protected bool CreateTexture(OpenGL GL,ref uint[] textureArray, String strFileName, int textureID) 781 { 782 Bitmap image = null; 783 try 784 { 785 image = new Bitmap(strFileName); 786 } 787 catch (ArgumentException) 788 { 789 Debug.WriteLine("Could not load " + strFileName + " ."); 790 return false; 791 } 792 if (image != null) 793 { 794 image.RotateFlip(RotateFlipType.RotateNoneFlipY); 795 BitmapData bitmapdata; 796 Rectangle rect = new Rectangle(0, 0, image.Width, image.Height); 797 bitmapdata = image.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); 798 799 uint[] tArray = new uint[1]; 800 GL.GenTextures(1, tArray); 801 textureArray[textureID] = tArray[0]; 802 803 GL.PixelStore(OpenGL.GL_UNPACK_ALIGNMENT, 1); 804 805 GL.BindTexture(OpenGL.GL_TEXTURE_2D, textureArray[textureID]); 806 GL.Build2DMipmaps(OpenGL.GL_TEXTURE_2D, 3, image.Width, image.Height, OpenGL.GL_BGR, OpenGL.GL_UNSIGNED_BYTE, bitmapdata.Scan0); 807 808 GL.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, OpenGL.GL_LINEAR_MIPMAP_NEAREST); 809 GL.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, OpenGL.GL_LINEAR_MIPMAP_LINEAR); 810 811 return true; 812 } 813 return false; 814 } 815 816 public void DrawModel(OpenGL GL,bool isLines) //画出模型 817 { 818 for (int i = 0; i < this.model.numOfObjects; i++) 819 { 820 if (this.model.pObject.Count <= 0) break; 821 822 t3DObject pObject = this.model.pObject[i]; 823 824 if (pObject.bHasTexture) 825 { 826 GL.Enable(OpenGL.GL_TEXTURE_2D); 827 GL.Color(1f,1f,1f); 828 GL.BindTexture(OpenGL.GL_TEXTURE_2D, this.g_Texture[i]); //pObject.materialID]); 829 830 } 831 else 832 { 833 GL.Disable(OpenGL.GL_TEXTURE_2D); 834 GL.Color(1f, 1f, 1f); //GL.Color3ub(255, 255, 255); 835 } 836 837 if (isLines) 838 GL.Begin(OpenGL.GL_LINE_STRIP); 839 else 840 GL.Begin(OpenGL.GL_TRIANGLES); 841 842 for (int j = 0; j < pObject.numOfFaces; j++) 843 { 844 for (int whichVertex = 0; whichVertex < 3; whichVertex++) 845 { 846 int index = pObject.pFaces[j].vertIndex[whichVertex]; 847 848 GL.Normal(-pObject.pNormals[index].x, -pObject.pNormals[index].y, -pObject.pNormals[index].z); 849 850 if (pObject.bHasTexture) 851 { 852 if (pObject.pTexVerts != null) 853 { 854 GL.TexCoord(pObject.pTexVerts[index].x, pObject.pTexVerts[index].y); 855 } 856 } 857 else 858 { 859 860 if (this.model.pMaterials.Count != 0 && pObject.materialID >= 0) 861 { 862 int[] color = this.model.pMaterials[pObject.materialID].color; 863 GL.Color((byte)color[0], (byte)color[1], (byte)color[2]); 864 865 } 866 } 867 868 GL.Vertex(pObject.pVerts[index].x, pObject.pVerts[index].y, pObject.pVerts[index].z); 869 870 } 871 872 } 873 GL.End(); 874 } 875 } 876 877 public void DrawBorder(OpenGL GL) //画出边框 878 { 879 if (this.boxMax.x != float.MinValue && this.boxMin.x != float.MaxValue) 880 { 881 GL.Color(1f,1f,1f); 882 float[] v = new float[6]; 883 v[0] = boxMin.x; 884 v[1] = boxMin.y; 885 v[2] = boxMin.z; 886 v[3] = boxMax.x; 887 v[4] = boxMax.y; 888 v[5] = boxMax.z; 889 890 GL.Begin(OpenGL.GL_LINE_LOOP); 891 { 892 GL.Vertex(v[0], v[1], v[2]); 893 GL.Vertex(v[0], v[4], v[2]); 894 GL.Vertex(v[3], v[4], v[2]); 895 GL.Vertex(v[3], v[1], v[2]); 896 } 897 GL.End(); 898 GL.Begin(OpenGL.GL_LINE_LOOP); 899 { 900 GL.Vertex(v[0], v[1], v[5]); 901 GL.Vertex(v[0], v[4], v[5]); 902 GL.Vertex(v[3], v[4], v[5]); 903 GL.Vertex(v[3], v[1], v[5]); 904 } 905 GL.End(); 906 GL.Begin(OpenGL.GL_LINES); 907 { 908 GL.Vertex(v[0], v[1], v[2]); 909 GL.Vertex(v[0], v[1], v[5]); 910 GL.Vertex(v[0], v[4], v[2]); 911 GL.Vertex(v[0], v[4], v[5]); 912 GL.Vertex(v[3], v[4], v[2]); 913 GL.Vertex(v[3], v[4], v[5]); 914 GL.Vertex(v[3], v[1], v[2]); 915 GL.Vertex(v[3], v[1], v[5]); 916 } 917 GL.End(); 918 } 919 else 920 { 921 Debug.WriteLine("No Objects"); 922 } 923 924 } 925 public CVector3[] getOriginalBorder() //得到模型边框的8个点 926 { 927 CVector3[] vs = new CVector3[8]; 928 float[] v = new float[6]; 929 v[0] = boxMin.x; 930 v[1] = boxMin.y; 931 v[2] = boxMin.z; 932 v[3] = boxMax.x; 933 v[4] = boxMax.y; 934 v[5] = boxMax.z; 935 for (int i = 0; i < 8; i++) 936 vs[i] = new CVector3(); 937 vs[0].x = v[0]; vs[0].y = v[1]; vs[0].z = v[2]; 938 vs[1].x = v[0]; vs[1].y = v[4]; vs[1].z = v[2]; 939 vs[2].x = v[3]; vs[2].y = v[4]; vs[2].z = v[2]; 940 vs[3].x = v[3]; vs[3].y = v[1]; vs[3].z = v[2]; 941 vs[4].x = v[0]; vs[4].y = v[1]; vs[4].z = v[5]; 942 vs[5].x = v[0]; vs[5].y = v[4]; vs[5].z = v[5]; 943 vs[6].x = v[3]; vs[6].y = v[4]; vs[6].z = v[5]; 944 vs[7].x = v[3]; vs[7].y = v[1]; vs[7].z = v[5]; 945 return vs; 946 } 947 948 } 949 }
效果如下:
重要的说明:
1. 第848行要注意,这里的法线方向很重要,你可以尝试一下,XYZ的法线要么全部为正,要么为负。如果法线方向搞错了会怎么样?如下图
2. 第806行Build2DMipmaps()中,把 OpenGL.GL_BGR换 OpenGL.GL_RGB这两个参数,会影响贴图的颜色,如果贴图失真偏色,就要考虑这个参数是否有问题。
比如下图中的贴图就偏蓝色。正确的颜色应该为本节代码的运行效果图。
3. 本源码没能很好的处理材质,只能处理有贴图的普通材质,如果是颜色材质或者其它材质则无法处理。读者可以尝试修改,记得把改好的代码共享给笔者参考一下哦!
4. 另外,如果导出没有材质的模型,例如本文这样的组合的模型,笔者不知道如何单独为茶壶或者地板赋不同的材质,组合导入的3ds模型貌似是一个整体,不能打散为多个对象。
5. 源代码目录提供了多个3ds模型,你可以试验一下区别。
6. 如果你自己用3dsmax导出带材质的三维模型,注意把贴图和3ds文件都拷贝到bin目录。
原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/