当前位置:   article > 正文

ThreeJS:光线投射与3D场景交互_getraycastersbyobject3d

getraycastersbyobject3d

光线投射Raycaster

        光线投射详细介绍可参考:https://en.wikipedia.org/wiki/Ray_casting

        ThreeJS中,提供了Raycaster类,用于进行鼠标拾取,即:当三维场景中鼠标移动时,利用光线投射,可计算出鼠标划过了哪些物体。

        ThreeJS官方案例webgl_interactive_cubes演示了光线投射的具体应用,

        通过Raycaster类实例的intersectObjects方法,可以获取到与射线Ray相交的一组物体;

        通过Raycaster类实例的intersectObject方法,可以获取到与Ray射线相交的第一个物体,

        此外,在鼠标点击或移动时,我们是需要不断去更新Ray射线的,这样才能选中正确的3D物体,可以通过setFromCamera方法,来更新射线,

        接着,还有一个要解决的问题,就是这里的标准化设备坐标中的二维坐标,如何进行处理才是符合ThreeJS的接口规范的?

        核心代码如下,

  1. const mousePosition = new THREE.Vector2(); //记录鼠标位置
  2. //TODO:监听鼠标移动事件
  3. function onPointerMove( event ) {
  4. mousePosition.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  5. mousePosition.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  6. }
  7. document.addEventListener("mousemove",onPointerMove);

注意到:计算mousePosition.y的计算结果,相比mousePosition.x多乘了一个-1,这是因为屏幕坐标系和ThreeJS的XYZ空间直角坐标系,两者的Y轴是相反的。

3D场景鼠标交互案例

       下面来实现一个利用光线投射控制鼠标选中物体高亮显示的案例,主要效果如下:

①鼠标选中物体,高亮显示,

