当前位置: 首页 > article >正文

探索3D世界:使用 lib3ds 读取和解析 3DS 文件

在3D图形开发中,读取和解析3DS文件是创建和渲染3D场景的第一步。3DS(3D Studio)文件格式是一种广泛使用的3D模型文件格式,它包含了多种类型的数据,用于描述3D场景中的物体、材质、相机、灯光和动画等。lib3ds 是一个开源的C库,专门用于读取和解析 .3ds 文件。本文将详细介绍如何使用 lib3ds 库来读取3DS文件,并理解其组成部分。


一、3DS文件的组成部分

3DS文件包含以下主要组成部分:

  1. 物体(Object)

    • 3DS文件可以包含多个物体,每个物体都是一个复杂的3D模型。
    • 物体包含顶点数据、三角形索引数据、纹理坐标数据和材质列表数据等。
    • 每种数据由一个特定的ID标识,以区分不同类型的数据。
  2. 材质(Material)

    • 材质定义了物体的外观属性,如颜色、透明度、纹理等。
    • 3DS文件可以包含多个材质,也可以不包含材质信息。
  3. 相机(Camera)

    • 相机数据描述了场景中的观察点,包括相机的位置、朝向和焦距等信息。
  4. 灯光(Light)

    • 灯光数据定义了场景中的光源,包括灯光的位置、类型和颜色等信息。
    • 灯光可以是泛光灯、聚光灯等不同类型的光源。
  5. 关键帧(Keyframe)

    • 关键帧用于描述动画,包含了每个物体在每一关键帧处的变换矩阵。
    • 通过在绘制每一帧动画前给物体应用相应的变换矩阵,可以实现动画效果。
    • 需要注意的是,3DS文件只能描述刚体动画,不能描述柔体动画。

二、使用 lib3ds 库读取 3DS 文件

lib3ds 库提供了一个简单的API来读取和解析3DS文件。以下是使用 lib3ds 库读取3DS文件的基本步骤:

1. 包含头文件

在你的代码中包含 lib3ds 的头文件:

#include <lib3ds/lib3ds.h>
2. 加载 3DS 文件

使用 lib3ds_file_load 函数加载3DS文件。这个函数会返回一个 Lib3dsFile 结构体指针,它包含了3DS文件的所有信息。

Lib3dsFile *file = lib3ds_file_load("path/to/your/file.3ds");
if (!file) {
    fprintf(stderr, "Failed to load 3DS file.\n");
    return 1;
}
3. 获取模型的包围盒

为了计算场景的中心点和大小,我们需要得到模型的最小边界和最大边界。这可以通过调用 lib3ds_file_bounding_box_of_nodes 函数来实现,它接收一个 Lib3dsFile 指针以及几个布尔参数来指定是否考虑节点、网格、相机和灯光。然后,它会填充两个 Lib3dsVector 类型的变量 bminbmax,分别表示包围盒的最小和最大坐标。

Lib3dsVector bmin, bmax;
lib3ds_file_bounding_box_of_nodes(file, LIB3DS_TRUE, LIB3DS_FALSE, LIB3DS_FALSE, bmin, bmax);

float sx = bmax[0] - bmin[0];
float sy = bmax[1] - bmin[1];
float sz = bmax[2] - bmin[2];
// 这里 MAX 是一个宏定义,用来找出两个值中较大的那个。cx, cy, cz 分别是包围盒的中心点坐标,而 size 则是包围盒的最大尺寸。
float size = fmaxf(sx, fmaxf(sy, sz));
float cx = (bmin[0] + bmax[0]) / 2;
float cy = (bmin[1] + bmax[1]) / 2;
float cz = (bmin[2] + bmax[2]) / 2;
4. 添加默认相机

有时 3DS 文件中可能没有定义任何相机,或者我们希望为场景添加额外的视角。为此,我们可以创建多个默认相机,每个相机都位于不同的方向,以便从不同角度查看模型。以下是添加四个标准相机的代码:X 轴方向、Y 轴方向、Z 轴方向和一个等距视图(ISO)。

if (!file->cameras) {
    // 定义一个辅助函数来简化相机创建过程
    auto addCamera = [this, &file, cx, cy, cz, size](const char* name, int axis, float offset) {
        Lib3dsCamera *camera = lib3ds_camera_new(name);
        camera->target[0] = cx;
        camera->target[1] = cy;
        camera->target[2] = cz;
        memcpy(camera->position, camera->target, sizeof(camera->position));

        switch(axis) {
            case 0: // X轴
                camera->position[0] = bmax[0] + offset * fmaxf(sy, sz);
                break;
            case 1: // Y轴
                camera->position[1] = bmin[1] - offset * fmaxf(sx, sz);
                break;
            case 2: // Z轴
                camera->position[2] = bmax[2] + offset * fmaxf(sx, sy);
                break;
            default: // ISO
                camera->position[0] = bmax[0] + .75f * size;
                camera->position[1] = bmin[1] - .75f * size;
                camera->position[2] = bmax[2] + .75f * size;
        }

        camera->near_range = (camera->position[axis % 3] - (axis == 2 ? bmax[axis % 3] : bmin[axis % 3])) * .5f;
        camera->far_range = (camera->position[axis % 3] - (axis == 2 ? bmin[axis % 3] : bmax[axis % 3])) * 2.0f;
        lib3ds_file_insert_camera(file, camera);
    };

    // 添加四个相机
    addCamera("Camera_X", 0, 1.5f);
    addCamera("Camera_Y", 1, 1.5f);
    addCamera("Camera_Z", 2, 1.5f);
    addCamera("Camera_ISO", 3, .75f);
}
5. 添加默认灯光

