renderModel.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. import * as THREE from 'three'; //导入整个 three.js核心库
  2. import {FBXLoader} from 'three/examples/jsm/loaders/FBXLoader';
  3. import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'; //导入控制器模块,轨道控制器
  4. import {CSS3DRenderer, CSS3DSprite} from 'three/examples/jsm/renderers/CSS3DRenderer.js';
  5. import {GUI} from 'three/examples/jsm/libs/lil-gui.module.min.js';
  6. import * as TWEEN from '@tweenjs/tween.js';
  7. import {getBottomMaterial} from './material.js';
  8. // 定义一个 class类
  9. class renderModel {
  10. constructor(selector) {
  11. this.container = document.querySelector(selector);
  12. // 相机
  13. this.camera;
  14. // 场景
  15. this.scene;
  16. //渲染器
  17. this.renderer;
  18. // 控制器
  19. this.controls;
  20. // 3d文字渲染器
  21. this.css3DRenderer = null;
  22. // 3d文字控制器
  23. this.css3dControls = null;
  24. // 模型
  25. this.model;
  26. // 环境光
  27. this.ambientLight;
  28. //模型平面
  29. this.planeGeometry;
  30. }
  31. // 初始化加载模型方法
  32. init() {
  33. //初始化场景
  34. this.initScene();
  35. //初始化相机
  36. this.initCamera();
  37. //初始化渲染器
  38. this.initRender();
  39. // 创建灯光
  40. this.createLight();
  41. //初始化控制器,控制摄像头,控制器一定要在渲染器后
  42. this.initControls();
  43. this.setFBXModel();
  44. //监听场景大小改变,跳转渲染尺寸
  45. window.addEventListener('resize', this.onWindowResizes);
  46. //场景渲染
  47. this.sceneAnimation();
  48. }
  49. //创建场景
  50. initScene() {
  51. this.scene = new THREE.Scene();
  52. this.scene.background = new THREE.Color(0x000000);
  53. const axesHelper = new THREE.AxesHelper(150);
  54. this.scene.add(axesHelper);
  55. //底部平面
  56. const geometry = new THREE.PlaneGeometry(1, 1);
  57. // 创建材质,可以设置贴图
  58. const material = getBottomMaterial(
  59. '#ffffff', // 设置模型的颜色
  60. require('@/assets/images/models/bg.png'),
  61. 1,
  62. 5
  63. );
  64. // 创建平面网格
  65. const plane = new THREE.Mesh(geometry, material);
  66. plane.scale.set(1000, 1000, 1000);
  67. plane.position.y = 0;
  68. const plane2 = plane.clone();
  69. plane2.material = getBottomMaterial(
  70. '#000000', // 设置模型的颜色
  71. require('@/assets/images/models/bg.png'),
  72. 2,
  73. 5
  74. );
  75. this.scene.add(plane2);
  76. this.scene.add(plane);
  77. }
  78. // 创建相机
  79. initCamera() {
  80. const {clientHeight, clientWidth} = this.container;
  81. this.camera = new THREE.PerspectiveCamera(18, clientWidth / clientHeight, 0.1, 10000);
  82. }
  83. // 创建渲染器
  84. initRender() {
  85. this.renderer = new THREE.WebGLRenderer({
  86. logarithmicDepthBuffer: true,
  87. antialias: true, // true/false表示是否开启反锯齿
  88. alpha: true, // true/false 表示是否可以设置背景色透明
  89. precision: 'mediump', // highp/mediump/lowp 表示着色精度选择
  90. premultipliedAlpha: true // true/false 表示是否可以设置像素深度(用来度量图像的分辨率)
  91. // preserveDrawingBuffer: false, // true/false 表示是否保存绘图缓冲
  92. // physicallyCorrectLights: true, // true/false 表示是否开启物理光照
  93. });
  94. //设置屏幕像素比
  95. this.renderer.setPixelRatio(window.devicePixelRatio);
  96. //渲染的尺寸大小
  97. const {clientHeight, clientWidth} = this.container;
  98. this.renderer.setSize(clientWidth, clientHeight);
  99. this.container.appendChild(this.renderer.domElement);
  100. // 创建一个CSS3渲染器CSS3DRenderer
  101. this.css3DRenderer = new CSS3DRenderer();
  102. this.css3DRenderer.setSize(clientWidth, clientHeight);
  103. // HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
  104. this.css3DRenderer.domElement.style.position = 'absolute';
  105. this.css3DRenderer.domElement.style.top = '0px';
  106. this.css3DRenderer.domElement.style.zIndex = '9999';
  107. //设置.pointerEvents=none,解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
  108. this.css3DRenderer.domElement.style.pointerEvents = 'none';
  109. this.css3DRenderer.domElement.style.className = 'css3DRenderer';
  110. this.container.appendChild(this.css3DRenderer.domElement);
  111. }
  112. // 创建光源
  113. createLight() {
  114. this.scene.add(new THREE.AmbientLight(0xffffff, 10));
  115. }
  116. initControls() {
  117. this.controls = new OrbitControls(this.camera, this.renderer.domElement);
  118. // this.controls.enableDamping = true;
  119. // this.controls.screenSpacePanning = false;
  120. // this.controls.maxPolarAngle = Math.PI / 1.1;
  121. // this.controls.minPolarAngle = Math.PI / 2;
  122. // this.controls.minAzimuthAngle = 0;
  123. // this.controls.maxAzimuthAngle = 0;
  124. // this.controls.maxDistance = 2000;
  125. //标签控制器
  126. this.css3dControls = new OrbitControls(this.camera, this.css3DRenderer.domElement);
  127. this.css3dControls.enablePan = false;
  128. this.css3dControls.enableDamping = true;
  129. this.controls.mouseButtons = {
  130. LEFT: THREE.MOUSE.PAN,
  131. MIDDLE: THREE.MOUSE.DOLLY,
  132. RIGHT: THREE.MOUSE.ROTATE
  133. };
  134. }
  135. animate() {
  136. this.renderer.render(this.scene, this.camera);
  137. this.controls.update();
  138. this.css3DRenderer.render(this.scene, this.camera);
  139. TWEEN.update();
  140. // // 检查模型位置是否发生变化
  141. // if (this.model) {
  142. // console.log('Model Position:', this.model.position);
  143. // console.log('Camera Position:', this.camera.position);
  144. // console.log('Controls Target:', this.controls.target);
  145. // }
  146. }
  147. // 使用动画器不断更新场景
  148. sceneAnimation() {
  149. this.renderer.setAnimationLoop(this.animate.bind(this));
  150. }
  151. initGUI() {
  152. const gui = new GUI();
  153. // 添加控制器目标位置的控制器
  154. const controlsFolder = gui.addFolder('Controls Target');
  155. controlsFolder
  156. .add(this.controls.target, 'x', -1000, 1000)
  157. .name('Target X')
  158. .onChange(() => {
  159. this.controls.update();
  160. });
  161. controlsFolder
  162. .add(this.controls.target, 'y', -1000, 1000)
  163. .name('Target Y')
  164. .onChange(() => {
  165. this.controls.update();
  166. });
  167. controlsFolder
  168. .add(this.controls.target, 'z', -1000, 1000)
  169. .name('Target Z')
  170. .onChange(() => {
  171. this.controls.update();
  172. });
  173. controlsFolder.open();
  174. // 添加相机位置的控制器
  175. const cameraFolder = gui.addFolder('Camera Position');
  176. cameraFolder
  177. .add(this.camera.position, 'x', -1000, 1000)
  178. .name('Camera X')
  179. .onChange(() => {
  180. this.controls.update();
  181. });
  182. cameraFolder
  183. .add(this.camera.position, 'y', -1000, 1000)
  184. .name('Camera Y')
  185. .onChange(() => {
  186. this.controls.update();
  187. });
  188. cameraFolder
  189. .add(this.camera.position, 'z', -1000, 1000)
  190. .name('Camera Z')
  191. .onChange(() => {
  192. this.controls.update();
  193. });
  194. cameraFolder.open();
  195. // 添加重置按钮
  196. const initialTarget = this.controls.target.clone();
  197. const initialCameraPosition = this.camera.position.clone();
  198. gui
  199. .add(
  200. {
  201. reset: () => {
  202. this.controls.target.copy(initialTarget);
  203. this.camera.position.copy(initialCameraPosition);
  204. this.controls.update();
  205. }
  206. },
  207. 'reset'
  208. )
  209. .name('Reset Position');
  210. }
  211. flyTo(target, scale = 1, duration = 1000) {
  212. const targetPosition = new THREE.Vector3().copy(target.position);
  213. const targetFocus = new THREE.Vector3().copy(target.targetContent);
  214. // 移动相机位置
  215. new TWEEN.Tween(this.camera.position)
  216. .to(targetPosition, target.time || 500)
  217. .easing(TWEEN.Easing.Quadratic.InOut)
  218. .onUpdate(() => {
  219. this.controls.update(); // 更新控制器
  220. })
  221. .start();
  222. // 移动控制器目标
  223. new TWEEN.Tween(this.controls.target)
  224. .to(targetFocus, target.time || 500)
  225. .easing(TWEEN.Easing.Quadratic.InOut)
  226. .onUpdate(() => {
  227. this.controls.update(); // 更新控制器
  228. })
  229. .start();
  230. new TWEEN.Tween(this.model.scale).to({
  231. x: scale,
  232. y: scale,
  233. z: scale
  234. }, duration).easing(TWEEN.Easing.Quadratic.InOut).start();
  235. }
  236. tag3D(name) {
  237. // 创建div元素(作为标签)
  238. let div = document.createElement('div');
  239. div.innerHTML = name;
  240. div.classList.add('tag3d');
  241. //div元素包装为CSS3模型对象CSS3DObject
  242. let label = new CSS3DSprite(div);
  243. div.style.pointerEvents = 'none'; //避免HTML标签遮挡三维场景的鼠标事件
  244. //缩放CSS3DObject模型对象
  245. label.scale.set(0.15, 0.15, 0.15); //根据相机渲染范围控制HTML 3D标签尺寸
  246. label.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度
  247. return label; //返回CSS3模型标签
  248. }
  249. createDialog(html) {
  250. //div元素包装为CSS3模型对象CSS3DSprite
  251. const element = document.createElement('div');
  252. element.className = 'customDialog';
  253. element.appendChild(html);
  254. const dialog = new CSS3DSprite(element);
  255. element.style.pointerEvents = 'none'; //避免HTML标签遮挡三维场景的鼠标事件
  256. // 设置HTML元素标签在three.js世界坐标中位置
  257. // label.position.set(x, y, z);
  258. //缩放CSS3DSprite模型对象
  259. dialog.scale.set(0.15, 0.15, 0.15); //根据相机渲染范围控制HTML 3D标签尺寸
  260. dialog.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度
  261. return dialog; //返回CSS3模型标签
  262. }
  263. /**
  264. * 设置加载模型居中
  265. * {Object} object 模型对象
  266. */
  267. setModelPosition(object) {
  268. object.updateMatrixWorld();
  269. // 获得包围盒得min和max
  270. const box = new THREE.Box3().setFromObject(object);
  271. // 返回包围盒的中心点
  272. const center = box.getCenter(new THREE.Vector3());
  273. object.position.x += object.position.x - center.x;
  274. object.position.y += object.position.y - center.y;
  275. object.position.z = 0;
  276. }
  277. setFBXModel() {
  278. var fbxLoader = new FBXLoader();
  279. fbxLoader.load('/models/model2.FBX', object => {
  280. this.calcMeshCenter(object);
  281. this.model = object;
  282. object.traverse(function (child) {
  283. if (child.isMesh) {
  284. child.material.emissive = child.material.color;
  285. child.material.emissiveMap = child.material.map;
  286. }
  287. });
  288. // 设置相机位置
  289. this.camera.position.set(-20, -652, 500);
  290. this.controls.target.set(-20, -20, 0);
  291. this.model.scale.set(1, 1, 1);
  292. // 设置相机坐标系
  293. this.camera.lookAt(0, 0, 0);
  294. // 将模型添加到场景中去
  295. this.scene.add(this.model);
  296. // window.addEventListener('click', event => {
  297. // const raycaster = new THREE.Raycaster();
  298. // const mouse = new THREE.Vector2();
  299. // mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  300. // mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  301. // raycaster.setFromCamera(mouse, this.camera);
  302. // const intersects = raycaster.intersectObjects(this.scene.children);
  303. // if (intersects.length > 0) {
  304. // const point = intersects[0].point;
  305. // console.log(`Clicked at: (${point.x}, ${point.y}, ${point.z})`);
  306. // }
  307. // });
  308. // this.initGUI();
  309. });
  310. }
  311. onWindowResizes() {
  312. if (!this.container) return false;
  313. const {clientHeight, clientWidth} = this.container;
  314. //调整屏幕大小
  315. this.camera.aspect = clientWidth / clientHeight; // 摄像机宽高比例
  316. this.camera.updateProjectionMatrix(); //相机更新矩阵,将3d内容投射到2d面上转换
  317. this.renderer.setSize(clientWidth, clientHeight);
  318. }
  319. calcMeshCenter(group) {
  320. /**
  321. * 包围盒全自动计算:模型整体居中
  322. */
  323. var box3 = new THREE.Box3();
  324. // 计算层级模型group的包围盒
  325. // 模型group是加载一个三维模型返回的对象,包含多个网格模型
  326. box3.expandByObject(group);
  327. // 计算一个层级模型对应包围盒的几何体中心在世界坐标中的位置
  328. var center = new THREE.Vector3();
  329. box3.getCenter(center);
  330. // 重新设置模型的位置,使之居中。
  331. group.position.x = 0;
  332. group.position.y = 0;
  333. group.position.z = 17;
  334. }
  335. addDialog(html, labelName, position) {
  336. const {x, y, z} = position
  337. const label3D = this.tag3D(labelName);
  338. const dialog3D = this.createDialog(html); //设置标签名称
  339. label3D.position.y += y;
  340. label3D.position.x += x;
  341. label3D.position.z += z;
  342. dialog3D.position.copy(label3D.position);
  343. dialog3D.position.z = z + 20;
  344. this.scene.add(label3D);
  345. this.scene.add(dialog3D);
  346. }
  347. }
  348. export default renderModel;