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

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

春节忙完一轮,总算可以继续来写博客了。希望在春节假期结束之前能多更新几篇。

这一篇会偏理论多一点。笔者本没打算在这一系列里面重点讲理论,所以像相机矩阵推导这种网上已经很多优质文章的内容,笔者就一笔带过。

然而关于负缩放,笔者未能找到从正负缩放到行列式正负的完整推导过程(可能是笔者没用对关键词吧,GPT也还没去用),中途也就找到了一个正/负定矩阵的概念。该说法已经有点正负缩放的意思了,所以本篇虽然侧重理论,但也会结合第5篇的坑进行阐述。

回顾第5篇

(5.2):THREE.Mesh和THREE.Line2在镜像处理上的区别

笔者在排查Line2镜像丢失的bug时,发现了three.js处理正反面的逻辑,它对接原生WebGL的正反面定义——三角面3个点的绕序(顺逆时针)。

three.js的实现机制是,如果物体的变换矩阵的秩(行列式)为负数,则指示物体做了带有负缩放矩阵的变换,就把逆时针旋转的三角面从背面改为正面。

从这一过程可以发现,对一个三角面应用负缩放时,其三个点的绕序会发生变化,而正缩放则不会。而一个缩放的正负,three.js是通过矩阵的秩进行判断的。

这里笔者再多嘴一下,如果矩阵的秩等于0,那么它就会把三角面的三个点变换到在同一直线上,或者会让两个或者3个点完全重合。笔者姑且称之为零定矩阵。与此同时,它也是个不可逆的矩阵。

本篇我们先拿2D的3*3矩阵来探讨行列式的正负对三点绕序的影响。3D的情况比较复杂,因为它涉及到不同轴的情况,笔者有很长一段时间以为3D无法定义负矩阵,直到研读了three.js的代码才恍然大悟。

推导过程有点繁琐,此处先给结论:矩阵缩放的正负,正负定矩阵,行列式的正负性这3者完全等价,正定矩阵不改变绕序,负定矩阵改变,零定矩阵是不可逆矩阵,它会让不共线的点变成共线甚至重合。

好,下面进入正题。

给定2D上的3个不共线的点O(x0, y0),A(x1,y1),B(x2,y2),要判断这3个点的绕序,可以作向量OA,OB,然后通过计算这两个2D向量的叉乘值得出。如下图所示,OA旋转到OB是顺时针。

对这3个点应用正定矩阵,对应两向量的叉乘结果不变,而应用负定矩阵,则叉乘结果改变,如下图所示。

然后我们给定一个3*3的2D变换矩阵(不管是2D点还是3D点,矩阵的阶数都比点的维数多1,目的是加法和乘法合并,前面讲过比较多次了,此处不再重复,实在不懂可以问我)。

对一个2D点应用矩阵,运算过程如下:

可见通过给点的最后一行补1,最后一列就实现了加法。

由于此处只研究2D点的绕序问题,因此最后一行的结果我们无需关注,在此场景它只为了让矩阵乘法计算可以正常进行。

下面我们的推导过程将分以下几步:

1 计算OA,OB向量及它们的叉乘值c

2 计算点O,A,B应用矩阵后的结果O',A',B'

3 计算向量O'A',O'B'以及它们的叉乘值c'

4 考察c和c'符号的一致性,并给出一致性跟矩阵元素的关系

5 上述关系对应到行列式上验证上面给出的结论

OA和OB是(x1-x0,y1-y0),以及(x2-x0, y2-y0),为了书写简便,后面会把x1-x0记为Δx1,y1-y0记为Δy1,其它类似。

两向量(x1,y1),(x2,y2)的2D叉乘公式为x1*y2-x2*y1,所以OA和OB的叉乘结果为

至此,第一步计算完成。

第二步,计算矩阵变换后的O,A,B,如上所述,其结果为

第三步,计算O'A',O'B'和他们俩的叉乘值

类似地有

然后叉乘值为

