SharpGL学习笔记(十八) 解析3ds模型并显示

笔者设想的3D仿真中的元件,是不可能都是“画”出来的。这样就玩复杂了,应该把任务分包出去,让善于制作模型的软件来制作三维模型,我们只需要解析并且显示它即可。

3dsmax制作三维模型的方便,快捷,专业,我想是没有人提反对意见的。它可以把制作好的模型导出为业界通用的3ds格式,如果你愿意的话,3ds格式也可以包含材质和uvw贴图坐标。这样的模型我们在opengl中导入后只用打光和显示,非常省事。

解析3ds格式比较复杂,不过读者可以拿来主义,直接用下面的代码就可以了。

代码已经加入了必要的注释,笔者就不罗嗦了。

SharpGL学习笔记(十八) 解析3ds模型并显示

源代码: SharpGLForm.cs

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SharpGL;
using Model3D;
using System.IO; namespace SharpGLWinformsApplication1
{
//原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/
public partial class SharpGLForm : Form
{
private string configPath = AppDomain.CurrentDomain.BaseDirectory + "config";
private H3DModel h3d;
private float rotation = 0.0f;
private bool isRotate = false;
private bool isLines = false;
private bool isFrontView = false;
private bool isLeftView = false;
private bool isTopView = false;
private bool isPerspective = true;
private float[] lightPos = new float[] { -, -, , };
private float[] lightSphereColor = new float[] { 0.2f, 0.5f, 0.8f };
private IList<float[]> lightColor = new List<float[]>();
private double[] lookatValue = { , , , , , , , , }; float[] no_mat = new float[] { 0.0f, 0.0f, 0.0f, 1.0f }; // 无材质颜色
float[] mat_ambient = new float[] { 0.7f, 0.7f, 0.7f, 1.0f }; // 环境颜色
float[] mat_ambient_color = new float[] { 0.8f, 0.6f, 0.2f, 1.0f };
float[] mat_diffuse = new float[] { 0.2f, 0.5f, 0.8f, 1.0f }; // 散射颜色
float[] no_shininess = new float[] { 0.0f }; // 镜面反射指数为0
float[] mat_emission = new float[] { 0.3f, 0.2f, 0.3f, 0.0f }; // 发射光颜色
float[] high_shininess = new float[] { 100.0f }; // 镜面反射指数为100.0
float[] low_shininess = new float[] { 5.0f }; // 镜面反射指数为5.0
float[] mat_specular = new float[] { 1.0f, 1.0f, 1.0f, 1.0f }; // 镜面反射颜色 private IList<double[]> viewDefaultPos = new List<double[]>();
public SharpGLForm()
{
InitializeComponent(); } private void openGLControl_OpenGLDraw(object sender, PaintEventArgs e)
{
OpenGL gl = openGLControl.OpenGL;
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
gl.LoadIdentity();
gl.Rotate(rotation, 0.0f, 1.0f, 0.0f);
drawGrid(gl);
draw3DSModel(gl);
if (isRotate)
rotation += 3.0f;
} private void draw3DSModel(OpenGL Gl)
{
Gl.PushMatrix();
{
//Gl.PixelStore(OpenGL.GL_UNPACK_ALIGNMENT, 4);
//Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_AMBIENT, mat_specular);
//Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_DIFFUSE, mat_specular);
//Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SPECULAR, no_mat);
//Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_SHININESS, no_mat);
//Gl.Material(OpenGL.GL_FRONT, OpenGL.GL_EMISSION, no_mat);
Gl.Scale(0.05, 0.05, 0.05);
Gl.Translate(, , );
h3d.DrawModel(Gl,isLines);
h3d.DrawBorder(Gl);
}
Gl.PushMatrix();
} private void setLightColor(OpenGL gl)
{
gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_AMBIENT, lightColor[]);
gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_DIFFUSE, lightColor[]);
gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_SPECULAR, lightColor[]);
} private void openGLControl_OpenGLInitialized(object sender, EventArgs e)
{
OpenGL gl = openGLControl.OpenGL; //四个视图的缺省位置
viewDefaultPos.Add(new double[] { , , , , , , , , }); //透视
viewDefaultPos.Add(new double[] { , , , , , , , , }); //前视
viewDefaultPos.Add(new double[] { , , , , , , , , }); //左视
viewDefaultPos.Add(new double[] { , , , -, , , , , }); //顶视
lookatValue =(double[])viewDefaultPos[].Clone(); lightColor.Add(new float[] { 1f, 1f, 1f, 1f }); //环境光(ambient light)
lightColor.Add(new float[] { 1f, 1f, 1f, 1f }); //漫射光(diffuse light)
lightColor.Add(new float[] { 1f, 1f, 1f, 1f }); //镜面反射光(specular light) setLightColor(gl);
gl.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, lightPos); gl.Enable(OpenGL.GL_LIGHTING);
gl.Enable(OpenGL.GL_LIGHT0);
gl.Enable(OpenGL.GL_NORMALIZE); gl.ClearColor(, , , );
h3d = H3DModel.FromFile(gl, "teport3.3DS"); loadConfig(); } private void openGLControl_Resized(object sender, EventArgs e)
{ OpenGL gl = openGLControl.OpenGL;
gl.MatrixMode(OpenGL.GL_PROJECTION);
gl.LoadIdentity();
gl.Perspective(40.0f, (double)Width / (double)Height, 0.01, 100.0); gl.LookAt(lookatValue[], lookatValue[], lookatValue[],
lookatValue[], lookatValue[], lookatValue[],
lookatValue[], lookatValue[], lookatValue[]); gl.MatrixMode(OpenGL.GL_MODELVIEW);
updateLabInfo();
} void drawGrid(OpenGL gl)
{
//关闭纹理和光照
gl.Disable(OpenGL.GL_TEXTURE_2D);
gl.Disable(OpenGL.GL_LIGHTING); //绘制过程
gl.PushAttrib(OpenGL.GL_CURRENT_BIT); //保存当前属性
gl.PushMatrix(); //压入堆栈
gl.Translate(0f, -2f, 0f);
gl.Color(0f, 0f, 1f); //在X,Z平面上绘制网格
for (float i = -; i <= ; i += )
{
//绘制线
gl.Begin(OpenGL.GL_LINES);
{
if (i == )
gl.Color(0f, 1f, 0f);
else
gl.Color(0f, 0f, 1f); //X轴方向
gl.Vertex(-50f, 0f, i);
gl.Vertex(50f, 0f, i);
//Z轴方向
gl.Vertex(i, 0f, -50f);
gl.Vertex(i, 0f, 50f); }
gl.End();
}
gl.PopMatrix();
gl.PopAttrib();
gl.Enable(OpenGL.GL_LIGHTING);
} void drawSphere(OpenGL gl,double radius,int segx,int segy,bool isLines)
{
gl.PushMatrix();
gl.Translate(2f, 1f, 2f);
var sphere = gl.NewQuadric();
if (isLines)
gl.QuadricDrawStyle(sphere, OpenGL.GL_LINES);
else
gl.QuadricDrawStyle(sphere, OpenGL.GL_QUADS);
gl.QuadricNormals(sphere, OpenGL.GLU_SMOOTH);
gl.QuadricOrientation(sphere, (int)OpenGL.GLU_OUTSIDE);
gl.QuadricTexture(sphere, (int)OpenGL.GLU_FALSE);
gl.Sphere(sphere, radius, segx, segy);
gl.DeleteQuadric(sphere);
gl.PopMatrix();
} private void moveObject(int obj,string keyName)
{
//obj==0移动视图
switch (keyName)
{
case "btnQ":
if (obj == ) ++lookatValue[]; //y
else
++lightPos[];
break;
case "btnE":
if (obj == ) --lookatValue[];
else
--lightPos[];
break;
case "btnW":
if (obj == ) --lookatValue[]; //z
else
--lightPos[];
break;
case "btnS":
if (obj == ) ++lookatValue[];
else
++lightPos[];
break;
case "btnA":
if (obj == ) --lookatValue[]; //X
else
--lightPos[];
break;
case "btnD":
if (obj == ) ++lookatValue[];
else
++lightPos[];
break;
}
} private void rbPerspective_CheckedChanged(object sender, EventArgs e)
{
switch (((RadioButton)sender).Name)
{
case "rbPerspective":
isPerspective = !isPerspective;
isFrontView = false;
isTopView = false;
isLeftView = false;
break;
case "rbLeft":
isLeftView = !isLeftView;
isFrontView = false;
isPerspective = false;
isTopView = false;
break;
case "rbFront":
isFrontView = !isFrontView;
isTopView = false;
isPerspective = false;
isLeftView = false;
break;
case "rbTop":
isTopView = !isTopView;
isPerspective = false;
isLeftView = false;
isFrontView = false;
break;
default:
return;
}
setViewDefaultValue();
openGLControl_Resized(null, null);
} private void cbxRotate_CheckedChanged(object sender, EventArgs e)
{
var cbx=((CheckBox)sender);
switch (cbx.Name)
{
case "cbxRotate":
isRotate = cbx.Checked;
break;
case "cbxLines":
isLines = cbx.Checked;
break;
case "cbxLightOff":
if (!cbx.Checked)
this.openGLControl.OpenGL.Enable(OpenGL.GL_LIGHT0);
else
this.openGLControl.OpenGL.Disable(OpenGL.GL_LIGHT0);
break;
}
} private void SharpGLForm_Load(object sender, EventArgs e)
{
this.cbxLightType.SelectedIndex = ;
updateLabInfo();
} private void loadConfig()
{
var ary= File.ReadAllText(configPath).Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (ary.Length == )
{
var lightary = ary[].Split(',').Select(s => {
float f1=;
float.TryParse(s, out f1);
return f1;
}).ToArray();
var lookAtary = ary[].Split(',').Select(s =>
{
double d1=;
double.TryParse(s,out d1);
return d1;
}).ToArray();
for (int i = ; i < lightPos.Length; i++)
lightPos[i] = lightary[i];
for (int i = ; i < lookatValue.Length; i++)
lookatValue[i] = lookAtary[i];
}
} private void saveConfig()
{
try
{
File.WriteAllText(configPath, tbLightPos.Text + Environment.NewLine + tbLookAt.Text + Environment.NewLine);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
} private void updateLabInfo()
{
tbLightPos.Text = string.Format("{0},{1},{2},{3}", lightPos[], lightPos[], lightPos[], lightPos[]);
tbLookAt.Text = string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}", lookatValue[], lookatValue[], lookatValue[],
lookatValue[], lookatValue[], lookatValue[], lookatValue[], lookatValue[], lookatValue[]);
btnSetPos_Click(null, null);
} private void rbWhite_CheckedChanged(object sender, EventArgs e)
{
var rad = ((RadioButton)sender);
var lightType = this.cbxLightType.SelectedIndex;
if (rad.Checked)
{
switch (rad.Name)
{
case "rbBlack":
lightColor[lightType][] = 0f;
lightColor[lightType][] = 0f;
lightColor[lightType][] = 0f;
lightColor[lightType][] = 1f;
break;
case "rbWhite":
lightColor[lightType][] = 1f;
lightColor[lightType][] = 1f;
lightColor[lightType][] = 1f;
lightColor[lightType][] = 1f;
break;
case "rbRed":
lightColor[lightType][] = 1f;
lightColor[lightType][] = 0f;
lightColor[lightType][] = 0f;
lightColor[lightType][] = 1f;
break;
case "rbGreen":
lightColor[lightType][] = 0f;
lightColor[lightType][] = 1f;
lightColor[lightType][] = 0f;
lightColor[lightType][] = 1f;
break;
case "rbBlue":
lightColor[lightType][] = 0f;
lightColor[lightType][] = 0f;
lightColor[lightType][] = 1f;
lightColor[lightType][] = 1f;
break;
}
setLightColor(openGLControl.OpenGL);
}
} private void cbxLightType_SelectedIndexChanged(object sender, EventArgs e)
{
var lightType = this.cbxLightType.SelectedIndex;
if (lightType >= )
judgeColor(lightColor[lightType]);
} private void judgeColor(float[] color)
{
if (color[] == 1f && color[] == 1f && color[] == 1f && color[] == 1f)
rbWhite.Checked = true;
else if (color[] == 1f && color[] == 0f && color[] == 0f && color[] == 1f)
rbRed.Checked = true;
else if (color[] == 0f && color[] == 1f && color[] == 0f && color[] == 1f)
rbGreen.Checked = true;
else if (color[] == 0f && color[] == 0f && color[] == 1f && color[] == 1f)
rbBlue.Checked = true;
else if (color[] == 0f && color[] == 0f && color[] ==0f && color[] ==1f)
rbBlack.Checked = true;
} private void btnQ_Click(object sender, EventArgs e)
{
moveObject(radioButton1.Checked ? : ,((Button)sender).Name);
openGLControl_Resized(null, null);
openGLControl.OpenGL.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, lightPos);
} private void setViewDefaultValue()
{
if (isPerspective)
{
lookatValue = (double[])viewDefaultPos[].Clone();
}
else if (isFrontView)
{
lookatValue = (double[])viewDefaultPos[].Clone();
}
else if (isLeftView)
{
lookatValue = (double[])viewDefaultPos[].Clone();
}
else if (isTopView)
{
lookatValue = (double[])viewDefaultPos[].Clone();
}
} private void btnDefaultPOS_Click(object sender, EventArgs e)
{
if (radioButton1.Checked)
{
setViewDefaultValue();
}
else
{
lightPos = new float[] { -, -, , };
openGLControl.OpenGL.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, lightPos);
}
openGLControl_Resized(null, null);
} private void openGLControl_KeyDown(object sender, KeyEventArgs e)
{
string name = string.Empty;
switch (e.KeyCode)
{
case Keys.W:
name = "btnW";
break;
case Keys.A:
name = "btnA";
break;
case Keys.S:
name = "btnS";
break;
case Keys.D:
name = "btnD";
break;
case Keys.Q:
name = "btnQ";
break;
case Keys.E:
name = "btnE";
break;
}
moveObject(radioButton1.Checked ? : , name);
openGLControl_Resized(null, null);
} private void btnSetPos_Click(object sender, EventArgs e)
{
if (radioButton1.Checked)
{
double[] ary = tbLookAt.Text.Split(',').Select(s => Convert.ToDouble(s)).ToArray();
lookatValue = ary;
openGLControl_Resized(null, null);
}
else
{
float[] ary = tbLightPos.Text.Split(',').Select(s => Convert.ToSingle(s)).ToArray();
lightPos = ary;
openGLControl.OpenGL.Light(OpenGL.GL_LIGHT0, OpenGL.GL_POSITION, ary);
} } private void tbLightPos_TextChanged(object sender, EventArgs e)
{
saveConfig();
} }
}

