冯乐乐 unity_Unity常用矩阵运算的推导补遗——切线空间
在上一篇文章中,我寫了一些關于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常用矩阵运算的推导补遗——切线空间的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mongodb创建local库用户_mo
- 下一篇: android sd卡不可写,Andro