这个推导过程可能会让对数学不敏感的小伙伴们感到蛋疼,所以笔者曾经考虑过要不要牺牲一定的严谨性,拿个特殊点,简单点的变量来做演示。如果实在看不下去,小伙伴们可以让O=(0,0),A=(1,0),B=(0,1)来演算一遍,找下感觉。对于2D来说,拿特殊点是没有任何毛病的,除非你拿了重合的点或者共线的3点。

我们对照下OA和OB的叉乘,以及O'A'和O'B的叉乘,后者等于前者乘以(m11m22-m22*m11), 因此判断向量变换前叉积符号的一致性,只需要判断m11m22-m22*m11的符号即可。可见,矩阵对不共线3点绕序的影响仅跟矩阵的4个元素有关,而跟你的取点无关。

现在我们把刚才说到的矩阵搬过来,看看它跟行列式的关系。

发现计算结果只取了3*3矩阵中的前面两阶的元素做了一个行列式的计算。而第三列中的元素,因为它只管平移,并且跟点坐标无关,所以它在计算向量的时候就已经被消去了。至于第三行,因为它是为了将加法和乘法合并,不是本文的关注点,所以全程我们都没有让它参与到计算当中。

然而three.js自带的Matrix3类,它的行列式值则是严格按照数学的定义,按3阶行列式来书写:

那为什么我们在使用的过程中没有出现任何问题呢?我们把刚才3*3矩阵乘以点的计算公式拿过来。

为了让点变换的过程中,为合并而补充的那个1保持不变,我们应该让第三行的计算结果

m31*x+m32*y+m33恒等于1,因此结果不能受x和y影响,所以m31和m32要等于0,而剩下的m33,自然就等于1了。也就是说,这一行填充的是单位矩阵的数值。

熟悉行列式运算法则的小伙伴应该一眼就看得出来,用到第三列元素的项都要跟0相乘,否则就是跟1相乘。但为了照顾更多的小伙伴,笔者也按3阶行列式的定义演算一遍。

可见,用单位矩阵填充第3行以后,其行列式的值跟2阶行列式出来的结果完全一致,证毕。

这里需要注意的一点是,three.js的Matrix3允许第三行不使用单位矩阵的数值。这时候,它的行列式符号不能在2D变换的场景下判断矩阵的正负性。

推导完成了,来小结一下(注意以下结论均建立在Matrix3做2D变换的场景):

1 正负缩放矩阵,正负定矩阵,行列式正负完全等价(此外还有零的情况)

2 正负矩阵的性质:不共线的3个2D点,正矩阵不会修改它们的绕序,负矩阵会

3 three.js利用这一性质,根据WebGL正反面的规则修复单面材质镜像后不能正确显示的问题

4 零矩阵会使它们共线或者共点,且不可逆

5 判断一个矩阵的正负,用的是3*3矩阵前两行两列所构成的行列式的符号。若想对齐到3阶矩阵,则第三行必须按照单位矩阵进行填充,否则会出现错误的结果

现在我们来简单聊聊3D,为什么前面说3D比2D复杂。

大家脑补一下,我们给2D点直接加上z坐标,全部设置为0。如果我们应用x方向负缩放或者y方向负缩放的话,那么应用前面的结论一点毛病都没有,但若应用z负缩放,则所有的点都不会有任何变化,哪怕你不设置为0,只要z值完全一样,那翻转后,也就是z值统一改成一样的数字,但这时候,其实正反面已经颠倒了。

就这么一个简单的情况,本文的结论就已经无法应用到3D上了,所以今天我们就先到这里,后面我会跟大家继续探讨3D矩阵的正负缩放问题,敬请期待!

相关文章:

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

春节忙完一轮,总算可以继续来写博客了。希望在春节假期结束之前能多更新几篇。 这一篇会偏理论多一点。笔者本没打算在这一系列里面重点讲理论,所以像相机矩阵推导这种网上已经很多优质文章的内容,笔者就一笔带过。 然而关于负缩放&#xf…...