源代码:Model3D.cs

 using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using SharpGL;
//原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/
namespace Model3D
{
internal class FileHead
{
//基本块
public static UInt32 PRIMARY { get { return 0x4D4D; } set { } } //主块
public static UInt32 OBJECTINFO { get { return 0x3D3D; } set { } } // 网格对象的版本号
public static UInt32 VERSION { get { return 0x0002; } set { } } // .3ds文件的版本
public static UInt32 EDITKEYFRAME { get { return 0xB000; } set { } } // 所有关键帧信息的头部 // 对象的次级定义(包括对象的材质和对象)
public static UInt32 MATERIAL { get { return 0xAFFF; } set { } } // 保存纹理信息
public static UInt32 OBJECT { get { return 0x4000; } set { } } // 保存对象的面、顶点等信息 // 材质的次级定义
public static UInt32 MATNAME { get { return 0xA000; } set { } } // 保存材质名称
public static UInt32 MATDIFFUSE { get { return 0xA020; } set { } } // 对象/材质的颜色
public static UInt32 MATMAP { get { return 0xA200; } set { } } // 新材质的头部
public static UInt32 MATMAPFILE { get { return 0xA300; } set { } } // 保存纹理的文件名 public static UInt32 OBJECT_MESH { get { return 0x4100; } set { } } // 新的网格对象 // OBJECT_MESH的次级定义
public static UInt32 OBJECT_VERTICES { get { return 0x4110; } set { } } // 对象顶点
public static UInt32 OBJECT_FACES { get { return 0x4120; } set { } } // 对象的面
public static UInt32 OBJECT_MATERIAL { get { return 0x4130; } set { } } // 对象的材质
public static UInt32 OBJECT_UV { get { return 0x4140; } set { } } // 对象的UV纹理坐标 //转换字符
public static int byte2int(byte[] buffer) { return BitConverter.ToInt32(buffer, ); }
public static float byte2float(byte[] buffer) { return BitConverter.ToSingle(buffer, ); }
} // 定义3D点的类,用于保存模型中的顶点
public class CVector3
{
public float x, y, z;
}
// 定义2D点类,用于保存模型的UV纹理坐标
public class CVector2
{
public float x, y;
}
// 面的结构定义
public class tFace
{
public int[] vertIndex = new int[]; //顶点坐标
public int[] coordIndex = new int[]; //纹理坐标索引 }
// 材质信息结构体
public class tMaterialInfo
{
public String strName = ""; //纹理名称
public String strFile = ""; //如果存在纹理映射,则表示纹理文件名称
public int[] color = new int[]; //对象的RGB颜色
public int texureId; //纹理ID
public float uTile; //u重复
public float vTile; //v重复
public float uOffset; //u纹理偏移
public float vOffset; //v纹理偏移
}
//对象信息结构体
public class t3DObject
{
public int numOfVerts; // 模型中顶点的数目
public int numOfFaces; // 模型中面的数目
public int numTexVertex; // 模型中纹理坐标的数目
public int materialID; // 纹理ID
public bool bHasTexture; // 是否具有纹理映射
public String strName; // 对象的名称
public CVector3[] pVerts; // 对象的顶点
public CVector3[] pNormals; // 对象的法向量
public CVector2[] pTexVerts; // 纹理UV坐标
public tFace[] pFaces; // 对象的面信息
}
//模型信息结构体
public class t3DMdoel
{
public int numOfObjects; // 模型中对象的数目
public int numOfMaterials; // 模型中材质的数目
public List<tMaterialInfo> pMaterials = new List<tMaterialInfo>(); // 材质链表信息
public List<t3DObject> pObject = new List<t3DObject>(); // 模型中对象链表信息
}
public class tIndices
{
public UInt16 a, b, c, bVisible;
}
// 保存块信息的结构
public class tChunk
{
public UInt32 ID; //块的ID
public UInt32 length; //块的长度
public UInt32 bytesRead; //需要读的块数据的字节数
} public class CLoad3DS
{
private tChunk m_CurrentChunk = new tChunk();
private tChunk m_TempChunk = new tChunk();
private FileStream m_FilePointer; public bool Import3DS(t3DMdoel pModel, String strFileName) // 装入3ds文件到模型结构中
{
if (pModel == null)
return false;
pModel.numOfMaterials = ;
pModel.numOfObjects = ;
try
{
this.m_FilePointer = new FileStream(strFileName, FileMode.Open);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return false;
}
// 当文件打开之后,首先应该将文件最开始的数据块读出以判断是否是一个3ds文件
// 如果是3ds文件的话,第一个块ID应该是PRIMARY // 将文件的第一块读出并判断是否是3ds文件
ReadChunk(this.m_CurrentChunk); //读出块的id和块的size
// 确保是3ds文件
if (m_CurrentChunk.ID != FileHead.PRIMARY)
{
Debug.WriteLine("Unable to load PRIMARY chuck from file: " + strFileName);
return false;
}
// 现在开始读入数据,ProcessNextChunk()是一个递归函数 // 通过调用下面的递归函数,将对象读出
ProcessNextChunk(pModel, m_CurrentChunk); // 在读完整个3ds文件之后,计算顶点的法线
ComputeNormals(pModel); m_FilePointer.Close(); return true;
}
//读出3ds文件的主要部分
void ProcessNextChunk(t3DMdoel pModel, tChunk pPreviousChunk)
{
t3DObject newObject = new t3DObject();
int version = ; m_CurrentChunk = new tChunk(); // 下面每读一个新块,都要判断一下块的ID,如果该块是需要的读入的,则继续进行
// 如果是不需要读入的块,则略过 // 继续读入子块,直到达到预定的长度
while (pPreviousChunk.bytesRead < pPreviousChunk.length)
{
//读入下一个块
ReadChunk(m_CurrentChunk); //判断ID号
if (m_CurrentChunk.ID == FileHead.VERSION)
{
m_CurrentChunk.bytesRead += fread(ref version, m_CurrentChunk.length - m_CurrentChunk.bytesRead, m_FilePointer); // 如果文件版本号大于3,给出一个警告信息
if (version > )
Debug.WriteLine("Warning: This 3DS file is over version 3 so it may load incorrectly");
}
else if (m_CurrentChunk.ID == FileHead.OBJECTINFO)
{
//读入下一个块
ReadChunk(m_TempChunk); //获得网络的版本号
m_TempChunk.bytesRead += fread(ref version, m_TempChunk.length - m_TempChunk.bytesRead, m_FilePointer); //增加读入的字节数
m_CurrentChunk.bytesRead += m_TempChunk.bytesRead; //进入下一个块
ProcessNextChunk(pModel, m_CurrentChunk);
}
else if (m_CurrentChunk.ID == FileHead.MATERIAL)//材质信息
{
//材质的数目递增
pModel.numOfMaterials++;
//在纹理链表中添加一个空白纹理结构
pModel.pMaterials.Add(new tMaterialInfo());
//进入材质装入函数
ProcessNextMaterialChunk(pModel, m_CurrentChunk);
}
else if (m_CurrentChunk.ID == FileHead.OBJECT)//对象的名称
{
//对象数目递增
pModel.numOfObjects++; //添加一个新的tObject节点到对象的链表中
pModel.pObject.Add(new t3DObject()); //获得并保存对象的名称,然后增加读入的字节数
m_CurrentChunk.bytesRead += getStr(ref pModel.pObject[pModel.numOfObjects - ].strName); //进入其余对象信息的读入
ProcessNextObjectChunk(pModel, pModel.pObject[pModel.numOfObjects - ], m_CurrentChunk);
}
else
{
// 跳过关键帧块的读入,增加需要读入的字节数 EDITKEYFRAME
// 跳过所有忽略的块的内容的读入,增加需要读入的字节数
while (m_CurrentChunk.bytesRead != m_CurrentChunk.length)
{
int[] b = new int[];
m_CurrentChunk.bytesRead += fread(ref b, , m_FilePointer);
} }
//添加从最后块中读入的字节数
pPreviousChunk.bytesRead += m_CurrentChunk.bytesRead; }
//当前快设置为前面的块
m_CurrentChunk = pPreviousChunk;
}
//处理所有的文件中的对象信息
void ProcessNextObjectChunk(t3DMdoel pModel, t3DObject pObject, tChunk pPreviousChunk)
{
m_CurrentChunk = new tChunk(); //继续读入块的内容直至本子块结束
while (pPreviousChunk.bytesRead < pPreviousChunk.length)
{
ReadChunk(m_CurrentChunk); if (m_CurrentChunk.ID == FileHead.OBJECT_MESH)//正读入的是一个新块
{
//使用递归函数调用,处理该新块
ProcessNextObjectChunk(pModel, pObject, m_CurrentChunk); }
else if (m_CurrentChunk.ID == FileHead.OBJECT_VERTICES)//读入的是对象顶点
{
ReadVertices(pObject, m_CurrentChunk);
}
else if (m_CurrentChunk.ID == FileHead.OBJECT_FACES)//读入的是对象的面
{
ReadVertexIndices(pObject, m_CurrentChunk);
}
else if (m_CurrentChunk.ID == FileHead.OBJECT_MATERIAL)//读入的是对象的材质名称
{
//该块保存了对象材质的名称,可能是一个颜色,也可能是一个纹理映射。
//同时在该块中也保存了纹理对象所赋予的面 //下面读入对象的材质名称
ReadObjectMaterial(pModel, pObject, m_CurrentChunk);
}
else if (m_CurrentChunk.ID == FileHead.OBJECT_UV)//读入对象的UV纹理坐标
{
ReadUVCoordinates(pObject, m_CurrentChunk);
}
else
{
//掠过不需要读入的块
while (m_CurrentChunk.bytesRead != m_CurrentChunk.length)
{
int[] b = new int[];
m_CurrentChunk.bytesRead += fread(ref b, , m_FilePointer);
}
} //添加从最后块中读入的字节数
pPreviousChunk.bytesRead += m_CurrentChunk.bytesRead; }
//当前快设置为前面的块
m_CurrentChunk = pPreviousChunk;
}
//处理所有的材质信息
void ProcessNextMaterialChunk(t3DMdoel pModel, tChunk pPreviousChunk)
{
//给当前块分配存储空间
m_CurrentChunk = new tChunk(); //继续读入这些块,直到该子块结束
while (pPreviousChunk.bytesRead < pPreviousChunk.length)
{
//读入下一块
ReadChunk(m_CurrentChunk); //判断读入的是什么块
if (m_CurrentChunk.ID == FileHead.MATNAME)//材质的名称
{
//读入材质的名称
m_CurrentChunk.bytesRead += fread(ref pModel.pMaterials[pModel.numOfMaterials - ].strName, m_CurrentChunk.length - m_CurrentChunk.bytesRead, m_FilePointer);
}
else if (m_CurrentChunk.ID == FileHead.MATDIFFUSE)//对象的RGB颜色
{
ReadColorChunk(pModel.pMaterials[pModel.numOfMaterials - ], m_CurrentChunk);
}
else if (m_CurrentChunk.ID == FileHead.MATMAP)//纹理信息头部
{
//进入下一个材质块信息
ProcessNextMaterialChunk(pModel, m_CurrentChunk);
}
else if (m_CurrentChunk.ID == FileHead.MATMAPFILE)
{
//读入材质文件名称
m_CurrentChunk.bytesRead += fread(ref pModel.pMaterials[pModel.numOfMaterials - ].strName, m_CurrentChunk.length - m_CurrentChunk.bytesRead, m_FilePointer);
}
else
{
//掠过不需要读入的块
while (m_CurrentChunk.bytesRead != m_CurrentChunk.length)
{
int[] b = new int[];
m_CurrentChunk.bytesRead += fread(ref b, , m_FilePointer);
}
}
//添加从最后块中读入的字节数
pPreviousChunk.bytesRead += m_CurrentChunk.bytesRead;
}
//当前快设置为前面的块
m_CurrentChunk = pPreviousChunk;
}
//读下一个块
private void ReadChunk(tChunk pChunk)
{
//pChunk.bytesRead = fread(ref pChunk.ID, 2, this.m_FilePointer); Byte[] id = new Byte[];
Byte[] length = new Byte[];
pChunk.bytesRead = (UInt32)this.m_FilePointer.Read(id, , );
pChunk.bytesRead += (UInt32)this.m_FilePointer.Read(length, , );
pChunk.ID = (UInt32)(id[] * + id[]);
pChunk.length = (UInt32)(((length[] * + length[]) * + length[]) * + length[]); }
//读入RGB颜色
void ReadColorChunk(tMaterialInfo pMaterial, tChunk pChunk)
{
//读入颜色块信息
ReadChunk(m_TempChunk); //读入RGB颜色
m_TempChunk.bytesRead += fread(ref pMaterial.color, m_TempChunk.length - m_TempChunk.bytesRead, m_FilePointer); //增加读入的字节数
pChunk.bytesRead += m_TempChunk.bytesRead;
}
//读入顶点索引
void ReadVertexIndices(t3DObject pObject, tChunk pPreviousChunk)
{
int index = ;
//读入该对象中面的数目
pPreviousChunk.bytesRead += fread(ref pObject.numOfFaces, , m_FilePointer); //分配所有的储存空间,并初始化结构
pObject.pFaces = new tFace[pObject.numOfFaces]; //遍历对象中所有的面
for (int i = ; i < pObject.numOfFaces; i++)
{
pObject.pFaces[i] = new tFace();
for (int j = ; j < ; j++)
{
//读入当前对象的第一个点
pPreviousChunk.bytesRead += fread(ref index, , m_FilePointer); if (j < )
{
pObject.pFaces[i].vertIndex[j] = index;
}
}
}
}
//读入对象的UV坐标
void ReadUVCoordinates(t3DObject pObject, tChunk pPreviousChunk)
{
//为了读入对象的UV坐标,首先需要读入数量,再读入具体的数据 //读入UV坐标的数量
pPreviousChunk.bytesRead += fread(ref pObject.numTexVertex, , m_FilePointer); //初始化保存UV坐标的数组
pObject.pTexVerts = new CVector2[pObject.numTexVertex]; //读入纹理坐标
pPreviousChunk.bytesRead += fread(ref pObject.pTexVerts, pPreviousChunk.length - pPreviousChunk.bytesRead, m_FilePointer);
}
//读入对象的顶点
void ReadVertices(t3DObject pObject, tChunk pPreviousChunk)
{
//在读入实际的顶点之前,首先必须确定需要读入多少个顶点。 //读入顶点的数目
pPreviousChunk.bytesRead += fread(ref pObject.numOfVerts, , m_FilePointer); //分配顶点的储存空间,然后初始化结构体
pObject.pVerts = new CVector3[pObject.numOfVerts]; //读入顶点序列
pPreviousChunk.bytesRead += fread(ref pObject.pVerts, pPreviousChunk.length - pPreviousChunk.bytesRead, m_FilePointer); //因为3DMax的模型Z轴是指向上的,将y轴和z轴翻转——y轴和z轴交换,再把z轴反向 //遍历所有的顶点
for (int i = ; i < pObject.numOfVerts; i++)
{
float fTempY = pObject.pVerts[i].y;
pObject.pVerts[i].y = pObject.pVerts[i].z;
pObject.pVerts[i].z = - * fTempY;
}
}
//读入对象的材质名称
void ReadObjectMaterial(t3DMdoel pModel, t3DObject pObject, tChunk pPreviousChunk)
{
String strMaterial = ""; //用来保存对象的材质名称
int[] buffer = new int[]; //用来读入不需要的数据 //读入赋予当前对象的材质名称
pPreviousChunk.bytesRead += getStr(ref strMaterial); //遍历所有的纹理
for (int i = ; i < pModel.numOfMaterials; i++)
{
//如果读入的纹理与当前纹理名称匹配 if (true)//strMaterial.Equals(pModel.pMaterials[i].strName))
{
//设置材质ID
pObject.materialID = i;
//判断是否是纹理映射,如果strFile是一个长度大于1的字符串,则是纹理
if (pModel.pMaterials[i].strName.Length > ) //if (pModel.pMaterials[i].strFile.Length > 0)
{
//设置对象的纹理映射标志
pObject.bHasTexture = true;
}
break;
}
else
{
//如果该对象没有材质,则设置ID为-1
pObject.materialID = -;
}
}
pPreviousChunk.bytesRead += fread(ref buffer, pPreviousChunk.length - pPreviousChunk.bytesRead, m_FilePointer);
} //下面的这些函数主要用来计算顶点的法向量,顶点的法向量主要用来计算光照
//计算对象的法向量
private void ComputeNormals(t3DMdoel pModel)
{
CVector3 vVector1, vVector2, vNormal;
CVector3[] vPoly; vPoly = new CVector3[];
//如果模型中没有对象,则返回
if (pModel.numOfObjects <= )
return; //遍历模型中所有的对象
for (int index = ; index < pModel.numOfObjects; index++)
{
//获得当前对象
t3DObject pObject = pModel.pObject[index]; //分配需要的空间
CVector3[] pNormals = new CVector3[pObject.numOfFaces];
CVector3[] pTempNormals = new CVector3[pObject.numOfFaces];
pObject.pNormals = new CVector3[pObject.numOfVerts]; //遍历对象所有面
for (int i = ; i < pObject.numOfFaces; i++)
{
vPoly[] = pObject.pVerts[pObject.pFaces[i].vertIndex[]];
vPoly[] = pObject.pVerts[pObject.pFaces[i].vertIndex[]];
vPoly[] = pObject.pVerts[pObject.pFaces[i].vertIndex[]]; //计算面的法向量
vVector1 = Vector(vPoly[], vPoly[]);
vVector2 = Vector(vPoly[], vPoly[]); vNormal = Cross(vVector1, vVector2);
pTempNormals[i] = vNormal;
vNormal = Normalize(vNormal);
pNormals[i] = vNormal;
} //下面求顶点的法向量
CVector3 vSum = new CVector3();
vSum.x = ; vSum.y = ; vSum.z = ;
int shared = ; //遍历所有的顶点
for (int i = ; i < pObject.numOfVerts; i++)
{
for (int j = ; j < pObject.numOfFaces; j++)
{
if (pObject.pFaces[j].vertIndex[] == i ||
pObject.pFaces[j].vertIndex[] == i ||
pObject.pFaces[j].vertIndex[] == i)
{
vSum = AddVector(vSum, pTempNormals[j]);
shared++;
}
}
pObject.pNormals[i] = DivideVectorByScaler(vSum, (float)(- * shared)); //规范化最后的顶点法向量
pObject.pNormals[i] = Normalize(pObject.pNormals[i]); vSum.x = ; vSum.y = ; vSum.z = ;
shared = ;
}
}
}
//求两点决定的矢量
CVector3 Vector(CVector3 p1, CVector3 p2)
{
CVector3 v = new CVector3();
v.x = p1.x - p2.x;
v.y = p1.y - p2.y;
v.z = p1.z - p2.z;
return v;
}
//返回两个矢量的和
CVector3 AddVector(CVector3 p1, CVector3 p2)
{
CVector3 v = new CVector3();
v.x = p1.x + p2.x;
v.y = p1.y + p2.y;
v.z = p1.z + p2.z;
return v;
}
//返回矢量的缩放
CVector3 DivideVectorByScaler(CVector3 v, float Scaler)
{
CVector3 vr = new CVector3();
vr.x = v.x / Scaler;
vr.y = v.y / Scaler;
vr.z = v.z / Scaler;
return vr;
}
//返回两个矢量的叉积
CVector3 Cross(CVector3 p1, CVector3 p2)
{
CVector3 c = new CVector3();
c.x = ((p1.y * p2.z) - (p1.z * p2.y));
c.y = ((p1.z * p2.x) - (p1.x * p2.z));
c.z = ((p1.x * p2.y) - (p1.y * p2.x));
return c;
}
//规范化矢量
CVector3 Normalize(CVector3 v)
{
CVector3 n = new CVector3();
double mag = Mag(v);
n.x = v.x / (float)mag;
n.y = v.y / (float)mag;
n.z = v.z / (float)mag;
return n;
}
//矢量的模
double Mag(CVector3 v)
{
return Math.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
} //读出一个字符串
uint getStr(ref String str)
{
str = "";
char c = (char)m_FilePointer.ReadByte();
while (c != )
{
str += c;
c = (char)m_FilePointer.ReadByte();
} return (uint)(str.Length + );
}
//读出byte数组
public static uint fread(ref int[] buffer, uint length, FileStream f)
{
for (uint i = ; i < length; i++)
{
try
{
buffer[i] = f.ReadByte();
}
catch (Exception ex)
{
Debug.WriteLine(f.Name + " 读取出错");
Debug.WriteLine(ex.ToString());
return i;
}
}
return length;
}
//读出2个字节或4个字节的int
public static uint fread(ref int buffer, uint length, FileStream f)
{
if (length == )
{
Byte[] buf = new Byte[];
uint len = (UInt32)f.Read(buf, , );
buffer = (buf[] * + buf[]);
return len;
}
else if (length == )
{
Byte[] buf = new Byte[];
uint len = (UInt32)f.Read(buf, , );
buffer = (((buf[] * + buf[]) * + buf[]) * + buf[]);
return len;
}
return ;
}
//读出CVector3数组
public static uint fread(ref CVector3[] buffer, uint length, FileStream f)
{
uint l = ;
try
{
for (uint i = ; i < length / ; i++)
{
buffer[i] = new CVector3();
Byte[] bts = new Byte[];
l += (uint)f.Read(bts, , );
buffer[i].x = FileHead.byte2float(bts);
l += (uint)f.Read(bts, , );
buffer[i].y = FileHead.byte2float(bts);
l += (uint)f.Read(bts, , );
buffer[i].z = FileHead.byte2float(bts);
}
return l;
}
catch (Exception ex)
{
Debug.WriteLine(f.Name + " 读取出错");
Debug.WriteLine(ex.ToString());
return l;
}
}
//读出CVector数组
public static uint fread(ref CVector2[] buffer, uint length, FileStream f)
{
uint l = ;
try
{
for (uint i = ; i < length / ; i++)
{
buffer[i] = new CVector2();
Byte[] bts = new Byte[];
l += (uint)f.Read(bts, , );
buffer[i].x = FileHead.byte2float(bts);
l += (uint)f.Read(bts, , );
buffer[i].y = FileHead.byte2float(bts);
}
return l;
}
catch (Exception ex)
{
Debug.WriteLine(f.Name + " 读取出错");
Debug.WriteLine(ex.ToString());
return l;
}
}
//读出字符串
public static uint fread(ref String buffer, uint length, FileStream f)
{
uint l = ;
buffer = "";
try
{
for (int i = ; i < length; i++)
{
Byte[] b = new Byte[];
l += (uint)f.Read(b, , );
if (i != length - )
buffer += (char)(b[]);
} return l;
}
catch (Exception ex)
{
Debug.WriteLine(f.Name + " 读取出错");
Debug.WriteLine(ex.ToString());
return l;
}
}
} public class H3DModel
{
public const int CHANGE = ;
public const int IGNORE = ;
public const int ADD = ;
t3DMdoel model = null;
uint[] g_Texture;
CVector3 boxMin, boxMax; public H3DModel()
{
this.model = new t3DMdoel();
}
public static H3DModel FromFile(OpenGL gl, string fileName) //从文件中加载3D模型
{
H3DModel h3d = new H3DModel();
CLoad3DS load = new CLoad3DS();
load.Import3DS(h3d.model, fileName);
if (!h3d.LoadTextrue(gl))
return null;
h3d.LoadBox();
return h3d;
}
public t3DMdoel getModelData() //得到3D模型数据
{
return this.model;
} protected bool LoadTextrue(OpenGL gl)
{
this.g_Texture = new uint[];
for (int i = ; i < model.numOfMaterials; i++)
{
if (model.pMaterials[i].strName.Length > ) //if (model.pMaterials[i].strFile.Length > 0)
if (!CreateTexture(gl, ref this.g_Texture, model.pMaterials[i].strName, i)) // if (!CreateTexture(gl, ref this.g_Texture, model.pMaterials[i].strFile, i))
return false;
model.pMaterials[i].texureId = i;
}
return true;
}
protected void LoadBox()
{
boxMax = new CVector3();
boxMin = new CVector3();
boxMax.x = float.MinValue;
boxMax.y = float.MinValue;
boxMax.z = float.MinValue;
boxMin.x = float.MaxValue;
boxMin.y = float.MaxValue;
boxMin.z = float.MaxValue;
for (int i = ; i < model.numOfObjects; i++)
{
t3DObject pObject = model.pObject[i];
for (int j = ; j < pObject.numOfVerts; j++)
{
float x = pObject.pVerts[j].x;
float y = pObject.pVerts[j].y;
float z = pObject.pVerts[j].z;
if (boxMin.x > x)
boxMin.x = x;
if (boxMin.y > y)
boxMin.y = y;
if (boxMin.z > z)
boxMin.z = z;
if (boxMax.x < x)
boxMax.x = x;
if (boxMax.y < y)
boxMax.y = y;
if (boxMax.z < z)
boxMax.z = z;
}
} }
protected bool CreateTexture(OpenGL GL,ref uint[] textureArray, String strFileName, int textureID)
{
Bitmap image = null;
try
{
image = new Bitmap(strFileName);
}
catch (ArgumentException)
{
Debug.WriteLine("Could not load " + strFileName + " .");
return false;
}
if (image != null)
{
image.RotateFlip(RotateFlipType.RotateNoneFlipY);
BitmapData bitmapdata;
Rectangle rect = new Rectangle(, , image.Width, image.Height);
bitmapdata = image.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); uint[] tArray = new uint[];
GL.GenTextures(, tArray);
textureArray[textureID] = tArray[]; GL.PixelStore(OpenGL.GL_UNPACK_ALIGNMENT, ); GL.BindTexture(OpenGL.GL_TEXTURE_2D, textureArray[textureID]);
GL.Build2DMipmaps(OpenGL.GL_TEXTURE_2D, , image.Width, image.Height, OpenGL.GL_BGR, OpenGL.GL_UNSIGNED_BYTE, bitmapdata.Scan0); GL.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, OpenGL.GL_LINEAR_MIPMAP_NEAREST);
GL.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, OpenGL.GL_LINEAR_MIPMAP_LINEAR); return true;
}
return false;
} public void DrawModel(OpenGL GL,bool isLines) //画出模型
{
for (int i = ; i < this.model.numOfObjects; i++)
{
if (this.model.pObject.Count <= ) break; t3DObject pObject = this.model.pObject[i]; if (pObject.bHasTexture)
{
GL.Enable(OpenGL.GL_TEXTURE_2D);
GL.Color(1f,1f,1f);
GL.BindTexture(OpenGL.GL_TEXTURE_2D, this.g_Texture[i]); //pObject.materialID]); }
else
{
GL.Disable(OpenGL.GL_TEXTURE_2D);
GL.Color(1f, 1f, 1f); //GL.Color3ub(255, 255, 255);
} if (isLines)
GL.Begin(OpenGL.GL_LINE_STRIP);
else
GL.Begin(OpenGL.GL_TRIANGLES); for (int j = ; j < pObject.numOfFaces; j++)
{
for (int whichVertex = ; whichVertex < ; whichVertex++)
{
int index = pObject.pFaces[j].vertIndex[whichVertex]; GL.Normal(-pObject.pNormals[index].x, -pObject.pNormals[index].y, -pObject.pNormals[index].z); if (pObject.bHasTexture)
{
if (pObject.pTexVerts != null)
{
GL.TexCoord(pObject.pTexVerts[index].x, pObject.pTexVerts[index].y);
}
}
else
{ if (this.model.pMaterials.Count != && pObject.materialID >= )
{
int[] color = this.model.pMaterials[pObject.materialID].color;
GL.Color((byte)color[], (byte)color[], (byte)color[]); }
} GL.Vertex(pObject.pVerts[index].x, pObject.pVerts[index].y, pObject.pVerts[index].z); } }
GL.End();
}
} public void DrawBorder(OpenGL GL) //画出边框
{
if (this.boxMax.x != float.MinValue && this.boxMin.x != float.MaxValue)
{
GL.Color(1f,1f,1f);
float[] v = new float[];
v[] = boxMin.x;
v[] = boxMin.y;
v[] = boxMin.z;
v[] = boxMax.x;
v[] = boxMax.y;
v[] = boxMax.z; GL.Begin(OpenGL.GL_LINE_LOOP);
{
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
}
GL.End();
GL.Begin(OpenGL.GL_LINE_LOOP);
{
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
}
GL.End();
GL.Begin(OpenGL.GL_LINES);
{
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
GL.Vertex(v[], v[], v[]);
}
GL.End();
}
else
{
Debug.WriteLine("No Objects");
} }
public CVector3[] getOriginalBorder() //得到模型边框的8个点
{
CVector3[] vs = new CVector3[];
float[] v = new float[];
v[] = boxMin.x;
v[] = boxMin.y;
v[] = boxMin.z;
v[] = boxMax.x;
v[] = boxMax.y;
v[] = boxMax.z;
for (int i = ; i < ; i++)
vs[i] = new CVector3();
vs[].x = v[]; vs[].y = v[]; vs[].z = v[];
vs[].x = v[]; vs[].y = v[]; vs[].z = v[];
vs[].x = v[]; vs[].y = v[]; vs[].z = v[];
vs[].x = v[]; vs[].y = v[]; vs[].z = v[];
vs[].x = v[]; vs[].y = v[]; vs[].z = v[];
vs[].x = v[]; vs[].y = v[]; vs[].z = v[];
vs[].x = v[]; vs[].y = v[]; vs[].z = v[];
vs[].x = v[]; vs[].y = v[]; vs[].z = v[];
return vs;
} }
}

