在上一篇文章中,我写了一些关于Unity中各个坐标空间及其转换矩阵是如何得到的,说实在的,我是那种“记忆需要依靠外部装置存储”类、如同《攻壳机动队》的电子脑一样的人,每次遇到问题了再去对着笔记慢慢翻找才是我的风格:

破晓:Unity中常用矩阵的推导​zhuanlan.zhihu.com

在文章中我漏了一个本该提到的内容,那就是切线空间。

一般在法线贴图中,颜色都是偏蓝的,按照颜色空间和坐标空间的对应来看,z值一般都很大,大部分情况下,都是接近垂直于物体表面的。

如果是不需要法线贴图的情况下还好说,用NORMAL语义可以直接从顶点获得模型空间下的法线,如果要使用法线贴图?那就需要切线空间了。

常识告诉我们,构建一个空间笛卡尔直角坐标系需要三个互相垂直的基向量,而常识又告诉我们,两个向量的叉乘结果垂直于这两个向量构成的平面,故我们可以用两个向量完成切线空间的构建。

一个就是法线(Normal),一个是切线(Tangent),其叉乘结果为副切线(Bitangent)或者副法线(Binormal),这两种叫法我都看到过。以Unity官方的Shader例子举例:

v2f vert (float4 vertex : POSITION, float3 normal : NORMAL, float4 tangent : TANGENT, float2 uv : TEXCOORD0)
{v2f o;//...half3 wNormal = UnityObjectToWorldNormal(normal);half3 wTangent = UnityObjectToWorldDir(tangent.xyz);// 计算其方向half tangentSign = tangent.w * unity_WorldTransformParams.w;// 用世界空间下的法线、切线做叉乘,得到副法线half3 wBitangent = cross(wNormal, wTangent) * tangentSign;// 得到转换矩阵o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x);o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y);o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z);//...return o;
}fixed4 frag (v2f i) : SV_Target
{// 从法线贴图里采样并解码法线half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv));// 从切线空间转换到世界空间half3 worldNormal;worldNormal.x = dot(i.tspace0, tnormal);worldNormal.y = dot(i.tspace1, tnormal);worldNormal.z = dot(i.tspace2, tnormal);//...
}

众所周知,两个向量的叉乘不支持交换律,会因为顺序的不同而具备有两个方向的结果,而很多时候模型可能被缩放过,导致它是镜像的,为了避免该问题,使用这个Vector4向量unity_WorldTransformParams的w值来保证,如果模型被镜像偶数次,则w=1,反之则为-1。

这个矩阵一般被简称TBN矩阵,用于将切线空间向其他坐标空间转换,它需要使用到其他坐标空间下的法线和切线进行计算。

而Unity又在UnityCG.cginc里提供了这样一个宏:

// Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )

这里居然直接用模型坐标空间的法线和切线在做叉乘?莫慌,看看其他地方都用它干了啥,哦,视差贴图啊?那没事了……

冯乐乐已经谈到过这件事情:

【常见问题】关于法线转换的问题(以及在切线空间下计算法线纹理的问题) · Issue #45 · candycat1992/Unity_Shaders_Book​github.com

这涉及到非标准缩放的问题——

这是因为Unity 5之前,如果我们对一个模型A进行了非统一缩放,Unity内部会重新在内存中创建一个新的模型B,模型B的大小和缩放后的A是一样的,但是它的缩放系数是统一缩放。换句话说,在Unity 5以前,实际上我们在Shader中根本不需要考虑模型的非统一缩放问题,因为在Shader阶段非统一缩放根本就不存在了。但从Unity 5以后,我们就需要考虑非统一缩放的问题了。

说了这么多介绍类的东西,还没说正题:那就是为什么左乘TBN矩阵可以把向量转换出去?

我们先把TBN矩阵的运算写一下:

这个形式相当于与形如

的向量做了点积,而
就是世界空间的基向量在切线空间的表示,因为
在世界空间下对
轴基向量的投影:
反过来,也可以认为是
轴基向量在切线空间基向量
轴上的投影,同理
也是如此。
做点积,就相当于把自己投影了过去,三个分量就是在世界坐标三个基向量上的投影长度,组合起来的新的向量,就是
在世界坐标空间下的表示。