②鼠标离开物体,取消高亮显示,

         完整示例代码如下,

  1. import * as THREE from "three";
  2. import { OrbitControls } from "three/addons/controls/OrbitControls.js"; //轨道控制器
  3. import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js"; //lil-gui调试工具
  4. import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; //HDR加载器
  5. import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; //GLTF加载器
  6. import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
  7. const gui = new GUI();
  8. //TODO:打印版本
  9. console.warn("threejs版本:", THREE.REVISION);
  10. //TODO:创建时钟
  11. const clock = new THREE.Clock();
  12. //TODO:创建场景
  13. const scene = new THREE.Scene();
  14. //TODO:创建透视相机
  15. const camera = new THREE.PerspectiveCamera(
  16. 45, //视角
  17. window.innerWidth / window.innerHeight,
  18. 0.1, //近平面
  19. 1000.0 //远平面
  20. );
  21. //TODO:创建渲染器
  22. const renderer = new THREE.WebGLRenderer({ antialias: true });
  23. renderer.setSize(window.innerWidth, window.innerHeight);
  24. //TODO:轨道控制器
  25. const orbitControls = new OrbitControls(camera, renderer.domElement);
  26. //设置阻尼效果
  27. // orbitControls.enableDamping = true;
  28. orbitControls.update();
  29. document.body.appendChild(renderer.domElement);
  30. //TODO:添加光源
  31. const light = new THREE.PointLight(0xffffff, 1000, 1000);
  32. light.position.set(15, 15, 15);
  33. scene.add(light);
  34. scene.background = new THREE.Color(0xbfe3dd);
  35. //TODO:添加3个球体
  36. const ball1 = new THREE.SphereGeometry(1);
  37. const ballMaterial = new THREE.MeshBasicMaterial({color:0xffff00});
  38. const ballMesh1 = new THREE.Mesh(ball1,ballMaterial);
  39. ballMesh1.geometry.scale(0.5,0.5,0.5);
  40. ballMesh1.position.set(-1.0,-1.0,-1.0);
  41. const ballMesh2 = new THREE.Mesh(ball1,ballMaterial);
  42. ballMesh2.geometry.scale(0.5,0.5,0.5);
  43. ballMesh2.position.set(1.0,-1.0,-1.0);
  44. const ballMesh3 = new THREE.Mesh(ball1,ballMaterial);
  45. ballMesh3.geometry.scale(0.5,0.5,0.5);
  46. ballMesh3.position.set(1.0,1.0,-1.0);
  47. const ballMesh4 = new THREE.Mesh(ball1,ballMaterial);
  48. ballMesh4.geometry.scale(0.5,0.5,0.5);
  49. ballMesh4.position.set(1.0,1.0,1.0);
  50. scene.add(ballMesh1);
  51. scene.add(ballMesh2);
  52. scene.add(ballMesh3);
  53. scene.add(ballMesh4);
  54. //TODO:设置相机位置
  55. camera.position.z = 5.0;
  56. camera.position.y = 2.0;
  57. camera.position.x = 2.0;
  58. camera.lookAt(0, 0, 0);
  59. //TODO:利用光线投射,使用鼠标选中Mesh
  60. const rayCaster = new THREE.Raycaster();
  61. const mousePosition = new THREE.Vector2(); //记录鼠标位置
  62. const hightLightColor = new THREE.Color(0xff0000);//高亮颜色
  63. const highlightMaterial = new THREE.MeshBasicMaterial({color:hightLightColor});
  64. const lastSelected = {
  65. originMaterial:null,//被选中的3D物体的material
  66. instance:null,//被选中的3D物体
  67. }
  68. //TODO:监听鼠标移动事件
  69. function onPointerMove( event ) {
  70. mousePosition.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  71. mousePosition.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  72. }
  73. document.addEventListener("mousemove",onPointerMove);
  74. //TODO:还原上一次被选中的3D物体
  75. function resetLastSelected(){
  76. if(lastSelected.instance && lastSelected.originMaterial){
  77. lastSelected.instance.material = lastSelected.originMaterial;
  78. lastSelected.instance = null;
  79. lastSelected.originMaterial = null;
  80. }
  81. }
  82. function pick3DObject(){
  83. // 通过摄像机和鼠标位置更新射线_[使用一个新的原点和方向来更新射线。]
  84. rayCaster.setFromCamera(mousePosition,camera);
  85. /**
  86. * objects —— 检测和射线相交的一组物体。
  87. recursive —— 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分。默认值为true。
  88. */
  89. const interscets = rayCaster.intersectObjects(scene.children,false);
  90. if(interscets.length>0){
  91. const interscetObject = interscets[ 0 ].object;
  92. console.log("interscets::",interscetObject);
  93. //TODO:还原上一次选中的3D物体
  94. resetLastSelected();
  95. if(interscetObject){
  96. //TODO:鼠标选中3D物体,设置高亮效果
  97. //记录当前被选中的物体
  98. lastSelected.instance = interscetObject;
  99. lastSelected.originMaterial = interscetObject.material;
  100. //设置高亮
  101. interscetObject.material = highlightMaterial;
  102. }
  103. }else{
  104. //TODO:鼠标离开3D物体,还原Material
  105. resetLastSelected();
  106. }
  107. }
  108. //TODO:创建坐标辅助器
  109. // const axesHelper = new THREE.AxesHelper(5);
  110. // scene.add(axesHelper);
  111. //TODO:渲染函数
  112. function animate() {
  113. requestAnimationFrame(animate);
  114. //TODO:旋转立方体
  115. // mesh.rotation.x += 0.01;
  116. // mesh.rotation.y += 0.01;
  117. pick3DObject();
  118. //TODO:更新轨道控制器
  119. orbitControls.update();
  120. //TODO:渲染
  121. renderer.render(scene, camera);
  122. }
  123. window.onresize = function () {
  124. //TODO:重置渲染器宽高比
  125. renderer.setSize(window.innerWidth, window.innerHeight);
  126. //TODO:重置相机宽高比
  127. camera.aspect = window.innerWidth / window.innerHeight;
  128. //TODO:更新相机投影矩阵
  129. camera.updateProjectionMatrix();
  130. };
  131. animate();
  132. //TODO:lil-gui添加调试按钮
  133. const myObject = {
  134. // myBoolean: true,
  135. fullScreenBtnFunction: function () {
  136. document.body.requestFullscreen();
  137. },
  138. exitFullScreenBtnFunction: function () {
  139. document.exitFullscreen();
  140. },
  141. wireframeMode: false,
  142. renderMode: 1,
  143. colorSpace: 0,
  144. };
  145. gui.add(myObject, "fullScreenBtnFunction").name("全屏显示"); // Button
  146. gui.add(myObject, "exitFullScreenBtnFunction").name("退出全屏"); // Button
  147. //TODO:开启/关闭线框模式
  148. gui
  149. .add(myObject, "wireframeMode")
  150. .name("线框模式")
  151. .onChange(function (value) {
  152. console.log(value);
  153. mesh.material.wireframe = value;
  154. });
  155. // gui.add(myObject, "myString"); // Text Field
  156. // gui.add(myObject, "myNumber"); // Number Field
  157. //TODO:控制Mesh网格正反面显示模式
  158. gui
  159. .add(myObject, "renderMode", { 双面模式: 0, 正面模式: 1, 背面模式: 2 })
  160. .name("三角形正反面显示模式")
  161. .onChange(function (value) {
  162. console.log(value);
  163. switch (value) {
  164. case 0: {
  165. triangleMesh.material.side = THREE.DoubleSide;
  166. break;
  167. }
  168. case 1: {
  169. triangleMesh.material.side = THREE.FrontSide;
  170. break;
  171. }
  172. case 2: {
  173. triangleMesh.material.side = THREE.BackSide;
  174. break;
  175. }
  176. }
  177. });
  178. //TODO:控制颜色空间
  179. gui
  180. .add(myObject, "colorSpace", {
  181. NoColorSpace: 0,
  182. SRGBColorSpace: 1,
  183. LinearSRGBColorSpace: 2,
  184. })
  185. .onChange((value) => {
  186. console.log("修改颜色空间::", value);
  187. switch (value) {
  188. case 0: {
  189. diffuseMap.colorSpace = THREE.NoColorSpace;
  190. break;
  191. }
  192. case 1: {
  193. diffuseMap.colorSpace = THREE.SRGBColorSpace;
  194. break;
  195. }
  196. case 2: {
  197. diffuseMap.colorSpace = THREE.LinearSRGBColorSpace;
  198. break;
  199. }
  200. }
  201. });

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/926330?site
推荐阅读
相关标签
  

闽ICP备14008679号