SpecularAA?楚留香的「粗糙度+=法線長度」是為了什麼?

SpecularAA?楚留香的「粗糙度+=法線長度」是為了什麼?

先大概列下這段代碼:

fixed4 tangentNormal = tex2D(_MainNormalTex,i.uv.zw);fixed3 bump = UnpackNormal(tangentNormal);fixed3 worldBump = (half3( dot(i.T2W0.xyz,bump), dot(i.T2W1.xyz,bump), dot(i.T2W2.xyz,bump)));half bumpLen = length(worldBump);//獲得法線長度worldBump /= bumpLen;roughness = saturate(roughness + min(0.4,saturate(1.0 - bumpLen) * _Factor));

前面都是耳熟能詳的法線貼圖相關代碼,但在歸一化的時候,卻獲取了法線長度,並且將它和1的差值以一定比例加到了粗糙度上。

由於T2W012計算是在頂點上完成的,frag階段進行的硬體插值,因為是線性插值(Lerp)而不是球插值(Slerp),確實會不等於1。

放大3倍著色出來確實也能看到。多邊形中間的部分,算出來的法線長度會稍微短一點。

但這樣的東西又有什麼意義呢?


所以跑群里問了下(雖然問的是完全不同的東西),搞到中途也開始偏題了,開始扯別的。這時候突然有人冒出來插了這麼一句:

我問「你在說啥」是因為目前在說的話題有兩個

因為我既沒有貼代碼也沒貼圖,也完全沒提粗糙度的事情,也就提到了法線貼圖和彌賽亞,他卻突然冒出來roughness這個詞,我就覺得他應該知道點什麼。

但可能是他覺得我們這點事都不知道很LOW吧,說完「說個幾把」之後就退群了,我也不好去問了。

而他提到的這個SpecularAA,我查了下大概是指這類問題:

Fighting aliasing on specular highlights

這個確實是個問題,是採樣不足,採樣點又恰好到了高光最亮的地方產生的現象。準確的解決方法就只有增加採樣,或者用不僅僅針對幾何邊緣的屏幕反鋸齒技術,諸如FXAA,TAA一類。

而這個地址還有人給了一個利用fwidth的方法(根據屏幕兩個像素法線的變化率來決定高光的衰減量,相當於增加粗糙度)

float3 ddxN = ddx(Normal.rgb);float3 ddyN = ddy(Normal.rgb);float curv2 = max( dot( ddxN, ddxN ), dot( ddyN, ddyN ) );float gloss_max = -0.0909h - 0.0909h * log2(CURV_MOD*curv2);gloss = min(gloss, gloss_max);gloss = min(MAX_POW, exp2(1 + lerp(POW_MOD_MIN, POW_MOD_MAX, gloss )));

這些做法我都能理解。

但是單單取法線的長度值來增加粗糙度,這為啥就能解決高光採樣不足的問題呢?

是,如果把「法線換算成世界空間後才取長度」看做一個筆誤,又或者是覺得插值導致的誤差可以忽略,想省略一次normalize指令(後面的normalize指令在歸一的化的時候本來就必須做),那有可能他們實際想獲取的是法線貼圖當前像素的長度。假如法線紋理數據不是歸一化的,就可以利用其長度儲存一些信息,比如「法線的梯度」,再利用這個梯度增加粗糙度。

也就是讓法線變化快的區域高光弱一點。

但是我們要的並不是「法線變化快->高光變弱」吧?而是「屏幕上兩點法線變化快->高光變弱」吧?

不考慮對近處物體的影響,fwidth確實能解決問題。那麼是嫌fwidth是ES3專用,不得不用其他效果更差的方式代替?

但不用fwidth的話,用法線紋理上的固定梯度來減少粗糙度,相當於直接修改粗糙度紋理。這一切計算都是和視距沒有關係的,而AA則應該在高光採樣不足的遠處開始執行。所以說,就算真的要這樣做,至少也該把視距當做粗糙度增加的係數吧?

當然我突然想到了,除了在frag上直接求視距外,還有什麼是本身就和視距相關的呢?

MipMap。

所以,我們並不需要計算視距,只要讓法線貼圖各級mipMap的長度值影響係數依次遞減,就能夠讓近處的物體不受影響,而遠處的物體高光在某些部分變糊。


但在實際做的時候,出了一個問題:我很難去復現SpecularAA出現的情形(就是那個門的白色雜點)。復現不了,也就無法去驗證是否修復。

因為按照一般的mipMap演算法,法線貼圖的xy坐標在mipMap的高層級會因為插值變得很小,也就是會變得「方向幾乎一致」「平滑」,只有把MipMap去掉才能看到比較明顯的高光反走樣。

原圖

但如果開著MipMap,法線基本上就被抹平了,想想也是該這麼回事兒。

