探索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: // ISOcamera->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: // ISOcamera->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.mzph.cn/pingmian/63046.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从 HTML 到 CSS:开启网页样式之旅(五)—— CSS盒子模型

从 HTML 到 CSS&#xff1a;开启网页样式之旅&#xff08;五&#xff09;—— CSS盒子模型 前言一、盒子模型的组成margin&#xff08;外边距&#xff09;&#xff1a;border&#xff08;边框&#xff09;&#xff1a;padding&#xff08;内边距&#xff09;&#xff1a;conten…

注解 实现原理 详解

Java 注解实现原理详解 注解&#xff08;Annotation&#xff09;是 Java 提供的一种元数据机制&#xff0c;用于为代码元素&#xff08;类、方法、字段、参数等&#xff09;添加额外的信息。注解不会直接影响程序逻辑&#xff0c;但可以通过 工具&#xff08;如编译器、运行时…

使用Feign远程调用丢失请求头问题

在使用Feign进行远程调用时&#xff0c;当前服务是能拿到请求头信息的&#xff0c;请求头包含有登录认证Cookie等重要信息&#xff0c;但是在调用远程服务时&#xff0c;远程服务却拿不到请求头信息&#xff0c;因为使用Feign进行远程调用实际上是发起新的Request请求了&#x…

SpringBoot 整合 Avro 与 Kafka 详解

SpringBoot 整合 Avro 与 Kafka 详解 在大数据处理和实时数据流场景中&#xff0c;Apache Kafka 和 Apache Avro 是两个非常重要的工具。Kafka 作为一个分布式流处理平台&#xff0c;能够高效地处理大量数据&#xff0c;而 Avro 则是一个用于序列化数据的紧凑、快速的二进制数…

2021数学分析【南昌大学】