【开源免费】基于SpringBoot+Vue.JS体育馆管理系统(JAVA毕业设计)

本文项目编号 T 165 ,文末自助获取源码 \color{red}{T165,文末自助获取源码} T165,文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…...

《大数据时代“快刀”:Flink实时数据处理框架优势全解析》

在数字化浪潮中,数据呈爆发式增长,实时数据处理的重要性愈发凸显。从金融交易的实时风险监控,到电商平台的用户行为分析,各行业都急需能快速处理海量数据的工具。Flink作为一款开源的分布式流处理框架,在这一领域崭露头…...

antdesignvue统计数据源条数、计算某列合计值、小数计算不精确多了很多小数位

1.在</a-table>下方加如下代码 <div>数据总条数&#xff1a;{ {tableData.length}}&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp <template>A列合计&#xff1a;{ {sum}}</template> </div> 注&#xff1a;tableData为<a-tabl…...

02.05、链表求和

02.05、[中等] 链表求和 1、题目描述 给定两个用链表表示的整数&#xff0c;每个节点包含一个数位。 这些数位是反向存放的&#xff0c;也就是个位排在链表首部。 编写函数对这两个整数求和&#xff0c;并用链表形式返回结果。 2、解题思路 本题要求对两个链表表示的整数…...

dmfldr实战

dmfldr实战 本文使用达梦的快速装载工具&#xff0c;对测试表进行数据导入导出。 新建测试表 create table “BENCHMARK”.“TEST_FLDR” ( “uid” INTEGER identity(1, 1) not null , “name” VARCHAR(24), “begin_date” TIMESTAMP(0), “amount” DECIMAL(6, 2), prim…...

Kafka 副本机制(包含AR、ISR、OSR、HW 和 LEO 介绍)

文章目录 Kafka 副本机制&#xff08;包含AR、ISR、OSR、HW 和 LEO 介绍&#xff09;1. 副本的基本概念2. 副本同步和一致性2.1 AR&#xff08;Assigned Replicas&#xff09;2.2 ISR&#xff08;In-Sync Replicas&#xff09;2.3 OSR&#xff08;Out-of-Sync Replicas&#xf…...

爬虫基础(二)Web网页的基本原理

一、网页的组成 网页由三部分构成&#xff1a;HTML、JavaScript、CSS。 &#xff08;1&#xff09;HTML HTML 相当于网页的骨架&#xff0c;它通过使用标签来定义网页内容的结构。 举个例子&#xff1a; 它把图片标签为img、把视频标签为video&#xff0c;然后组合到一个界面…...

外网访问禅道软件项目管理系统

禅道项目管理软件是一款国产的开源免费项目管理软件&#xff0c;专注于研发项目管理&#xff0c;旨在帮助企业或团队提高项目管理的效率和质量。 本文将详细的介绍如何在 Windows 系统电脑端下载运行禅道软件项目管理系统&#xff0c;并且结合路由侠内网穿透实现外网访问本地的…...

Python 梯度下降法(五):Adam Optimize

文章目录 Python 梯度下降法&#xff08;五&#xff09;&#xff1a;Adam Optimize一、数学原理1.1 介绍1.2 符号说明1.3 实现流程 二、代码实现2.1 函数代码2.2 总代码2.3 遇到的问题2.4 算法优化 三、优缺点3.1 优点3.2 缺点 四、相关链接 Python 梯度下降法&#xff08;五&a…...

笔试-二进制

应用题 将符合区间[l,r]内的十进制整数转换为二进制表示&#xff0c;请问不包含“101”的整数个数是多少&#xff1f; 实现 l int(input("请输入下限l&#xff0c;其值大于等于1&#xff1a;")) r int(input("请输入上限r&#xff0c;其值大于等于l&#x…...

springboot 2.7.6 security mysql redis jwt配置例子

