法线变换

法线(normal),也被称为法矢量(normal vector)。在以前我们已经讲过如何使用变换矩阵来变换一个顶点或方向矢量,但法线是需要我们特殊处理的一种方向矢量。在游戏中,模型的顶点往往会携带额外的信息,而顶点法线就是其中一种信息。当我们变换一个模型的时候,不仅需要变换它的顶点,还需要变换顶点法线,以便在后续处理(如片元着色器)中计算光照等。
一般来说,点和绝大部分方向矢量都可以使用同一个4×4或3×3的变换矩阵Ma->b把其从坐标空间A变换到坐标空间B中。但在变换法线的时候,如果使用同一个变换矩阵,可能就无法确保维持法线的垂直型。下面就来了解一下为什么会出现这样的问题。
我们先来了解另一种方向矢量——切线(tangent),也被称为切矢量(tangent vector)。与法线类似,切线往往也是模型顶点携带的一种信息。它通常与纹理空间对其,而且与法线方向垂直,如图所示。

由于切线是由两个顶点之间的差值计算得到的,因此我们可以直接使用用于变换顶点的变换矩阵来变换切线。假设,我们使用3×3变换矩阵Ma->b来变换顶点(注意,这里涉及到的变换矩阵都是3×3矩阵,不考虑平移变换,这因为切线和法线都是矢量,不会受平移影响),可以由下面的式子直接得到变换后的切线:

其中Ta和Tb分别表示在空间坐标A下和坐标空间B下的切线方向。但如果直接食用Ma->b来变换法线,得到的新的法线可能就不会与表面垂直了,下图给出了这样的一个例子:

那么,我们应该用哪个矩阵来变换法线呢?我们可以由数学约束条件来推出这个矩阵。我们知道一个顶点的切线Ta和法线Na必须满足垂直条件,即Ta·Na=0。给定变换矩阵Ma->b,我们已经知道Tb=Ma->bTa。我们现在想要找到一个矩阵G来变换法线Na,使得变换后的法线让然与切线垂直。即

对上式进行一些推导可得

由于

因此如果

那么上式即可成立。也就是说如果

即使用原变换矩阵的逆转置矩阵来变换法线就可以得到正确的结果。
值得注意的是,如果变换矩阵Ma->b是正交矩阵,那么

因此

也就是说我们可以使用用于变换顶点的变换矩阵来直接变换法线。如果变换只包括旋转变换,那么这个变换矩阵就是正交矩阵。而如果变换只包含旋转和统一缩放,而不包含非统一缩放,我们利用统一缩放系数k来得到变换矩阵Ma->b的逆转置矩阵

这样就可以避免计算逆矩阵的过程。而如果变换中包含了非统一的变换,那么我们就必须要求解逆矩阵来得到变换法线的矩阵。

