three.js+WebGL踩坑经验合集(4.1):THREE.Line2的射线检测问题(注意本篇说的是Line2,同样也不是阈值方面的问题)

上篇大家消化得如何了?

笔者说过,1级编号不同的两篇博文相对独立,所以这里笔者还是先给出完整代码,哪怕跟(3)没有太大区别。

这里我们把线的粗细调成5(排除难选中的因素),同时去掉参照物圆柱体。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>threeLine2_raycaster</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/lines/LineSegmentsGeometry.js"></script>
  <script src="three/examples/js/lines/LineGeometry.js"></script>
  <script src="three/examples/js/lines/LineMaterial.js"></script>
  <script src="three/examples/js/lines/LineSegments2.js"></script>
  <script src="three/examples/js/lines/Line2.js"></script>
</head>

<body>
  <script>
    var scene = new THREE.Scene();

    var srcColor = new THREE.Color(1, 1, 1);
    var overColor = new THREE.Color(0.8, 0.3, 0);

    var geometry = new THREE.LineGeometry();
    geometry.setPositions([0, 0, 600, 0, 0, -600]);
    var material = new THREE.LineMaterial({linewidth: 5, color: 0xFFFFFF, resolution: new THREE.Vector2(window.innerWidth, window.innerHeight)});    
    var line = new THREE.Line2(geometry, material);
    scene.add(line);
    
    var width = window.innerWidth; 
    var height = window.innerHeight; 
    var camera = new THREE.PerspectiveCamera(60, 1, 0.1, 20000);
    camera.position.set(10, 10, 620);  
    camera.lookAt(0, 0, 0);

    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    renderer.setClearColor(0x000000, 1); 
    document.body.appendChild(renderer.domElement); 
    
    function render() {
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    
    render();
    
    var controls = new THREE.OrbitControls(camera,renderer.domElement);
    controls.addEventListener('change', render);
    
    // var axisHelper = new THREE.AxesHelper(250);
    // scene.add(axisHelper);	

    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);
      material.color = srcColor;
      //把scene换成line,确保新增的圆柱体不参与射线检测
      var intersects = raycaster.intersectObject(line, true);      
      for(let intersect of intersects)
      {
        intersect.object.material.color = overColor;       
      }
    }
    
    window.addEventListener("mousemove", onMouseMove);

  </script>
</body>
</html>

运行效果如下,按照代码设定的线条位置和相机参数,它可以工作得很好,但是一旦用鼠标滚轮把镜头拉近,线就会莫名选不中了,只有当整根线都在屏幕范围内的时候,射线检测才能正常。

在排查问题之前,笔者先给大家简单介绍一下THREE.Line2的实现原理。核心逻辑在LineMaterial.js的shader里面,小伙伴们可以对照着代码进行理解。

1 以一段线为单位,取其start和end。

2 对start和end应用世界矩阵,相机矩阵和投影矩阵后,转换到WebGL使用的NDC坐标系上。此过程跟笔者前面的一篇博文中提到的THREE.Vector3.project方法有共通之处。three.js+WebGL踩坑经验合集(2):3D场景被相机裁切后,被裁切的部分依然可以被鼠标碰撞检测得到(射线检测)-CSDN博客

这一步的关键词为MVP矩阵,不懂的小伙伴可以自行搜索学习。

3 在步骤2的基础上,分别过S和E作垂直于SE(代码中为dir)的法线,共4条,如下图所示。

4 然后对法线向量进行缩放,使其长度等于linewidth的一半,这个法线向量在代码中对应offset属性。

此处为shader代码,需要把linewidth转换到ndc坐标系,但是shader在GPU层是无法直接获取到画布大小的,所以才有了LineMaterial的resolution属性,它是屏幕坐标转ndc坐标的条件之一。

5 取出上图中的ABCD点生成两个三角面。

6 此时若直接填充纯色,那么端点处就会不太光滑,所以后面还有一段代码,通过判断当前像素点到端点的距离来生成圆角。

笔者跳过了LineMaterial中的trimSegment代码,它虽然不是实现原理的主体部分,但是跟选不中的bug却有着密不可分的关系。

现在我们来看看Line2的射线检测代码,它的实现在父类LineSegments2上。

