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

【开源免费】基于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>数据总条数:{ {tableData.length}}       <template>A列合计:{ {sum}}</template> </div> 注:tableData为<a-tabl…...
02.05、链表求和
02.05、[中等] 链表求和 1、题目描述 给定两个用链表表示的整数,每个节点包含一个数位。 这些数位是反向存放的,也就是个位排在链表首部。 编写函数对这两个整数求和,并用链表形式返回结果。 2、解题思路 本题要求对两个链表表示的整数…...

dmfldr实战
dmfldr实战 本文使用达梦的快速装载工具,对测试表进行数据导入导出。 新建测试表 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 副本机制(包含AR、ISR、OSR、HW 和 LEO 介绍)1. 副本的基本概念2. 副本同步和一致性2.1 AR(Assigned Replicas)2.2 ISR(In-Sync Replicas)2.3 OSR(Out-of-Sync Replicas…...

爬虫基础(二)Web网页的基本原理
一、网页的组成 网页由三部分构成:HTML、JavaScript、CSS。 (1)HTML HTML 相当于网页的骨架,它通过使用标签来定义网页内容的结构。 举个例子: 它把图片标签为img、把视频标签为video,然后组合到一个界面…...

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

Python 梯度下降法(五):Adam Optimize
文章目录 Python 梯度下降法(五):Adam Optimize一、数学原理1.1 介绍1.2 符号说明1.3 实现流程 二、代码实现2.1 函数代码2.2 总代码2.3 遇到的问题2.4 算法优化 三、优缺点3.1 优点3.2 缺点 四、相关链接 Python 梯度下降法(五&a…...
笔试-二进制
应用题 将符合区间[l,r]内的十进制整数转换为二进制表示,请问不包含“101”的整数个数是多少? 实现 l int(input("请输入下限l,其值大于等于1:")) r int(input("请输入上限r,其值大于等于l&#x…...
springboot 2.7.6 security mysql redis jwt配置例子
数据库结构用的是若依的数据库基本结构,ruoyi.vip。 总体参考了文章:https://blog.csdn.net/qq_45847507/article/details/126681110 本文章只包含不同的地方,相同的不再赘述。 1、创建spring工程,jdk1.8,maven。 pom.xml中依赖部…...

FreeRTOS从入门到精通 第十六章(任务通知)
参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili 一、任务通知简介 1、概述 (1)任务通知顾名思义是用来通知任务的,任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。 (2&#…...

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

无人机图传模块 wfb-ng openipc-fpv,4G
openipc 的定位是为各种模块提供底层的驱动和linux最小系统,openipc 是采用buildroot系统编译而成,因此二次开发能力有点麻烦。为啥openipc 会用于无人机图传呢?因为openipc可以将现有的网络摄像头ip-camera模块直接利用起来,从而…...
.cc扩展名是什么语言?C语言必须用.c为扩展名吗?主流编程语言扩展名?Java为什么不能用全数字的文件名?
.cc扩展名是什么语言? .cc是C语言使用的扩展名,一种说法是它是c with class的简写,当然C语言使用的扩展名不止.cc和.cpp, 还包含.cxx, .c, .C等,这些在不同编译器系统采用的默认设定不同,需要区分使用。当然,编译器提…...

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

【Linux】使用管道实现一个简易版本的进程池
文章目录 使用管道实现一个简易版本的进程池流程图代码makefileTask.hppProcessPool.cc 程序流程: 使用管道实现一个简易版本的进程池 流程图 代码 makefile ProcessPool:ProcessPool.ccg -o $ $^ -g -stdc11 .PHONY:clean clean:rm -f ProcessPoolTask.hpp #pr…...
【OpenGL】OpenGL游戏案例(二)
文章目录 特殊效果数据结构生成逻辑更新逻辑 文本渲染类结构构造函数加载函数渲染函数 特殊效果 为提高游戏的趣味性,在游戏中提供了六种特殊效果。 数据结构 PowerUp 类只存储存活数据,实际逻辑在游戏代码中通过Type字段来区分执行 class PowerUp …...
28. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表定时器与报表数据修正
这篇文章是《.NET 8 实战–孢子记账–从单体到微服务》系列专栏的《单体应用》专栏的最后一片和开发有关的文章。在这片文章中我们一起来实现一个数据统计的功能:报表数据汇总。这个功能为用户查看月度、年度、季度报表提供数据支持。 一、需求 数据统计方面&…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...