当前位置: 首页 > news >正文

three.js+WebGL踩坑经验合集(6.2):负缩放,负定矩阵和行列式的关系(3D版本)

本篇将紧接上篇的2D版本对3D版的负缩放矩阵进行解读。

(6.1):负缩放,负定矩阵和行列式的关系(2D版本)

既然three.js对3D版的负缩放也使用行列式进行判断,那么,2D版的结论用到3D上其实是没毛病的,THREE.Line2出问题,是因为顶点生成规则跟Mesh不一致。而我们上篇提到的案例,则是用了2D平面的绕序来套到3D上,想必也是不合理的。

下面我们先针对上篇最后提到的问题做一个测试用例

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>three_3Ddeterminant</title><style>body {margin: 0;overflow: hidden;}</style><script src="three/build/three.js"></script><script src="three/examples/js/controls/OrbitControls.js"></script>
</head><body><script>var scene = new THREE.Scene();var container = new THREE.Object3D();scene.add(container);var geometry = new THREE.PlaneGeometry(100, 50);var material = new THREE.MeshBasicMaterial({color: 0x993300, side:THREE.DoubleSide});var mesh = new THREE.Mesh(geometry, material);mesh.position.z = 100;container.add(mesh);var geometryBack = new THREE.PlaneGeometry(50, 100);var materialBack = new THREE.MeshBasicMaterial({color: 0x006600, side:THREE.DoubleSide});var meshBack = new THREE.Mesh(geometryBack, materialBack);meshBack.position.z = 90;container.add(meshBack);var width = window.innerWidth; var height = window.innerHeight; var camera = new THREE.PerspectiveCamera(60, width / height, 1, 20000);camera.position.set(0, 0, 500);  var renderer = new THREE.WebGLRenderer();renderer.setSize(width, height);renderer.setClearColor(0x000000, 1); document.body.appendChild(renderer.domElement); function render() {container.rotation.y += Math.PI / 180;renderer.render(scene, camera);requestAnimationFrame(render);}render();var controls = new THREE.OrbitControls(camera,renderer.domElement);controls.addEventListener('change', render);</script>
</body>
</html>

现在我给两个面片都开启了双面渲染,并且让面片所在的容器一直绕y轴旋转,让大家大致了解这个测试用例的结构。

真正测试的时候,我会把红色设置为正面,绿色设置为反面,效果如下。

可见,容器旋转到正面时,红色面显示绿色面隐藏,反过来则显示绿色面。

单改容器的rotation虽然按我们的理解是一个正定矩阵,但当它的rotation.y在90度到270度之间时,点的绕序确实发生了变更,所以也隐藏了,这跟我们在上篇2D版本上的结论不吻合。

然后我们再试试不旋转,而是改scale:

var t = 0;function render() {t += 3 * Math.PI / 180;container.scale.z = Math.sin(t);renderer.render(scene, camera);requestAnimationFrame(render);
}

缩放的情况,点的绕序没发生变更,但是正反面却也交替了。

是不是有点神奇?嗯看来是我们对3D绕序的理解不正确。

为了方便观察,我们把面片的xy向量绘制到容器上进行观察。与此同时,既然我们是研究3D的版本,那么我们直接用THREE.AxisHelper把z轴也一并绘制出来(红绿蓝分别代表xyz)。

var axisHelper = new THREE.AxisHelper(300);
axisHelper.position.z = 100;
container.add(axisHelper);

接下来,为了让每条轴在不同的视角下都能看得见,我们再让相机的位置适当偏移一下

camera.position.set(100, 150, 500);

然后,旋转和缩放的情况我们都看一下

咦这下我们似乎看出了一点端倪来,不管是通过旋转到达背面还是缩放到达背面,蓝线都是朝着屏幕向里,这时候用蓝线判断正反面似乎更为合理。

没错!我们抛开绕序,返璞归真,重新审视一下正反面,无非就是个朝向问题。而作为面片的法线——垂直于面片的蓝线正好与之相匹配。

我们再取个静止的图像看看

初始状态

旋转180度

scaleZ = -1

初始时,x轴正向到y轴正向是逆时针旋转,z轴向前

旋转180度时,x到y变成顺时针,z轴同时变成向后

scaleZ=-1时,x到y的旋转方向不变,但z轴向后了

这么一看,旋转似乎是负负得正的结果,而z方向缩放-1的操作,则只有一个负向的变换。

事实上,图2和图3的两个坐标轴是无法只通过旋转而完全重合。它就像我们的左右手,以及基于左右手定义的坐标系一样,是镜像关系。

