Three.js为外部引入模型添加点击事件

写在前头

首先是很感谢https://blog.csdn.net/ithanmang/article/details/80965712?spm=1001.2014.3001.5501这篇文章的作者,给予了指点。

以下就是对于外部模型点击事件的添加过程。

import错误:

解决**** is not a constructor 问题处理
https://blog.csdn.net/weixin_40532650/article/details/107537017

计算错误:

canvas的大小不是整个屏幕的大小,参考:https://blog.csdn.net/u013090676/article/details/77188088

无法识别模型:

问题:

  1. 首先是,关于导入模型类型。看这篇,https://blog.csdn.net/ithanmang/article/details/80965712?spm=1001.2014.3001.5501。大体就是说,导入模型是Group,需要new一个THREE.Group()对象来保存,再存入scene中。具体代码看下面。
  2. 第二个就是,鼠标点击模型后,会获取到一个数组。这个数组里面保存着射线首先穿透到最后穿透的对象,就是由近到远的对象。这个数组的类型虽然是Mesh类型的,但是无法使用instanceof,下面代码的解决方法是看object.type === ‘Mesh’,也就是判断类型字符串是不是Mesh

完整代码(设置画布):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>点击事件</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }

        #label {
            position: absolute;
            padding: 10px;
            background: rgba(255, 255, 255, 0.6);
            line-height: 1;
            border-radius: 5px;
        }

        #display {
            height: 600px;
            width: 600px;
        }
    </style>
</head>
<body>
<div id="WebGL-output"></div>
<div id="Stats-output"></div>
<div id="label"></div>
<div>
    <canvas id="display"></canvas>
</div>

