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):负缩放,负定矩阵和行列式的关系(2D版本) 既然three.js对3D版的负缩放也使用行列式进行判断,那么,2D版的结论用到3D上其实是没毛病的,THREE.Li…...
【ubuntu】双系统ubuntu下一键切换到Windows
ubuntu下一键切换到Windows 1.4.1 重启脚本1.4.2 快捷方式1.4.3 移动快捷方式到系统目录 按前文所述文档,开机默认启动ubuntu。Windows切换到Ubuntu直接重启就行了,而Ubuntu切换到Windows稍微有点麻烦。可编辑切换重启到Windows的快捷方式。 1.4.1 重启…...
力扣第149场双周赛
文章目录 题目总览题目详解找到字符串中合法的相邻数字重新安排会议得到最多空余时间I 第149场双周赛 题目总览 找到字符串中合法的相邻数字 重新安排会议得到最多空余时间I 重新安排会议得到最多空余时间II 变成好标题的最少代价 题目详解 找到字符串中合法的相邻数字 思…...
在线课堂小程序设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
https的原理
HTTPS 的原理 HTTPS(HyperText Transfer Protocol Secure)是一种通过计算机网络进行安全通信的传输协议。它在 HTTP 的基础上增加了 SSL/TLS 协议,以实现数据传输的安全性和完整性。以下是 HTTPS 的基本原理: 1. 基本概念 HTTP…...
当卷积神经网络遇上AI编译器:TVM自动调优深度解析
从铜线到指令:硬件如何"消化"卷积 在深度学习的世界里,卷积层就像人体中的毛细血管——数量庞大且至关重要。但鲜有人知,一个简单的3x3卷积在CPU上的执行路径,堪比北京地铁线路图般复杂。 卷积的数学本质 对于输入张…...
Flask 使用Flask-SQLAlchemy操作数据库
username db.Column(db.String(64), uniqueTrue, indexTrue); password db.Column(db.String(64)); 建立对应关系 如果是多对多关系就建一张表,关联两个表的id role_id db.Column(db.Integer, db.ForeignKey(‘roles.id’)) ‘’’ 帮助作关联查询 relati…...
[EAI-023] FAST,机器人动作专用的Tokenizer,提高VLA模型的能力和训练效率
Paper Card 论文标题:FAST: Efficient Action Tokenization for Vision-Language-Action Models 论文作者:Karl Pertsch, Kyle Stachowicz, Brian Ichter, Danny Driess, Suraj Nair, Quan Vuong, Oier Mees, Chelsea Finn, Sergey Levine 论文链接&…...
使用Pygame制作“太空侵略者”游戏
1. 前言 在 2D 游戏开发中,“太空侵略者”是一款入门难度适中、却能覆盖多种常见游戏机制的项目: 玩家控制飞船(Player)左右移动,发射子弹。敌人(Enemy)排列成一行或多行,从屏幕顶…...
《逆向工程核心原理》第三~五章知识整理
查看上一章节内容《逆向工程核心原理》第一~二章知识整理 对应《逆向工程核心原理》第三章到第五章内容 小端序标记法 字节序 多字节数据在计算机内存中存放的字节顺序分为小端序和大端序两大类 大端序与小端序 BYTE b 0x12; WORD w 0x1234; DWORD dw 0x12345678; cha…...
2025 AI行业变革:从DeepSeek V3到o3-mini的技术演进
【核心要点】 DeepSeek V3引领算力革命,成本降至1/20o3-mini以精准优化回应市场挑战AI技术迈向真正意义的民主化行业生态正在深刻重构 一、市场格局演变 发展脉络 2025年初,AI行业迎来重要转折。DeepSeek率先发布V3模型,通过革命性的架构创…...
SAP SD学习笔记28 - 请求计划(开票计划)之2 - Milestone请求(里程碑开票)
上一章讲了请求计划(开票计划)中的 定期请求。 SAP SD学习笔记27 - 请求计划(开票计划)之1 - 定期请求-CSDN博客 本章继续来讲请求计划(开票计划)的其他内容: Milestone请求(里程碑请求)。 目录 1,Miles…...
算法随笔_27:最大宽度坡
上一篇:算法随笔_26: 按奇偶排序数组-CSDN博客 题目描述如下: 给定一个整数数组 nums,坡是元组 (i, j),其中 i < j 且 nums[i] < nums[j]。这样的坡的宽度为 j - i。 找出 nums 中的坡的最大宽度,如果不存在,返回 0 。 …...
SpringBoot+Vue的理解(含axios/ajax)-前后端交互前端篇
文章目录 引言SpringBootThymeleafVueSpringBootSpringBootVue(前端)axios/ajaxVue作用响应式动态绑定单页面应用SPA前端路由 前端路由URL和后端API URL的区别前端路由的数据从哪里来的 Vue和只用三件套axios区别 关于地址栏url和axios请求不一致VueJSPS…...
大白话讲清楚embedding原理
Embedding(嵌入)是一种将高维数据(如单词、句子、图像等)映射到低维连续向量的技术,其核心目的是通过向量表示捕捉数据之间的语义或特征关系。以下从原理、方法和应用三个方面详细解释Embedding的工作原理。 一、Embe…...
2025年1月22日(网络编程 udp)
系统信息: ubuntu 16.04LTS Raspberry Pi Zero 2W 系统版本: 2024-10-22-raspios-bullseye-armhf Python 版本:Python 3.9.2 已安装 pip3 支持拍摄 1080p 30 (1092*1080), 720p 60 (1280*720), 60/90 (640*480) 已安装 vim 已安装 git 学习…...
【RAG】SKLearnVectorStore 避免使用gpt4all会connection err
gpt4all 列表中包含了多个开源的大模型,如 Qwen2.5、Llama 3、DeepSeek、Mistral 等,但 不包含 OpenAI 的 GPT-4o。GPT-4o 是 OpenAI 提供的闭源模型,目前只能通过 OpenAI API 或 ChatGPT 官方应用(网页版、移动端)访问,并不支持本地运行,也没有 GGUF 量化格式的模型文件…...
ios swift画中画技术尝试
继上篇:iOS swift 后台运行应用尝试失败-CSDN博客 为什么想到画中画,起初是看到后台模式里有一个picture in picture,去了解了后发现这个就是小窗口视频播放,方便用户执行多任务。看小窗口视频的同时,可以作其他的事情…...
Prometheus 中的 Exporter
在 Prometheus 生态系统中,Exporter 扮演着至关重要的角色,它们负责从不同的服务或系统中收集和暴露度量数据。本文将详细介绍 Exporter 的概念、类型以及如何有效使用它们将 Prometheus 集成到各种系统中进行监控。 什么是 Exporter? Exporter 是一段软件,它从应用程序或…...
玄奘的启示
今天没事,又看了一遍央视拍的《玄奘大师》(程池、齐秦配乐版)伪纪录片,很有感触。 古罗马哲学家塞内加说“人最可怕的事情莫过于死前只留下活过的岁数。” 他在《论生命之短暂》中这样写道:“生命并非短促࿰…...
车载以太网---数据链路层
在上一章节中,我们讲解了数据链路层与物理层的接口MIIM,在本章中我们主要介绍车载网络中的数据链路层。 目录 数据链路层与网络层的区别 数据链路层:负责“同一链路”或“局域网/子网”内的可靠传输 传输范围: 主要功能: 通路…...
文本复制兼容方案最佳实现落地。
文章目录 一、navigator.clipboard.writeText二、方案落地总结 一、navigator.clipboard.writeText navigator.clipboard.writeText 是一个Web API,它允许网页脚本将文本数据写入用户的系统剪贴板。这个API是异步的,并且设计用于提高安全性和用户体验&a…...
ArkTS高性能编程实践
文章目录 概述声明与表达式函数数组异常 概述 本文主要提供应用性能敏感场景下的高性能编程的相关建议,助力开发者开发出高性能的应用。高性能编程实践,是在开发过程中逐步总结出来的一些高性能的写法和建议,在业务功能实现过程中࿰…...
阿里新发的大模型Qwen2.5-max如何?
阿里新发布的大模型Qwen2.5-Max是一款性能卓越、技术先进的大型语言模型,其在多个方面展现了突出的表现。以下是基于我搜索到的资料对Qwen2.5-Max的详细评价: 技术特点 超大规模预训练数据:Qwen2.5-Max采用了超过20万亿tokens的超大规模预训…...
吴晓波 历代经济变革得失@简明“中国经济史” - 读书笔记
目录 《历代经济变革得失》读书笔记一、核心观点二、主要内容(一)导论(二)春秋战国时期(三)汉代(四)北宋(五)明清时期(六)近现代&…...
SQL GROUP BY 详解
SQL GROUP BY 详解 引言 在数据库查询中,GROUP BY 子句是一个非常有用的工具,它允许我们对查询结果进行分组,并基于这些分组进行聚合计算。本文将详细介绍 GROUP BY 的用法、注意事项以及在实际应用中的场景。 什么是 GROUP BY? GROUP BY 子句用于对查询结果进行分组。…...
走向基于大语言模型的新一代推荐系统:综述与展望
HightLight 论文题目:Towards Next-Generation LLM-based Recommender Systems: A Survey and Beyond作者机构:吉林大学、香港理工大学、悉尼科技大学、Meta AI论文地址: https://arxiv.org/abs/2410.1974 基于大语言模型的下一代推荐系统&…...
6 Flink 状态管理
6 Flink 状态管理 1. State-Keyed State2. State-Operator State3. Broadcast State 我们前面写的 wordcount 的例子,没有包含状态管理。如果一个task在处理过程中挂掉了,那么它在内存中的状态都会丢失,所有的数据都需要重新计算。从容错和消…...
第1章 量子暗网中的血色黎明
月球暗面的危机与阴谋 量子隧穿效应催生的幽蓝电弧,于环形山表面肆意跳跃,仿若无数奋力挣扎的机械蠕虫,将月球暗面的死寂打破,徒增几分诡异。艾丽伫立在被遗弃的“广寒宫”量子基站顶端,机械义眼之中,倒映着…...
爬虫基础(六)代理简述
目录 一、什么是代理 二、基本原理 三、代理分类 一、什么是代理 爬虫一般是自动化的,当我们自动运行时 爬虫自动抓取数据,但一会就出现了错误: 如,您的访问频率过高! 这是因为网站的反爬措施,如果频…...