顺带说句废话:如果您是名学霸或者读的专业跟化学有关系的话,您大概还会知道对映异构体这个词。它其实也是这种镜像关系,不同绕序化学性质也不一样,因此有的药名会带着左旋右旋这样的字眼。希望这个例子能帮助一部分小伙伴理解镜像变换。

笔者直接从百度百科偷了个图过来,让大家更形象的理解两套坐标系跟我们的左右手一样无法完全重合,总有一个方向是反的。

讲了这么多,我们对3D绕序的定义是时候跟2D区分开来了。它应该基于3个轴,如果定义右手坐标系为正绕序,那左手坐标系就是负绕序。

three.js使用的是右手坐标系,大家比划一下就可以发现,旋转180度后,坐标系仍为右手,而缩放-1的则把坐标系变成右手了。

因此对于3D版本的正负定矩阵,我们有这样的一个性质。

如果一个矩阵M可以把一个3维坐标系从左手坐标系变成右手坐标系,或者反过来,则矩阵M为负定矩阵,无更改则为正定矩阵,变换到共线共点就是零定矩阵。

下面我们就来推导一下,更改坐标系绕序(左右手方向)的矩阵是不是也正好对应上行列式的值。

初始时,x轴向量为(1,0,0),y轴向量为(0,1,0),z轴向量是(0,0,1),如果矩阵是负定矩阵,那么不管你如何去旋转它尝试让它跟变换前重合,就会发现总会有一个轴会变反的,这里我们取z为变反的轴。但是我们不能直接说它就是(0, 0, -1),因为可能会被拉长,缩短或者扭曲。但不管怎样,这个轴变换后的结果一定是跟初始方向不一致,也就是夹角大于90度。

这里想搞得通用点,也可以用任意的3个不共面向量来做推导,但是看上篇的2D版的式子都比较繁琐了,这里就还是不要折磨大家。

先来说步骤:

1 给定初始向量x(1,0,0),y(0,1,0),z(0,0,1),对xy做3D向量的叉乘(3D叉乘的结果为同时垂直于x和y的一个向量)计算,结果为(0,0,1),跟初始z的方向一致,也就是说,初始状态的坐标轴绕序为正(演算过程从略)

2 给定矩阵M,对初始点o(0,0,0),初始点x(1,0,0),初始点(0,1,0),初始点(0,0,1)做变换,得到新点o',x',y',z',同时算出向量o'x',o'y',o'z'

3 对o'x',o'y'做叉乘运算,得到向量V,判断它跟o'z'的夹角是否大于90度,可通过向量点乘运算求得,大于0为小于90度,小于0则相反

4 点乘结果的正负即为矩阵缩放的正负

下面就让我们开始吧!

4*4矩阵对点的变换计算如下所示(按照前面2D版的经验,本次直接给最后一行填充单位矩阵的数值)

3D向量点乘和叉乘的公式也在此处给出

点乘:

叉乘:

下面我们分别计算矩阵对4个点变换的结果。

可以看到,这个变换跟2D的真的很像,都是最后一列完全一样,所以在计算向量的过程中,最后一列会被完全消去。

得到的向量为

嗯,果然用给定的特殊坐标算出来的值简单很多。有兴趣的小伙伴可以自行捣鼓一般坐标的演算,笔者就不发上来折磨大家了。

下面我们算o'x'和o'y'的叉乘值,公式前面已给出,这里我们直接套用。

然后再跟o'z'做点乘运算

这两步计算连一起可称为向量的混合积

然后你会很惊喜地发现,向量混合积的结果跟4*4矩阵中前3阶的行列式一点不差!

加的部分

减的部分

同样地,three.js也严格按照4阶行列式定义书写Matrix4的determinant,跟2D版本一样,在最后一行用单位矩阵填充时,4*4的结果跟3*3是没有区别的。

然而4*4直接按定义写,跟3*3相比,多项式的项数会从6个飙升到24个。对性能来说并不友好。

所以我们看到three.js给的实现和注释给出的链接的写法是不太一样的。

该实现用了行列式的余子式写法(不懂的可以自行百度)把4*4行列式化为4个3阶,并且很巧妙地取到最后一行和一列作为剔除因子。因为在考察3D绕序,不研究透视w因子的情况下,最后一列一定是单位矩阵的数值,0,0,0,1,懂得编译原理的朋友应该就会明白,n41,n42和n43这3部分会因为第一个数为0而会对后面的运算过程做优化(当然脚本语言未必优化得彻底哈)。所以这一写法可以体现出three.js开发团队的功力确实够深的,这个细节优化也做到位了。

