ThreeJs入门35-WebGL模型篇:OBJLoader的parse函数解析

754 阅读3分钟

「这是我参与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创建我们的几何体,最终创建出我们的模型