本例参照 https://threejsfundamentals.org/threejs/lessons/zh_cn/threejs-picking.html
原理:
首先,物体有id,并且自身颜色根据id数值计算出的,
然后,获取鼠标点击位置的1像素的颜色值,反推id,将对应物体就移除。
缺陷就是基于颜色,如果场景中颜色很多,及物体的颜色比较相近,在视野比较远时-物体离相机很远,会出现‘隔山打牛’的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
<canvas id="canvas"/> <script> let head = document.getElementsByTagName("head")[0]; let three_script = document.createElement("script"); three_script.src = "https://github.com/mrdoob/three.js/blob/dev/build/three_r132_min.js"; head.appendChild(three_script); three_script.onload = function () { let cinema = new Cinema(); cinema.render(); }; class LightFactory { static getDirectionalLight(color, x, y, z) { let light = new THREE.DirectionalLight(color, 1); light.position.set(x, y, z); return light; } static getAmbientLight(color) { let light = new THREE.AmbientLight(); light.color.set(color); return light; } } class BoxFactory { constructor(scene, pickingScene) { this.scene = scene; this.pickingScene = pickingScene; this.boxGeom = new THREE.BoxGeometry(1, 1, 1); let loader = new THREE.TextureLoader(); this.texture = loader.load('https://threejsfundamentals.org/threejs/resources/images/frame.png'); } addBox(size) { let idToObject = {}; for (let i = 0; i < size; ++i) { let id = i + 1; var color = "#"; for (let k = 0; k < 6; k++) color += parseInt(Math.random() * 16).toString(16); let material = new THREE.MeshPhongMaterial({ color: color, map: this.texture, transparent: true, side: THREE.DoubleSide, alphaTest: 0.5, }); let cube = new THREE.Mesh(this.boxGeom, material); let x = THREE.Math.randFloatSpread(40); let y = THREE.Math.randFloatSpread(40); let z = THREE.Math.randFloatSpread(40); cube.position.set(x, y, z); let rx = THREE.Math.randFloatSpread(Math.PI); let ry = THREE.Math.randFloatSpread(Math.PI); cube.rotation.set(rx, ry, 0); let sx = THREE.Math.randFloat(3, 6); let sy = THREE.Math.randFloat(3, 6); let sz = THREE.Math.randFloat(3, 6); cube.scale.set(sx, sy, sz); idToObject[id] = cube; this.scene.add(cube); } return idToObject; } addPickingBox(boxs) { let idToObject = {}; let size = Object.keys(boxs).length; for (let i = 0; i < size; ++i) { let id = i + 1; let color = new THREE.Color(); color.setHex(id); // 生成16进制颜色 let pickingMaterial = new THREE.MeshPhongMaterial({ emissive: color, // 【 设置此属性才有交互,因为后面就是根据颜色判断的 】 map: this.texture, // 透明区域不交互 transparent: true, // 透明区域不交互 color: new THREE.Color(0, 0, 0), // 提高颜色判断的精度 specular: new THREE.Color(0, 0, 0), // 提高颜色判断的精度 side: THREE.DoubleSide, alphaTest: 0.5, }); let cube = boxs[id]; let pickingCube = new THREE.Mesh(this.boxGeom, pickingMaterial); pickingCube.position.copy(cube.position); pickingCube.rotation.copy(cube.rotation); pickingCube.scale.copy(cube.scale); idToObject[id] = pickingCube; this.pickingScene.add(pickingCube); } return idToObject; } } class Cinema { constructor() { this.canvas = document.getElementById('canvas'); this.canvas.width = 800; this.canvas.height = 350; this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas, }); let aspect = this.canvas.width / this.canvas.height; this.camera = new THREE.PerspectiveCamera(60, aspect, 0.5, 500); this.camera.position.z = 30; let _controls = new THREE.OrbitControls(this.camera, this.canvas); this.scene = new THREE.Scene(); // 主场景 this.scene.background = new THREE.Color('#cfefdd'); this.pickingScene = new THREE.Scene(); // 副本场景 this.pickingScene.background = new THREE.Color(0); let ambientLight = LightFactory.getAmbientLight("#ffffff"); this.scene.add(ambientLight); let directionalLight = LightFactory.getDirectionalLight("#ffffff", 1, 2, 3); this.scene.add(directionalLight); let bof = new BoxFactory(this.scene, this.pickingScene); this.boxs = bof.addBox(90); // 主场景添加物体 this.pickingBoxs = bof.addPickingBox(this.boxs); // 为主场景中的物体制作副本 this.pickPosition = {x: -100000, y: -100000}; // 鼠标的实时位置 this.canvas.addEventListener('click', this.clickCallback.bind(this)); // 鼠标点击 // 鼠标点击后滚轮推远视窗的处理。本例this.canvas添加事件无效,可能和OrbitControls冲突 document.addEventListener('mousewheel', this.clearCallback.bind(this)); document.addEventListener('mousemove', this.clearCallback.bind(this)); } clearCallback(event) { this.pickPosition = {x: -100000, y: -100000}; } clickCallback(event) { let rect = this.canvas.getBoundingClientRect(); let pos = { x: (event.clientX - rect.left) * this.canvas.width / rect.width, y: (event.clientY - rect.top) * this.canvas.height / rect.height, }; this.pickPosition.x = pos.x; this.pickPosition.y = pos.y; } pick() { let pixelRatio = this.renderer.getPixelRatio(); // 当前设备像素比率 let fullWidth = this.renderer.getContext().drawingBufferWidth; let fullHeight = this.renderer.getContext().drawingBufferHeight; this.camera.setViewOffset( // 视图偏移。添加一个'副相机'设置1像素的视图 fullWidth, // 主相机宽 fullHeight, // 主相机高 this.pickPosition.x * pixelRatio | 0, // 副相机x,对应鼠标的位置 this.pickPosition.y * pixelRatio | 0, // 副相机y,对应鼠标的位置 1, // 副相机width 1, // 副相机height ); let pixelBuffer = new Uint8Array(4); let pickingTexture = new THREE.WebGLRenderTarget(1, 1); // 1像素存储对象 this.renderer.setRenderTarget(pickingTexture); // 设置渲染结果的存储对象 this.renderer.render(this.pickingScene, this.camera); // 渲染副相机-鼠标位置 this.renderer.setRenderTarget(null); // 恢复默认渲染 this.camera.clearViewOffset(); // 清除'副相机'视图 this.renderer.readRenderTargetPixels( // 读取副相机的渲染结果,存到pixelbuffer pickingTexture, 0, 0, // x, y 1, 1, // w, h pixelBuffer); let id = // 【 将物体的颜色转为id 】 (pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | (pixelBuffer[2]); return id; } shoot(id) { let intersectedObject = this.boxs[id]; if (typeof(intersectedObject) !== "undefined" && null !== intersectedObject) { // 删除物体 this.scene.remove(intersectedObject); delete this.boxs[id]; // 删除副本 let pb = this.pickingBoxs[id]; this.pickingScene.remove(pb); delete this.pickingBoxs[id]; // 鼠标定位复位 this.clearCallback(); } } render() { let id = this.pick(); // 鼠标接触物体检测 this.shoot(id); // 射击 this.renderer.render(this.scene, this.camera); requestAnimationFrame(this.render.bind(this)); } } </script> |
- end
声明
本文由崔维友 威格灵 cuiweiyou vigiles cuiweiyou 原创,转载请注明出处:http://www.gaohaiyan.com/3127.html
承接App定制、企业web站点、办公系统软件 设计开发,外包项目,毕设