有时3DS文件中可能没有定义任何灯光,或者我们需要为场景添加额外的光源以改善渲染效果。以下是如何添加默认灯光的示例代码:

void addDefaultLights(Lib3dsFile *file, float cx, float cy, float cz, float size) {
    // 定义一个辅助函数来简化灯光创建过程
    auto addLight = [file, cx, cy, cz, size](const char* name, int type, float x, float y, float z) {
        Lib3dsLight *light = lib3ds_light_new(name);
        light->type = type;
        light->position[0] = x;
        light->position[1] = y;
        light->position[2] = z;

        // 设置灯光的颜色,默认为白色
        light->color[0] = 1.0f;
        light->color[1] = 1.0f;
        light->color[2] = 1.0f;

        // 如果是聚光灯,设置目标点
        if (type == LIB3DS_LIGHT_SPOT) {
            light->target[0] = cx;
            light->target[1] = cy;
            light->target[2] = cz;
        }

        // 插入到文件的灯光链表中
        lib3ds_file_insert_light(file, light);
    };

    // 添加一个泛光灯
    addLight("Default_Omni_Light", LIB3DS_LIGHT_OMNI, cx, cy + size, cz);

    // 添加一个聚光灯
    addLight("Default_Spot_Light", LIB3DS_LIGHT_SPOT, cx + size, cy, cz);

    // 可以根据需要添加更多的灯光
}

在加载完3DS文件之后,你可以检查是否存在灯光,并在必要时调用 addDefaultLights 函数来添加默认灯光:

if (!file->lights) {
    addDefaultLights(file, cx, cy, cz, size);
}
6. 遍历 3DS 文件

一旦加载了3DS文件,你可以遍历它的内容。lib3ds 库将不同类型的数据组织成以 file 为根节点的树状结构,而同类数据以链表的形式存放。你可以使用以下方式遍历3DS文件中的数据:

  • 遍历物体(Object)file->objects
  • 遍历材质(Material)file->materials
  • 遍历相机(Camera)file->cameras
  • 遍历灯光(Light)file->lights
  • 遍历关键帧(Keyframe):这通常涉及到遍历物体并检查每个物体的关键帧数据。

例如,遍历物体的代码如下所示:

Lib3dsObject *object;
for (object = file->objects; object != NULL; object = object->next) {
    // 处理物体数据,例如打印物体名称或几何形状
    printf("Object name: %s\n", object->name);

    // 这里可以添加更多代码来处理物体的其他属性,例如顶点数据、材质等。
}
7. 释放资源

完成处理后,记得释放 lib3ds 库分配的资源。使用 lib3ds_file_free 函数来释放 Lib3dsFile 结构体。

lib3ds_file_free(file);

三、注意事项

  1. 错误处理

    • 在实际应用中,你应该添加更多的错误处理代码来确保程序的健壮性。例如,检查 lib3ds_file_load 的返回值,并在失败时打印更详细的错误信息。
  2. 内存管理

    • lib3ds 库会为你分配内存来存储3DS文件的内容。确保在完成处理后调用 lib3ds_file_free 来释放这些资源,以避免内存泄漏。
  3. 文件路径

    • 确保你提供的3DS文件路径是正确的,并且文件具有读取权限。
  4. lib3ds 版本

    • 不同版本的 lib3ds 库可能有不同的API和函数签名。确保你使用的代码与你的 lib3ds 库版本兼容。

四、完整示例代码

以下是一个完整的示例代码,展示了如何使用 lib3ds 库读取3DS文件并打印出其中的物体信息:

#include <stdio.h>
#include <lib3ds/lib3ds.h>

