renderModel.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. import * as THREE from 'three'; //导入整个 three.js核心库
  2. import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'; //导入控制器模块,轨道控制器
  3. import {CSS3DRenderer, CSS3DSprite} from 'three/examples/jsm/renderers/CSS3DRenderer.js';
  4. import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js'
  5. // 用于模型边缘高亮
  6. import {EffectComposer} from "three/examples/jsm/postprocessing/EffectComposer.js";
  7. import {RenderPass} from "three/examples/jsm/postprocessing/RenderPass.js";
  8. import {OutlinePass} from "three/examples/jsm/postprocessing/OutlinePass.js";
  9. import {UnrealBloomPass} from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
  10. import * as TWEEN from '@tweenjs/tween.js';
  11. import {getBottomMaterial} from './material.js';
  12. import GltfModelManager from "@/views/largeScreen/three/GltfModelManager";
  13. // 定义一个 class类
  14. class renderModel {
  15. constructor(selector) {
  16. this.container = document.querySelector(selector);
  17. // 相机
  18. this.camera;
  19. // 场景
  20. this.scene;
  21. //渲染器
  22. this.renderer;
  23. // 控制器
  24. this.controls;
  25. // 3d文字渲染器
  26. this.css3DRenderer = null;
  27. // 3d文字控制器
  28. this.css3dControls = null;
  29. // 模型
  30. this.model;
  31. // 环境光
  32. this.ambientLight;
  33. //模型平面
  34. this.planeGeometry;
  35. this.outlineObjs = [];
  36. this.sharedComposer = null;
  37. this.sharedOutlinePass = null;
  38. this.sharedBloomPass = null;
  39. this.isComposerInitialized = false;
  40. this.raycaster = new THREE.Raycaster();
  41. this.mouse = new THREE.Vector2();
  42. }
  43. // 初始化共享后处理系统
  44. initSharedPostProcessing() {
  45. if (this.isComposerInitialized) return;
  46. // 创建共享的EffectComposer
  47. this.sharedComposer = new EffectComposer(this.renderer);
  48. this.sharedComposer.renderTarget1.texture.outputColorSpace = THREE.sRGBEncoding;
  49. this.sharedComposer.renderTarget2.texture.outputColorSpace = THREE.sRGBEncoding;
  50. this.sharedComposer.renderTarget1.texture.encoding = THREE.sRGBEncoding;
  51. this.sharedComposer.renderTarget2.texture.encoding = THREE.sRGBEncoding;
  52. // 添加基础渲染通道
  53. const renderPass = new RenderPass(this.scene, this.camera);
  54. this.sharedComposer.addPass(renderPass);
  55. // 创建共享的OutlinePass
  56. this.sharedOutlinePass = new OutlinePass(
  57. new THREE.Vector2(window.innerWidth, window.innerHeight),
  58. this.scene,
  59. this.camera
  60. );
  61. // 设置轮廓效果参数
  62. this.sharedOutlinePass.edgeStrength = 5.0; // 边框的亮度
  63. this.sharedOutlinePass.edgeGlow = 0.3; // 光晕[0,1]
  64. this.sharedOutlinePass.edgeThickness = 1.0; // 边框宽度
  65. // this.sharedOutlinePass.pulsePeriod = 3; // 呼吸闪烁的速度
  66. // this.sharedOutlinePass.visibleEdgeColor.set(0x00ff00); // 呼吸显示的颜色
  67. // this.sharedOutlinePass.hiddenEdgeColor.set(0x000000); // 呼吸消失的颜色
  68. // this.sharedOutlinePass.clear = true;
  69. this.sharedComposer.addPass(this.sharedOutlinePass);
  70. // 创建共享的UnrealBloomPass
  71. this.sharedBloomPass = new UnrealBloomPass();
  72. this.sharedBloomPass.strength = 0.1;
  73. this.sharedBloomPass.radius = 0;
  74. this.sharedBloomPass.threshold = 1;
  75. this.sharedComposer.addPass(this.sharedBloomPass);
  76. this.isComposerInitialized = true;
  77. // 监听窗口大小变化,更新后处理分辨率
  78. window.addEventListener('resize', () => {
  79. if (this.sharedOutlinePass) {
  80. this.sharedOutlinePass.resolution.set(window.innerWidth, window.innerHeight);
  81. }
  82. });
  83. }
  84. // 优化后的模型高亮方法
  85. outlineObj(selectedObjects) {
  86. // 初始化共享后处理系统
  87. this.initSharedPostProcessing();
  88. // 更新选中对象
  89. if (this.sharedOutlinePass) {
  90. this.sharedOutlinePass.selectedObjects = selectedObjects;
  91. // 如果之前有高亮对象,先清除
  92. if (this.outlineObjs.length > 0) {
  93. this.outlineObjs = [];
  94. }
  95. // 添加当前共享的composer到数组
  96. this.outlineObjs.push(this.sharedComposer);
  97. }
  98. }
  99. // 清理高亮效果
  100. clearHighlight() {
  101. if (this.sharedOutlinePass) {
  102. this.sharedOutlinePass.selectedObjects = [];
  103. this.outlineObjs = [];
  104. }
  105. }
  106. // 初始化加载模型方法
  107. init() {
  108. //初始化场景
  109. this.initScene();
  110. //初始化相机
  111. this.initCamera();
  112. //初始化渲染器
  113. this.initRender();
  114. // 创建灯光
  115. this.createLight();
  116. //初始化控制器,控制摄像头,控制器一定要在渲染器后
  117. this.initControls();
  118. this.setFBXModel();
  119. //监听场景大小改变,跳转渲染尺寸
  120. window.addEventListener('resize', this.onWindowResizes.bind(this));
  121. //场景渲染
  122. this.sceneAnimation();
  123. // 添加鼠标点击事件
  124. window.addEventListener('click', this.onMouseClick.bind(this));
  125. }
  126. initGltfModel() {
  127. //初始化场景
  128. this.initScene();
  129. //初始化相机
  130. this.initCamera();
  131. //初始化渲染器
  132. this.initRender();
  133. // 创建灯光
  134. this.createLight();
  135. //初始化控制器,控制摄像头,控制器一定要在渲染器后
  136. this.initControls();
  137. this.setGlbModel();
  138. //监听场景大小改变,跳转渲染尺寸
  139. window.addEventListener('resize', this.onWindowResizes.bind(this));
  140. //场景渲染
  141. this.sceneAnimation();
  142. // 添加鼠标点击事件
  143. window.addEventListener('click', this.onMouseClick.bind(this));
  144. }
  145. onMouseClick(event) {
  146. // 计算鼠标在标准化设备坐标中的位置 (-1 to +1)
  147. this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  148. this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  149. // 更新射线
  150. this.raycaster.setFromCamera(this.mouse, this.camera);
  151. // 计算射线与场景中物体的交点
  152. const intersects = this.raycaster.intersectObjects(this.scene.children, true);
  153. if (intersects.length > 0) {
  154. const point = intersects[0].point;
  155. const object = intersects[0].object;
  156. console.log(`Clicked on object: ${object.name} at (${point.x}, ${point.y}, ${point.z})`);
  157. // 示例:点击时高亮对象
  158. // this.outlineObj([object]);
  159. }
  160. }
  161. //创建场景
  162. initScene() {
  163. this.scene = new THREE.Scene();
  164. this.scene.background = new THREE.Color(0x000000);
  165. //底部平面
  166. const geometry = new THREE.PlaneGeometry(1, 1);
  167. // 创建材质,可以设置贴图
  168. const material = getBottomMaterial(
  169. '#ffffff', // 设置模型的颜色
  170. require('@/assets/images/models/bg.png'),
  171. 1,
  172. 5
  173. );
  174. // 创建平面网格
  175. const plane = new THREE.Mesh(geometry, material);
  176. plane.scale.set(1000, 1000, 1000);
  177. plane.position.y = 0;
  178. const plane2 = plane.clone();
  179. plane2.material = getBottomMaterial(
  180. '#000000', // 设置模型的颜色
  181. require('@/assets/images/models/bg.png'),
  182. 2,
  183. 5
  184. );
  185. this.scene.add(plane2);
  186. this.scene.add(plane);
  187. }
  188. // 创建相机
  189. initCamera() {
  190. const {clientHeight, clientWidth} = this.container;
  191. this.camera = new THREE.PerspectiveCamera(18, clientWidth / clientHeight, 0.1, 10000);
  192. }
  193. // 创建渲染器
  194. initRender() {
  195. this.renderer = new THREE.WebGLRenderer({
  196. logarithmicDepthBuffer: true,
  197. antialias: true, // true/false表示是否开启反锯齿
  198. alpha: true, // true/false 表示是否可以设置背景色透明
  199. precision: 'mediump', // highp/mediump/lowp 表示着色精度选择
  200. premultipliedAlpha: true // true/false 表示是否可以设置像素深度(用来度量图像的分辨率)
  201. // preserveDrawingBuffer: false, // true/false 表示是否保存绘图缓冲
  202. // physicallyCorrectLights: true, // true/false 表示是否开启物理光照
  203. });
  204. //设置屏幕像素比
  205. this.renderer.setPixelRatio(window.devicePixelRatio);
  206. //渲染的尺寸大小
  207. const {clientHeight, clientWidth} = this.container;
  208. this.renderer.setSize(clientWidth, clientHeight);
  209. this.container.appendChild(this.renderer.domElement);
  210. // 创建一个CSS3渲染器CSS3DRenderer
  211. this.css3DRenderer = new CSS3DRenderer();
  212. this.css3DRenderer.setSize(clientWidth, clientHeight);
  213. // HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
  214. this.css3DRenderer.domElement.style.position = 'absolute';
  215. this.css3DRenderer.domElement.style.top = '0px';
  216. this.css3DRenderer.domElement.style.zIndex = '9999';
  217. //设置.pointerEvents=none,解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
  218. this.css3DRenderer.domElement.style.pointerEvents = 'none';
  219. this.css3DRenderer.domElement.style.className = 'css3DRenderer';
  220. this.container.appendChild(this.css3DRenderer.domElement);
  221. }
  222. // 创建光源
  223. createLight() {
  224. this.scene.add(new THREE.AmbientLight(0xffffff, 10));
  225. }
  226. initControls() {
  227. this.controls = new OrbitControls(this.camera, this.renderer.domElement);
  228. // this.controls.enableDamping = true;
  229. // this.controls.screenSpacePanning = false;
  230. // this.controls.maxPolarAngle = Math.PI / 1.1;
  231. // this.controls.minPolarAngle = Math.PI / 2;
  232. // this.controls.minAzimuthAngle = 0;
  233. // this.controls.maxAzimuthAngle = 0;
  234. // this.controls.maxDistance = 2000;
  235. //标签控制器
  236. this.css3dControls = new OrbitControls(this.camera, this.css3DRenderer.domElement);
  237. this.css3dControls.enablePan = false;
  238. this.css3dControls.enableDamping = true;
  239. this.controls.mouseButtons = {
  240. LEFT: THREE.MOUSE.PAN,
  241. MIDDLE: THREE.MOUSE.DOLLY,
  242. RIGHT: THREE.MOUSE.ROTATE
  243. };
  244. }
  245. animate() {
  246. this.renderer.render(this.scene, this.camera);
  247. this.renderer.autoClear = true;
  248. this.controls.update();
  249. this.css3DRenderer.render(this.scene, this.camera);
  250. TWEEN.update();
  251. // 使用共享的composer进行后处理渲染
  252. // if (this.isComposerInitialized && this.sharedComposer) {
  253. // this.sharedComposer.render();
  254. // }
  255. }
  256. // 使用动画器不断更新场景
  257. sceneAnimation() {
  258. this.renderer.setAnimationLoop(this.animate.bind(this));
  259. }
  260. flyTo(target, scale = 1, duration = 1000) {
  261. const targetPosition = new THREE.Vector3().copy(target.position);
  262. const targetFocus = new THREE.Vector3().copy(target.targetContent);
  263. // 移动相机位置
  264. new TWEEN.Tween(this.camera.position)
  265. .to(targetPosition, target.time || 500)
  266. .easing(TWEEN.Easing.Quadratic.InOut)
  267. .onUpdate(() => {
  268. this.controls.update(); // 更新控制器
  269. })
  270. .start();
  271. // 移动控制器目标
  272. new TWEEN.Tween(this.controls.target)
  273. .to(targetFocus, target.time || 500)
  274. .easing(TWEEN.Easing.Quadratic.InOut)
  275. .onUpdate(() => {
  276. this.controls.update(); // 更新控制器
  277. })
  278. .start();
  279. new TWEEN.Tween(this.model.scale).to({
  280. x: scale,
  281. y: scale,
  282. z: scale
  283. }, duration).easing(TWEEN.Easing.Quadratic.InOut).start();
  284. }
  285. tag3D(name) {
  286. // 创建div元素(作为标签)
  287. let div = document.createElement('div');
  288. div.innerHTML = name;
  289. div.classList.add('tag3d');
  290. //div元素包装为CSS3模型对象CSS3DObject
  291. let label = new CSS3DSprite(div);
  292. div.style.pointerEvents = 'none'; //避免HTML标签遮挡三维场景的鼠标事件
  293. //缩放CSS3DObject模型对象
  294. label.scale.set(0.15, 0.15, 0.15); //根据相机渲染范围控制HTML 3D标签尺寸
  295. label.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度
  296. return label; //返回CSS3模型标签
  297. }
  298. createDialog(html) {
  299. //div元素包装为CSS3模型对象CSS3DSprite
  300. const element = document.createElement('div');
  301. element.className = 'customDialog';
  302. element.appendChild(html);
  303. const dialog = new CSS3DSprite(element);
  304. element.style.pointerEvents = 'none'; //避免HTML标签遮挡三维场景的鼠标事件
  305. // 设置HTML元素标签在three.js世界坐标中位置
  306. // label.position.set(x, y, z);
  307. //缩放CSS3DSprite模型对象
  308. dialog.scale.set(0.15, 0.15, 0.15); //根据相机渲染范围控制HTML 3D标签尺寸
  309. dialog.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度
  310. return dialog; //返回CSS3模型标签
  311. }
  312. /**
  313. * 设置加载模型居中
  314. * {Object} object 模型对象
  315. */
  316. setModelPosition(object) {
  317. object.updateMatrixWorld();
  318. // 获得包围盒得min和max
  319. const box = new THREE.Box3().setFromObject(object);
  320. // 返回包围盒的中心点
  321. const center = box.getCenter(new THREE.Vector3());
  322. object.position.x += object.position.x - center.x;
  323. object.position.y += object.position.y - center.y;
  324. object.position.z = 0;
  325. }
  326. setGlbModel() {
  327. new GltfModelManager().loadModel('model-road-pv', '/models/model-road-pv.glb').then(object => {
  328. this.calcMeshCenter(object.scene);
  329. this.model = object.scene;
  330. const selectedObjects = [];
  331. object.scene.traverse(function (child) {
  332. if (child.isMesh) {
  333. child.material.emissive = child.material.color;
  334. child.material.emissiveMap = child.material.map;
  335. }
  336. // console.log(child.name)
  337. // if (["solar_cell097001", "solar_cell097002", "solar_cell097003", "solar_cell097004"].includes(child.name)) {
  338. // selectedObjects.push(child);
  339. // }
  340. });
  341. // 设置相机位置
  342. this.camera.position.set(-20, -652, 500);
  343. this.controls.target.set(-20, -20, 0);
  344. this.model.scale.set(2, 2, 2);
  345. // 设置相机坐标系
  346. this.camera.lookAt(0, 0, 0);
  347. // 将模型添加到场景中去
  348. this.scene.add(this.model);
  349. // 高亮收集的对象
  350. // if (selectedObjects.length > 0) {
  351. // this.outlineObj(selectedObjects);
  352. // }
  353. });
  354. }
  355. setFBXModel() {
  356. var fbxLoader = new GLTFLoader();
  357. fbxLoader.load('/models/main.glb', object => {
  358. this.calcMeshCenter(object.scene);
  359. this.model = object.scene;
  360. object.scene.traverse(function (child) {
  361. if (child.isMesh) {
  362. child.material.emissive = child.material.color;
  363. child.material.emissiveMap = child.material.map;
  364. }
  365. });
  366. // 设置相机位置
  367. this.camera.position.set(-20, -652, 500);
  368. this.controls.target.set(-20, -20, 0);
  369. this.model.scale.set(1, 1, 1);
  370. // 设置相机坐标系
  371. this.camera.lookAt(0, 0, 0);
  372. // 将模型添加到场景中去
  373. this.scene.add(this.model);
  374. });
  375. }
  376. onWindowResizes() {
  377. if (!this.container) return false;
  378. const {clientHeight, clientWidth} = this.container;
  379. //调整屏幕大小
  380. this.camera.aspect = clientWidth / clientHeight; // 摄像机宽高比例
  381. this.camera.updateProjectionMatrix(); //相机更新矩阵,将3d内容投射到2d面上转换
  382. this.renderer.setSize(clientWidth, clientHeight);
  383. this.css3DRenderer.setSize(clientWidth, clientHeight);
  384. }
  385. calcMeshCenter(group) {
  386. /**
  387. * 包围盒全自动计算:模型整体居中
  388. */
  389. var box3 = new THREE.Box3();
  390. // 计算层级模型group的包围盒
  391. // 模型group是加载一个三维模型返回的对象,包含多个网格模型
  392. box3.expandByObject(group);
  393. // 计算一个层级模型对应包围盒的几何体中心在世界坐标中的位置
  394. var center = new THREE.Vector3();
  395. box3.getCenter(center);
  396. // 重新设置模型的位置,使之居中。
  397. group.position.x = 0;
  398. group.position.y = 0;
  399. group.position.z = 17;
  400. }
  401. addDialog(html, labelName, position) {
  402. const {x, y, z} = position
  403. const label3D = this.tag3D(labelName);
  404. const dialog3D = this.createDialog(html); //设置标签名称
  405. label3D.position.y += y;
  406. label3D.position.x += x;
  407. label3D.position.z += z;
  408. dialog3D.position.copy(label3D.position);
  409. dialog3D.position.z = z + 20;
  410. this.scene.add(label3D);
  411. this.scene.add(dialog3D);
  412. }
  413. }
  414. export default renderModel;