<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