難道說是因為他們不開法線貼圖的MipMap?不至於吧?

這種做法倒是也聽說過,畢竟開了MipMap,就必須開三線以及各向異性過濾,否則紋理很容易出現斜面模糊,MipMap的斷層。此外,就是這個法線細節丟失的問題。

但是,關閉MipMap會面臨帶寬壓力和更嚴重的紋理走樣問題,這通常是沒有其他辦法後的最終手段。

這時候我想到了之前看過的這篇文章的內容:

【SIGGRAPH2017】Physically Based Material Where Are We

既然開了mipMap存在這個變「光滑」的問題,而開了mipMap實際上不容易出現高光反走樣,那麼有沒有可能,楚留香這個「粗糙度+=法線長度」根本就不是為了解決高光反走樣,而是為了解決這個物體遠距離變光滑的問題?

原有的法線細節被搞沒了,用粗糙度補回來一些,合情合理。


但在實際編輯法線的多級MipMap數據時,很自然地發現了一個現象。

而且之前就有疑問,既然要在法線紋理上藏粗糙度偏移,那幹嘛不直接在粗糙度紋理上直接偏移呢?就算有多級MipMap要做的事情也是一樣的——但是,假如法線部分什麼都不處理就能就預期的效果,直接取就好了,當然就沒必要去修改粗糙度紋理了。

要點就是,原始法線圖本身事先編輯成歸一化的(相對於0.5,0.5),但是經過Box演算法插值出其他級別的MipMap後,並不對它們歸一化,而是任由它們由於相鄰像素線性插值而損失長度(紋理類型應該是Default,而非Normal Map)。產生的結果就如同下圖:

會因為相鄰像素差異而失去「向量長度」,而且方向差異越大,失去的「長度」越多。

這直接就是我們想要的結果。

著色後可以看到length的平均值確實會因為距離遠而變低。

最近的距離不是純白是因為在同一級mipMap中相鄰像素依然會因為Bilinear過濾而降低length,如果用點採樣就是純白的。

所以這樣處理法線後,再直接用

_Factor = 0.3;roughness = saturate(roughness + min(0.4,saturate(1.0 - bumpLen) * _Factor));

就能有「遠距離法線變化劇烈部分粗糙度增加」的效果。

Before

After

但僅僅粗糙度增加……由於高光分布的問題並不能好好地模擬出原本法線產生的粗糙效果,也僅僅是讓遠處的小球不至於變得「光滑」。但這個Shader產生的效果,恐怕也只能這樣。而且這東西也能夠稍微緩解SpecularAA的問題。

另外,如果選擇將法線長度的變化量生成到對應物體的粗糙度貼圖上,就不用修改Shader了(同時也能避免第一級MipMap用Bilinear過濾導致多餘降低的粗糙度)

Texture2D tex = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/bump.png");Texture2D resultTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, true);for (int mipLevel = 0; mipLevel < tex.mipmapCount; mipLevel++){ Color[] colors = tex.GetPixels(mipLevel); for (int i = 0; i < colors.Length; i++) { Color c = colors[i]; float len = (new Vector3(c.r, c.g, c.b) * 2f - Vector3.one).magnitude; float smooth = Mathf.Clamp01(1f - Mathf.Max(0, 1f - len) * 0.3f); colors[i] = new Color(1, 1, 1, smooth); } resultTex.SetPixels(colors,mipLevel);}resultTex.Apply(false, false);resultTex.anisoLevel = 4;AssetDatabase.CreateAsset(resultTex, "Assets/smoothness.asset");AssetDatabase.Refresh();

其實我最後也不知道彌賽亞這段代碼是幹嘛的,因為對實際畫面的影響太小了,不容易測試。

但管它的,自己能用,能出效果不就行了。

咱又不是它肚裡的蛔蟲,他自己不出來說,誰知道這個有什麼用。

反正這東西不開MipMap是肯定沒效果的,但開了MipMip又很難看到劇烈變化的法線。除非它的MipMap生成方式特殊。


最後一定要說明的一點是,我這個球的紋理只是個演示例子,搞張比較容易出事兒的法線貼圖而已。

真想解決這個球本身的問題,其實把mipMap Filter設成Kaiser就可以了。

兩者疊加?

Before

After


另外當時群里討論的其實是個和上面完全無關的問題,是關於這個的,所以有人能扯到roughness和高光上才令人意外:

就是普通的凹凸貼圖,無高光

這個要有誰知道能怎麼解決還望提點一下啊。

推薦閱讀:

從玩具總動員到汽車總動員2
【GDC2017】Terrain Technology and Tools
billboard效果的實現以及延伸(空間變換、批處理)
讓角色半透明:後期模糊(二)
淺談後處理技術中的各種坑

TAG:計算機圖形學 | Unity遊戲引擎 | 遊戲開發 |