赞
踩
光线投射详细介绍可参考:https://en.wikipedia.org/wiki/Ray_casting,
ThreeJS中,提供了Raycaster类,用于进行鼠标拾取,即:当三维场景中鼠标移动时,利用光线投射,可计算出鼠标划过了哪些物体。
ThreeJS官方案例webgl_interactive_cubes演示了光线投射的具体应用,
通过Raycaster类实例的intersectObjects方法,可以获取到与射线Ray相交的一组物体;
通过Raycaster类实例的intersectObject方法,可以获取到与Ray射线相交的第一个物体,
此外,在鼠标点击或移动时,我们是需要不断去更新Ray射线的,这样才能选中正确的3D物体,可以通过setFromCamera方法,来更新射线,
接着,还有一个要解决的问题,就是这里的标准化设备坐标中的二维坐标,如何进行处理才是符合ThreeJS的接口规范的?
核心代码如下,
- const mousePosition = new THREE.Vector2(); //记录鼠标位置
- //TODO:监听鼠标移动事件
- function onPointerMove( event ) {
- mousePosition.x = ( event.clientX / window.innerWidth ) * 2 - 1;
- mousePosition.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
- }
- document.addEventListener("mousemove",onPointerMove);
注意到:计算mousePosition.y的计算结果,相比mousePosition.x多乘了一个-1,这是因为屏幕坐标系和ThreeJS的XYZ空间直角坐标系,两者的Y轴是相反的。
下面来实现一个利用光线投射控制鼠标选中物体高亮显示的案例,主要效果如下:
①鼠标选中物体,高亮显示,
②鼠标离开物体,取消高亮显示,
完整示例代码如下,
- import * as THREE from "three";
- import { OrbitControls } from "three/addons/controls/OrbitControls.js"; //轨道控制器
- import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js"; //lil-gui调试工具
- import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; //HDR加载器
- import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; //GLTF加载器
- import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
-
- const gui = new GUI();
-
- //TODO:打印版本
- console.warn("threejs版本:", THREE.REVISION);
- //TODO:创建时钟
- const clock = new THREE.Clock();
- //TODO:创建场景
- const scene = new THREE.Scene();
- //TODO:创建透视相机
- const camera = new THREE.PerspectiveCamera(
- 45, //视角
- window.innerWidth / window.innerHeight,
- 0.1, //近平面
- 1000.0 //远平面
- );
-
- //TODO:创建渲染器
- const renderer = new THREE.WebGLRenderer({ antialias: true });
- renderer.setSize(window.innerWidth, window.innerHeight);
-
- //TODO:轨道控制器
- const orbitControls = new OrbitControls(camera, renderer.domElement);
- //设置阻尼效果
- // orbitControls.enableDamping = true;
- orbitControls.update();
-
- document.body.appendChild(renderer.domElement);
-
- //TODO:添加光源
- const light = new THREE.PointLight(0xffffff, 1000, 1000);
- light.position.set(15, 15, 15);
- scene.add(light);
-
- scene.background = new THREE.Color(0xbfe3dd);
-
-
- //TODO:添加3个球体
- const ball1 = new THREE.SphereGeometry(1);
- const ballMaterial = new THREE.MeshBasicMaterial({color:0xffff00});
- const ballMesh1 = new THREE.Mesh(ball1,ballMaterial);
- ballMesh1.geometry.scale(0.5,0.5,0.5);
- ballMesh1.position.set(-1.0,-1.0,-1.0);
-
- const ballMesh2 = new THREE.Mesh(ball1,ballMaterial);
- ballMesh2.geometry.scale(0.5,0.5,0.5);
- ballMesh2.position.set(1.0,-1.0,-1.0);
-
- const ballMesh3 = new THREE.Mesh(ball1,ballMaterial);
- ballMesh3.geometry.scale(0.5,0.5,0.5);
- ballMesh3.position.set(1.0,1.0,-1.0);
-
- const ballMesh4 = new THREE.Mesh(ball1,ballMaterial);
- ballMesh4.geometry.scale(0.5,0.5,0.5);
- ballMesh4.position.set(1.0,1.0,1.0);
-
- scene.add(ballMesh1);
- scene.add(ballMesh2);
- scene.add(ballMesh3);
- scene.add(ballMesh4);
-
- //TODO:设置相机位置
- camera.position.z = 5.0;
- camera.position.y = 2.0;
- camera.position.x = 2.0;
- camera.lookAt(0, 0, 0);
-
- //TODO:利用光线投射,使用鼠标选中Mesh
- const rayCaster = new THREE.Raycaster();
- const mousePosition = new THREE.Vector2(); //记录鼠标位置
- const hightLightColor = new THREE.Color(0xff0000);//高亮颜色
- const highlightMaterial = new THREE.MeshBasicMaterial({color:hightLightColor});
- const lastSelected = {
- originMaterial:null,//被选中的3D物体的material
- instance:null,//被选中的3D物体
- }
- //TODO:监听鼠标移动事件
- function onPointerMove( event ) {
- mousePosition.x = ( event.clientX / window.innerWidth ) * 2 - 1;
- mousePosition.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
- }
- document.addEventListener("mousemove",onPointerMove);
-
-
- //TODO:还原上一次被选中的3D物体
- function resetLastSelected(){
- if(lastSelected.instance && lastSelected.originMaterial){
- lastSelected.instance.material = lastSelected.originMaterial;
- lastSelected.instance = null;
- lastSelected.originMaterial = null;
- }
- }
-
- function pick3DObject(){
- // 通过摄像机和鼠标位置更新射线_[使用一个新的原点和方向来更新射线。]
- rayCaster.setFromCamera(mousePosition,camera);
- /**
- * objects —— 检测和射线相交的一组物体。
- recursive —— 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分。默认值为true。
- */
- const interscets = rayCaster.intersectObjects(scene.children,false);
- if(interscets.length>0){
- const interscetObject = interscets[ 0 ].object;
- console.log("interscets::",interscetObject);
- //TODO:还原上一次选中的3D物体
- resetLastSelected();
- if(interscetObject){
- //TODO:鼠标选中3D物体,设置高亮效果
- //记录当前被选中的物体
- lastSelected.instance = interscetObject;
- lastSelected.originMaterial = interscetObject.material;
- //设置高亮
- interscetObject.material = highlightMaterial;
- }
- }else{
- //TODO:鼠标离开3D物体,还原Material
- resetLastSelected();
- }
- }
-
- //TODO:创建坐标辅助器
- // const axesHelper = new THREE.AxesHelper(5);
- // scene.add(axesHelper);
-
- //TODO:渲染函数
- function animate() {
- requestAnimationFrame(animate);
- //TODO:旋转立方体
- // mesh.rotation.x += 0.01;
- // mesh.rotation.y += 0.01;
- pick3DObject();
- //TODO:更新轨道控制器
- orbitControls.update();
- //TODO:渲染
- renderer.render(scene, camera);
- }
- window.onresize = function () {
- //TODO:重置渲染器宽高比
- renderer.setSize(window.innerWidth, window.innerHeight);
- //TODO:重置相机宽高比
- camera.aspect = window.innerWidth / window.innerHeight;
- //TODO:更新相机投影矩阵
- camera.updateProjectionMatrix();
- };
-
- animate();
-
- //TODO:lil-gui添加调试按钮
- const myObject = {
- // myBoolean: true,
- fullScreenBtnFunction: function () {
- document.body.requestFullscreen();
- },
- exitFullScreenBtnFunction: function () {
- document.exitFullscreen();
- },
- wireframeMode: false,
- renderMode: 1,
- colorSpace: 0,
- };
-
- gui.add(myObject, "fullScreenBtnFunction").name("全屏显示"); // Button
- gui.add(myObject, "exitFullScreenBtnFunction").name("退出全屏"); // Button
- //TODO:开启/关闭线框模式
- gui
- .add(myObject, "wireframeMode")
- .name("线框模式")
- .onChange(function (value) {
- console.log(value);
- mesh.material.wireframe = value;
- });
- // gui.add(myObject, "myString"); // Text Field
- // gui.add(myObject, "myNumber"); // Number Field
-
- //TODO:控制Mesh网格正反面显示模式
- gui
- .add(myObject, "renderMode", { 双面模式: 0, 正面模式: 1, 背面模式: 2 })
- .name("三角形正反面显示模式")
- .onChange(function (value) {
- console.log(value);
- switch (value) {
- case 0: {
- triangleMesh.material.side = THREE.DoubleSide;
- break;
- }
- case 1: {
- triangleMesh.material.side = THREE.FrontSide;
- break;
- }
- case 2: {
- triangleMesh.material.side = THREE.BackSide;
- break;
- }
- }
- });
-
- //TODO:控制颜色空间
- gui
- .add(myObject, "colorSpace", {
- NoColorSpace: 0,
- SRGBColorSpace: 1,
- LinearSRGBColorSpace: 2,
- })
- .onChange((value) => {
- console.log("修改颜色空间::", value);
- switch (value) {
- case 0: {
- diffuseMap.colorSpace = THREE.NoColorSpace;
- break;
- }
- case 1: {
- diffuseMap.colorSpace = THREE.SRGBColorSpace;
- break;
- }
- case 2: {
- diffuseMap.colorSpace = THREE.LinearSRGBColorSpace;
- break;
- }
- }
- });
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。