不难看出,射线检测的实现代码跟LineMaterial是配套编写的,过程极其相似,但是当时笔者并没有看懂LineMaterial上的trimSegment到底是干嘛用,就在那儿盲目断点了一把,这个过程分享出来没有任何意义,所以就直接给研究结果。

因为射线检测出错是发生在线条超出屏幕的那一刹那,所以我们看看端点在出界前后转换到NDC坐标系的结果。

这个状态下,在控制台输入

new THREE.Vector3(0, 0, 600).project(camera),得到的结果为

这个点是左侧端点,看百分比没有啥毛病

我们用滚轮拉近一下镜头再输出一下

嗯-0.82,基本对的上。

再滚轮一下,这个端点会穿出屏幕,结果如下

嗯,这下有毛病了,端点在左下角,应该是负一点多才是对的,然而变成了正数,飞到了另一侧去。

至此,我们发现project方法的一个大坑,对于超出可视范围的点,其计算结果并不可靠。

对于显示来说,这个bug太明显了,所以LineMaterial类加了trimSegment在边界处进行了修正。但是射线检测那个地方,大概没有人仔细地测试过,因此就留下了这样的bug。

笔者用跟trimSegment类似的方法进行修正

结果是对了,但是笔者在写这段代码的时候,实际上是还没看懂trimSegment的原理,所以用的变量是w而非z。但不管用的哪个变量,这背后隐藏着的,是齐次坐标以及透视相机矩阵公式等内容,比较复杂,所以笔者打算放到下一篇再写。因为即使笔者把trimSegment抄过来,也是没有解释为什么出界了之后,project的计算会有问题,还会再解释为什么trimSegment只需要修复z而不需要修复xy。

进入下一篇之前,还是先小结一下

1 THREE.Vector3的project方法在出界之后,结果可能会不正确

2 为确保界内部分的结果正确,我们应该把出界的部分裁剪掉,让线段的计算由始至终都在可靠的范围内进行

3 THREE.Line2的显示部分做了裁剪,但是射线检测没有,从而导致射线检测结果出错

好了,时间不早了,今天先到这里,希望明天还有时间来给大家继续分享,晚安喽~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue2下篇

插槽&#xff1a; 基本插槽&#xff1a; 普通插槽&#xff1a;父组件向子组件传递静态内容。基本插槽只能有一个slot标签&#xff0c;因为这个是默认的位置&#xff0c;所以只能有一个 <!-- ParentComponent.vue --> <template> <ChildComponent> <p>…

【Unity3D】aab包太大无法上传Google问题

目录 一、勾选Split Application Binary&#xff0c;Unity直接打aab包 勾选Split Application Binary选项的影响 不勾选Split Application Binary选项的影响 总结 2、导出Android工程打包aab 一、勾选Split Application Binary&#xff0c;Unity直接打aab包 超出150MB部分…

第6章 数据结构—列表与列表项讲解--总结

整理 野火 《FreeRTOS 内核实现与应用开发实战指南》—基于野火 STM32 全系列&#xff08;M3/4/7&#xff09;开发板 文章目录 第6章 数据结构—列表与列表项讲解--总结6.1 C 语言链表简介6.1.1 单向链表6.1.2 双向链表6.1.3 链表与数组的对比 6.2 FreeRTOS 中链表的实现6.2.1 …

强化学习-Deep Q Network

文章目录 Deep Q Networkzip(*batch)的内部实现假设&#xff1a;结果&#xff1a; Deep Q Network 这种方式很适合格子游戏。因为格子游戏中的每一个格子就是一个状态&#xff0c;这是离散的&#xff0c;但在现实生活中&#xff0c;很多状态并不是离散而是连续的。所以我们可以…

C语言-构造数据类型

1、构造数据类型 结构体、共用体、枚举。 2、结构体 1、结构体的定义 结构体是一个自定义的复合数据类型&#xff0c;它允许将不同类型的数据组合在一起。 struct 结构体名 {数据类型1 成员变量1;数据类型2 成员变量2;数据类型3 成员变量3;数据类型4 成员变量4; } 2、结构体变…

FPGA实现任意角度视频旋转(二)视频90度/270度无裁剪旋转

本文主要介绍如何基于FPGA实现视频的90度/270度无裁剪旋转&#xff0c;旋转效果示意图如下&#xff1a; 为了实时对比旋转效果&#xff0c;采用分屏显示进行处理&#xff0c;左边代表旋转前的视频在屏幕中的位置&#xff0c;右边代表旋转后的视频在屏幕中的位置。 分屏显示的…

