相比立方體貼圖,球面貼圖的結果雖然不很精確,但是只需要一張貼圖。這個時候球面環境貼圖還是有用武之地的。
隨便用搜尋引擎在網上找了下,基本上沒發現關於球面環境貼圖的內容,本來想找個簡單的shader直接來用的,沒想到的是找了半天也沒找到,於是只能自己動手寫了。以下文章是我參考了一些資料加上我自己的理解寫成的,如果有錯誤的地方還望大家指正。
環境映射是一種近似,它基於這樣的假設:相對於光潔物體的大小而言,環境中的物體離光潔物體很遠,也就是說,將一個很小的光潔物體放在大房間中。對於物體表面上的點,假設有一條從眼睛到該點的光線,這條光線被反射出去的方向決定該點的顏色。在一個二維紋理圖中對各個方向的顏色進行編碼,相當於將一個光潔度非常高的球體放在環境中央,然後在很遠的地方用帶長焦鏡頭的相機拍攝球體。從數學上說,鏡頭的焦距為無限長,相機位於無窮遠處。因此,需要進行編碼是紋理圖的內切圓形地區,該圓形地區外的紋理值沒有影響,因為進行環境印射時沒有使用它們。(摘抄自OpenGL紅寶書)
根據這段來自紅寶書中的文字,在一個環境中產生球面貼圖的時候,由於環境相對於球體而言無限大,所以可以把球體看成是一個單位球體。同時,有由於相機位於無限遠處,所以相機到球體上的各個點的向量可以看成是相互平行的。
於是很容易的想到:
1、產生視線向量V。
2、根據頂點法線產生反射向量R。
3、尋找R和球面的交點。
4、根據交點求出UV座標。
R可以很容易的求出。為了求出UV,需要求出R和球面的交點E在球面的位置。這個時候回到產生球面紋理圖時候的情境,由於球面是單位球面,利用單位球面的一個性質:球面上的點的歸一化法線就是該點在球面上的位置。
我們只需要知道在產生球面紋理圖的時候,在球面上相同的點,當反射向量也為R的時候該點的法線為多少即可。
根據向量加法原則,法線是視線向量和反射向量的和。為了類比視點位於無限遠處的情況,可以假象產生球面紋理圖的過程是位於View Space,這樣的話,Eye Vec就總是(0,0,1)。於是只需要把反射向量也轉化到View Space就可以得出球面的法線向量。
還剩下最後一個問題,求出來的法線每個分量的範圍是[-1,1],而uv要求的範圍是[0,1],所以需要轉換一下。以下是關鍵的VS程式碼片段:
Code
1 //world normal
2
3 float3 normalWorld=OUT.WorldNormal;
4
5 //world space eye vec
6
7 float3 eyeVecWorld=OUT.WorldView;
8
9 //world reflect vec
10
11 float3 reflectWorld=reflect(eyeVecWorld,normalWorld);
12
13 //eye space relect vec
14
15 float3 reflectView=mul(reflectWorld,matViewIT);
16
17 //unit sphere normal in view space
18
19 float3 SphereNormal=float3(0,0,1)+reflectView;
20
21 SphereNormal=normalize(SphereNormal);
22
23 float2 newUV=SphereNormal.xy;
24
25 newUV.x=newUV.x*0.5+0.5;
26
27 newUV.y=newUV.y*0.5+0.5;
28
29 OUT.UV=newUV;
30
31
後記:寫這篇文章以前,我參考過DirectX SDK中文檔的內容,文檔中說,只需要把頂點的法線轉換到Camera Space然後除以2加上0.5即可。我使用這個方法在FxComposer中實驗,沒有得到正確的結果,我不知道是否是哪寫錯了。SDK的文檔中也沒有詳細說明這個方法的來龍去脈,不知道有沒有知道的朋友。