對各向異性高光的理解

在渲染頭髮、絲綢等材質時,常要用到各向異性高光(Anisotropic highlighting)。什麼是各向異性高光呢?先來個直觀的對比,以下面圖一、圖2。html

Blinn-Phong
圖1 普通的Blinn-Phong高光web

Aniso
圖2 各項異性高光瀏覽器

圖1和圖2是在同一個場景裏、同一個模型(一個球)、相機、光源(只有一個平行光),甚至它們的漫反射光照也同樣,只有高光的計算方法不一樣。ide

圖1使用的傳統的Blinn-Phong高光,能夠看到高光呈一個圓形亮光斑,比較集中。code

// Blinn-Phong specular highlight
vec3 col = vec3(0.);
vec3 halfDir = normalize(viewDir + lightDir);
float nh = dot(normal, halfDir);
float spec = pow(nh, 100.);        
col += nl * lightColor * albedo + specColor * spec;

圖2中渲染的是各向異性高光,呈環形,在頭髮渲染中又稱爲「天使環」,這裏使用的光照模型是Kajiya-Kay Model。在不少遊戲中,頭髮渲染都使用了Kajiya-Kay Model,好比崩壞3(固然崩壞3在這個基礎的模型基礎上進行了一些創新,主要是一些參數的控制,我後面會說)。orm

// anisotropic highlighting
// http://web.engr.oregonstate.edu/~mjb/cs519/Projects/Papers/HairRendering.pdf 
// 計算球在當前點的切線
vec3 tangent = SphereTangent(pos, normal);
// 切線偏移,能夠移動「天使環」的位置,在上面連接裏的PDF中詳細說明,我不細說了
//float shift = texelFetch(iChannel0, ivec2(0), 0).r;
//shift = shift * 2. - 1.;
//tangent = ShiftTangent(tangent, normal, shift);

vec3 col = vec3(0.);
vec3 halfDir = normalize(viewDir + lightDir);
float dotTH = dot(tangent, halfDir);
// 關於dirAtten的計算說明見下文
float dirAtten = smoothstep(-1., 0., dotTH);
float sinTH = sqrt(1. - dotTH * dotTH);
// Kajiya-Kay Model
col += nl * lightColor * albedo + dirAtten * specColor * pow(sinTH, 100.);

sinT
圖3 Kajyiya-Kay Model視頻

從上面代碼來看Kajiya-Kay Model模型使用切線T和半角向量H(即代碼中的halfDir)之間夾角的正弦值來計算高光係數(即pow(sinTH,100)),而不是Blinn-Phong中的法線和H向量之間夾角的餘弦(即pow(nh, 100.))。htm

圖3中,黃色的粗圓柱表示一根頭髮,T是其切線,V是視線,L是光源方向, H是L和V之間夾角的一半。其實T和H夾角的正弦剛好就是圖3中H和N之間的餘弦值,這樣說來Kajiya-Kay Model本質上仍是使用的餘弦值來計算高光係數。可是值得注意的是在每根頭髮的固定點處,T老是保持不變的,N是隨視線V變化而變化的,N老是在T和V組成平面內。也就是在一個被渲染的點處,其法線在各個(視線)方向是不一樣的這大概所謂就是的「各向異性」。而咱們之因此要有T就是爲了計算出這個隱藏在背後的法線N。固然,咱們不須要計算出這個N的確切值,其蘊含在T和H的正弦值中,V蘊含在H中。因此,我認爲Kajiyaa-Kay Model本質上仍然是Blinn-Phong,只是它用切線T幫咱們找到當前視線下的使高光最強的法線Nblog

另外,注意上面的方向衰減係數dirAtten的計算:遊戲

float dirAtten = smoothstep(-1., 0., dotTH);

爲何要這麼計算?貌似好多人都只知道這是個衰減係數,而不知道爲何要這樣計算,或者說不太清楚背後的幾何意義。這個方向衰減係數其實涉及到兩個方向,一個是光源的方向L,一個是視線方向V,它們都蘊含在H中。首先,這裏smoothstep的第3個參數傳的是dotTH,即切線和和H的餘弦值。而這裏smoothstep的第1個參數-1代表當dotTH小於或等於-1時(實際上最多等於-1,不會比-1還小,由於全部參與計算的向量都是規範化的),dirAtten值爲0。何時dotTH爲-1呢,就是圖3中,H的方向剛好和T的方向相反時,這時候T和H之間的夾角爲180度。而smoothstep的第2個參數0代表當dotTH大於或等於0時,dirAtten的值爲1。注意dotTH最大值爲1,此時T和H的方向恰好相同,二者之間的夾角爲0度。因此smoothstep(-1., 0., dotTH)的做用就是取和切線T角夾在0至180度之間的H。當T和H之間的夾角不在這個範圍內時,必然出現H和對應N的點積小於0,即此時高光爲0 (此時光源照不到當前着色點或相機看不到當前着色點),此時dirAtten的值就應當爲0(即沒有高光)。

另外,通常的咱們能夠定製各向異性高光的參數,好比計算各向異性高光時dirAtten * specColor * pow(sinTH, 100.);,其中的100就是一個可調參數,這個參數值越大,「天使環」的越細,其值越大,「天使環」越寬。這一點你們均可以理解。而咱們能夠更進一步,直接使用一張貼圖來控制「天使環」在各處的寬度,使其在各處的寬度不一樣,甚至能夠把「天使環」調成各類有趣的圖案,以達到想要的藝術效果。還有渲染頭髮通常有兩個「天」使環,第二個會較暗一些,且是有本身的顏色的,更靠近髮根。只要理解了第一個「天使環」的原理,第二個只是在第一個基礎上進行了偏移,顏色稍微有點不一樣而已,我再也不贅述。

我寫了一個Shadertoy: Anisotropic highlighting (shadertoy.com),有源代碼,在電腦上打開瀏覽器,可在線運行,可進行交互,調成天使環的位置,但願能幫助到一些同窗。若是由於某些緣由,打不開網址,我也錄製了一個[視頻](A shdertoy: Anisotropic Hightlighting - 知乎 (zhihu.com)),可在知乎上觀看。

最後,我想記錄一下在寫這個Shadertoy時用到的一個小技巧: 球的切線的計算。

在代碼中,那個紅色的球是用數學公式建模的即 $ length(p - center) = r$,而後從相機處發射射線,判斷射線與球是否相交以及距相機的距離,來求得交點,即當前着色點。法線的計算很簡單,不贅述。可是怎麼計算出切線呢?我是這樣作的: 用當前着色點的三維座標及其法線肯定一個平面,而後把當前着色點的y座標加1(固然加別的數值應該也能夠)獲得偏移後的一個點,而後把這個偏移後的點再投影到剛剛肯定的那個平面上,則投影獲得點減去當前着色點獲得的向量就是當前着色點的一個切線向量,進行規範化便可。關於平面方程及點投影到平面,可參考這篇文章:平面(Plane) - 知乎 (zhihu.com)。

代碼以下:

// pos是當前着色點的三維座標
vec3 SphereTangent(vec3 pos, vec3 normal) {
    vec3 posOffseted = pos;
    posOffseted.y += 1.;
    float D = - dot(normal, pos);
    float distToPlane = dot(normal, posOffseted) + D;
    vec3 proj = posOffseted - normal * distToPlane;
    vec3 tangent = normalize(proj - pos);
    return tangent;
}

我不知道有沒有人這樣作過,我是臨時想到的,實現了一下效果還行。

參考:

Anisotropic highlighting (shadertoy.com)

Hair Rendering and Shading

平面(Plane) - 知乎 (zhihu.com)

相關文章
相關標籤/搜索