而3*3矩阵的行列式,就没有做这样的优化,大概是这里还没遇到瓶颈吧,换写法未必合适。

当然这里让笔者有点困惑,求逆的时候它也拿了余子式写法,并且取的不是最后一行最后一列,就有点谜,是怎么样顺手就怎么样写么?欢迎大佬们留言讨论。

写了这么多,结论是证明出来了,用左右手坐标系的方式定义3D绕序后,绕序,正负缩放,正负定矩阵是完全等价的。

回过头来说说2D,2D的叉乘是个阉割版,因为运算过程中通常不想扩展到3D,所以结果是一个数而非一个向量。但严格来说,2D向量叉乘的结果,它等于跟z轴平行的向量,长度等于2D叉乘的数值。如果给2D坐标全部加上z坐标,赋值为0,大家就会发现,其结论跟3D版完全一致。感兴趣的读者可自行演算。

因此,2D版的正负定矩阵是3D版的一个特例,它们的原理完全一致。

下面来小结一下:

1 3D中的点绕序需要结合3轴,用跟左右手坐标系类似的方式进行定义

2 绕序有没变更可通过两向量叉乘后跟第三向量点乘求得,也就是混合积

3 混合积结果跟4*4矩阵前3阶行列式一致

4 矩阵的正负缩放完全跟3*3矩阵的秩(或者最后一行填充了单位矩阵的4*4的秩)的符号直接对应

5 3D正负定矩阵的性质:正定矩阵不修改坐标系的左右手方向(绕序),负定矩阵会修改,零定矩阵会把坐标系压缩为共面,共线或者共点。零定矩阵不可逆。

好了,折腾完这波理论,下篇就暂时不折磨大家了,换回踩坑经验方面的分享。感谢小伙伴们的支持和关注,我们下篇再见!

相关文章:

three.js+WebGL踩坑经验合集(6.2):负缩放,负定矩阵和行列式的关系(3D版本)

本篇将紧接上篇的2D版本对3D版的负缩放矩阵进行解读。 (6.1):负缩放&#xff0c;负定矩阵和行列式的关系&#xff08;2D版本&#xff09; 既然three.js对3D版的负缩放也使用行列式进行判断&#xff0c;那么&#xff0c;2D版的结论用到3D上其实是没毛病的&#xff0c;THREE.Li…...

使用 OpenResty 构建高效的动态图片水印代理服务20250127

使用 OpenResty 构建高效的动态图片水印代理服务 在当今数字化的时代&#xff0c;图片在各种业务场景中广泛应用。为了保护版权、统一品牌形象&#xff0c;动态图片水印功能显得尤为重要。然而&#xff0c;直接在后端服务中集成水印功能&#xff0c;往往会带来代码复杂度增加、…...

Kafka下载

一、Kafka下载 下载地址&#xff1a;https://kafka.apache.org/downloads 二、Kafka安装 因为选择下载的是 .zip 文件&#xff0c;直接跳过安装&#xff0c;一步到位。 选择在任一磁盘创建空文件夹&#xff08;不要使用中文路径&#xff09;&#xff0c;解压之后把文件夹内容…...

【C++语言】卡码网语言基础课系列----5. A+B问题VIII

文章目录 练习题目AB问题VIII具体代码实现 小白寄语诗词共勉 练习题目 AB问题VIII 题目描述&#xff1a; 你的任务是计算若干整数的和。 输入描述&#xff1a; 输入的第一行为一个整数N&#xff0c;接下来N行每行先输入一个整数M&#xff0c;然后在同一行内输入M个整数。 输出…...

IP服务模型

1. IP数据报 IP数据报中除了包含需要传输的数据外&#xff0c;还包括目标终端的IP地址和发送终端的IP地址。 数据报通过网络从一台路由器跳到另一台路由器&#xff0c;一路从IP源地址传递到IP目标地址。每个路由器都包含一个转发表&#xff0c;该表告诉它在匹配到特定目标地址…...

仿真设计|基于51单片机的温湿度、一氧化碳、甲醛检测报警系统

目录 具体实现功能 设计介绍 51单片机简介 资料内容 仿真实现&#xff08;protues8.7&#xff09; 程序&#xff08;Keil5&#xff09; 全部内容 资料获取 具体实现功能 &#xff08;1&#xff09;温湿度传感器、CO传感器、甲醛传感器实时检测温湿度值、CO值和甲醛值进…...

QModbusTCPClient 服务器断开引起的程序崩溃

