3D正方形颜色和信息展示
练习一个小功能,把知识点串起来使用,写一个中间正方形模型,我可以使用按钮改变它的颜色,并且点击模型,利用射线拾取,获取模型信息,弹框粗略的展示一下信息,增加记忆点。
涉及代码:
<template> <div class="container"> <div class="dialogBox" v-if="textShow"> <div style="font-size: 20px; font-weight: bold;margin: 5px 0;">物体信息</div> <div>选中物体: {{ selectedObject.name }}</div> <div>选中物体位置: {{ selectedObject.position }}</div> </div> <div class="btnBox"> <div class="btn" v-for="item in btnList" :key="item.name" :style="{ backgroundColor: item.color }" @click="handleColorClick(item.color)" > {{ item.name }} </div> </div> </div> </template> <script setup> import { ref, onMounted } from "vue"; import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; // 轨道控制器 let scene = null; //场景 let camera = null; //相机 let renderer = null; //渲染器 let controls = null; //控制器 const cubes = []; const textShow = ref(false); //是否显示弹框 const btnList = ref([ { name: "红色", color: "#ff0000", }, { name: "绿色", color: "#00ff00", }, { name: "蓝色", color: "#0000ff", }, ]); //按钮列表 import imgUrl from "../assets/img/backImg.png"; const initThreeMap = () => { scene = new THREE.Scene(); //场景 scene.background = new THREE.Color("black"); //设置场景背景色 const fog = new THREE.Fog(0xffffff, 0.005, 1000); //雾 scene.fog = fog; //添加雾 // 添加光源 const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); setCamera(); //设置相机 setRenderer(); //设置渲染器 initMesh(); //初始化网格 window.addEventListener("resize", () => { //监听窗口大小变化 camera.aspect = window.innerWidth / window.innerHeight; //设置相机纵横比 camera.updateProjectionMatrix(); //更新相机投影矩阵 renderer.setSize(window.innerWidth, window.innerHeight); //设置渲染器大小 }); setControls(); //设置控制器 }; const setCamera = () => { camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); //透视相机 camera.position.z = 10; //设置相机位置 camera.position.y = 5; //设置相机位置 camera.lookAt(0, 0, 0); //设置相机目标点 }; const setRenderer = () => { renderer = new THREE.WebGLRenderer({ antialias: true }); //渲染器 renderer.setSize(window.innerWidth, window.innerHeight); //设置渲染器大小 // 【关键步骤1】开启渲染器的阴影映射功能 renderer.shadowMap.enabled = true; // 可选:使用柔和阴影,边缘更自然 renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild(renderer.domElement); //将渲染器添加到页面中 }; let cubeMesh = null; //立方体网格 const initMesh = () => { const cubeGeometry = new THREE.BoxGeometry(3, 3, 3); //立方体 const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000, metalness: 0.5, roughness: 0.0, }); //材质 cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial); //网格 cubeMesh.position.set(0, 1.5, 2); //设置网格位置 cubeMaterial.transparent = true; //设置透明度 // cubeMaterial.opacity = 0.5; //设置透明度 cubeMesh.castShadow = true; //设置网格投射阴影 cubeMesh.name = "测试模型"; //设置立方体网格名称 console.log(cubeMesh); const light = new THREE.DirectionalLight(0xffffff, 2, 100); //点光源 light.position.set(0, 5, 10); //设置点光源位置 light.castShadow = true; scene.add(light); //添加到场景 const lightHelper = new THREE.DirectionalLightHelper(light, 1); // scene.add(lightHelper); // 4. 创建阴影接收平面 (地面) const planeGeometry = new THREE.PlaneGeometry(20, 20); const planeMaterial = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.8, metalness: 1, side: THREE.DoubleSide, }); const plane = new THREE.Mesh(planeGeometry, planeMaterial); // 【关键步骤3】旋转平面使其水平 plane.rotation.x = -Math.PI / 2; plane.position.y = 0; // 位于 y=0 高度 // 【关键步骤4】允许平面接收阴影 plane.receiveShadow = true; scene.add(plane); scene.add(cubeMesh); //添加到场景 }; const raycaster = new THREE.Raycaster(); //射线投射器 const mouse = new THREE.Vector2(); //鼠标 let selectedObject = ref(null); //选中物体 const handleClick = (event) => { // 获取 Canvas 在页面中的位置 const rect = renderer.domElement.getBoundingClientRect(); console.log("rect =>", rect); // 计算归一化设备坐标 (NDC) mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; checkIntersection(event); }; window.addEventListener("click", handleClick, false); const checkIntersection = (event) => { raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(scene.children, true); console.log("intersects =>", intersects); if (intersects.length > 0) { console.log("选中物体:", intersects[0].object); textShow.value = true; if (intersects[0].object.name === "测试模型") { selectedObject.value = intersects[0].object; } } }; const handleColorClick = (color) => { console.log(color); cubeMesh.material.color.set(color); cubeMesh.material.needsUpdate = true; cubeMesh.name = "测试模型:" + color; cubeMesh.color2 = color; textShow.value = false; }; const setControls = () => { controls = new OrbitControls(camera, renderer.domElement); //控制器 controls.minPolarAngle = 0; controls.maxPolarAngle = Math.PI / 2; controls.enableDamping = true; // 给控制器添加阻尼,必须在动画循环里调用.update()。 controls.update(); //更新控制器 }; const animate = () => { requestAnimationFrame(animate); //请求动画帧 renderer.render(scene, camera); //渲染场景和相机 controls.update(); //更新控制器 }; onMounted(() => { initThreeMap(); //初始化 animate(); //动画 }); </script> <style scoped> .container { width: 100%; height: 100vh; position: relative; } .dialogBox { position: absolute; top: 30%; left: 50%; width: 400px; height: 100px; color: #000; background-color: #fff; z-index: 9999; } .btnBox { position: absolute; bottom: 80px; left: 50%; right: 50%; } .btn { display: inline-block; margin: 10px; width: 100px; height: 30px; line-height: 30px; text-align: center; border-radius: 5px; background-color: #ff0000; color: #fff; cursor: pointer; } </style>