文章目录

  • ​​目的​​
  • ​​局限​​
  • ​​解决方案​​
  • ​​原理​​
  • ​​git 仓库​​
  • ​​代码​​
  • ​​创建webworker​​
  • ​​worker 解析模型​​
  • ​​创建THREE模型解析器​​
  • ​​监听主线程信息 加载模型​​
  • ​​生成抽象语法树​​
  • ​​主线程解析语法树生成真正的模型​​
  • ​​相关类型定义​​

目的

使用web worker可以开启新的线程 减少主线程的计算 让three有更高的帧率 减少页面卡顿
此次尝试web worker与主线程并行加载模型 来实现减少模型加载时间 提前将页面展现出来

局限

web worker 可以解析glb模型 但是postMessage发送的数据类型只能是普通的对象,
不能存在方法 方法无法传递,带方法会导致发送失败,并且不会触发onerror
THREE构建物体所需的bufferGeometry,还是BufferAttribute 或者Material等原型对象无法被传递
传递到主线程的只能是一个普通对象其上面的属性就是THRE对象需要的属性

解决方案

可以通过生成一个THREE所需的数据结构 把传递过来的对象上的参数赋值给THREE需要的对象上
这样在主线程生成一个同样的模型,但是省去了解析模型时间(模型解析在web worker中与js主线程并发执行)
实现并发加载

原理

web worker 解析模型 生成一棵树 主线程解析这棵树 生成真正的THREE.js支持的模型

git 仓库

代码有不详尽之处 以及实际业务使用 将同步在​​gitee.com/honbingitee/three-dimensional-blog​

代码

创建webworker
const myWorker = new Worker(new URL("./worker/worker_script.ts", import.meta.url));

myWorker.onmessage = function (e) {
const { work_type } = e.data;
if (work_type === "parseModel") {
console.log("接收到返回", e.data);
const mesh = pressModel(e.data);
scene.add(mesh);
}
};

myWorker.onerror = (err) => {
console.error("work出错:", err, err.message);
};
//发送消息 web worker 接收后解析模型
myWorker.postMessage({ work_type: "start" });
worker 解析模型

要在worker中解析模型需要导入THREE.js 普通worker无法使用导入的库 所以使用时采用这种形式
​​​new Worker(new URL("./worker/worker_script.ts", import.meta.url))​

创建THREE模型解析器
import { Vector3Arr } from "./../types";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import acter from "../static/model/acter.glb";

const manager = new THREE.LoadingManager();
const dracoLoader = new DRACOLoader(manager);
dracoLoader.setDecoderConfig({ type: "js" });
dracoLoader.setDecoderPath("https://api.hongbin.xyz:3002/kmyc/");
const gltfLoader = new GLTFLoader(manager);
gltfLoader.setDRACOLoader(dracoLoader);

manager.onStart = function () {
postMessage({ msg: "开始加载" });
};

manager.onLoad = function () {
postMessage({ msg: "加载完成!" });
};

manager.onProgress = function (url, itemsLoaded, itemsTotal) {
postMessage({ msg: "加载中文件: " + url + ".\nLoaded " + itemsLoaded + " of " + itemsTotal + " files." });
};

manager.onError = function (url) {
postMessage({ meg: "加载出错:" + url });
};
监听主线程信息 加载模型
onmessage = function (e) {
switch (e.data.work_type) {
case "start":
gltfLoader.load(
acter,
(gltf) => {
return postMessage({
work_type: "parseModel",
...genGroupStruct(gltf.scene),
sceneAnimations: genAnimations(gltf.animations)
});
},
undefined,
(err) => {
postMessage({ msg: "开始err" });
}
);
break;
case "calculate":
calculate(e);
break;
}
};
生成抽象语法树
/**
* 生成动画结构
*/
const genAnimations = (animations: THREE.AnimationClip[]) =>
animations.map((animation) => {
//删除这个方法就可以传递过去了
//@ts-ignore
animation["tracks"].forEach((t) => delete t["createInterpolant"]);
return animation;
});