Spark/Kafka

文章目录 项目地址一、Spark1. RDD1.1 五大核心属性1.2 执行原理1.3 四种创建方式二、Kafka2.1 生产者(1)分区器(2)生产者提高吞吐量(3) 生产者数据可靠性数据传递语义幂等性和事务数据有序2.2 Broker(1)Broker工作流程(2)节点服役和退役2.3 副本(1)Follower故障细…

win32汇编环境,函数的编写与调用、传值或返回值等

;运行效果 ;win32汇编环境,函数的编写与调用、传值或返回值等 ;函数在被调用的时候&#xff0c;如果此函数实体在前面&#xff0c;可以不用声明。如果实体在后面&#xff0c;则需要先声明。类似于下面的DlgProc函数&#xff0c;因为它的实体在后面&#xff0c;所以需要在调用之…

[Spring] Gateway详解

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

回顾2024,展望2025

项目 LMD performance phase2 今年修修补补&#xff0c;设计和做了很多item&#xff0c;有时候自己都数不清做了什么大大小小的item&#xff0c;但是for LMD performance phase2的go-live确实是最大也是最难的了&#xff0c;无论什么系统&#xff0c;只要用的人多了&#xff…

旅游风景的代码项目

敦煌莫高窟&#xff1a;用代码打开千年艺术的大门 ——一个零基础也能看懂的神奇项目 前言&#xff1a;当古老艺术遇上现代代码 想象一下&#xff0c;你坐在电脑前&#xff0c;指尖轻轻一点&#xff0c;就能穿越到敦煌莫高窟——看飞天的衣袂飘飘、听千年的驼铃声声。这不是科…

解决lombok注解失效

问题描述 当出现使用lombok的注解, 但是找不到符号, 或者使用Getter注解却获取不到属性值 就像下面这样 原因: 新版本lombok自动引入了一个插件, 将下面这串代码删除后, 刷新并清除缓存即可解决

leetcode hot 100 搜索二维矩阵II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,2…

CentOS7安装使用containerd

一&#xff0c;安装 1.1、安装containerd 下载 https://github.com/containerd/containerd/releases/download/v1.7.24/cri-containerd-cni-1.7.24-linux-amd64.tar.gz wget https://github.com/containerd/containerd/releases/download/v1.7.24/cri-containerd-cni-1.7.24-…

easyexcel读取写入excel easyexceldemo

1.新建springboot项目 2.添加pom依赖 <name>excel</name> <description>excelspringboot例子</description><parent> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId&…

2025数学建模美赛|F题成品论文

国家安全政策与网络安全 摘要 随着互联网技术的迅猛发展&#xff0c;网络犯罪问题已成为全球网络安全中的重要研究课题&#xff0c;且网络犯罪的形式和影响日益复杂和严重。本文针对网络犯罪中的问题&#xff0c;基于多元回归分析和差异中的差异&#xff08;DiD&#xff09;思…

QT QTableWidget控件 全面详解

本系列文章全面的介绍了QT中的57种控件的使用方法以及示例,包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizontalSpacer)、…

SpringBoot--基本使用(配置、整合SpringMVC、Druid、Mybatis、基础特性)

这里写目录标题 一.介绍1.为什么依赖不需要写版本&#xff1f;2.启动器(Starter)是何方神圣&#xff1f;3.SpringBootApplication注解的功效&#xff1f;4.启动源码5.如何学好SpringBoot 二.SpringBoot3配置文件2.1属性配置文件使用2.2 YAML配置文件使用2.3 YAML配置文件使用2.…

QT TLS initialization failed

qt使用QNetworkAccessManager下载文件&#xff08;给出的链接可以在浏览器里面下载文件&#xff09;&#xff0c;下载失败&#xff0c; 提示“TLS initialization failed”通常是由于Qt在使用HTTPS进行文件下载时&#xff0c;未能正确初始化TLS&#xff08;安全传输层协议&…

WebODM之python实现

1、安装webodm_slam 主要是了解API文档,查看之前的文章 安装WebODM_slate 2、安装webodm 查看之前的文章 Win10安装WebODM和操作全流程 3、python脚本 项目案例 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of…