最近使用QModbusTCPClient 与一套设备通信&#xff0c;有一个QTimer频繁的通过读取设备寄存器。程序运行良好&#xff0c;但是有个问题&#xff1a;正常进行中设备断电了&#xff0c;整个程序都会崩溃。解决过程如下&#xff1a; 1.失败方案一 在QModbusTCPClient的errorOccu…...

Vue 3 30天精进之旅:Day 11 - 状态管理

在开发复杂的前端应用时&#xff0c;状态管理是一个不可或缺的部分。Vuex是Vue.js官方提供的状态管理库&#xff0c;能够高效地管理应用中的共享状态。它使得各个组件之间的状态共享和维护变得更加简单和清晰。今天&#xff0c;我们将探讨以下几个方面&#xff1a; Vuex概述安…...

npm 和 pip 安装中常见问题总结

安装路径的疑惑&#xff1a;NPM 和 PIP 的安装机制 NPM 安装路径规则&#xff1a; 依赖安装在项目目录下&#xff1a; 当你运行 npm install --save-dev jest&#xff0c;它会在当前目录&#xff08;例如 F:\&#xff09;下创建一个 node_modules 文件夹&#xff0c;把 jest 安…...

Flutter开发环境配置

下载 Flutter SDK 下载地址&#xff1a;https://docs.flutter.cn/get-started/install M1/M2芯片选择带arm64字样的Flutter SDK。 解压 cd /Applications unzip ~/Downloads/flutter_macos_arm64_3.27.3-stable.zip执行 /Applications/flutter/bin/flutterManage your Flut…...

Two Divisors ( Educational Codeforces Round 89 (Rated for Div. 2) )

Two Divisors &#xff08; Educational Codeforces Round 89 (Rated for Div. 2) &#xff09; You are given n n n integers a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1​,a2​,…,an​. For each a i a_i ai​ find its two divisors d 1 > 1 d_1 > 1 d1​…...

亚博microros小车-原生ubuntu支持系列:17 gmapping

前置依赖 先看下亚博官网的介绍 Gmapping简介 gmapping只适用于单帧二维激光点数小于1440的点&#xff0c;如果单帧激光点数大于1440&#xff0c;那么就会出【[mapping-4] process has died】 这样的问题。 Gmapping是基于滤波SLAM框架的常用开源SLAM算法。 Gmapping基于RBp…...

Java面试题2025-并发编程进阶(线程池和并发容器类)

线程池 一、什么是线程池 为什么要使用线程池 在开发中&#xff0c;为了提升效率的操作&#xff0c;我们需要将一些业务采用多线程的方式去执行。 比如有一个比较大的任务&#xff0c;可以将任务分成几块&#xff0c;分别交给几个线程去执行&#xff0c;最终做一个汇总就可…...

Stable Diffusion 3.5 介绍

Stable Diffusion 3.5 是由 Stability AI 推出的最新一代图像生成模型&#xff0c;是 Stable Diffusion 系列的重要升级版本。以下是关于 Stable Diffusion 3.5 的详细信息&#xff1a; 版本概述 Stable Diffusion 3.5 包含三个主要版本&#xff1a; Stable Diffusion 3.5 L…...

云计算部署模式全面解析

目录 引言公有云私有云混合云三种部署模式的对比选择建议未来趋势结语 1. 引言 随着云计算技术的快速发展,企业在选择云部署模式时面临着多种选择。本文将深入探讨云计算的三种主要部署模式:公有云、私有云和混合云,帮助读者全面了解它们的特点、优势及适用场景。 © iv…...

Vue 与 Electron 结合开发桌面应用

1. 引言 1.1 什么是 Electron? Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序的框架。它结合了 Chromium 渲染引擎和 Node.js 运行时,使得开发者可以使用 Web 技术创建原生桌面应用。1.2 为什么选择 Vue.js 和 Electron? Vue.js 是一个渐进式 JavaSc…...

数据库优化:提升性能的关键策略

1. 引言 在后端开发中&#xff0c;数据库的性能直接影响系统的稳定性和响应速度。随着业务增长&#xff0c;数据库查询变慢、负载过高等问题可能会影响用户体验。 本文将介绍数据库优化的关键策略&#xff0c;包括索引优化、查询优化、分库分表、缓存机制等&#xff0c;并结合…...

使用openAI与Deepseek的感受

今天简单介绍下使用OpenAI和DeepSeek的感觉&#xff0c;有些地方可能存在不准确的地方&#xff0c;望指正&#xff1a; 从2023年的秋冬到现在2025年的1月间&#xff0c;OpenAI和DeepSeek我都用它们来帮我&#xff0c;当然更多的是OpenAI&#xff0c;但整体感受如下&#xff1a;…...