/**
* 生成基本参数 旋转 位移 缩放等属性
*/
const genBaseStruct = (obj: THREE.Object3D): IBaseProps => {
const { name, quaternion: q, position: p, rotation: r, scale: s, up: u, userData, visible, matrix } = obj;
const quaternion: IBaseProps["quaternion"] = [q.x, q.y, q.z, q.w];
const position: IBaseProps["position"] = [p.x, p.y, p.z];
const rotation: IBaseProps["rotation"] = [r.x, r.y, r.z, r.order];
const scale: IBaseProps["scale"] = [s.x, s.y, s.z];
const up: IBaseProps["up"] = [u.x, u.y, u.z];

return {
name,
quaternion,
position,
rotation,
scale,
up,
matrix,
userData,
visible,
children: genObject3DChildren(obj.children),
animations: genAnimations(obj.animations),
};
};

/**
* 生成物体参数
*/
const genMeshStruct = (mesh: THREE.Mesh) => {
const { geometry, material } = mesh;

return {
geometry,
material,
...genBaseStruct(mesh),
};
};

/**
* 生成子元素结构
*/
const genObject3DChildren = (children: THREE.Object3D[]) => {
const childStruct: IGroupParams["children"] = [];
for (const child of children) {
if (child.type === "Mesh") {
childStruct.push(genMeshStruct(child as THREE.Mesh));
} else if (child.type === "Group") {
childStruct.push(genGroupStruct(child as THREE.Group));
}
}
return childStruct;
};

/**
* 生成物体组结构
*/
const genGroupStruct = (group: THREE.Group) => {
const struct: IGroupParams = { ...genBaseParams(group) };
return struct;
};

最终生成类似这样一个对象

three.js使用web worker解析模型并添加到主线程_主线程

主线程解析语法树生成真正的模型
/*
* @Author: hongbin
* @Date: 2022-08-24 22:00:00
* @LastEditors: hongbin
* @LastEditTime: 2022-08-25 20:31:33
* @Description:处理worker功能
*/

import * as THREE from "three";
import { THREEMaterialType, Vector3Arr } from "../types";
import { IBaseProps, IGroupParams, IMeshParams } from "../worker/worker_script";

interface IParams {
geometry: THREE.Mesh["geometry"];
material: THREE.Mesh["material"];
matrix: THREE.Mesh["matrix"];
position: Vector3Arr;
quaternion: [...Vector3Arr, number];
rotation: [...Vector3Arr, THREE.Mesh["rotation"]["order"]];
scale: Vector3Arr;
up: Vector3Arr;
userData: THREE.Mesh["userData"];
visible: THREE.Mesh["visible"];
}
/**
* 通过设置attributes index来复刻一个集合体
*/
const genGeometry = (geometry: IParams["geometry"]) => {
const geom = new THREE.BufferGeometry();
const {
attributes: { position, uv, normal },
index,
} = geometry;

//处理几何坐标
const attributes = {
position: new THREE.BufferAttribute(position.array, position.itemSize, position.normalized),
uv: new THREE.BufferAttribute(uv.array, uv.itemSize, uv.normalized),
normal: new THREE.BufferAttribute(normal.array, normal.itemSize, normal.normalized),
};
geom.attributes = attributes;
geom.index = index ? new THREE.BufferAttribute(index.array, index.itemSize, index.normalized) : null;
return geom;
};
/**
* 根据传入纹理的参数生成真正有效的Material类型数据
*/
const genMaterial = (mate: IParams["material"]) => {
if (!mate) return undefined;
const multipleMaterial = Array.isArray(mate);
const material = multipleMaterial ? ([] as THREE.Material[]) : new THREE[mate.type as THREEMaterialType]();
//处理材质
//多个材质
if (multipleMaterial && Array.isArray(material)) {
for (const m of mate) {
const im = new THREE[m.type as THREEMaterialType]();
material.push(im);
}
} else if (mate) {
//单个材质
Object.assign(material, mate);
}
console.log(mate, material);
return material;
};

