amixer 如何切通道_切空间及法线贴图那点儿事儿

本文详细介绍了模型的切线、副切线及其构成的切线空间,阐述了切线空间在法线贴图制作和使用中的重要性。通过Unity中Terrain系统的例子,解释了切线和副切线的生成方法,并提供了任意三角面TBN的求解算法。此外,还讨论了切线空间在光照计算中的应用,特别是在地形材质法线效果中的挑战和解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

dec37dcda90758c5a30c0750372eb417.png

前几天公司有同事,要搞一下项目的地形材质,增加法线效果。Unity的Terrain系统嘛,大家也都知道,因为性能问题,对手游来说,基本没法用。我们之前就把地表的Terrain转成了Mesh来使用(在计算了光照贴图之后)。

但因为要加法线效果(就是在lightmap的基础上,增加一些凹凸高光的感觉),所以同事就直接按照通常的方式,给地表写了带法线的Shader。但发现最后的光照怎么着都不对。哈,好吧,问题之后再说,我就借机会聊一聊关于模型的切线及切线空间。

一:什么是切线?

嗯,简单来说,用个图表示一下吧:

22869111e1b70215dacb2568ebb7841b.png

顶点的法线、切线及副切线

假设在一个模型的表面有一个点P(如上图,x,y,z坐标系为物体的Local坐标系),那么点P会有个固有属性叫法线,就是上图的N(Normal),这个大家应该都知道,它一般是垂直表面的,就是表示这个点的一个朝向(三个点确定一个三角面,通过插值,组合出这个三角面的法线)。而与法线垂直且过点P的那个平面,就是由切线和副法线组成的切平面,就如上图的T(Tangent),即为切线,b就是副切线(bi-Tangen或叫副法线bi-Normal)。而由TBN组成的这个空间,就是切线空间

为什么要定义切线和切线空间呢?

1、 用来计算法线(凹凸、视差、置换等等)贴图。这里以法线贴图的制作和使用为例。

4be57e12e87be3e44e35078c79cfc0e9.png

法线贴图目前基本成了标配

在目前大部分游戏里,法线贴图基本已经成为了标配。法线贴图的作用就是增加物体表面的法线细节。对美术来说,就是通过3dsmax,maya,zbrush等3D软件来制作高模,然后将高模的顶点细节法线映射到低模上(大面积法线效果),或者通过Photoshop的nv插件,Crazybump等工具,来根据贴图的色彩明暗等,将其转成法线贴图使用。

而这个法线通常就是以切空间为基准来生成的。虽然根据世界空间也可以生成法线贴图,但这样法线贴图就是去了共用性(想想一下,你右手平伸指向自己的右边,如果你面向北,则右手在你东面。如果你保持姿势转一下,右手还是在你右边,但却已经不在你的东面了)。

6d405675caa13d5930085590b01fad98.png

切空间法线贴图及世界空间法线贴图

那么法线贴图是如何生成的呢?

因为法线贴图也是贴图,也必须通过模型的UV进行采样使用,所以,一般来说,切空间也就和模型的UV空间联系了起来。将模型UV的u轴正向作为了UV空间的切线方向,v轴正向作为了UV空间的副切线方向。而点的UV方向映射到模型空间后,映射后的u正向也就成了真正的TBN空间中的切线方向(如何映射,算法之后介绍),副切线同理。法线贴图的生成过程,就是将高模的法线方向的值转换到低模的TBN坐标系下,然后再将转换后的值根据插值得到的UV位置,写入到对应的贴图上即可。顶点的法线值都是单位化的,就是说,表示法线方向的xyz的平方和为1,所以每个方向的值范围都在-1~1之间。而为了能保存-1~1的法线值,法线贴图就有了这样的定义:法线贴图的RGB分别保存法线的xyz值,像素值0~255,对应法线值的-1~1,128对应的法线值就是0。

因为法线一般都是垂直模型表面的,如第一个图,所以它的值一般就是(0,0,1),对应到法线贴图中,就是RGB(128,128,255)。而对于世界空间的法线,就要看面的朝向了。对于一个正常模型来说,它的法线相对与高模,一般就是在切线和副切线方向上有些许的变化,这也就是为什么切空间的法线贴图都是偏蓝色的,而世界空间的法线贴图都是五颜六色的原因了。

cb6ace5ea2a8dc88af930f7c944074f1.png

一张简单的法线贴图及其RGB通道

为了更形象的说明一下,法线贴图中所存的信息,我们手绘一张法线贴图。如上图,一张简单的法线,平面中间,有个圆形突起。我们可以直接在photoshop中绘制完成。先在RG通道平铺(128,128,128),B通道平铺(255,255,255)。R通道其实可以当作有光从UV坐标的u正方向打过来时,表面的亮度变化(右边法线向u正向偏转,左边法线向u负向偏转),G通道可以当作有光从UV坐标的v正向打过来时,表面的亮度变化(上边法线向v正向偏转,下边法线向v负向偏转)。所以我们可以圈起一个圆,将范围内的R通道从右往左做一个从亮到暗的渐变,将范围内的G通道从上至下,做一个从亮到暗的渐变。然后用Photoshop的nv插件单位化一下,自动生成一下B通道,一个中间隆起的平面的法线贴图就做好了。