<script src="libs/three/build/three.js"></script>
<script src="libs/jquery/dist/jquery.js"></script>
<script src="libs/three/examples/js/controls/TrackballControls.js"></script>
<script src="libs/three/examples/js/libs/dat.gui.min.js"></script>
<script src="libs/three/examples/js/libs/stats.min.js"></script>
<script type="module">
    // import * as THREE from './libs/three/build/three.js'
    // import * as $ from './libs/jquery/dist/jquery.js'
    // import * as dat from './libs/three/examples/js/libs/dat.gui.min.js'
    // import * as Stats from './libs/three/examples/js/libs/stats.min.js'
    import {GLTFLoader} from "./libs/three/examples/jsm/loaders/GLTFLoader.js";
    import {OrbitControls} from "./libs/three/examples/jsm/controls/OrbitControls.js";

    //性能优化插件
    let stats = initStats();
    let scene, camera, renderer, controls, light, selectObject, canvas;
    //加载器
    let gltfLoader;
    //用来存外部引入的模型
    let group = new THREE.Group();

    //场景
    function initScene() {
        scene = new THREE.Scene();
    }

    //相机
    function initCamera() {
        //与视点坐标系联动
        camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
        camera.position.set(-4, 2, 0);
        camera.lookAt(new THREE.Vector3(0, 0, 0));
    }

    //渲染器
    function initRenderer() {
        canvas = document.querySelector('#display');
        renderer = new THREE.WebGLRenderer({
            canvas,
            antialias: true //抗锯齿
        });
        renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
        renderer.setClearColor(0x050505);
    }

    //初始化模型
    function initContent() {
        let helper = new THREE.GridHelper(100, 50, 0xCD3700, 0x4A4A4A);
        scene.add(helper);

        gltfLoader = new GLTFLoader();
        gltfLoader.load('static/seraphine/scene.gltf', (gltf) => {

            let model = gltf.scene;

            //遍历模型的每一部分
            //traverse这个方法可以遍历调用者和调用者的所有后代
            //所以这里的o就是模型的每一部分
            model.traverse((o) => {
                //将图片作为纹理加载
                let explosionTexture = new THREE.TextureLoader().load(
                    'static/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png'
                );
                //调整纹理方向,默认为真。翻转图像的Y轴以匹配WebGL纹理坐标空间。
                //此处不需要反转,当然也可以试试反转以后什么效果
                explosionTexture.flipY = false;
                //将纹理图生成基础网格材质(meshBasicMaterial)
                const material = new THREE.MeshBasicMaterial({
                    map: explosionTexture
                });
                //给模型每部分上材质
                o.material = material;

            });
            //加载外部模型时候基本上都是一个组合对象.

            group.add(model)
            scene.add(group);
        });

    }

    //函数:重新设置渲染器的展示大小
    function resizeRendererToDisplaySize(renderer) {
        //这里没看明白往上翻
        const canvas = renderer.domElement;
        let width = window.innerWidth;
        let height = window.innerHeight;
        //判断css像素分辨率就和物理像素分辨率是否统一
        let canvasPixelWidth = canvas.width / window.devicePixelRatio;
        let canvasPixelHeight = canvas.height / window.devicePixelRatio;
        //判断是否需要调整
        const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height;
        if (needResize) {
            renderer.setSize(width, height, false);
        }
        return needResize;
    }


    //鼠标双击触发的方法
    function onMouseDblclick(event) {
        //获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
        let intersects = getIntersects(event);
        // console.log(intersects);
        // console.log(intersects[0].object);

        //获取选中最近的Mesh对象
        //instance坐标是对象,右边是类,判断对象是不是属于这个类的
        if (intersects.length !== 0 && intersects[0].object.type === 'Mesh') {
            selectObject = intersects[0].object;
            //changeMaterial(selectObject)
        } else {
            console.log('未选中 Mesh!');
        }
    }

    //获取与射线相交的对象数组
    function getIntersects(event) {
        event.preventDefault();// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault
        //console.log("event.clientX:" + event.clientX);
        //console.log("event.clientY:" + event.clientY);

        //声明 rayCaster 和 mouse 变量
        let rayCaster = new THREE.Raycaster();
        let mouse = new THREE.Vector2();

        //通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1
        mouse.x = ((event.clientX - canvas.getBoundingClientRect().left) / canvas.offsetWidth) * 2 - 1;
        mouse.y = -((event.clientY - canvas.getBoundingClientRect().top) / canvas.offsetHeight) * 2 + 1; //这里为什么是-号,没有就无法点中

        //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
        rayCaster.setFromCamera(mouse, camera);

        //获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。
        //+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。
        let intersects = rayCaster.intersectObjects(group.children, true);

        //返回选中的对象
        return intersects;
    }

    // 窗口变动触发的方法
    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }

    //键盘按下触发的方法
    function onKeyDown(event) {
        switch (event.keyCode) {
            case 13:
                initCamera();
                initControls();
                break;
        }
    }

    //改变对象材质属性
    function changeMaterial(object) {
        let material = new THREE.MeshLambertMaterial({
            color: 0xffffff * Math.random(),
            transparent: object.material.transparent ? false : true,
            opacity: 0.8
        });
        object.material = material;
    }

    //初始化轨道控制器
    function initControls() {
        controls = new OrbitControls(camera, renderer.domElement);
        //controls.enableDamping = true;
    }

    // 初始化灯光
    function initLight() {
        light = new THREE.SpotLight(0xffffff);
        light.position.set(-300, 600, -400);
        light.castShadow = true;

        scene.add(light);
        scene.add(new THREE.AmbientLight(0x5C5C5C));
    }

    //初始化 dat.GUI
    function initGui() {
        //保存需要修改相关数据的对象
        let gui = new function () {

        }
        //属性添加到控件
        let guiControls = new dat.GUI();
    }

    //初始化性能插件
    function initStats() {
        let stats = new Stats();

        stats.domElement.style.position = 'absolute';
        stats.domElement.style.left = '0px';
        stats.domElement.style.top = '0px';

        document.body.appendChild(stats.domElement);
        return stats;
    }

    //更新div的位置
    // function renderDiv(object) {
    //     //获取窗口的一半高度和宽度
    //     let halfWidth = window.innerWidth / 2;
    //     let halfHeight = window.innerHeight / 2;
    //
    //     //逆转相机求出二维坐标
    //     let vector = object.position.clone().project(camera);
    //
    //     //修改div的位置
    //     $("#label").css({
    //         left: vector.x * halfWidth + halfWidth,
    //         top: -vector.y * halfHeight + halfHeight - object.position.y
    //     });
    //
    //     //显示模型信息
    //     $("#label").text("name:" + object.name);
    // }

    //更新控件
    function update() {
        stats.update();
        controls.update();
    }

    //初始化
    function init() {
        initScene();
        initCamera();
        initRenderer();
        initContent();
        initLight();
        initControls();
        initGui();
        addEventListener('click', onMouseDblclick, false);
        addEventListener('resize', onWindowResize, false);
        addEventListener('keydown', onKeyDown, false);
    }

    function animate() {
        if (selectObject != undefined && selectObject != null) {
            //renderDiv(selectObject);
        }
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        update();
        //判断渲染器是否调整,若调整,相机也需要调整aspect
        if (resizeRendererToDisplaySize(renderer)) {
            const canvas = renderer.domElement;
            //重新设置摄像机看视锥体的横纵比,横纵比一般为屏幕宽高比,不然会挤压变形
            camera.aspect = canvas.clientWidth / canvas.clientHeight;
            camera.updateProjectionMatrix();
        }
    }

    init();
    animate();
</script>

</body>
</html>


画布为整个屏幕:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>点击事件</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }

        #label {
            position: absolute;
            padding: 10px;
            background: rgba(255, 255, 255, 0.6);
            line-height: 1;
            border-radius: 5px;
        }


    </style>
