GLSL 程式設計/Unity/反射表面
本教程介紹了反射對映(以及用於實現它的立方體貼圖)。
這是關於在 Unity 中使用立方體貼圖進行環境對映的一系列教程中的第一個。本教程基於 “平滑鏡面高光”部分 中描述的逐畫素光照,以及在 “紋理球體”部分 中介紹的紋理對映概念。

左側的插圖描繪了使用靜態天空盒的反射對映的概念:視點射線在物體表面的一個點反射,反射射線與天空盒相交,以確定相應畫素的顏色。天空盒只是一個帶有紋理面的包圍整個場景的大立方體。需要注意的是,天空盒通常是靜態的,不包含場景中的任何動態物體。但是,用於反射對映的“天空盒”通常被渲染以包含從特定視角看到的場景。然而,這超出了本教程的範圍。
此外,本教程只涵蓋了反射的計算,不涵蓋天空盒的渲染,這在 “天空盒”部分 中討論。對於物體中天空盒的反射,我們必須渲染物體,並將攝像機射線從攝像機反射到表面法線向量上的表面點。這種反射的數學原理與光線在表面法線向量上的反射相同,這在 “鏡面高光”部分 中討論過。
一旦我們有了反射射線,就必須計算它與大天空盒的交點。如果天空盒是無限大的,這種計算實際上就變得更容易了:在這種情況下,表面點的座標位置根本無關緊要,因為其距離座標系原點的距離與天空盒的大小相比是無限小的;因此,只有反射射線的方向很重要,而不是它的位置。因此,我們實際上也可以認為是一條從一個小天空盒的中心發出的射線,而不是從無限大的天空盒中的某個地方發出的射線。(如果您不熟悉這個概念,您可能需要一些時間來接受它。)根據反射射線的方向,它將與紋理天空盒的六個面之一相交。我們可以計算出哪個面被相交,以及面被相交的位置,然後在特定面的紋理影像中進行紋理查詢(參見 “紋理球體”部分)。但是,GLSL 提供了立方體貼圖,它完全支援使用方向向量在立方體的六個面上進行這種紋理查詢。因此,我們只需要提供一個環境立方體貼圖作為著色器屬性,並使用textureCube指令和反射方向來獲得立方體貼圖中相應位置的顏色。
名為_Cube的立方體貼圖著色器屬性可以在 Unity 著色器中這樣定義
Properties {
_Cube ("Reflection Map", Cube) = "" {}
}
相應的 uniform 變數在 GLSL 著色器中這樣定義
uniform samplerCube _Cube;
要建立立方體貼圖,請在專案檢視中選擇建立 > 立方體貼圖。然後,您必須在檢查器檢視中為立方體的面指定六個紋理影像。此類紋理的示例可以在標準資源 > 天空盒 > 紋理中找到。此外,您應該為立方體貼圖在檢查器檢視中選中MipMaps;這應該為反射對映產生明顯更好的視覺效果。
頂點著色器必須計算視點方向viewDirection和法線方向normalDirection,如 “鏡面高光”部分 中所述。為了在片段著色器中反射視點方向,我們可以使用 GLSL 函式reflect,這也如 “鏡面高光”部分 中所述
vec3 reflectedDirection =
reflect(viewDirection, normalize(normalDirection));
要執行立方體貼圖中的紋理查詢並將結果顏色儲存在片段顏色中,我們只需使用
gl_FragColor = textureCube(_Cube, reflectedDirection);
就是這樣。
然後著色器程式碼變為
Shader "GLSL shader with reflection map" {
Properties {
_Cube("Reflection Map", Cube) = "" {}
}
SubShader {
Pass {
GLSLPROGRAM
// User-specified uniforms
uniform samplerCube _Cube;
// The following built-in uniforms
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec3 _WorldSpaceCameraPos;
// camera position in world space
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
// Varyings
varying vec3 normalDirection;
varying vec3 viewDirection;
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
// is unnecessary because we normalize vectors
normalDirection = normalize(vec3(
vec4(gl_Normal, 0.0) * modelMatrixInverse));
viewDirection = vec3(modelMatrix * gl_Vertex
- vec4(_WorldSpaceCameraPos, 1.0));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
vec3 reflectedDirection =
reflect(viewDirection, normalize(normalDirection));
gl_FragColor = textureCube(_Cube, reflectedDirection);
}
#endif
ENDGLSL
}
}
}
恭喜!您已經完成了第一個關於環境貼圖的教程。我們已經看到了
- 如何在物體中計算天空盒的反射。
- 如何在 Unity 中生成立方體貼圖,以及如何將它們用於反射對映。
如果您仍然想了解更多
- 關於向量的反射,您應該閱讀 “鏡面高光”部分。
- 關於 Unity 中的立方體貼圖,您應該閱讀 Unity 關於立方體貼圖的文件。