2021 数学分析 求极限 lim ⁡ n → ∞ 1 n ( n + 1 ) ( n + 2 ) ⋯ ( n + n ) n \lim_{n \to \infty} \frac{1}{n} \sqrt [n]{(n+1)(n+2) \cdots (n+n)} n→∞lim​n1​n(n+1)(n+2)⋯(n+n) ​ lim ⁡ n → ∞ 1 n ( n + 1 ) ( n + 2 ) ⋯ ( n + n ) n = lim ⁡ n → ∞ ( n + …

vue+mars3d点击图层展示炫酷的popup弹窗

展示效果 目录 一&#xff1a;叠加数据图层到地图上&#xff0c;此时需要使用bindPopup绑定popup 二、封装自定义的popup&#xff0c;样式可以自行调整 一&#xff1a;叠加数据图层到地图上&#xff0c;此时需要使用bindPopup绑定popup 这里我根据数据不同&#xff0c;展示的…

【Python】用Python和Paramiko实现远程服务器自动化管理

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在现代IT环境中,远程服务器管理已成为运维工作的常态。随着自动化运维的需求不断增加,如何高效地管理远程服务器,提升操作的灵活性和效率…

框架建设实战2——创建frame-bom和out-bom

根frame-parent的创建一样&#xff0c;分别创建两个工程。主要管理公司内部的二方包和外部的三方包。 1.frame-bom GAV定义为&#xff1a; <groupId>com.test</groupId> <artifactId>frame-bom</artifactId> <name>frame-bom</name> …

【AIGC】如何使用高价值提示词Prompt提升ChatGPT响应质量

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | 提示词Prompt应用实例 文章目录 &#x1f4af;前言&#x1f4af;提示词英文模板&#x1f4af;提示词中文解析1. 明确需求2. 建议额外角色3. 角色确认与修改4. 逐步完善提示5. 确定参考资料6. 生成和优化提示7.…

FPGA存在的意义:为什么adc连续采样需要fpga来做,而不会直接用iic来实现

FPGA存在的意义&#xff1a;为什么adc连续采样需要fpga来做&#xff0c;而不会直接用iic来实现 原因ADS111x连续采样实现连续采样功能说明iic读取adc的数据速率 VS adc连续采样的速率adc连续采样的速率iic读取adc的数据速率结论分析 FPGA读取adc数据问题一&#xff1a;读取adc数…

千益畅行,旅游卡有些什么优势?

千益畅行共享旅游卡是一种创新的旅游服务模式&#xff0c;旨在通过整合各类旅游资源&#xff0c;为用户提供一站式的旅游解决方案。这张旅游卡支持2至6人同行&#xff0c;涵盖了接机、酒店、用餐、大巴、导游、景区门票等服务&#xff0c;用户只需自行承担往返交通费用即可享受…

LobeChat-46.6k星!顶级AI工具集,一键部署,界面美观易用,ApiSmart 是你肉身体验学习LLM 最好IDEA 工具

LobeChat LobeChat的开源&#xff0c;把AI功能集合到一起&#xff0c;真的太爽了。 我第一次发现LobeChat的时候&#xff0c;就是看到那炫酷的页面&#xff0c;这么强的前端真的是在秀肌肉啊&#xff01; 看下它的官网&#xff0c;整个网站的动效简直闪瞎我&#xff01; GitH…

[报错] Error: PostCSS plugin autoprefixer requires PostCSS 8 问题解决办法

报错&#xff1a;Error: PostCSS plugin autoprefixer requires PostCSS 8 原因&#xff1a;autoprefixer版本过高 解决方案&#xff1a; 降低autoprefixer版本 执行&#xff1a;npm i postcss-loader autoprefixer8.0.0 参考&#xff1a; Error: PostCSS plugin autoprefix…

基于STM32的Wi-Fi无人机项目

引言 随着无人机技术的快速发展&#xff0c;基于微控制器的DIY无人机变得越来越流行。本项目将介绍如何使用STM32微控制器制作一架简单的Wi-Fi无人机。通过本项目&#xff0c;您将了解到无人机的基本组成部分&#xff0c;如何进行硬件连接&#xff0c;代码编写&#xff0c;以及…

IDEA注释格式、匹配补全调整

1.注释格式调整 目前重新捡起一部分Java&#xff0c;写代码时候发现注释快捷键总是放在第一列&#xff0c;看起来很难受&#xff0c;故寻找方法如下&#xff1a; 分别点击 编辑器-代码样式-Java 修改注释代码选项如下 2.大小写匹配补全问题 还发现在写代码过程中&#xff0c…

麒麟 V10(ky10.x86_64)无网环境下 openssl - 3.2.2 与 openssh - 9.8p1 升级【最全教程】

目录 背景 安装包下载 上传解压安装包 安装zlib 安装OpenSSL 安装OpenSSH 验证 背景 近期&#xff0c;项目上线已进入倒计时阶段&#xff0c;然而在至关重要的安全检查环节中&#xff0c;却惊现现有的 OpenSSH 存在一系列令人担忧的漏洞&#xff1a; OpenSSH 资源管理错…

Android EventBus最全面试题及参考答案

目录 什么是 EventBus? 请解释 EventBus 是什么,以及它的工作原理。 简述 EventBus 的工作原理。 EventBus 的主要组成部分有哪些? EventBus 是如何实现发布订阅模式的? EventBus 与观察者模式有什么区别? EventBus 的优点在哪、不用 EventBus 怎么解决? EventBu…

高级架构二 Git基础到高级

一 Git仓库的基本概念和流程 什么是版本库&#xff1f;版本库又名仓库&#xff0c;英文名repository,你可以简单的理解一个目录&#xff0c;这个目录里面的所有文件都可以被Git管理起来&#xff0c;每个文件的修改&#xff0c;删除&#xff0c;Git都能跟踪&#xff0c;以便任何…

从excel数据导入到sqlsever遇到的问题

1、格式问题时间格式&#xff0c;excel中将日期列改为日期未生效&#xff0c;改完后&#xff0c;必须手动单击这个单元格才能生效&#xff0c;那不可能一个一个去双击。解决方案如下 2、导入之后表字段格式问题&#xff0c;数据类型的用navicat导入之后默认是nvarchar类型的&a…

21天掌握javaweb--->第4天:MyBatis-Plus基础与进阶

21天掌握JavaWeb--->第4天&#xff1a;MyBatis-Plus基础与进阶 MyBatis-Plus简介 MyBatis-Plus&#xff08;简称MP&#xff09;是一个MyBatis的增强工具&#xff0c;在MyBatis的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。它具有以下核心优势&#xff…