pytorch实现长短期记忆网络 (LSTM)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 LSTM 通过 记忆单元&#xff08;cell&#xff09; 和 三个门控机制&#xff08;遗忘门、输入门、输出门&#xff09;来控制信息流&#xff1a; 记忆单元&#xff08;Cell State&#xff09; 负责存储长期信息&…...

【ubuntu】双系统ubuntu下一键切换到Windows

ubuntu下一键切换到Windows 1.4.1 重启脚本1.4.2 快捷方式1.4.3 移动快捷方式到系统目录 按前文所述文档&#xff0c;开机默认启动ubuntu。Windows切换到Ubuntu直接重启就行了&#xff0c;而Ubuntu切换到Windows稍微有点麻烦。可编辑切换重启到Windows的快捷方式。 1.4.1 重启…...

【PyTorch】6.张量形状操作:在深度学习的 “魔方” 里,玩转张量形状

目录 1. reshape 函数的用法 2. transpose 和 permute 函数的使用 4. squeeze 和 unsqueeze 函数的用法 5. 小节 个人主页&#xff1a;Icomi 专栏地址&#xff1a;PyTorch入门 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&am…...

大模型GUI系列论文阅读 DAY4续:《Large Language Model Agent for Fake News Detection》

摘要 在当前的数字时代&#xff0c;在线平台上虚假信息的迅速传播对社会福祉、公众信任和民主进程构成了重大挑战&#xff0c;并影响着关键决策和公众舆论。为应对这些挑战&#xff0c;自动化假新闻检测机制的需求日益增长。 预训练的大型语言模型&#xff08;LLMs&#xff0…...

论文阅读(九):通过概率图模型建立连锁不平衡模型和进行关联研究:最新进展访问之旅

1.论文链接&#xff1a;Modeling Linkage Disequilibrium and Performing Association Studies through Probabilistic Graphical Models: a Visiting Tour of Recent Advances 摘要&#xff1a; 本章对概率图模型&#xff08;PGMs&#xff09;的最新进展进行了深入的回顾&…...

python小知识-typing注解你的程序

python小知识-typing注解你的程序 1. Typing的简介 typing 是 Python 的一个标准库&#xff0c;它提供了类型注解的支持&#xff0c;但并不会强制类型检查。类型注解在 Python 3.5 中引入&#xff0c;并在后续版本中得到了增强和扩展。typing 库允许开发者为变量、函数参数和…...

git基础使用--1--版本控制的基本概念

git基础使用–1–版本控制的基本概念 1.版本控制的需求背景&#xff0c;即为啥需要版本控制 先说啥叫版本&#xff0c;这个就不多说了吧&#xff0c;我们写代码的时候肯定不可能一蹴而就&#xff0c;肯定是今天写一点&#xff0c;明天写一点&#xff0c;对于项目来讲&#xff…...

“新月智能武器系统”CIWS,开启智能武器的新纪元

新月人物传记&#xff1a;人物传记之新月篇-CSDN博客 相关文章链接&#xff1a;星际战争模拟系统&#xff1a;新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 “新月之智”智能战术头盔系统&#xff08;CITHS&#xff09;-CSDN博客 目录 智能武…...

JVM运行时数据区域-附面试题

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域 有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的区域随着虚拟机进程的启动而一直存在&#xff0c;有些区域则是 依赖用户线程的启动和结束而建立和销毁。 1. 程序计…...

增删改查(CRUD)操作

文章目录 MySQL系列&#xff1a;1.CRUD简介2.Create(创建)2.1单行数据全列插入2.2 单行数据指定插入2.3 多⾏数据指定列插⼊ 3.Retrieve(读取)3.1 Select查询3.1.1 全列查询3.1.2 指定列查询3.1.3 查询字段为表达式&#xff08;都是临时表不会对原有表数据产生影响&#xff09;…...

Vue.js `Suspense` 和异步组件加载

Vue.js Suspense 和异步组件加载 今天我们来聊聊 Vue 3 中的一个强大特性&#xff1a;<Suspense> 组件&#xff0c;以及它如何帮助我们更优雅地处理异步组件加载。如果你曾在 Vue 项目中处理过异步组件加载&#xff0c;那么这篇文章将为你介绍一种更简洁高效的方式。 什…...

HTB:LinkVortex[WriteUP]

目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用gobuster对靶机进行路径FUZZ 使用ffuf堆靶机…...