<style> body { margin: 0; overflow: hidden; } #label { position: absolute; padding: 10px; background: rgba(255, 255, 255, 0.6); line-height: 1; border-radius: 5px; } #display { height: 600px; width: 1200px; } </style>
<script src="js/three.js"></script> <script src="js/GLTFLoader.js"></script> <script src="js/OrbitControls.js"></script> <div id="WebGL-output"></div> <div id="Stats-output"></div> <div id="label"></div> <div> <canvas id="display"></canvas> </div> <script src="js/stats.min.js"></script> <script src="js/dat.gui.min.js"></script> <script type="module"> //性能优化插件 let stats = initStats(); let scene, camera, renderer, controls, light, selectObject, canvas; //加载器 let gltfLoader; //用来存外部引入的模型 let group = new THREE.Group(); //场景 function initScene() { scene = new THREE.Scene(); } //相机 function initCamera() { //与视点坐标系联动 camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000); camera.position.set(-1, 0.5, 0); camera.lookAt(new THREE.Vector3(0, 0, 0)); } //渲染器 function initRenderer() { canvas = document.querySelector('#display'); renderer = new THREE.WebGLRenderer({ canvas, antialias: true //抗锯齿 }); renderer.setSize(canvas.offsetWidth, canvas.offsetHeight); renderer.setClearColor(0xFDE7CA); } //初始化模型 function initContent() { let helper = new THREE.GridHelper(100, 50, 0xCD3700, 0x4A4A4A);//网格线 scene.add(helper); gltfLoader = new THREE.GLTFLoader(); gltfLoader.load('model/c1.glb', (gltf) => { let model = gltf.scene; //遍历模型的每一部分 //traverse这个方法可以遍历调用者和调用者的所有后代 //所以这里的o就是模型的每一部分 model.traverse((o) => { //将图片作为纹理加载 //let explosionTexture = new THREE.TextureLoader().load( // 'static/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png' //); ////调整纹理方向,默认为真。翻转图像的Y轴以匹配WebGL纹理坐标空间。 ////此处不需要反转,当然也可以试试反转以后什么效果 //explosionTexture.flipY = false; ////将纹理图生成基础网格材质(meshBasicMaterial) //const material = new THREE.MeshBasicMaterial({ // map: explosionTexture //}); ////给模型每部分上材质 //o.material = material; }); //加载外部模型时候基本上都是一个组合对象. group.add(model) scene.add(group); }); } //函数:重新设置渲染器的展示大小 function resizeRendererToDisplaySize(renderer) { //这里没看明白往上翻 const canvas = renderer.domElement; let width = window.innerWidth; let height = window.innerHeight; //判断css像素分辨率就和物理像素分辨率是否统一 let canvasPixelWidth = canvas.width / window.devicePixelRatio; let canvasPixelHeight = canvas.height / window.devicePixelRatio; //判断是否需要调整 const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height; if (needResize) { renderer.setSize(width, height, false); } return needResize; } //鼠标双击触发的方法 function onMouseDblclick(event) { //alert("a"); //获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前 let intersects = getIntersects(event); console.log(intersects); // console.log(intersects[0].object); //获取选中最近的Mesh对象 //instance坐标是对象,右边是类,判断对象是不是属于这个类的 if (intersects.length !== 0 && intersects[0].object.type === 'Mesh') { intersects[0].object.material.color.set(0x00FF00); //console.log(intersects[0].object.material.color); selectObject = intersects[0].object; //changeMaterial(selectObject) } else { console.log('未选中 Mesh!'); } } //获取与射线相交的对象数组 function getIntersects(event) { event.preventDefault();// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault //console.log("event.clientX:" + event.clientX); //console.log("event.clientY:" + event.clientY); //声明 rayCaster 和 mouse 变量 let rayCaster = new THREE.Raycaster(); let mouse = new THREE.Vector2(); //通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1 mouse.x = ((event.clientX - canvas.getBoundingClientRect().left) / canvas.offsetWidth) * 2 - 1; mouse.y = -((event.clientY - canvas.getBoundingClientRect().top) / canvas.offsetHeight) * 2 + 1; //这里为什么是-号,没有就无法点中 //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置 rayCaster.setFromCamera(mouse, camera); //获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。 //+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。 let intersects = rayCaster.intersectObjects(group.children, true); //返回选中的对象 return intersects; } // 窗口变动触发的方法 function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } //键盘按下触发的方法 function onKeyDown(event) { switch (event.keyCode) { case 13: initCamera(); initControls(); break; } } //改变对象材质属性 function changeMaterial(object) { let material = new THREE.MeshLambertMaterial({ color: 0xffffff * Math.random(), transparent: object.material.transparent ? false : true, opacity: 0.8 }); object.material = material; } //初始化轨道控制器 function initControls() { controls = new THREE.OrbitControls(camera, renderer.domElement); //controls.enableDamping = true; } // 初始化灯光 function initLight() { light = new THREE.SpotLight(0xffffff); light.position.set(-300, 600, -400); light.castShadow = true; scene.add(light); scene.add(new THREE.AmbientLight(0x5C5C5C)); } //初始化 dat.GUI function initGui() { //保存需要修改相关数据的对象 let gui = new function () { } //属性添加到控件 let guiControls = new dat.GUI(); } //初始化性能插件 function initStats() { let stats = new Stats(); stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; document.body.appendChild(stats.domElement); return stats; } //更新div的位置 // function renderDiv(object) { // //获取窗口的一半高度和宽度 // let halfWidth = window.innerWidth / 2; // let halfHeight = window.innerHeight / 2; // // //逆转相机求出二维坐标 // let vector = object.position.clone().project(camera); // // //修改div的位置 // $("#label").css({ // left: vector.x * halfWidth + halfWidth, // top: -vector.y * halfHeight + halfHeight - object.position.y // }); // // //显示模型信息 // $("#label").text("name:" + object.name); // } //更新控件 function update() { stats.update(); controls.update(); } //初始化 function init() { initScene(); initCamera(); initRenderer(); initContent(); initLight(); initControls(); initGui();//显示隐藏右上角的控制器开关 addEventListener('click', onMouseDblclick, false); addEventListener('resize', onWindowResize, false); addEventListener('keydown', onKeyDown, false); } function animate() { if (selectObject != undefined && selectObject != null) { //renderDiv(selectObject); } requestAnimationFrame(animate); renderer.render(scene, camera); update(); //判断渲染器是否调整,若调整,相机也需要调整aspect if (resizeRendererToDisplaySize(renderer)) { const canvas = renderer.domElement; //重新设置摄像机看视锥体的横纵比,横纵比一般为屏幕宽高比,不然会挤压变形 camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } } init(); animate(); </script>
用到的一些JS:https://github.com/ThisCabbage/CabbageGarden/blob/58cedeb52eed245fbbe349f21a5cfaf6905b794f/threeJs.zip