void traverseObjects(Lib3dsFile *file) {
    Lib3dsObject *object;
    for (object = file->objects; object != NULL; object = object->next) {
        printf("Object name: %s\n", object->name);

        // 打印顶点数据
        if (object->mesh) {
            Lib3dsMesh *mesh = object->mesh;
            printf("  Vertices: %d\n", mesh->points);
            for (int i = 0; i < mesh->points; ++i) {
                printf("    Vertex %d: (%f, %f, %f)\n",
                       i, mesh->pointL[i].v[0], mesh->pointL[i].v[1], mesh->pointL[i].v[2]);
            }

            // 打印面片数据
            printf("  Faces: %d\n", mesh->faces);
            for (int i = 0; i < mesh->faces; ++i) {
                Lib3dsFace *face = &mesh->faceL[i];
                printf("    Face %d: %d, %d, %d\n",
                       i, face->points[0], face->points[1], face->points[2]);
            }
        }
    }
}

int main() {
    const char *filename = "path/to/your/file.3ds";
    Lib3dsFile *file = lib3ds_file_load(filename);
    if (!file) {
        fprintf(stderr, "Failed to load 3DS file: %s\n", filename);
        return 1;
    }

    printf("Loaded 3DS file: %s\n", filename);

    // 获取模型的包围盒
    Lib3dsVector bmin, bmax;
    lib3ds_file_bounding_box_of_nodes(file, LIB3DS_TRUE, LIB3DS_FALSE, LIB3DS_FALSE, bmin, bmax);

    float sx = bmax[0] - bmin[0];
    float sy = bmax[1] - bmin[1];
    float sz = bmax[2] - bmin[2];
    float size = fmaxf(sx, fmaxf(sy, sz));
    float cx = (bmin[0] + bmax[0]) / 2;
    float cy = (bmin[1] + bmax[1]) / 2;
    float cz = (bmin[2] + bmax[2]) / 2;

    // 添加默认相机
    if (!file->cameras) {
        // 定义一个辅助函数来简化相机创建过程
        auto addCamera = [file, cx, cy, cz, size](const char* name, int axis, float offset) {
            Lib3dsCamera *camera = lib3ds_camera_new(name);
            camera->target[0] = cx;
            camera->target[1] = cy;
            camera->target[2] = cz;
            memcpy(camera->position, camera->target, sizeof(camera->position));

            switch(axis) {
                case 0: // X轴
                    camera->position[0] = bmax[0] + offset * fmaxf(sy, sz);
                    break;
                case 1: // Y轴
                    camera->position[1] = bmin[1] - offset * fmaxf(sx, sz);
                    break;
                case 2: // Z轴
                    camera->position[2] = bmax[2] + offset * fmaxf(sx, sy);
                    break;
                default: // ISO
                    camera->position[0] = bmax[0] + .75f * size;
                    camera->position[1] = bmin[1] - .75f * size;
                    camera->position[2] = bmax[2] + .75f * size;
            }

            camera->near_range = (camera->position[axis % 3] - (axis == 2 ? bmax[axis % 3] : bmin[axis % 3])) * .5f;
            camera->far_range = (camera->position[axis % 3] - (axis == 2 ? bmin[axis % 3] : bmax[axis % 3])) * 2.0f;
            lib3ds_file_insert_camera(file, camera);
        };

        // 添加四个相机
        addCamera("Camera_X", 0, 1.5f);
        addCamera("Camera_Y", 1, 1.5f);
        addCamera("Camera_Z", 2, 1.5f);
        addCamera("Camera_ISO", 3, .75f);
    }

    // 添加默认灯光
    if (!file->lights) {
        addDefaultLights(file, cx, cy, cz, size);
    }

    // 遍历并打印物体信息
    traverseObjects(file);

    // 释放资源
    lib3ds_file_free(file);

    return 0;
}

http://www.kler.cn/a/418887.html

相关文章:

  • go-学习
  • SQL面试题——抖音SQL面试题 最近一笔有效订单
  • 本地学习axios源码-如何在本地打印axios里面的信息
  • 在WSL 2 (Ubuntu 22.04)安装Docker Ce 启动错误解决
  • [极客大挑战 2019]PHP--详细解析
  • 排序学习整理(2)
  • CentOS使用chrony服务进行时间同步源设置脚本
  • CSS3网站
  • 欧拉函数——acwing
  • 挑战用React封装100个组件【005】
  • 【linux】(23)对象存储服务-MinIo
  • Linux 僵尸进程和孤儿进程, 进程优先级
  • Android 12.0新增自定义HIDL问题记录
  • 内网穿透步骤
  • Spring Data JPA(二) 高级进阶
  • linux——进程间通信及管道的应用场景
  • 蓝桥杯经验分享
  • 医院分诊管理系统|Java|SSM|VUE| 前后端分离
  • 2. STM32_中断
  • 深入理解 PyTorch .pth 文件和 Python pickle 模块:功能、应用及实际示例
  • 前端学习week8——vue.js
  • 支持向量机算法:原理、实现与应用
  • LeetCode题解:34.在排序数组中查找元素的第一个和最后一个位置【Python题解超详细,二分查找法、index法】,知识拓展:index方法详解
  • [MySQL]流程控制语句
  • SpringCloud书单推荐
  • 深度学习常见数据集处理方法