ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

threeJS射线拾取机制及案例

2019-06-26 08:49:41  阅读:722  来源: 互联网

标签:threeJS scene 拾取 THREE 案例 camera let 坐标 new


前言

在浏览器中浏览三维图形的时候,有时想要与三维图形之间做一些点击事件和交互操作,其中比较常用的一个解决方案就是使用Raycaster对象来实现(射线拾取)。

基础知识

  1. 世界坐标系:webGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。面对屏幕时,右边是x正轴,上面是y正轴,由屏幕内指向屏幕外的是z正轴。
  2. 屏幕坐标系:webGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终计算出它在显示设备上对应的位置,这个位置就称为设备坐标,也就是二维坐标。
  3. 视点坐标系: 是以视点(照相机)为原点,以视线的方向为Z轴正方向的坐标系。

原理

利用webGL,既可以将三维坐标换算为二维坐标,也可以将二维坐标换算成三维坐标。webGL会将世界坐标先换算到视点坐标,然后进行裁剪,只有在视线范围之内的场景才会进入下一阶段的计算。

效果图1

  1. 图中圆柱体是演示拾取的三维图形;
  2. 图中绿、红、蓝三条轴线分别代表三维坐标中的Y正轴、X正轴和Z正轴;
  3. 图中蓝色五角形,其实是由视点(照相机)坐标出发,以鼠标点击位置为正方向,绘制的一条箭头图形,代表绘制的射线,五角形中心即鼠标点击位

当鼠标点击圆柱体边缘以外时,圆柱体材质颜色没有发生变化,说明此时鼠标点击位置并没有拾取到圆柱体。

 

效果图2

当鼠标点击圆柱体边缘以外时,圆柱体材质颜色发生了变化,说明此时鼠标点击位置已经拾取到了圆柱体,并在控制台打印出了圆柱体的mesh对象,并可以对它进行操作。

 

 演示代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>钉钉分享</title>

    <!--threejs版本是71版本-->
    <script src="three.js"></script>
</head>

<body style="margin: 0;overflow: hidden;">

    <!-- 输出容器 -->
    <div id="webgl-output"></div>

    <!-- 操作代码 -->
    <script>

        // 声名Threejs对象(场景、相机、渲染器)
        var scene, camera, renderer;

        // 初始化
        function init() {

            // 添加浏览器窗口大小监听事件(画布自适应方法onResize)
            window.addEventListener('resize', onResize, false);

            // 鼠标点击拾取
            document.addEventListener('click', initRay, false);

            // 创建场景
            scene = new THREE.Scene();

            // 创建相机(设置相机位置,设置相机朝向)
            camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(10, 50, 50);
            camera.lookAt(scene.position);

            // 创建渲染器
            renderer = new THREE.WebGLRenderer();
            renderer.setClearColor(0xEEEEEE);
            renderer.setSize(window.innerWidth, window.innerHeight);

            // 创建辅助轴线
            var axis = new THREE.AxisHelper(100);
            scene.add(axis);

            // 添加图形---圆柱体
            let _radius = 5;
            var cylinderGeometry = new THREE.CylinderGeometry(_radius, _radius, 20, 40, 40);
            var cylinderMaterial = new THREE.MeshBasicMaterial({ color: 0x765432 });
            var cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
            scene.add(cylinder);

            // 将图形输出到页面
            document.getElementById('webgl-output').appendChild(renderer.domElement);

            // 逐帧绘制方法
            function renderScene() {
                requestAnimationFrame(renderScene);
                renderer.render(scene, camera);
            }

            // 开启渲染
            renderScene();
        }

        // 画布自适应
        function onResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        // 射线拾取
        function initRay(event) {

            // 获取画布
            let mainCanvas = document.querySelector("#webgl-output canvas");

            // 将屏幕坐标转为标准设备坐标(支持画布非全屏的情况)
            let x = ((event.clientX - mainCanvas.getBoundingClientRect().left) / mainCanvas.offsetWidth) * 2 - 1;   // 设备横坐标
            let y = -((event.clientY - mainCanvas.getBoundingClientRect().top) / mainCanvas.offsetHeight) * 2 + 1;  // 设备纵坐标
            let standardVector = new THREE.Vector3(x, y, 1);                                                        // 设备坐标

            // 标准设备坐标转为世界坐标
            let worldVector = standardVector.unproject(camera);

            // 射线投射方向单位向量(worldVector坐标减相机位置坐标)
            let ray = worldVector.sub(camera.position).normalize();

            // 绘制射线
            drawRay(scene, camera.position, ray);

            // 创建射线投射器对象
            let rayCaster = new THREE.Raycaster(camera.position, ray);

            // 返回射线选中的对象数组(第二个参数默认值是false,意为是否遍历图形内部的所有子图形)
            let intersects = rayCaster.intersectObjects(scene.children, true);
            if (intersects.length > 0) {

                // 射线拾取的首个对象
                let currObj = intersects[0];

                console.log(currObj);

                // 改变被拾取对象的材质颜色(随机)
                let currcolor = `rgb(${Math.floor(Math.random() * 256).toString()},${Math.floor(Math.random() * 256).toString()},${Math.floor(Math.random() * 256).toString()})`
                currObj.object.material.color.set(currcolor);
            }
        }


        // 绘制射线(箭头射线)
        function drawRay(scene, start, dir) {
            let prevRay = scene.getObjectByName("customRay");
            if (prevRay) {
                scene.remove(prevRay);
            }

            let arrow = new THREE.ArrowHelper(dir, start, 1000, 0x0000ff);
            arrow.name = "customRay";
            scene.add(arrow);
        }

        // 初始化
        window.onload = init();
    </script>
</body>

</html>

 

参考原文地址: https://segmentfault.com/a/1190000010490845

标签:threeJS,scene,拾取,THREE,案例,camera,let,坐标,new
来源: https://www.cnblogs.com/sangzs/p/11087838.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有