切线空间法线贴图生成和应用的理解

2019-04-14 19:45发布

一、基于切线空间坐标系的法线贴图理解:

使用法线贴图是因为在低模下想获得高模凹凸表面光照效果。 因为基于模型坐标系的法线贴图,在换到不同的模型下时候法线贴图不能复用。故引入切线空间让法线贴图可以在相同网格形状模型(或许是子模型)但不同模型坐标系的多个模型间可以共享法线贴图 以下是我个人对切线空间求取,法线贴图求取,法线贴图计算光照的理解,不对之处欢迎指出:

1)低模切线空间求取:

假设:dp是网格上某三角形的两顶点点之间的空间位置差向量(取点可以是索引缓存中的组成三角形的第一个顶点),du是两点对应的uv坐标系下的u坐标差向量。 则切线空间下的TBN坐标轴: T tangent=dp/du =dp. du = |dp|cos thetadu 是dp在du上的映射向量,该映射向量可以通过上述三角形三个顶点和三个uv纹理坐标系求得。 N直接取得低模三角形下的顶点N法向量值。 B binormal = T * N。

2)高模在低模切线空间下进行法线向量的求取, 且烘培到法线贴图中:

上述的N是低模下三角形的切线空间坐标系上的N轴值,实际三角形内部像素级别的法线向量设为Nvalue'是根据高模在更多的子三角形表面点的法线向量: Nvalue' = Nvalue * (TBN^-1), Nvalue基于高模模型坐标系的法线向量,TBN是高模子三角形对应的低模大三角形的TBN切线空间,TBN^-1是该切线空间的逆矩阵用于从模型空间变换到切线空间。因此Nvalue'是法线贴图记录的是高模的上计算得到的在低模切线空间下的法线值,一般情况下高模不够像素级别因此还要进行插值处理。 如果存在切线贴图,则存储的是上述计算得到的低模三角形下的切线向量值,低模的一个三角形内的所有像素点应该是相同的T切线向量值。

3)shader在低模切线空间中应用高模烘培的法线贴图进行实时光照计算:

在Vertex shader中将光线向量和视野向量转换到模型坐标系下,再转换到当前渲染的低模切线坐标系下(T来自tangent信息(现代硬件可来自渲染管线,也可以来自切线贴图),N是当前渲染的低模顶点的法向量,B=T*N,这里的BTN坐标只是用来旋转入射光线和观察向量)。在Fragment Shader中得到切线空间下的入射光线向量,观察向量,从高模烘培的法线贴图得到的法线向量 进行像素级别光照计算。 如果是光照贴图,非实时光照,那么在烘焙光照贴图时候进行上述计算。

二、拓展阅读:

1)求取低模TBN公式:


关于上图公式的解释:T是顶点在U方向的映射,N低模下三角形的顶点法向量,B=NXT。

2)具体切线空间法线贴图的计算拓展和Shader中的应用:

基于模型的法线贴图来自高模上N个三角形法线向量插值得到低模上一个面的法线贴图,切线空间法线贴图则来自模型法线贴图基于三角形面的TBN坐标系转换得到。 法线贴图是用来在低面数的网格,获得很好的光照效果,因为法线贴图将法线放到了纹理中,但是法线贴图也是来自于高模的,是高模通过光照烘培得到的纹理贴图。 基于物体坐标系的法线贴图,在同一个模型上,进行平移旋转时候可以直接使用,再进行等比缩放时候要重新进行规范化,在进行非等比缩放时候需要重新计算(乘以S = (M^-1)T)。就是不能用于其它模型。 切线空间法线贴图是一个低模的三角形切面,对应多个高模的三角形切面,故高模也是使用底模的三角形切面来记录切面法线向量。低模的表面发生形变时候和基于物体坐标系的法线一样的,平移和旋转不用处理,等比缩放时候需要对法线进行重新规范化,非等比缩放变换时候要重新计算(转换到物体坐标系进行S = (M^-1)T计算,再转换到切线空间中)。但是切线空间的法线贴图,可以用到其它模型中。切线空间的计算,取得低模三角形的两条边a1a2 * a1a3 = N也就是三角形面,T=某一条边或者某条边到u纹理坐标系的映射, B = N*T, 面的Nvalue = 高模的模型坐标面法线 * (TBN^-1), 存储该Nvalue即得到切线空间下法线贴图的值。即低模下一个切面中,就得到了多个来自高模多个面的NValue, 可以通过插值得到低模一个切面的像素级别的法线向量,这些Nvalue值因为偏移坐标系TBN的N不大,且存储时候基于切线空间坐标系TBN 用rgb存储,所以 blue通道值较大,所以显示为蓝 {MOD}。 Shader计算中在Vertex shader中,将世界坐标系下的光线向量用ToOjectSpaceDir(lightDir)转换到模型坐标系下,再在模型坐标系下转换到低模TBN空间,TBN空间由顶点的切线T(现代的shader能够为顶点提供一个tangent信息, 也可能在片断着 {MOD}器中T来自切线贴图),顶点的法线N,B = T*N得到M(TBN), lightDir * (TBN^-1)得到切线空间下的光线向量。在Fragment Shader中用光照模型计算像素的光照颜 {MOD}即可。 如漫反射颜 {MOD}:float3 resColor = attenuation * _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDir, lightDir)); 高光颜 {MOD}: 计算镜面高光需要viewDir也要转换的切线空间中进行计算。
specularReflection= attenuation* _LightColor0.rgb* _SpecColor.rgb* pow(max(0.0, dot( reflect(-lightDirection, normalDirection), viewDirection)), _Shininess); 最终光照颜 {MOD}: return float4(input.vertexLighting + ambientLighting+ diffuseReflection+ specularReflection,1.0); 实际做法可能会有些复杂.比如有些模型是镜像对称,贴图也是镜像对称的,计算时会省去另一半等等.这时如何处理看具体的法线贴图生成软件和处理它的引擎(shader)了.基本原理还是上边所说的。
参考文章:https://www.zhihu.com/question/23706933/answer/25591714 http://www.cnblogs.com/lookof/p/3509970.html http://blog.csdn.net/damenhanter/article/details/22481563