第三章 学习Shader所需的数学基础(4)的更多相关文章

  1. 第三章 学习Shader所需的数学基础(2)

    目录 1.坐标空间 1.2 坐标空间的变换 @ 1.坐标空间 我们在以前渲染流水线中就接触了坐标空间的变换.例如,在学习顶点着色器流水线阶段时,我们说过,顶点着色器的最基本功能就是把模型的顶点坐标从模 ...

  2. 第三章 学习Shader所需的数学基础(5)

    1. Unity Shader的内置变量(数学篇) 使用Unity写shader的一个好处在于,它提供了很多内置参数,这使得我们不在需要自己手动算一些值.本文给出Unity内置的用于空间变换和摄像机以 ...

  3. 第三章 学习Shader所需的数学基础(3)

    @[TOC] 1. 顶点的坐标空间变换过程 我们知道,在渲染流水线中,一个顶点要经过多个坐标空间的变换才能最终被画在屏幕上.一个顶点最开始是在模型空间中定义的,它最后会被变换到屏幕空间中,得到真正的屏 ...

  4. 第三章 学习Shader所需的数学基础(1)

    1. 笛卡尔坐标系 在游戏中,我们使用的数学大部分都是为了计算位置.距离和角度等变量.而这些就算大部分是在笛卡尔坐标系下进行的. 1.1 二维笛卡尔坐标系 一个二维笛卡尔坐标系包含了两个部分的信息 1 ...

  5. Unity Shader入门精要学习笔记 - 第4章 学习 Shader 所需的数学基础

    摘录自 冯乐乐的<Unity Shader入门精要> 笛卡尔坐标系 1)二维笛卡尔坐标系 在游戏制作中,我们使用的数学绝大部分都是计算位置.距离.角度等变量.而这些计算大部分都是在笛卡尔坐 ...

  6. 学习Shader所需的数学基础(坐标系,点和矢量)

    数学对于计算机图形学的重要性是不言而喻的.在学习Shader之前,首先就要打好数学基础,好在入门Unity Shader所需的数学知识都是线性代数中很基础的的内容.按部就班的来,第一篇文章记录总结的是 ...

  7. 《ORACLE数据库管理与开发》第三章学习之常用函数记录

    <ORACLE数据库管理与开发>第三章学习之常用函数记录 注:文章中的*代表所要操作的列名 1.lower(*)/upper(*),将此列下的值转为小写/大写 2.initcap(*):把 ...

  8. 《Linux内核设计与实现》第三章学习笔记

    第三章  进程管理 姓名:王玮怡  学号:20135116 一.进程 1.进程的含义 进程是处于执行期的程序以及相关资源的总称,程序本身并不是进程,实际上就是正在执行的代码的实时结果.Linux内核通 ...

  9. 20135202闫佳歆--week6 课本第三章学习笔记

    第三章 进程管理 一.进程 1.进程 进程就是处于执行期的程序. 进程就是正在执行的程序代码的实时结果. 进程是处于执行期的程序以及相关的资源的总称. 进程包括代码段和其他资源. 2.线程 执行线程, ...

随机推荐

  1. Go调用cpp类

    CGO是C语言和Go语言之间的桥梁,所以GO是没有办法直接使用CPP的类的. 我们可以通过增加一族C语言函数接口作为CPP类和CGO之前的桥梁的,这样 就可以实现C和Go之间的互联. my_buffe ...

  2. 题解和总结——noip2019集训测试赛(一)贪吃蛇+字符串+都城

    Problem A: 贪吃蛇 描述 Input Output Sample Input [样例输入1] 4 5 ##... ..1#@ 432#. ...#. [样例输出1] 4 [样例输入2] 4 ...

  3. Spring Boot 2.x实战之StateMachine

    本文首发于个人网站:Spring Boot 2.x实战之StateMachine Spring StateMachine是一个状态机框架,在Spring框架项目中,开发者可以通过简单的配置就能获得一个 ...

  4. P3521 [POI2011]ROT-Tree Rotations(线段树合并)

    一句话题意(不用我改了.....):给一棵n(1≤n≤200000个叶子的二叉树,可以交换每个点的左右子树,要求前序遍历叶子的逆序对最少. ......这题输入很神烦呐... 给你一棵二叉树的dfs序 ...

  5. js解析json报错Unexpected token i in JSON at position 1

    因为后台json是手动拼接的,在拼接时偷了懒,不想转义,所以就用了单引号,结果js解析时悲催了 这里记录一下,被解析的json字符串必须键值对都用双引号包起来,必须是双引号 默默罚抄一百遍

  6. Java多线程-CountDownLatch、CyclicBarrier、Semaphore

    上次简单了解了多线程中锁的类型,今天要简单了解下多线程并发控制的一些工具类了. 1. 概念说明: CountDownLatch:相当于一个待执行线程计数器,当计数减为零时表示所有待执行线程都已执行完毕 ...

  7. JS 暴虐算法查找

      @dd|ad|fds|d@dd|ad|fds|d@dd|ad|fds|d@   var e = [];     window.onload = function () {         var ...

  8. spark-宽依赖和窄依赖

    一.窄依赖(Narrow Dependency,) 即一个RDD,对它的父RDD,只有简单的一对一的依赖关系.也就是说, RDD的每个partition ,仅仅依赖于父RDD中的一个partition ...

  9. thinkphp6.0 多应用模块下提示控制器不存在

    thinkphp6.0 多应用模块下提示控制器不存在 在项目根目录下使用Composer composer require topthink/think-multi-app 参考链接

  10. nyoj 42-一笔画问题 (欧拉图 && 并查集)

    42-一笔画问题 内存限制:64MB 时间限制:3000ms Special Judge: No accepted:10 submit:25 题目描述: zyc从小就比较喜欢玩一些小游戏,其中就包括画 ...