如果生成法线的软件,UV坐标系和使用法线的软件有所不同的话,那就要做相应的调整了。例如早期的DirectX体系下,UV坐标系的v方向是从上往下的,而OpenGL的则是从下往上,这就是为什么在Zb中生成的法线贴图,需要在3dsmax中,进行G通道反转才能正常使用的原因了。

而法线贴图的使用,需要先把法线信息从图中读取出来(切空间),然后根据世界空间下的TBN矩阵,将其转换到世界空间,就可以进行光照等的计算了(当然,也可以用TBN的转置,先把各种世界空间下的方向转到切线空间下进行计算,也可以)。

2、切空间的另一个作用就是,需要根据切线或副切线的方向,来计算光照了。比如丝绸或头发等的高光计算(dot(N,H)变成了dot(B,H))。

回到最上面的问题,为什么地表模型的高光计算会有问题?

对于一般的光照计算,Mesh的法线和切线信息都是保存在顶点中,可以直接传入VS进行相关计算的。但我们的地表Mesh,因为是从Terrain转的,所以,相关的法线和切线都需要程序生成,才能使用。像一般模型那样去直接获取,肯定是没有的。而因为我们的terrain都是在lightmap计算完成以后,才转的Mesh,所以,Mesh中没有法线信息的问题,之前并没有被注意到。法线还好,通过unity的接口,GetInterpolatedNormal可以直接获取到插值后的光滑法线,但切线就……拿不到,也就无法传到Shader中了(很奇怪,明明法线是通过切平面确定的)。

在查看Unity内置的相关Shader中,可以在TerrainSplatmapCommon.cginc里,找到这么段代码:

e72c7d90479545a426e12b2b515ab179.png

是不是很懵比?这又是个啥?这里其实可以看作是一个特殊情形下,切线的求取方法了。我这里解释一下:

有几个前提,

1、 Terrain生成以后是不会进行人为的旋转。

2、 Terrain的高度笔刷,只能将顶点上下调整,不能前后左右的调整。

3、 Terrain的UV是平铺的,且坐标的对应关系为一个点的u正向的下一个点,一定在

世界空间中该点的x轴正向,而v方向的下一个点也一定对应于世界空间中该点的z轴的正向(Unity中y轴表示向上)。

基于这么几个前提下,法线方向没有改变时(竖直向上),切线方向一定是(1,0,0),副切线

方向一定是(0,0,1),TBN矩阵中,切线、副切线、法线相互垂直。不管顶点怎么上下移动,对切方向来说,它永远朝向指向右侧点的方向(同三角面的点或虚拟的那个点),而副切线也永远朝向指向z正方向的那个点(同三角面的点或虚拟的那个点)。所以,切方向永远在xy轴平面内,朝向x正向上下偏移,而副切线也永远在zy平面内,朝向z正方向上下偏移。法线应该是通过同三角面中的TB平面来确定的(但这里可以直接从Terrain数据中拿到)。

Shader中,unity随便定义了一个在yz平面上的B方向,然后cross(N,B),则大致也就确定了T的方向。之后,只要得到这条临时T与N确定的平面与xz轴组成的平面的相交线,也就得到了正确的T方向了。之后cross(N,T),也就得到了B方向。就算不对两平面进行相交求解,直接cross(N,T)得到B,此时的TBN也是相互垂直的矩阵,且相比正确的TBN偏差并不大,直接计算法线光照效果,也可以接受。相对较好的做法,是设定T=(1,0,0),然后cross出B,之后再通过cross(B,N)来确定出T,相对会更精确。

那么最后,说一下,任意三角面的TBN是如何求得的。

ce51727d26999aaa8cbcbf1e1e32ebb4.png

xyz对应Local空间,uv对应UV空间

算法其实也很简单,不用想的太复杂,原理就是将一个三角面从UV空间的T和B映射到物体空间即可。

比如上图的ABC三角面,求A点的T和B,在UV空间中,T就是穿过A点,平行u轴朝向v正向的矢量。而B,就是穿过A点,平行于v轴,朝向v正向的矢量。

假设A点local坐标为(0.3,0.2,0),uv坐标为(0.2,0), B点local坐标为(1.2,0.6,0),uv坐标为(1.0,0.5),C点local坐标为(0.2,0.8,0),uv坐标为(0,0.6)。

我们在Local空间中的ABC同平面上:

方向矢量AC = delU1 * T + delV1* B

方向矢量AB = delU2 * T + delV2* B

AC = C - A

AB = B - A

delU1 = Uc - Ua

delU2 = Ub - Ua

delV1 = Vc – Va

delV2 = Vb – Va

然后全部带入到上面式子中,得到:

T = (delV2*AC –delV1*AB) / (delU1*delV2 – delU2*delV1)

B = (delU2*AC –delU1*AB) / (delU2*delV1 – delU1*delV2 )

然后就能求出来了。

但有个注意的点:这里算出来的Local空间的T和B不一定会垂直哦。感兴趣的,大家可以想想为什么。但无所谓,并不会影响N = cross(T,B)的值。而且,保证烘焙法线贴图软件的切线空间计算方式能和你使用法线时候的算法一一致就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值