/**
* 处理变换 matrix scale rotate translate position
*/
const setTransform = (params: IBaseProps, object: THREE.Object3D) => {
const matrix = new THREE.Matrix4();
matrix.elements = params.matrix.elements;
object.name = params.name;
object.matrix = matrix;
object.rotation.set(...params.rotation);
object.position.set(...params.position);
object.scale.set(...params.scale);
object.quaternion.set(...params.quaternion);
object.up.set(...params.up);
object.userData = params.userData;
object.visible = params.visible;
// object.scale.x += 0.3;
// object.scale.y += 0.3;
// object.scale.z += 0.3;
};

const pressMesh = (meshParams: IMeshParams) => {
const geometry = genGeometry(meshParams.geometry);
const material = genMaterial(meshParams.material);

const mesh = new THREE.Mesh(geometry, material);
setTransform(meshParams, mesh);
meshParams.children.length && mesh.add(...pressChildren(meshParams.children));
meshParams.animations.length && (mesh.animations = genAnimations(meshParams.animations));
return mesh;
};

const pressGroup = (groupParams: IGroupParams) => {
const group = new THREE.Group();
setTransform(groupParams, group);
groupParams.children.length && group.add(...pressChildren(groupParams.children));
groupParams.animations.length && (group.animations = genAnimations(groupParams.animations));
return group;
};

const pressChildren = (children: (IGroupParams | IMeshParams)[]) => {
const objectList: THREE.Object3D[] = [];
for (const child of children) {
if (child.hasOwnProperty("geometry")) {
objectList.push(pressMesh(child as IMeshParams));
} else {
objectList.push(pressGroup(child));
}
}
return objectList;
};

/**
* 生成动画
*/
const genAnimations = (sceneAnimations: IGroupParams["sceneAnimations"]) => {
const animations: THREE.AnimationClip[] = [];

for (const animation of sceneAnimations!) {
const clip = new THREE.AnimationClip(animation.name, animation.duration, [], animation.blendMode);

for (const { name, times, values } of animation.tracks) {
const nreTrack = new THREE.QuaternionKeyframeTrack(name, times as any, values as any);
clip.tracks.push(nreTrack);
}

animations.push(clip);
}

return animations;
};

/**
* 解析传入的模型参数生成有效的three.js物体
*/
export const pressModel = (params: IGroupParams) => {
console.log(params);
const model = pressGroup(params);
// model.position.x += 10;
params.sceneAnimations && (model.animations = genAnimations(params.sceneAnimations));
return model;
};
相关类型定义
export interface IBaseProps {
name: string;
matrix: THREE.Mesh["matrix"];
position: Vector3Arr;
quaternion: [...Vector3Arr, number];
rotation: [...Vector3Arr, THREE.Mesh["rotation"]["order"]];
scale: Vector3Arr;
up: Vector3Arr;
userData: THREE.Mesh["userData"];
visible: THREE.Mesh["visible"];
children: Array<IMeshParams | IGroupParams>;
/**
* blender 制作的模型动画添载在scene上的animations上 这个参数导出scene上的动画
*/
sceneAnimations?: THREE.AnimationClip[];
}


export interface IMeshParams extends IBaseProps {
geometry: THREE.Mesh["geometry"];
material: THREE.Mesh["material"];
}

export interface IGroupParams extends IBaseProps {}


/**
* Three.js 支持的所有材料类型名称
*/
export type THREEMaterialType =
| "ShadowMaterial"
| "SpriteMaterial"
| "RawShaderMaterial"
| "ShaderMaterial"
| "PointsMaterial"
| "MeshPhysicalMaterial"
| "MeshStandardMaterial"
| "MeshPhongMaterial"
| "MeshToonMaterial"
| "MeshNormalMaterial"
| "MeshLambertMaterial"
| "MeshDepthMaterial"
| "MeshDistanceMaterial"
| "MeshBasicMaterial"
| "MeshMatcapMaterial"
| "LineDashedMaterial"
| "LineBasicMaterial"
| "Material";

export type Vector3Arr = [x: number, y: number, z: number];

这只是一一个模型为例实现基本功能 要满足实际业务 还需根据场景丰富内容