谈到这个就不得不说我在头发渲染中犯下的问题了:

破晓:使用Kajiya-Kay模型的头发渲染​zhuanlan.zhihu.com

此处做一个勘误。

当时我信誓旦旦地这么写:

一个是副切线的计算问题,不能直接用世界空间下的法线和切线cross,而是应当在模型空间cross好再转换:

o.tangent = mul(unity_ObjectToWorld, v.tangent);
o.normal = mul(unity_ObjectToWorld, v.normal);
o.bitangent = mul(unity_ObjectToWorld, cross(v.normal, v.tangent));
//错误写法:o.bitangent = cross(o.normal, o.tangent);

其实是错误的,因为本文提到过,叉乘结果是有两个方向的,我这边叉乘的顺序恰好得到的是相反的副切线,而当时数学不过关,大脑萎缩,一看副切线结果不同,想当然以为是没转换空间,正确的写法应是:

o.normal = UnityObjectToWorldNormal(v.normal);
o.tangent = UnityObjectToWorldDir(v.tangent.xyz);
o.bitangent = cross(v.normal, v.tangent) * v.tangent.w * unity_WorldTransformParams.w;

其次是使用副切线而不是切线的问题,这个是在Unity里材质预览窗看到的效果,在Preview窗口,切成Plane并输出tangent可以看到是红色的,是水平而非垂直方向的,所以高光偏移时要传入垂直方向上的副切线。


利用叉乘,同样还能从高度图中反推法线图:

fixed4 frag (v2f i) : SV_Target
{float2 uvx0 = i.uv - float2(_MainTex_TexelSize.x, 0) * 0.5;float2 uvx1 = i.uv + float2(_MainTex_TexelSize.x, 0) * 0.5;float2 uvy0 = i.uv - float2(0, _MainTex_TexelSize.y) * 0.5;float2 uvy1 = i.uv + float2(0, _MainTex_TexelSize.y) * 0.5;float3 dx = float3(_MainTex_TexelSize.x,0,(1 - tex2D(_MainTex, uvx1).r) - (1 - tex2D(_MainTex, uvx0).r));float3 dy = float3(0,_MainTex_TexelSize.y,(1 - tex2D(_MainTex, uvy1).r) - (1 - tex2D(_MainTex, uvy0).r));float3 n = cross(dx, dy);n.z *= 10;//这里可以控制法线收束于z轴的程度float3 normal = normalize(n);normal = normal * 0.5 + 0.5;  return float4(normal, 1);
}

我们分别在UV的x和y方向上单独讨论,以x方向为例,我们可以得知在该点处的梯度向量形如

此处,高度上的插值反映在z轴上,因为切线空间又可以被认为是局部物体表面空间,xy平面被认为是uv,而z轴穿插入物体表面。

使用x和y方向单独计算出来的梯度向量,就能够得到法线方向了。但有一点需要注意的是,计算出来的法线需要手动调整收束程度以适配项目使用。比如想要更平缓的法线图,就需要收束法线到z轴上更多一点。