</head>
<body>
<div id="WebGL-output"></div>
<div id="Stats-output"></div>
<div id="label"></div>

<script src="libs/three/build/three.js"></script>
<script src="libs/jquery/dist/jquery.js"></script>
<script src="libs/three/examples/js/controls/TrackballControls.js"></script>
<script src="libs/three/examples/js/libs/dat.gui.min.js"></script>
<script src="libs/three/examples/js/libs/stats.min.js"></script>
<script type="module">
    // import * as THREE from './libs/three/build/three.js'
    // import * as $ from './libs/jquery/dist/jquery.js'
    // import * as dat from './libs/three/examples/js/libs/dat.gui.min.js'
    // import * as Stats from './libs/three/examples/js/libs/stats.min.js'
    import {GLTFLoader} from "./libs/three/examples/jsm/loaders/GLTFLoader.js";
    import {OrbitControls} from "./libs/three/examples/jsm/controls/OrbitControls.js";

    //性能优化插件
    let stats = initStats();
    let scene, camera, renderer, controls, light, selectObject, canvas;
    //加载器
    let gltfLoader;
    //用来存外部引入的模型
    let group = new THREE.Group();

    //场景
    function initScene() {
        scene = new THREE.Scene();
    }

    //相机
    function initCamera() {
        //与视点坐标系联动
        camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
        camera.position.set(-4, 2, 0);
        camera.lookAt(new THREE.Vector3(0, 0, 0));
    }

    //渲染器
    function initRenderer() {
        renderer = new THREE.WebGLRenderer({
            antialias: true //抗锯齿
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x050505);
        document.body.appendChild(renderer.domElement);
    }

    //初始化模型
    function initContent() {
        let helper = new THREE.GridHelper(100, 50, 0xCD3700, 0x4A4A4A);
        scene.add(helper);

        gltfLoader = new GLTFLoader();
        gltfLoader.load('static/seraphine/scene.gltf', (gltf) => {

            let model = gltf.scene;

            //遍历模型的每一部分
            //traverse这个方法可以遍历调用者和调用者的所有后代
            //所以这里的o就是模型的每一部分
            model.traverse((o) => {
                //将图片作为纹理加载
                let explosionTexture = new THREE.TextureLoader().load(
                    'static/seraphine/textures/Mat_cwfyfr1_userboy17.bmp_diffuse.png'
                );
                //调整纹理方向,默认为真。翻转图像的Y轴以匹配WebGL纹理坐标空间。
                //此处不需要反转,当然也可以试试反转以后什么效果
                explosionTexture.flipY = false;
                //将纹理图生成基础网格材质(meshBasicMaterial)
                const material = new THREE.MeshBasicMaterial({
                    map: explosionTexture
                });
                //给模型每部分上材质
                o.material = material;

            });
            //加载外部模型时候基本上都是一个组合对象.

            group.add(model)
            scene.add(group);
        });

    }

    //函数:重新设置渲染器的展示大小
    function resizeRendererToDisplaySize(renderer) {
        //这里没看明白往上翻
        const canvas = renderer.domElement;
        let width = window.innerWidth;
        let height = window.innerHeight;
        //判断css像素分辨率就和物理像素分辨率是否统一
        let canvasPixelWidth = canvas.width / window.devicePixelRatio;
        let canvasPixelHeight = canvas.height / window.devicePixelRatio;
        //判断是否需要调整
        const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height;
        if (needResize) {
            renderer.setSize(width, height, false);
        }
        return needResize;
    }


    //鼠标双击触发的方法
    function onMouseDblclick(event) {
        //获取raycaster和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
        let intersects = getIntersects(event);
        // console.log(intersects);
        // console.log(intersects[0].object);

        //获取选中最近的Mesh对象
        //instance坐标是对象,右边是类,判断对象是不是属于这个类的
        if (intersects.length !== 0 && intersects[0].object.type === 'Mesh') {
            selectObject = intersects[0].object;
            //changeMaterial(selectObject)
        } else {
            console.log('未选中 Mesh!');
        }
    }

    //获取与射线相交的对象数组
    function getIntersects(event) {
        event.preventDefault();// 阻止默认的点击事件执行, https://developer.mozilla.org/zh-CN/docs/Web/API/Event/preventDefault
        //console.log("event.clientX:" + event.clientX);
        //console.log("event.clientY:" + event.clientY);

        //声明 rayCaster 和 mouse 变量
        let rayCaster = new THREE.Raycaster();
        let mouse = new THREE.Vector2();

        //通过鼠标点击位置,计算出raycaster所需点的位置,以屏幕为中心点,范围-1到1
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; //这里为什么是-号,没有就无法点中

        //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
        rayCaster.setFromCamera(mouse, camera);

        //获取与射线相交的对象数组, 其中的元素按照距离排序,越近的越靠前。
        //+true,是对其后代进行查找,这个在这里必须加,因为模型是由很多部分组成的,后代非常多。
        let intersects = rayCaster.intersectObjects(group.children, true);

        //返回选中的对象
        return intersects;
    }

    // 窗口变动触发的方法
    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }

    //键盘按下触发的方法
    function onKeyDown(event) {
        switch (event.keyCode) {
            case 13:
                initCamera();
                initControls();
                break;
        }
    }

    //改变对象材质属性
    function changeMaterial(object) {
        let material = new THREE.MeshLambertMaterial({
            color: 0xffffff * Math.random(),
            transparent: object.material.transparent ? false : true,
            opacity: 0.8
        });
        object.material = material;
    }

    //初始化轨道控制器
    function initControls() {
        controls = new OrbitControls(camera, renderer.domElement);
        //controls.enableDamping = true;
    }

    // 初始化灯光
    function initLight() {
        light = new THREE.SpotLight(0xffffff);
        light.position.set(-300, 600, -400);
        light.castShadow = true;

        scene.add(light);
        scene.add(new THREE.AmbientLight(0x5C5C5C));
    }

    //初始化 dat.GUI
    function initGui() {
        //保存需要修改相关数据的对象
        let gui = new function () {

        }
        //属性添加到控件
        let guiControls = new dat.GUI();
    }

    //初始化性能插件
    function initStats() {
        let stats = new Stats();

        stats.domElement.style.position = 'absolute';
        stats.domElement.style.left = '0px';
        stats.domElement.style.top = '0px';

        document.body.appendChild(stats.domElement);
        return stats;
    }

    //更新div的位置
    // function renderDiv(object) {
    //     //获取窗口的一半高度和宽度
    //     let halfWidth = window.innerWidth / 2;
    //     let halfHeight = window.innerHeight / 2;
    //
    //     //逆转相机求出二维坐标
    //     let vector = object.position.clone().project(camera);
    //
    //     //修改div的位置
    //     $("#label").css({
    //         left: vector.x * halfWidth + halfWidth,
    //         top: -vector.y * halfHeight + halfHeight - object.position.y
    //     });
    //
    //     //显示模型信息
    //     $("#label").text("name:" + object.name);
    // }

    //更新控件
    function update() {
        stats.update();
        controls.update();
    }

    //初始化
    function init() {
        initScene();
        initCamera();
        initRenderer();
        initContent();
        initLight();
        initControls();
        initGui();
        addEventListener('click', onMouseDblclick, false);
        addEventListener('resize', onWindowResize, false);
        addEventListener('keydown', onKeyDown, false);
    }

    function animate() {
        if (selectObject != undefined && selectObject != null) {
            //renderDiv(selectObject);
        }
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        update();
        //判断渲染器是否调整,若调整,相机也需要调整aspect
        if (resizeRendererToDisplaySize(renderer)) {
            const canvas = renderer.domElement;
            //重新设置摄像机看视锥体的横纵比,横纵比一般为屏幕宽高比,不然会挤压变形
            camera.aspect = canvas.clientWidth / canvas.clientHeight;
            camera.updateProjectionMatrix();
        }
    }

    init();
    animate();
