法线变换矩阵的推导

背景

在冯氏光照模型中,其中的漫反射项需要我们对法向量和光线做点乘计算。

从顶点着色器中读入的法向量数据处于模型空间,我们需要将法向量转换到世界空间,然后在世界空间中让法向量和光线做运算。这里便有一个问题,如何将法线从当前的模型空间变换到世界空间?

首先,法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点位置中的w分量)。这意味着,位移不应该影响到法向量。因此,如果我们打算把法向量乘以一个模型矩阵,我们就要从矩阵中移除位移部分,只选用模型矩阵左上角3×3的矩阵(注意,我们也可以把法向量的w分量设置为0,再乘以4×4矩阵;这同样可以移除位移)。对于法向量,我们只希望对它实施缩放和旋转变换。

其次,如果模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面了。因此,我们不能用这样的模型矩阵来变换法向量。下面的图展示了应用了不等比缩放的模型矩阵对法向量的影响:

在这里插入图片描述

当我们应用一个不等比缩放时(注意:等比缩放不会破坏法线,因为法线的方向没被改变,仅仅改变了法线的长度,而这很容易通过标准化来修复),法向量就不会再垂直于对应的表面了,这样光照就会被破坏。

修复这个行为的诀窍是使用一个为法向量专门定制的模型矩阵。这个矩阵称之为法线矩阵(Normal Matrix),它使用了一些线性代数的操作来移除对法向量错误缩放的影响。

推导过程

为了将一个顶点从模型空间转换到世界空间,我们可以乘上一个模型矩阵model,包含物体的移动、旋转、缩放信息。在shader中的代码如下:

FragPos = vec3(model * vec4(aPos, 1.0));

对于一个向量,正如上面的图展示的一样,我们不能简单乘上model矩阵。如果乘上model矩阵,向量就不再和原来的表面切线垂直了。

我们可以定义表面切线为 T = P 2 − P 1 T = P_2 - P1 T=P2P1,其中 P 1 , P 2 P_1,P_2 P1,P2都是表面上的顶点。当表面前线乘上model矩阵时,我们有:
m o d e l ∗ T = m o d e l ∗ P 2 − m o d e l ∗ P 1 T ′ = P 2 ′ − P 1 ′ model * T = model * P_2 - model * P_1 \\ T' = P_2' - P_1' modelT=modelP2modelP1T=P2P1
变换后的表面切线 T ′ T' T仍然可以表示成表面上顶点的差,因此乘上model矩阵之后,表面切线不会被破坏。

对于表面上的法线 N N N,我们无法从表面上找到两个顶点来表示,但是我们知道表面法线与切线互相垂直,即
N ⋅ T = 0 N \cdot T = 0 NT=0
我们假设矩阵 G G G就是可以将法线从模型空间转换到世界空间的正确矩阵,并用 M M M来表示模型矩阵model,于是有下式:
N ′ ⋅ T ′ = ( G N ) ⋅ ( M T ) = 0 N' \cdot T' = (GN)\cdot(MT) = 0 NT=(GN)(MT)=0
转化成矩阵表示的形式
( G N ) ⋅ ( M T ) = ( G N ) T ∗ ( M T ) = N T G T M T = 0 (GN)\cdot(MT) = (GN)^T*(MT) = N^TG^TMT = 0 (GN)(MT)=(GN)T(MT)=NTGTMT=0
我们知道 N ⋅ T = N T T = 0 N\cdot T = N^TT = 0 NT=NTT=0,所以如果 G T M = a I G^TM = aI GTM=aI a a a是任意非零常数,我们便有
N ′ ⋅ T ′ = N T G T M T = N T a I T = a N T T = 0 N'\cdot T' = N^TG^TMT = N^TaIT = aN^TT = 0 NT=NTGTMT=NTaIT=aNTT=0
由于我们不想改变法向量的模长,因此令 a = 1 a = 1 a=1,只要满足 G T M = I G^TM = I GTM=I的条件,我们就可以说 G G G是我们最终需要的矩阵,进一步计算
G T M = I ⟷ G = ( M − 1 ) T G^TM = I \longleftrightarrow G = (M^{-1})^T GTM=IG=(M1)T
最终可得,将法线从模型空间转换到世界空间的矩阵为 ( M − 1 ) T (M^{-1})^T (M1)T

