three.js+WebGL踩坑经验合集(2):3D场景被相机裁切后,被裁切的部分依然可以被鼠标碰撞检测得到(射线检测)

three.js内置了Raycaster类实现鼠标的碰撞检测,用它可以实现3D物体的鼠标点击,移入移出,触屏检测一类的业务功能。

该功能虽然强大,但同事们普遍反映不是那么好用,因为它不像其它配套了可视编辑的3D引擎一样,直接把这些交互事件挂载到3D物体上。

不好用没关系,只要研发团队有懂three.js的人,或者有人把写好的实现代码封装一下给到业务开发去用就可以了。

蛋疼的是,这个东西还有一些小bug,比如它不认相机裁剪。以下为测试用例代码

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>three_cameraNear</title><style>body {margin: 0;overflow: hidden;}</style><script src="three/build/three.js"></script><script src="three/examples/js/controls/OrbitControls.js"></script><script src="three/examples/js/libs/dat.gui.min.js"></script>
</head><body><script>var scene = new THREE.Scene();var geometry = new THREE.SphereGeometry(50, 100, 100);var srcColor = 0xFF6600;var material = new THREE.MeshLambertMaterial({color: srcColor});var mesh = new THREE.Mesh(geometry, material);scene.add(mesh);var light = new THREE.DirectionalLight({color: 0xFFFFFF, intensity: 0.5});light.position.set(-500, 500, 500);scene.add(light);var ambLight = new THREE.AmbientLight({color: 0xFFFFFF, intensity: 0.3});scene.add(ambLight);var width = window.innerWidth; var height = window.innerHeight; var camera = new THREE.PerspectiveCamera(60, width / height, 100, 20000);camera.position.set(0, 0, 150);  var renderer = new THREE.WebGLRenderer();renderer.setSize(width, height);renderer.setClearColor(0x000000, 1); document.body.appendChild(renderer.domElement); var gui = new dat.GUI(),folderCamera = gui.addFolder("相机"),propsCamera = {get '裁剪'() {return camera.near;},set '裁剪'( v ) {camera.near = v;camera.updateProjectionMatrix();},};folderCamera.add( propsCamera, '裁剪', 100, 150 );    folderCamera.open();function render() {renderer.render(scene, camera);requestAnimationFrame(render);}render();var controls = new THREE.OrbitControls(camera,renderer.domElement);controls.addEventListener('change', render);var raycaster = new THREE.Raycaster(); function onMouseMove(e){	//这里是屏幕坐标到ndc的转换,不懂的可以自行上webgl中文网学习var x = ((e.clientX - width * 0.5) / width * 2);var y = (-(e.clientY - height * 0.5) / height * 2);raycaster.setFromCamera(new THREE.Vector2(x, y), camera);var intersects = raycaster.intersectObject(scene, true);material.color = new THREE.Color().set(srcColor);for(let intersect of intersects){intersect.object.material.color = new THREE.Color().set(0x999900);}}window.addEventListener("mousemove", onMouseMove);</script>
</body>
</html>

场景上的球体在场景不被裁剪的时候工作得非常好,但是一旦用上了裁剪(设置camera的near/far,就会发现,Raycaster完全无视这一属性。

解决问题的时候,笔者还是先尝试去找现成的api,看是不是有设置项。果不其然,Raycaster就自带了near和far属性。

//在onMouseMove方法的intersects调用前加上这两行,同步相机的裁剪属性
raycaster.near = camera.near;
raycaster.far = camera.far;

这不是脱裤子放屁嘛,为什么就不直接在Raycaster内部去读取camera的这两条属性呢,非要业务层多此一举?

先不纠结这问题,我们看看这样写是否就能把问题解决掉。

有了显著改善,但是边缘处并不是那么准确,并且鼠标位置离画布中心越远,误差就越大。

笔者在解决这个问题之前,有大致了解过射线检测的原理,所以结合源码里的实现逻辑,盲猜到把相机改成正交就会立马变得非常准确。

var k = width / height;
var s = 100; 
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 100, 20000);

也就是说,near和far的同步适用于正交相机,透视相机用它不靠谱,这大概也就可以解释为什么three.js不直接在底层同步这两项属性了。

现在笔者就来跟大家解释下,为什么两种相机得到的结果会有所差异。

透视相机的特征是近大远小(实际上玩得花的可以搞成近小远大),其可视区域是下图中近裁剪面和远裁剪面及其顶点连线所围成的一个棱台,称作视锥体。

做基于射线检测的鼠标碰撞时,人眼的直观感受是从鼠标位置发射一条垂直于屏幕向里的射线,最先跟啥相交就算碰到谁。近裁剪面和远裁剪面在屏幕上将会被缩放到相同的大小从而形成近大远小的效果,那么垂直于屏幕向里的射线也将据此进行的倾斜变换(如上图的红绿蓝3条射线),图中P,A和A1在画面上是重叠的,P,蓝点和蓝箭头的头部也一样,可以看到,上图的3条射线跟近裁剪面交点和相机的距离不一样,绿点(屏幕中心)最近,红点次之,蓝点最远。

但是射线检测源码用的却是相机位置到交点的距离,因此对于透视相机而言,离屏幕中心越远,误差就越大,它用球面代替了平面来判断。

而正交相机则不存在正大远小的说法,它的视锥体是一个长方体,所以不管从哪里发出来的射线都是两两平行的。

下面给出解决方案,代码很简单,只有两行。

const proj = _intersectionPointWorld.clone().project(raycaster.camera);
if(proj.z > 1 || proj.z < -1) return null;

这段代码加到Mesh.js上

每个显示对象都有自己的raycast属性,其它对象的修改方法类似,不再赘述。

虽然只有两行,但这里的学问大着呢。

无论是OpenGL(WebGL是其子集)还是DirectX,这些GPU渲染底层使用的都是标准设备坐标系(Normal Device Coordinate,NDC),其xyz3轴的范围均为-1到1(不懂的小伙伴可以搜索NDC进一步学习)。上述代码中的方法,就是实现从屏幕像素坐标到NDC坐标的转换,它在three.js内已经封装好了。

NDC超出-1到1范围的物体将不予显示,因此该做法在视觉上是最准确的。但有同事不建议笔者这样改,他们认为能不动源码就不动,后续想要更新引擎也方便。但笔者当时坚持修改源码,因为这就是引擎的一个bug。

写本文的时候,笔者重新审视了一下,改源码也有它的不合理之处。因为它并不适用于所有的业务场景。比如线框材质,它的背面也是可见的,这时候,笔者的这一修改就反倒不正确。

由此可见,three.js不直接在raycaster里读取camera的near和far也是有他的道理。再者,如果哪天有人突发奇想,搞个哇哈哈效果那样的曲面相机,那机制又可能不是这么一回事了。

这也就使得three.js比其它引擎更加灵活,同时也变得没那么好用了。如果读者们有注意到笔者上面的滑块源码就会发现,在camera的near修改了之后,笔者还加了句updateProjectionMatrix触发刷新,这些都是灵活性高封装性不强的表现,对于萌新们来说,上手和查问题都会变得困难。这时候,笔者这一专栏的价值就体现出来了。

废话说完,来小结一下:

1 默认情况下,射线检测无视相机裁剪

2 正交相机,非线框材质的情况下,把camera的near和far属性同步到raycaster能完美解决问题

3 透视相机,非线框材质的情况下,用THREE.Vector3的project方法算出来的结果比用near和far要准确得多,同时此法也适用于正交相机

4 如果要纠结线框材质,那么可以通过修改Mesh.js源码来实现兼容

本文重点是project方法,事实上这个方法也有坑,后面还会提到,敬请期待!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/893575.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot spring.factories文件详细说明

优质博文&#xff1a;IT-BLOG-CN 前言&#xff1a;经常看到 spring.factories 文件&#xff0c;却没有对它进行深入的了解和分析&#xff0c;今天我们就一起揭开面纱看看它的内在。 spring.factories 文件是 Spring Boot 自动配置机制的核心部分之一。它位于每个 Spring Boo…

从前端视角看设计模式之行为型模式篇

上篇我们介绍了 设计模式之结构型模式篇&#xff0c;接下来介绍设计模式之行为型模式篇 责任链模式 责任链模式允许将请求沿着一条链传递&#xff0c;直到有一个对象处理它为止。每个处理者都有机会处理该请求&#xff0c;或者将其传递给链中的下一个处理者&#xff0c;每个处…

[2025分类时序异常检测指标R-AUC与VUS]

梳理了一下分类中常见的指标&#xff0c;这些指标与时序异常检测中新提出的A-RUC与VUS之间的关系 真正例(True Positive,TP): 被正确识别为正样本的数量。真负例(True Negative,TN): 被正确识别为负样本的数量。假正例(False Positive ,FP): 被错误识为正样本数量假负例(Fals…

Unity中实现伤害跳字效果(简单好抄)

第一步骤安装并导入Dotween插件&#xff08;也可以不用导入之后直接下载我的安装包&#xff09; 官网DOTween - 下载 第二步&#xff1a; 制作跳字预制体 建议把最佳适应打开&#xff0c;这样就不怕数字太大显示不全了。 第三步&#xff1a;创建一个空对象并编写脚本JumpNumbe…

【设计模式-行为型】观察者模式

一、什么是观察者模式 说起观察者模式&#xff0c;不得不说一位观察者模式的高级应用者&#xff0c;朱元璋。不知道大家有没有看过胡军演的电视剧《朱元璋》。这部剧背景是元朝末年&#xff0c;天下大乱&#xff0c;朱元璋自幼父母双亡&#xff0c;沦为乞丐&#xff0c;后遁入空…

考研机试:学分绩点

描述 北京大学对本科生的成绩施行平均学分绩点制&#xff08;GPA&#xff09;。 既将学生的实际考分根据不同的学科的不同学分按一定的公式进行计算。 公式如下&#xff1a; 一门课程的学分绩点 该课绩点 该课学分 总评绩点 所有学科学分绩点之和 / 所有课程学分之和 …

CentOS9 安装Docker+Dpanel+onlyoffice(https、更改字体、字号、去除限制)的避坑笔记

CentOS9 安装Dockeronlyoffice&#xff08;https、更改字体、字号、去除文件大小限制&#xff09;的避坑笔记 一、安装Docker二、更新docker镜像源&#xff1a;三、安装Dpanel四、安装onlyoffice&#xff08;开启https及一些碰到的问题&#xff09;五、更改字体和字号六、去除限…

【玩转全栈】----YOLO8训练自己的模型并应用

继上篇&#xff1a; 【玩转全栈】---基于YOLO8的图片、视频目标检测-CSDN博客 相信大家已经可以训练一些图片和视频了&#xff0c;接下来我将为大家介绍如何训练自己的特定模型&#xff0c;并用其进行检测 目录 准备数据 图片数据 标识数据 配置文件 运行 测试训练结果 存在的问…

OpenCV文字绘制支持中文显示

OpenCV版本&#xff1a;4.4 IDE&#xff1a;VS2019 功能描述 OpenCV绘制文本的函数putText()不支持中文的显示&#xff0c;网上很多方法推荐的都是使用FreeType来支持&#xff0c;FreeType是什么呢&#xff1f;FreeType的官网上有介绍 FreeType官网 https://www.freetype.or…

梯度下降法 (Gradient Descent) 算法详解及案例分析

梯度下降法 (Gradient Descent) 算法详解及案例分析 目录 梯度下降法 (Gradient Descent) 算法详解及案例分析1. 引言2. 梯度下降法 (Gradient Descent) 算法原理2.1 基本概念2.2 算法步骤2.3 梯度下降法的变种3. 梯度下降法的优势与局限性3.1 优势3.2 局限性4. 案例分析4.1 案…

GPSd定时检测保活TCP GPS源

为了在 TCP GPS 源丢失连接时自动重新连接&#xff0c;可以编写一个监控脚本&#xff0c;定期检查 gpspipe 输出中的 TCP 源数据是否存在。如果检测到丢失&#xff0c;则使用 gpsdctl 或直接命令重新添加 TCP 源。 1、工具 检查并安装必要工具&#xff0c;本例需要使用 gpspi…

我谈《概率论与数理统计》的知识体系

学习《概率论与数理统计》二十多年后&#xff0c;在廖老师的指导下&#xff0c;才厘清了各章之间的关系。首先&#xff0c;这是两个学科综合的一门课程&#xff0c;这一门课程中还有术语冲突的问题。这一门课程一条线两个分支&#xff0c;脉络很清晰。 概率论与统计学 概率论…

ElasticSearch JavaRestClient查询之快速入门

文章目录 查询操作流程概述构建并发起请求1. 创建请求对象2. 设置请求体3. 发送请求 查询结果的解析1. 解析结果结构2. 获取总条数3. 获取命中的数据 完整示例代码总结 查询操作流程概述 Elasticsearch 查询操作大致可以分为两个部分&#xff1a; 构建并发起请求&#xff1a;…

Quartus:开发使用及 Tips 总结

Quartus是Altera&#xff08;现已被Intel收购&#xff09;推出的一款针对其FPGA产品的综合性开发环境&#xff0c;用于设计、仿真和调试数字电路。以下是使用Quartus的一些总结和技巧(Tips)&#xff0c;帮助更高效地进行FPGA项目开发&#xff1a; 这里写目录标题 使用总结TIPS…

elementUI Table组件实现表头吸顶效果

需求描述 当 table 内容过多的时候&#xff0c;页面上滑滚动&#xff0c;表头的信息也会随着被遮挡&#xff0c;无法将表头信息和表格内容对应起来&#xff0c;需要进行表头吸顶 开始编码&#x1f4aa; 环境&#xff1a;vue2.6、element UI step1&#xff1a; 给el-table__h…

用于牙科的多任务视频增强

Multi-task Video Enhancement for Dental Interventions 2022 miccai Abstract 微型照相机牢牢地固定在牙科手机上&#xff0c;这样牙医就可以持续地监测保守牙科手术的进展情况。但视频辅助牙科干预中的视频增强减轻了低光、噪音、模糊和相机握手等降低视觉舒适度的问题。…

Vue3轮播图左右联动

1、轮播图部分&#xff0c;右边鼠标移入&#xff0c;左边对应展示轮播图 可以在swiper 官网 Swiper中文网-轮播图幻灯片js插件,H5页面前端开发 选择vue中使用swiper npm i swiper 左右两边的联动&#xff1a;左边的轮播图和右边的小的列表他们的列表组成结构是一样的&#…

windows下本地部署安装hadoop+scala+spark-【不需要虚拟机】

注意版本依赖【本实验版本如下】 Hadoop 3.1.1 spark 2.3.2 scala 2.11 1.依赖环境 1.1 java 安装java并配置环境变量【如果未安装搜索其他教程】 环境验证如下&#xff1a; C:\Users\wangning>java -version java version "1.8.0_261" Java(TM) SE Runti…

go-zero框架基本配置和错误码封装

文章目录 加载配置信息配置 env加载.env文件配置servicecontext 查询数据生成model文件执行查询操作 错误码封装配置拦截器错误码封装 接上一篇&#xff1a;《go-zero框架快速入门》 加载配置信息 配置 env 在项目根目录下新增 .env 文件&#xff0c;可以配置当前读取哪个环…

2025 最新flutter面试总结

目录 1.Dart是值传递还是引用传递&#xff1f; 2.Flutter 是单引擎还是双引擎 3. StatelessWidget 和 StatefulWidget 在 Flutter 中有什么区别&#xff1f; 4.简述Dart语音特性 5. Navigator 是什么&#xff1f;在 Flutter 中 Routes 是什么&#xff1f; 6、Dart 是不是…