冯乐乐 unity_Unity常用矩阵运算的推导补遗——切线空间相关推荐

  1. 切线空间(TBN) ---- 聊聊图形学中的矩阵运算

    本文没有详细讲解切线空间的来源和推导过程,更无法详细讲解矩阵的意义和运算.所以肯定需要一些预备知识. 先说说矩阵运算:列主矩阵中向量(或者点)乘在右侧.若是用正交的三个坐标轴来构成一个矩阵的旋转部分, ...

  2. pc端常用的分享QQ、QQ空间、微信、微博、复制链接

    pc端常用的分享QQ.QQ空间.微信.微博.复制链接 网上太多的关于这个的了,但是都没有符合我要求的插件.只好自己写,顺带分享出来给小白们 直接上代码了用的是最基础的代码 这个下载好放到对应的位置 q ...

  3. 常用数学公式,推导记录

    1 组合数计算公式 组合公式的推导由排列公式去掉重复的部分得来. 排列是,从n个不相同元素中取出m个排成一列(有序),第一个位置可以有n个选择,第二个位置可以有n-1个选择(已经有1个放在前一个位置) ...

  4. 逻辑代数常用公式及其推导

    一.基本公式 1.变量与常量运算规则: 0*A = 0 0+A = A 1*A = A 1+A = 1 2.重叠率: A*A = A A+A = A 3.互补率: A*A' = 0 A+A' = 1 ...

  5. MATLAB中的常用矩阵运算

    下面来介绍一下MATLAB中一些比较常见的矩阵运算,这也是我们进行算法调试的基础,具体如下所示: 1.显示矩阵A:在主界面的命令行窗口中输入下列代码: A = [1 3 5;1 0 1;5 0 9] ...

  6. python的常见矩阵除法_numpy常用矩阵运算方法【转】

    目录: 对于python中的numpy模块,一般用其提供的ndarray对象. 创建一个ndarray对象很简单,只要将一个list作为参数即可. 例如 import numpy as np #引入n ...

  7. lunixs 常用命令c语言,常用Lunix命令 - osc_271igh42的个人空间 - OSCHINA - 中文开源技术交流社区...

    计算机 1.硬件系统 输入单元.输出单元.算术逻辑单元.控制单元.记忆单元 中央处理单元:CPU(算术逻辑单元.控制单元) 电源.主板.CPU.内存(RAM).硬盘.(声卡.显卡.网卡)(集成在主板上 ...

  8. 个人空间html源码,HTML常用代码段 - lynn_xiao的个人空间 - OSCHINA - 中文开源技术交流社区...

    base64格式: ------------------------------- ie注释方式: ----------------------------------------------- ie ...

  9. 推迟势的简单推导,希尔伯特空间引入以及分离变量法

    推迟势 将非齐次的强迫振动,变成了速度函数所满足的齐次波动方程 用冲量法原理做一个积分变换 方程变为 我们令,这样子就变回了的初值问题 用冲量法原理和泊松公式一起代入解方程 我们得到 推迟势(reta ...

最新文章

  1. Python 常用内置函数map、zip、filter、reduce、enumerate
  2. 滴滴快的精打细算:利用大数据构建产业生态圈
  3. java 页面换行处理
  4. make的自动变量和预定义变量
  5. 鼠标滚轮事件及解决滚轮事件多次触发问题
  6. 红橙Darren视频笔记 仿QQ侧滑效果
  7. iframe调用父页面js方法_JS高级技巧
  8. Unity3D基础29:消息发送
  9. 连接Excel时出现未指定的错误
  10. c语言英美姓名,英美常用人名——C-D
  11. Spotfire 条形图属性 直线和曲线 格式设置
  12. Linux编程,vim/vi环境
  13. 依图科技CTO颜水成被曝离职!或加入东南亚某电商
  14. DSP之TMS320F28335学习总结与笔记(二)————ADC模块
  15. android 游戏遥感,Android2.2+游戏摇杆 MOPS魅影T800评测
  16. 嵌入式linux mtd,嵌入式Linux驱动设备之MTD技术详解
  17. 2022摄影摄像行业年度分析报告:单反小幅下滑,微单销额增长超32%
  18. 开放耳机有什么优缺点,推荐几款不错的开放式耳机
  19. 行级标签文本格式化标签
  20. 数据库定义语言(DDL)详解

热门文章

  1. dataframe 空值替换为0_dataframe取元素方法总结
  2. mysql rpl_MySQL管理工具MySQL Utilities — mysqlrplcheck(44)
  3. 生活质量衡量系统_数据质量与数据质量八个维度指标
  4. cad统计多条线段总长度插件_超级实用CAD技巧应用汇总!技巧大全、插件合集、快捷键合集等...
  5. java销售额查询_用JSP+JavaBean开发模式实现一个销售额的查询
  6. java replacefirst第n_Java中replace()、replaceFirst()和replaceAll()区别
  7. 贪吃蛇的编程python_python实现贪吃蛇游戏
  8. 腾讯视频怎样开启深色模式保护眼睛
  9. Python装饰器(二)
  10. java用户角色权限管理 只显示姓_快递物流管理系统SSM,JQUERYEASYUI,MYSQL