补充说明

当模型矩阵只进行了旋转或等比缩放时,我们用这个矩阵来变换法线向量,可以得到正确的结果。

这是因为旋转矩阵和等比缩放矩阵都是正交矩阵,正交矩阵有一个属性:矩阵的转置等于矩阵的逆。

因此
M − 1 = M T → G = ( M − 1 ) T = M M^{-1} = M^T \rightarrow G = (M^{-1})^T = M M1=MTG=(M1)T=M

参考

https://learnopengl-cn.github.io/02%20Lighting/02%20Basic%20Lighting/

http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix/

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线程安全--互斥锁

文章目录 一.线程安全问题读取无效(脏)数据丢失更新线程安全的保证--操作的原子性 二.互斥锁及其实现原理互斥锁的实现原理pthread线程库提供的锁操作 三.死锁问题 一.线程安全问题 当多个线程并发地对同一个共享资源进行修改操作时,可能会引发数据读写错误(比如读取无效(脏)数…

多维时序 | Matlab实现GRO-CNN-BiLSTM-Attention淘金算法优化卷积神经网络-双向长短期记忆网络结合注意力机制多变量时间序列预测

多维时序 | Matlab实现GRO-CNN-BiLSTM-Attention淘金算法优化卷积神经网络-双向长短期记忆网络结合注意力机制多变量时间序列预测 目录 多维时序 | Matlab实现GRO-CNN-BiLSTM-Attention淘金算法优化卷积神经网络-双向长短期记忆网络结合注意力机制多变量时间序列预测效果一览基…

基于随机抽样或最小二乘法 c++实现三维点云平面检测

随机抽样 std::vector<int> random(int n, int N){std::vector<int> rets;for(int i0; i<N; i){while(true){int v rand() % n;if(std::find(rets.begin(), rets.end(), v) rets.end()){rets.push_back(v);break;}}}return rets; } bool Plane(std::vector&l…

数据安全保障的具体措施有哪些

随着信息化时代的到来&#xff0c;数据已经成为企业和社会发展的重要资产。然而&#xff0c;数据安全问题也日益突出&#xff0c;如何保障数据的安全性、完整性和可用性成为了亟待解决的问题。以下将详细探讨数据安全保障的各个方面&#xff0c;以期为企业和社会提供更好的数据…

飞桨分子动力学模拟-论文复现第六期:复现TorchMD

飞桨分子动力学模拟-论文复现第六期&#xff1a;复现TorchMD Paddle for MD 飞桨分子动力学模拟科学计算 复现论文-TorchMD: A deep learning framework for molecular simulations 本项目可在AIStudio一键运行&#xff1a;飞桨分子动力学模拟PaddleMD-复现TorchMD 【论文复…

分布式系统的前世

文章目录 前言分布式系统解决了什么问题分布式系统存在什么问题总结 前言 大家好&#xff0c;我是醉墨居士&#xff0c;我准备和大家浅聊一下分布式系统&#xff0c;分享我一下我的心得体会&#x1fae0; 分布式系统解决了什么问题 如果用户的请求压力过于庞大&#xff0c;使…

原生Ajax的使用,四种请求方法示例(前后端代码)

目录 原生Ajax是什么 原生Ajax的优点 Ajax应用环境 Ajax的使用 基本使用步骤 AJAX请求状态和HTTP状态码 AJAX 请求状态 HTTP 状态码 XHR对象的方法 各种请求方式和数据获取 post请求 post 请求完整代码 get 请求 服务端 put 请求 服务端 delete 请求 服务端代…

TypeScript基础知识:类型守卫和类型推断

在 TypeScript 中&#xff0c;类型守卫和类型推断是两个重要的概念&#xff0c;它们可以帮助我们更好地理解和利用类型系统的优势。本文将详细介绍这两个概念&#xff0c;并提供示例代码来说明它们的用法和优势。 一、类型守卫 类型守卫是一种在 TypeScript 中用于缩小变量类型…

U盘安装XP纯净版系统教程软件安装教程(附软件下载地址)

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; U盘安装XP纯净版系统是一种便捷且快速的方式&#xff0c;以实现系统重装或升级的需求。这篇教程将为您详细介绍如何使用U盘来安装XP纯净版系统。XP纯…

代码随想录Day 17 | 110 平衡二叉树 257 二叉树的所有路径 404 左叶子之和

代码随想录Day 17 | 110 平衡二叉树 257 二叉树的所有路径 404 左叶子之和 平衡二叉树二叉树的所有路径左叶子之和 平衡二叉树 文档讲解&#xff1a;代码随想录 视频讲解&#xff1a; 后序遍历求高度&#xff0c;高度判断是否平衡 | LeetCode&#xff1a;110.平衡二叉树 状态 …

DEJA_VU3D - Cesium功能集 之 117-雷达扫描(圆环效果)

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小140个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(每篇博文都会奉上完整demo的源代码…

C++知识点总结(13):函数

一、定义 函数&#xff0c;指可以实现某个功能&#xff0c;可以重复使用的一段代码。不同的函数之间相互独立&#xff0c;即函数之间的功能互不影响&#xff08;互相的代码&#xff09;。 二、结构 1. 定义 返回值类型 函数名(形参1, 形参2, 形参3...形参n) {...return 值; }2…

Java初学习

Java代码示例&#xff1a; public class helloworld {public static void main(String[] args){System.out.println("hello world");} } Java程序的名字需要和文件名字一致&#xff0c;就是那个helloworld Java程序需要对类有深度的认识&#xff1a; 对象是类的…

2023年全国职业院校技能大赛软件测试赛题—单元测试卷②

单元测试 一、任务要求 题目1&#xff1a;任意输入2个正整数值分别存入x、y中&#xff0c;据此完成下述分析&#xff1a;若x≤0或y≤0&#xff0c;则提示&#xff1a;“输入不符合要求。”&#xff1b;若2值相同&#xff0c;则提示“可以构建圆形或正方形”&#xff1b;若2<…

ipad协议逆向分析实战篇-1

请使用dnspy环境进行学习研究&#xff0c;切勿用于非法操作 1.首先拿到得到的部署包进行逆向分析 2.解压部署包并找到bin这个文件夹 3.找到Wechat.Api.dll这个文件 4.这两个是协议的核心文件&#xff0c;破解了这个核心文件就可以得出逻辑源码 5.首先把Wechat.Api.dll这个…

Pandas实战100例 | 案例 23: 处理空值

案例 23: 处理空值 知识点讲解 处理空值是数据清洗过程中的一个关键步骤。Pandas 提供了多种方法来检测、填充和删除空值。 检测空值: 使用 isnull 方法可以检测 DataFrame 中的空值。填充空值: 使用 fillna 方法可以填充空值。删除包含空值的行或列: 使用 dropna 方法可以删…

C++ (MFC) 单程序运行(防止多开程序)

C (MFC) 单程序运行&#xff08;防止多开程序) 项目文件名:MFCAppTest 在 C*****App.cpp 文件中 CMFCAppTestApp::InitInstance 函数中 添加以下代码 //避免程序的多开 xxxx为信号量的名字 可随意CreateMutex(NULL, TRUE, TEXT("MFCAppTest")); if (GetLastError…

oracle—IMU机制

正常的情况下&#xff0c;当事务需要回滚块的时候&#xff0c;是去undo表空间找 现在是在sharepool中分一个IMUbuffer&#xff0c;将所有的回滚信息写入。直接就可以从中取。减少了物理IO 同时这个过程也产生redo&#xff0c;直接就是图中红色的&#xff0c;不防止崩溃 优点 1…

开机自启动android app

Android App开机自启动_android 开机自启动-CSDN博客 注意权限问题&#xff1a; 第二种实现方式&#xff1a;系统桌面应用 问&#xff1a;android的系统桌面应用启动是什么&#xff1a; 答&#xff1a; Android 系统桌面应用是指用户在设备主屏幕上看到的默认启动界面&…

代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点面试题 02.07. 链表相交、142.环形链表II

文档讲解&#xff1a;虚拟头节点&#xff0c;三指针&#xff0c;快慢指针&#xff0c;链表相交&#xff0c;环形链表&#xff0c; 技巧&#xff1a; 1、对于指针的操作要画图&#xff0c;明确步骤后好做了 2、使用虚拟头节点可以避免对头节点单独讨论&#xff0c;且方便对头节点…