修改后的 lib3ds 阅读器,用于 .3ds 格式






4.78/5 (15投票s)
在本文中,
引言
3DStudio 中存储 3D 模型和场景的格式差异很大。然而,它们中的大多数都与 3DStudio 的引擎绑定。只有少数以 OpenGL 中简单渲染的形式呈现数据。对于我的项目,我选择了 3DS 格式。我找到了一个非常强大的 3ds 格式阅读器代码 - lib3ds,在 http://www.lib3ds.org/ 网站上。尽管如此,该网站上的代码是用 C 语言编写的。因此,在 lib3ds 的原始代码中有很多静态函数,它们有复杂的链接,并且无法使用面向对象语言的功能。对于我的 C++ 项目,我将原始的 lib3ds 2.0.0-rc1 从 C 语言重写为 C++ 结构,并使用了类和接口。因此,我编写了代码,将读取 3ds 数据与使用数据进行渲染分离开来。这个决定可以用于 COM 类型的技术,并提供简单的数据管理。
为了测试代码,我制作了一个特殊的程序 3dsViewer,在这里可以找到 下载 3dsViewer_exe.zip
背景
为了使用接口结构,我制作了 10 个文件,描述了接口中使用的数据结构。接口包含在独立文件 ILib3ds_stage.h 中,如基类,并包含所有数据和虚拟函数。其他继承接口的类只包含函数。因此,顶级继承类的大小等于接口的大小,足以调用接口的删除以释放为所有数据选择的所有内存。此外,不同大小的数据,例如顶点和面,具有非常简单的删除工具。在原始代码中,有一个复杂的数据管理和数据删除算法。我将它们更改为简单的类:QString, QVector, QList, QMap - 相当于 Qt 框架的 std String, Vector, List, Map。因此,具有非固定大小的数据被包装在栈对象中并自动删除。
使用代码
所有代码都包含在 19 个类中,这些类具有继承的层次结构。但是,为了使用代码,只需使用类 Lib3ds_loader
的静态函数 makeILib3ds_stage,并传入 3ds 文件的路径即可。该函数返回接口 ILib3ds_stage
,其中包含 3D 模型的数据。
class Lib3ds_loader // Class of loading
{
public:
Lib3ds_loader();
static ILib3ds_stage *makeILib3ds_stage(QString filename); // Static
// function of making interface of 3ds data
};
此代码是使用接口 ILib3ds_stage
的一个例子,使用指针 Istage
的形式。当 3ds 数据中没有相机时,在检查 Istage->cameras.isEmpty()
后,在列表 Istage->cameras
中添加相机结构。对光源列表也执行相同的操作。
bool OpenGLWidget::LoadModel(QString filename)
{
if(Istage) delete Istage; // Deleting data
Istage = Lib3ds_loader::makeILib3ds_stage(filename); // Getting interface on data
if(Istage != NULL)
/*Checking of existence of inteface of 3ds data*/
{
Istage->lib3ds_stage_eval(1.0f); // Setting first frame animation
Istage->lib3ds_stage_bounding_box_of_nodes(1, 0, 0, bmin, bmax, NULL); //
// finding of borders in node structure
// Computing of size of box which surround of scene
sx = bmax[0] - bmin[0];
sy = bmax[1] - bmin[1];
sz = bmax[2] - bmin[2];
boxsize = MAX(sx, sy); boxsize = MAX(boxsize, sz);
cx = (bmin[0] + bmax[0])/2; // Computing of centres of box
cy = (bmin[1] + bmax[1])/2;
cz = (bmin[2] + bmax[2])/2;
if(Istage->cameras.isEmpty()) // Checking of list of cameras in 3ds data
{
// If thre is not any camera in 3ds data than created camera
Lib3dsCamera camera;
camera.name = "Camera_Z"; // Naming new camera
memset(&camera.setting, 0, sizeof(Lib3dsCamera_setting));
// setting of angle of view
camera.setting.fov = 45;
// Setting point of target of camera in center of scene
camera.setting.target[0] = cx;
camera.setting.target[1] = cy;
camera.setting.target[2] = cz;
// Setting position camera out of scene
camera.setting.position[0] = cx;
camera.setting.position[1] = cy;
camera.setting.position[2] = bmax[2] + 2 * MAX(sx,sy);
// Setting near and far borders of camera
camera.setting.near_range = ( camera.setting.position[2] - bmax[2] ) * .5;
camera.setting.far_range = ( camera.setting.position[2] - bmin[2] ) * 2;
// adding camera into list of cameras of 3ds data
Istage->cameras[camera.name] = camera;
}
if (Istage->lights.isEmpty())// Checking of list of lights in 3ds data
// If thre is not any light source in 3ds data than created light source
{
Lib3dsLight light;
memset(&light.setting, 0, sizeof(Lib3dsLight_setting));
light.name = "light0"; // Named light source
light.setting.spot_light = 0; // Setting of 3DStudio
light.setting.see_cone = 0;// Setting of 3DStudio
// Setting of color of light
light.setting.color[0] = light.setting.color[1] = light.setting.color[2] = .6;
// Setting position of light source out of scene
light.setting.position[0] = cx + boxsize * .75;
light.setting.position[1] = cy - boxsize * 1.;
light.setting.position[2] = cz + boxsize * 1.5;
light.setting.position[3] = 0.;
light.setting.outer_range = 100;// Setting of 3DStudio
light.setting.inner_range = 10;// Setting of 3DStudio
light.setting.multiplier = 1;// Setting of 3DStudio
Istage->lights[light.name] = light; // adding light
}
Istage->lib3ds_stage_eval(0.0f); // setting in start position
}
// If there is 3ds data than return true;
return (Istage != NULL);
}
下一个代码通过解析 3ds 的节点来渲染 3ds 数据。解析是对根节点及其子节点进行的。函数 void OpenGLWidget::updateAnimation()
执行顶点位置的动画。
QList<lib3dsnode>::iterator p = Istage->nodes.begin();
while(p!=Istage->nodes.end())
// Enumiration of root nodes in 3ds data which do not have parents
{
render_node(&(*p)); // Function of rendering of node
++p;
}
void OpenGLWidget::render_node(Lib3dsNode *node)
{
{
QList<lib3dsnode>::iterator p = node->children.begin();
while(p!=node->children.end())
// Enumiration of children nodes
{
render_node(&(*p)); // Rendering of children nodes
++p;
}
}
if (node->setting.type == LIB3DS_NODE_MESH_INSTANCE)
/*Checking type of node. if it equals flag LIB3DS_NODE_MESH_INSTANCE then
execute rendering of mesh's vertexes
{
Lib3dsMesh *mesh = NULL; // Pointer on mesh
if(node->name == "$$$DUMMY")
/*Checking name on valid*/
{
return;
}
if(Istage->meshes.contains(node->name))
/*Checking of name node in list of meses in 3ds model
{
/*Getting addres on mesh*/
mesh = &Istage->meshes[node->name];
}
if(mesh != NULL)
{
/*Checking the binding of mesh with list rendering of OpenGL*/
if (!mesh->setting.user_id)
{
// Creating of binding with mesh
mesh->setting.user_id=glGenLists(1);
glNewList(mesh->setting.user_id, GL_COMPILE);
// Iterator of trangles primitives in mesh
QVector<lib3dsface>::iterator p = mesh->faces.begin();
// Setting movement mesh in initial position
float M[4][4];
Istage->lib3ds_matrix_copy(M, mesh->setting.matrix);
Istage->lib3ds_matrix_inv(M);
glMultMatrixf(&M[0][0]);
// Allocated memory for normals for smooth mesh
Lib3dsVector *normalL = new Lib3dsVector[3*mesh->faces.size()];
// Calculation of smooth normals for vertexes
Istage->lib3ds_mesh_calculate_vertex_normals(mesh, normalL);
unsigned int f = 0;
// Enumiration of trangles primitives in mesh
while (p != mesh->faces.end())
{
Lib3dsMaterial *mat = NULL; // Material of triangle primitive
Lib3dsMaterial *oldmat = NULL;//Material of old triangle primitive
// Finding material in list of materials of 3ds data
if(Istage->materials.contains((*p).material))
{
mat = &Istage->materials[(*p).material];
}
if( mat)
{
// If material is founded than reading of data
if (mat != oldmat)
{
if( mat->setting.two_sided )
glDisable(GL_CULL_FACE);
else
glEnable(GL_CULL_FACE);
// Setting colors for lightning
glMaterialfv(GL_FRONT, GL_AMBIENT, mat->setting.ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat->setting.diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat->setting.specular);
glMaterialf(GL_FRONT, GL_SHININESS, pow(2, 10.0*
mat->setting.shininess));
}
oldmat = mat;
}
else
{
// Setting default colors in cases of there is not material
static const GLfloat a[]={0.7, 0.7, 0.7, 1.0};
static const GLfloat d[]={0.7, 0.7, 0.7, 1.0};
static const GLfloat s[]={1.0, 1.0, 1.0, 1.0};
glMaterialfv(GL_FRONT, GL_AMBIENT, a);
glMaterialfv(GL_FRONT, GL_DIFFUSE, d);
glMaterialfv(GL_FRONT, GL_SPECULAR, s);
glMaterialf(GL_FRONT, GL_SHININESS, pow(2, 10.0*0.5));
}
glBegin(GL_TRIANGLES);// Start drawing primitive
glNormal3fv((*p).normal);
for (int i=0; i<3; ++i)
{
// Setting normal for vertex
glNormal3fv(normalL[3*f+i]);
// Drawing of vertex
glVertex3f(mesh->vertices[(*p).index[i]].x(),
mesh->vertices[(*p).index[i]].y(),
mesh->vertices[(*p).index[i]].z());
}
glEnd();
++p;
f++;
}
delete []normalL;
glEndList();
}
//Rendering of list of calls of OpenGL
if (mesh->setting.user_id)
{
glPushMatrix();
//Multiplication of mesh position with current matrix
glMultMatrixf(&node->setting.matrix[0][0]);
//Movement mesh in point rotation
glTranslatef(-node->MeshInstanceNode.pivot[0],
-node->MeshInstanceNode.pivot[1],
-node->MeshInstanceNode.pivot[2]);
// Calling of list compiled commands
glCallList(mesh->setting.user_id);
glPopMatrix();
}
}
}
else
{
// There can be executed animation of other objects such as camera or light source
}
}
void OpenGLWidget::updateAnimation()
{
if(Istage)
{
current_frame+=1.0;
// Current frame cannot be more maximum frames of 3ds model
if (current_frame>Istage->frames)
{
current_frame=0.0;
}
// Function of execution computing matrexes nodes in current frame
Istage->lib3ds_stage_eval(current_frame);
this->update();
}
}
此代码是用 Qt 框架编写的,但所有特定类,如 QString
、QList
或 QVector3D
,都可以用几乎所有其他平台和语言的特定类重写。
关注点
我花了很多时间阅读原始的 lib3ds 代码。为了更清楚的理解,我注释了类中的几乎所有函数。我还注释了读取数据时几乎所有标志的应用。我希望我的决定可以帮助您理解 3ds 格式的结构。
更新 Qt5.3.2
我更新了 lib3ds 和 Qt 查看器的源代码,用于 Qt5.3.2 - 下载 3dsViewer.zip,用于 Qt5.3.2 的查看器和 lib3ds 源代码 - 115 KB。 用于测试的 3ds 模型 - 下载 UEXTREM.zip。