效果如下:

SharpGL学习笔记(十八) 解析3ds模型并显示

重要的说明:

1. 第848行要注意,这里的法线方向很重要,你可以尝试一下,XYZ的法线要么全部为正,要么为负。如果法线方向搞错了会怎么样?如下图

SharpGL学习笔记(十八) 解析3ds模型并显示

2. 第806行Build2DMipmaps()中,把 OpenGL.GL_BGR换 OpenGL.GL_RGB这两个参数,会影响贴图的颜色,如果贴图失真偏色,就要考虑这个参数是否有问题。

比如下图中的贴图就偏蓝色。正确的颜色应该为本节代码的运行效果图。

SharpGL学习笔记(十八) 解析3ds模型并显示

3. 本源码没能很好的处理材质,只能处理有贴图的普通材质,如果是颜色材质或者其它材质则无法处理。读者可以尝试修改,记得把改好的代码共享给笔者参考一下哦!

4. 另外,如果导出没有材质的模型,例如本文这样的组合的模型,笔者不知道如何单独为茶壶或者地板赋不同的材质,组合导入的3ds模型貌似是一个整体,不能打散为多个对象。

5. 源代码目录提供了多个3ds模型,你可以试验一下区别。

6. 如果你自己用3dsmax导出带材质的三维模型,注意把贴图和3ds文件都拷贝到bin目录。

本节源代码下载

原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/

上一篇:理解 OpenStack + Ceph (1):Ceph + OpenStack 集群部署和配置


下一篇:JS的作用域链