「这是我参与2022首次更文挑战的第39天,活动详情查看:2022首次更文挑战」
示例代码采用three.js-r73版本: cdnjs.cloudflare.com/ajax/libs/t…
看到此,我们已经讲解了OBJ模型的加载,OBJ模型的格式,OBJ模型赋予多个纹理等问题,那么OBJLoader到底是如何解析OBJ模型的呢?让我们一起来看看parse函数是如何实现的吧。
- 找到OBJLoader.js源码:examples/js/loaders/OBJLoader.js
- 找到parse函数
初始化变量
- 定义了一些变量,用来存储模型、模型数组、几何体、材质
var object, objects = [];
var geometry, material;
定义内部处理函数
- 这一部分源码只做函数说明,不做太多讲解
// 解析向量索引
function parseVertexIndex(value)
// 解析法线索引
function parseNormalIndex(value)
// 解析uv点索引
function parseUVIndex(value)
// 添加向量索引
function addVertex(a, b, c)
// 添加法线索引
function addNormal(a, b, c)
// 添加uv点索引
function addUV(a, b, c)
// 添加面
function addFace(a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd)
匹配项目名称
- 如果没有匹配到项目名称,也就是类似
o mesh1.002_mesh1-geometry
格式信息,那么会添加一个空的object对象
// 全局匹配模型名称 o mesh1.002_mesh1-geometry
if (/^o /gm.test(text) === false) {
geometry = {
vertices: [],
normals: [],
uvs: []
};
material = {
name: ''
};
object = {
name: '',
geometry: geometry,
material: material
};
objects.push(object);
}
正则表达式
- 通过定义各种正则表达式,来解析
.obj
文件
// 匹配顶点坐标
// v float float float
// v 4.649472 159.854965 5.793066
var vertex_pattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
// 匹配顶点法线
// vn float float float
// vn -0.253945 0.750999 0.609455
var normal_pattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
// 匹配贴图坐标,也叫uv坐标
// vt float float
// vt 0.446803 0.505387
var uv_pattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
// 匹配面 -f 顶点索引 顶点索引 顶点索引
// f vertex vertex vertex ...
var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/;
// 匹配面-f 顶点索引/uv坐标索引
// f vertex/uv vertex/uv vertex/uv ...
var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/;
// 匹配面-f 顶点索引/uv点索引/法线索引
// f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
// f 1/1/1 2/2/2 3/3/3
var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/;
// 匹配面-f 顶点索引//法线索引
// f vertex//normal vertex//normal vertex//normal ...
var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/;
匹配对应属性进行存储
初始化跳过某些数据的处理
- 这里初始化了一些变量,判断了如果数组为空或者是注释行,就跳过不做处理
var vertices = []; // 顶点数组
var normals = []; // 法线索引数组
var uvs = []; // uv点索引数组
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
line = line.trim(); // 去除两边空格
var result;
// 如果数组为空或者为# 开头(注释),跳过
if (line.length === 0 || line.charAt(0) === '#') {
continue;
}
// 处理各种属性数据
...
}
匹配的是顶点
- 如果匹配的是顶点索引,那么就添加到向量数组中
else if ((result = vertex_pattern.exec(line)) !== null) {
// ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
vertices.push(
parseFloat(result[1]),
parseFloat(result[2]),
parseFloat(result[3])
);
}
匹配的是法线
- 如果匹配的是法线索引,就添加到法线索引数组中
else if ((result = normal_pattern.exec(line)) !== null) {
// ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
normals.push(
parseFloat(result[1]),
parseFloat(result[2]),
parseFloat(result[3])
);
}
匹配的是uv坐标
- 如果匹配的是uv点索引,就添加到uvs中
else if ((result = uv_pattern.exec(line)) !== null) {
// ["vt 0.1 0.2", "0.1", "0.2"]
uvs.push(
parseFloat(result[1]),
parseFloat(result[2])
);
}
匹配的是面-顶点索引
- 如果匹配的是面,
顶点索引
格式的,添加到面中进行处理
else if ((result = face_pattern1.exec(line)) !== null) {
// ["f 1 2 3", "1", "2", "3", undefined]
addFace(
result[1], result[2], result[3], result[4]
);
//
}
匹配的是面-顶点索引/uv点索引
- 如果匹配的是面,
顶点索引/uv点索引
格式的,添加到面中进行处理
else if ((result = face_pattern2.exec(line)) !== null) {
// ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined]
addFace(
result[2], result[5], result[8], result[11],
result[3], result[6], result[9], result[12]
);
}
匹配的是面-顶点索引/uv点索引/法线索引
- 如果匹配的是面,
顶点索引/uv点索引/法线索引
格式的,添加到面中进行处理
else if ((result = face_pattern3.exec(line)) !== null) {
// ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined]
addFace(
result[2], result[6], result[10], result[14],
result[3], result[7], result[11], result[15],
result[4], result[8], result[12], result[16]
);
}
匹配的是面-顶点索引//法线索引
- 如果匹配的是面,
顶点索引//法线索引
格式的,添加到面中进行处理
else if ((result = face_pattern4.exec(line)) !== null) {
// ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined]
addFace(
result[2], result[5], result[8], result[11],
undefined, undefined, undefined, undefined,
result[3], result[6], result[9], result[12]
);
}
匹配项目名称
- 如果匹配的是项目名称,添加到模型数组中
else if (/^o /.test(line)) {
geometry = {
vertices: [],
normals: [],
uvs: []
};
material = {
name: ''
};
object = {
name: line.substring(2).trim(),
geometry: geometry,
material: material
};
objects.push(object)
// 不支持组、材质、光滑组
}
three.js不支持的功能处理
- three.js不支持组、材质、光滑组
else if (/^g /.test(line)) {
// group
} else if (/^usemtl /.test(line)) {
// material
material.name = line.substring(7).trim();
} else if (/^mtllib /.test(line)) {
// mtl file
} else if (/^s /.test(line)) {
// smooth shading
} else {
// console.log( "THREE.OBJLoader: Unhandled line " + line );
}
创建object3D模型
- 初始化object3D
var container = new THREE.Object3D();
- 遍历模型数组,创建好的Mesh添加到object3D中,作为子模型
// 遍历项目名称,也就是遍历子模型
for (var i = 0, l = objects.length; i < l; i++) {
object = objects[i];
geometry = object.geometry;
// 生成BufferGeometry,添加属性
var buffergeometry = new THREE.BufferGeometry();
// 添加顶点
buffergeometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(geometry.vertices), 3));
// 添加法线
if (geometry.normals.length > 0) {
buffergeometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(geometry.normals), 3));
}
// 添加uv点
if (geometry.uvs.length > 0) {
buffergeometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(geometry.uvs), 2));
}
// 创建材质
material = new THREE.MeshLambertMaterial();
material.name = object.material.name;
// 创建网格
var mesh = new THREE.Mesh(buffergeometry, material);
mesh.name = object.name;
// 把这些子对象添加到object3D中
container.add(mesh);
}
返回object3D模型
- 最终返回创建好的object3D模型
return container;
loader.load调用
- 我们在调用加载模型,返回的object也就是我们创建的object3D模型
var loader = new THREE.OBJLoader(manager)
loader.load(`xxx`, function (object) {
// object 拿到我们返回的模型
}, onProgress, onError)
总结
- 解析一种模型格式,其实就是对模型文件的参数按行解析点、线、面、法线、材质等信息,然后通过
THREE.BufferGeometry
或者THREE.Geometry
创建我们的几何体,最终创建出我们的模型