Appearance
obj文件查看、解析的技术实现
在 Web 3D 领域,如果说 STL 是 3D 打印领域的硬通货,那么 OBJ (Wavefront OBJ) 就是三维建模、游戏资产和 CG 动画里最经典的交换格式之一。
相比于只能存几何骨架的 STL,OBJ 格式不仅能描述复杂的几何体,还能保存纹理贴图坐标 (UV)、法线向量,并且能配合伴生的 .mtl (Material Template Library) 材质文件,呈现出逼真的颜色、光泽度与贴图效果。
要在 Web 端手写或调用渲染库实现 OBJ 文件的解析与在线查看,我们需要搞懂它的存储原理和解析步骤。
一、 OBJ 格式的本质与底层结构
OBJ 是一种基于纯文本的 3D 描述格式。虽然是文本,但它的设计比 ASCII 格式的 STL 聪明得多:它引入了“顶点复用”与“索引结构”。
在 STL 中,每个三角形独立存 3 个点,相邻三角形的重合顶点会被重复写入多次;而在 OBJ 中,所有顶点只存一次,通过 f (Face) 面标签引用顶点的索引编号。
一个典型的 OBJ 文件结构如下:
text
# 1. 声明伴生的材质库文件
mtllib model.mtl
# 2. 几何顶点 (v x y z)
v -1.000000 1.000000 1.000000
v 1.000000 1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 1.000000 1.000000
# 3. 纹理贴图坐标 (vt u v)
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
# 4. 顶点法线 (vn x y z)
vn 0.000000 1.000000 0.000000
# 5. 指定当前面使用的材质名
usemtl Material_Wood
# 6. 面索引定义 (f v/vt/vn)
f 1/1/1 2/2/1 3/3/1
f 1/1/1 4/4/1 2/2/1关键标签解析:
v x y z:顶点三维坐标;vt u v:二维纹理贴图坐标 (UV),范围在 0.0 ~ 1.0 之间;vn x y z:法线向量,用于计算渲染光照;f v/vt/vn:面(Face)。格式为顶点索引/纹理坐标索引/法线索引。- ⚠️ 特别注意 1:OBJ 中的索引是从 1 开始计数的(1-based),而不是 JS 数组常用的从 0 开始。
- ⚠️ 特别注意 2:面不仅支持三角形(3 个顶点),还支持四边形(4 个顶点)甚至多边形(N 个顶点)。
二、 如何在 Web 侧解析 OBJ 文本流
解析 OBJ 的核心思路是:逐行读取文本,收集点/法线/UV 原始池,再根据 f 面索引构造 WebGL 所需的顶点缓冲区。
1. 逐行正则与分割
可以通过 TextDecoder 把文件 ArrayBuffer 解码成字符串,用按行 split 遍历:
javascript
function parseOBJ(text) {
const lines = text.split('\n');
const rawVertices = [];
const rawUVs = [];
const rawNormals = [];
const finalPositions = [];
const finalUVs = [];
const finalNormals = [];
for (let line of lines) {
line = line.trim();
if (line.startsWith('#') || !line) continue; // 跳过注释和空行
const parts = line.split(/\s+/);
const type = parts[0];
if (type === 'v') {
rawVertices.push([parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3])]);
} else if (type === 'vt') {
rawUVs.push([parseFloat(parts[1]), parseFloat(parts[2])]);
} else if (type === 'vn') {
rawNormals.push([parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3])]);
} else if (type === 'f') {
// 提取面顶点索引 (格式如 1/1/1)
const faceVertices = parts.slice(1);
// 如果是 4 顶点四边形,进行三角化切分 (Fan Triangulation: 0-1-2 和 0-2-3)
const triangles = [];
if (faceVertices.length === 3) {
triangles.push(faceVertices);
} else if (faceVertices.length === 4) {
triangles.push([faceVertices[0], faceVertices[1], faceVertices[2]]);
triangles.push([faceVertices[0], faceVertices[2], faceVertices[3]]);
}
for (const tri of triangles) {
for (const vertStr of tri) {
const [vIdx, vtIdx, vnIdx] = vertStr.split('/').map(idx => parseInt(idx, 10));
// 注意:OBJ 索引是从 1 开始,需减 1 转为数组下标
if (vIdx) finalPositions.push(...rawVertices[vIdx - 1]);
if (vtIdx) finalUVs.push(...rawUVs[vtIdx - 1]);
if (vnIdx) finalNormals.push(...rawNormals[vnIdx - 1]);
}
}
}
}
return {
positions: new Float32Array(finalPositions),
uvs: new Float32Array(finalUVs),
normals: new Float32Array(finalNormals)
};
}三、 Web 端渲染与技术选型
如果只看纯几何体,解析后的点数据传给 WebGL BufferGeometry 即可。但真实的 OBJ 通常带有 .mtl 材质和 .jpg/.png 纹理贴图,因此推荐使用 Three.js 的 OBJLoader + MTLLoader。
落地实现方案 (Three.js)
javascript
import * as THREE from 'three';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js';
// 1. 先加载 .mtl 材质文件
const mtlLoader = new MTLLoader();
mtlLoader.load('model.mtl', (materials) => {
materials.preload();
// 2. 将材质挂载给 OBJLoader
const objLoader = new OBJLoader();
objLoader.setMaterials(materials);
// 3. 加载 OBJ 模型
objLoader.load('model.obj', (object) => {
// 自动遍历所有 Mesh 并开启双面材质渲染
object.traverse((child) => {
if (child.isMesh) {
child.material.side = THREE.DoubleSide; // 避免反面穿透
}
});
scene.add(object);
});
});四、 关键踩坑点与解决办法
在实际项目中加载 OBJ 文件,经常遇到模型黑屏、材质丢失或错位的问题:
- MTL / 贴图相对路径错误: 在加载伴生
.mtl或贴图时,如果材质文件里写的是绝对路径(如C:/Users/Design/texture.png),前端必然报错。建议使用mtlLoader.setPath('textures/')显式重定位贴图相对路径。 - 多边形(Polygon)未三角化导致的画面破洞: 某些 3D 软件导出的 OBJ 包含了大于 4 个顶点的多边形。手写解析器时,必须做 Ear Clipping 或 Fan 三角化算法 转换为三角形,否则在 GPU 绘制时会导致面破损。
- 模型居中与视角自适应: 和 STL 一样,OBJ 加载后应使用
THREE.Box3().setFromObject(object)计算包围盒尺寸并调整 Camera,防止模型偏离视口。
总结来说,OBJ 格式通过顶点索引复用大幅压缩了几何数据量,并通过 MTL 材质文件扩展了纹理贴图能力。在 Web 端使用 Three.js 的 OBJLoader + MTLLoader 是最成熟落地的技术方案。