Appearance
Three.js相关
Three.js 方法封装
TIP
本项目中 Three.js 的 API 相关的操作方法都统一抽离封装在 utils/renderScene 和 utils/sceneModules 文件夹中。
初始化项目的 Three.js 场景实例
js
<template>
<div id="scene-render"></div>
</template>;
import renderScene from "@/utils/renderScene";
import { onMounted } from "vue";
onMounted(async () => {
const renderScene = new renderScene("#scene-render");
// 初始化场景内容
await renderScene.init();
// 加载进度条
renderScene.onProgress((progressNum: number, totalSize: number) => {
console.log(progressNum);
});
});
注 ⚠️:完整的代码可查看 views/sceneEdit/index.vue 文件
全局使用 renderScene API
将 new 实例化出来的 renderScene 内容定义在 Pinia 全局 store 中实现多页面共享
js
<template>
<div id="scene-render"></div>
</template>;
import renderScene from "@/utils/renderScene";
import { useModelStore } from "@/store/sceneEditStore";
import { onMounted } from "vue";
const store = useModelStore();
onMounted(async () => {
const renderScene = new renderScene("#scene-render");
store.setSceneApi(renderScene);
// 初始化场景内容
await renderScene.init();
// 加载进度条
renderScene.onProgress((progressNum: number, totalSize: number) => {
console.log(progressNum);
});
});
引入 useModelStore 在其他页面中使用
js
import { useModelStore } from "@/store/sceneEditStore";
const store = useModelStore();
// 选择材质
const changeMaterialsNode = (node: MaterialNode) => {
store.setCurrentTransformMaterialUuid(node.uuid);
store.sceneApi?.chooseMaterial(node);
};
// 复制材质
const copyMaterial = (node: MaterialNode) => {
store.sceneApi?.copySceneMaterial(node.uuid);
};
模块化开发
注意 ⚠️
Three.js 相关方法的操作本身就是相当繁琐且复杂的,一个功能的实现可能就需要几十行甚至上百行的代码,在企业级项目 3D 开发中如果你的代码设计不太合理,可能会导致一个文件出现几千行的代码情况,这对于项目后期的维护和扩展将会是灾难性的。
为了项目后期的扩展和可维护性,项目根据实际的功能模块将 Three.js 相关方法分为了几个大的模块分别用于实现不同的功能
@/utils/renderScene.ts 是 Three.js 方法内容的主文件,包含了场景、相机、渲染器、控制器、地面、第一人称、模型加载等方法。
对于灯光、动画、天气、变换控制器、历史操作记录等模块内容我们也进行了单独的封装定义
如果你在实现项目开发过程中也会有新的模块需求拆分,可借鉴这种模式
js
import AnimationModules from "./sceneModules/animationModules";
import TransformControlsModules from "./sceneModules/transformControlsModules";
import LightModules from "./sceneModules/lightModules";
import WeatherEffectsModules from "./sceneModules/weatherEffectsModules";
import css3DRendererModules from "./sceneModules/css3DRendererModules";
class renderScene {
// 动画模块实例
animationModules: {
playAnimation: (animation: THREE.AnimationClip, model: THREE.Group) => void,
updateAnimationParams: (action: ActionParams, uuid: string) => void,
updateActionAnimationMap: (mapId: string, uuid: string) => void,
currentActions: Map<string, THREE.AnimationAction[]>,
clear: () => void,
};
// 光源模块实例
lightModules: {
createLight: (type: string, position: THREE.Vector3) => void,
updateHelper: (uuid?: string) => void,
initLight: () => void,
};
// 变换控制器模块实例
transformControlsModules: {
init: () => void,
transformControls: TransformControls | null,
transformControlsHelper: THREE.Object3D | null,
focusOnObject: (object: THREE.Object3D) => void,
destroy: () => void,
createTransformControls: () => void,
clearCurrentSelection: () => void,
};
// 天气效果模块实例
weatherEffectsModules: {
createWeatherEffect: (options: WeatherOptions) => void,
updateWeatherParams: (options: WeatherOptions) => void,
weatherConfig: WeatherOptions,
};
// 历史记录模块实例
historyModules: {
undo: () => void,
redo: () => void,
clear: () => void,
execute: (command: Command) => void,
};
//css3D实例
css3DRendererModules: {
init: (clientWidth: number, clientHeight: number) => void,
render: (scene: THREE.Scene, camera: THREE.PerspectiveCamera) => void,
css3DRenderer: CSS3DRenderer | null,
createEcharts: (options: unknown) => void,
initEcharts: () => void,
};
constructor(selector: string) {
this.animationModules = new AnimationModules();
this.lightModules = new LightModules();
this.transformControlsModules = new TransformControlsModules();
this.weatherEffectsModules = new WeatherEffectsModules();
this.historyModules = new HistoryModules();
this.css3DRendererModules = new css3DRendererModules();
}
}
export default renderScene;
在任何一个页面中去使用
js
import { useModelStore } from '@/store/sceneEditStore';
const store = useModelStore();
// 变换控制器类型切换
const handleTransformControlsType = (type: TRANSFORM_CONTROLS_TYPE) => {
store.sceneApi?.transformControlsModules.transformControls?.setMode(type);
};
// 键盘 f 键 镜头聚焦模型位置
const keyDownEventListener =(event: KeyboardEvent)=>{
const { currentTransformMaterialUuid , sceneApi } = store
if (event.key.toLowerCase() === 'f') {
event.preventDefault();
const mesh = store.sceneApi?.scene?.getObjectByProperty('uuid',currentTransformMaterialUuid
);
if (mesh) {
sceneApi?.transformControlsModules.focusOnObject(mesh);
}
}
}