</script>

</body>
</html>

要给Three.js导入的模型添加点击事件,需要使用射线(raycasting)技术来检测鼠标或触摸事件是否与模型相交。下面是一个基本的示例代码: 1. 添加一个点击事件监听器 ```javascript document.addEventListener('click', onDocumentClick, false); ``` 2. 在监听器函数中实现射线检测 ```javascript function onDocumentClick(event) { event.preventDefault(); // 计算鼠标点击位置和屏幕尺寸的比例 const mouse = new THREE.Vector2(); mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 创建一个射线投射器 const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); // 检测射线是否与模型相交 const intersects = raycaster.intersectObjects(scene.children, true); if (intersects.length > 0) { // 如果有相交的对象,则执行点击事件 const obj = intersects[0].object; // 在这里添加你的点击事件处理逻辑 console.log('clicked', obj); } } ``` 在这个例子中,我们首先计算了鼠标点击位置和屏幕尺寸的比例。然后,我们创建了一个射线投射器,并将其设置为从相机位置发射射线。最后,我们使用射线与场景中的所有对象进行相交检测,并在相交的对象数组中查找我们感兴趣的对象。如果找到了对象,则执行相应的点击事件处理逻辑。 你需要将上面的示例代码中的 `camera` 和 `scene` 替换为你自己的 Three.js 相机和场景对象。另外,你还需要将 `obj` 对象替换为你要添加点击事件模型对象。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值