数据库结构用的是若依的数据库基本结构,ruoyi.vip。 总体参考了文章&#xff1a;https://blog.csdn.net/qq_45847507/article/details/126681110 本文章只包含不同的地方&#xff0c;相同的不再赘述。 1、创建spring工程&#xff0c;jdk1.8&#xff0c;maven。 pom.xml中依赖部…...

FreeRTOS从入门到精通 第十六章(任务通知)

参考教程&#xff1a;【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili 一、任务通知简介 1、概述 &#xff08;1&#xff09;任务通知顾名思义是用来通知任务的&#xff0c;任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。 &#xff08;2&#…...

TensorFlow 简单的二分类神经网络的训练和应用流程

展示了一个简单的二分类神经网络的训练和应用流程。主要步骤包括&#xff1a; 1. 数据准备与预处理 2. 构建模型 3. 编译模型 4. 训练模型 5. 评估模型 6. 模型应用与部署 加载和应用已训练的模型 1. 数据准备与预处理 在本例中&#xff0c;数据准备是通过两个 Numpy 数…...

无人机图传模块 wfb-ng openipc-fpv,4G

openipc 的定位是为各种模块提供底层的驱动和linux最小系统&#xff0c;openipc 是采用buildroot系统编译而成&#xff0c;因此二次开发能力有点麻烦。为啥openipc 会用于无人机图传呢&#xff1f;因为openipc可以将现有的网络摄像头ip-camera模块直接利用起来&#xff0c;从而…...

.cc扩展名是什么语言?C语言必须用.c为扩展名吗?主流编程语言扩展名?Java为什么不能用全数字的文件名?

.cc扩展名是什么语言? .cc是C语言使用的扩展名&#xff0c;一种说法是它是c with class的简写&#xff0c;当然C语言使用的扩展名不止.cc和.cpp, 还包含.cxx, .c, .C等&#xff0c;这些在不同编译器系统采用的默认设定不同&#xff0c;需要区分使用。当然&#xff0c;编译器提…...

【MyDB】4-VersionManager 之 3-死锁及超时检测

【MyDB】4-VersionManager 之 3-死锁及超时检测 死锁及超时检测案例背景LockTable锁请求与等待管理 addvm调用addputIntoList&#xff0c;isInList&#xff0c;removeFromList 死锁检测 hasDeadLock方法资源释放与重分配 参考资料 死锁及超时检测 本章涉及代码&#xff1a;top/…...

【Linux】使用管道实现一个简易版本的进程池

文章目录 使用管道实现一个简易版本的进程池流程图代码makefileTask.hppProcessPool.cc 程序流程&#xff1a; 使用管道实现一个简易版本的进程池 流程图 代码 makefile ProcessPool:ProcessPool.ccg -o $ $^ -g -stdc11 .PHONY:clean clean:rm -f ProcessPoolTask.hpp #pr…...

【OpenGL】OpenGL游戏案例(二)

文章目录 特殊效果数据结构生成逻辑更新逻辑 文本渲染类结构构造函数加载函数渲染函数 特殊效果 为提高游戏的趣味性&#xff0c;在游戏中提供了六种特殊效果。 数据结构 PowerUp 类只存储存活数据&#xff0c;实际逻辑在游戏代码中通过Type字段来区分执行 class PowerUp …...

28. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表定时器与报表数据修正

这篇文章是《.NET 8 实战–孢子记账–从单体到微服务》系列专栏的《单体应用》专栏的最后一片和开发有关的文章。在这片文章中我们一起来实现一个数据统计的功能&#xff1a;报表数据汇总。这个功能为用户查看月度、年度、季度报表提供数据支持。 一、需求 数据统计方面&…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

go 里面的指针

指针 在 Go 中&#xff0c;指针&#xff08;pointer&#xff09;是一个变量的内存地址&#xff0c;就像 C 语言那样&#xff1a; a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10&#xff0c;通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

用鸿蒙HarmonyOS5实现中国象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...