Cg 程式設計/Unity/投影儀
本教程介紹了 **投影紋理對映投影儀**, 它是 Unity 的特殊渲染元件。
它基於 “Cookie”部分。如果你還沒有閱讀過該教程,請先閱讀。
Unity 的投影儀有點類似於聚光燈。事實上,它們可以用於類似的應用。然而,存在著一個重要的技術差異:對於聚光燈,所有被照亮物體的著色器都需要計算聚光燈的照明,如 “Cookie”部分 中所述。如果物體的著色器忽略了聚光燈,它就不會被聚光燈照亮。投影儀則不同:每個投影儀都與一個材質關聯,該材質包含一個著色器,該著色器應用於投影儀範圍內的所有物體。因此,物體的著色器不需要處理投影儀;相反,投影儀將它的著色器應用於它範圍內的所有物體,作為額外的渲染通道,以實現某些效果,例如新增投影影像的光線或衰減物體的顏色以模擬陰影。事實上,透過使用投影儀著色器的不同混合方程,可以實現各種效果。(混合方程在 “透明度”部分 中討論。)
人們甚至可以將投影儀視為實現光線的更“自然”方式。但是,光與材料之間的相互作用通常是特定於每種材料的,而投影儀的單個著色器無法處理所有這些差異。這將投影儀的可能性限制為三種基本行為:將光線新增到物體,調製物體的顏色,或兩者兼而有之,新增光線並調製物體的顏色。我們將以新增光線到物體和衰減物體的顏色為例來討論調製顏色。
為了建立一個投影儀,從主選單中選擇 **GameObject > Create Empty**,然後(在選擇新物件時)從主選單中選擇 **Component > Effects > Projector**。你現在擁有一個投影儀,可以像操作聚光燈一樣操作它。投影儀在 **Inspector Window** 中的設定在 Unity 手冊 中進行了討論。這裡,唯一重要的設定是投影儀的 **Material**,它將應用於它範圍內的所有物體。因此,我們必須建立另一個材質,併為它分配一個合適的著色器。這個著色器通常無法訪問它所應用的博弈物件的材質;因此,它無法訪問它們的紋理等。它也無法訪問任何有關光源的資訊。但是,它可以訪問博弈物件的頂點的屬性和它自己的著色器屬性。
用於將光線新增到物體的著色器可以用來將任何影像投影到其他物體上,類似於投影儀或電影投影儀。因此,它應該使用類似於聚光燈 Cookie 的紋理影像(參見 “Cookie”部分),只不過紋理影像的 RGB 顏色應該被新增以允許彩色投影。我們透過將片段顏色設定為紋理影像的 RGBA 顏色並使用混合方程來實現這一點
Blend One One
它只是將片段顏色新增到幀緩衝區中的顏色。(根據紋理影像,可能最好使用 Blend SrcAlpha One 來移除任何不透明度為零的顏色。)
與聚光燈的 Cookie 另一個不同之處在於,我們應該使用特定於 Unity 的統一矩陣 unity_Projector 將位置從物件空間轉換為投影儀空間,而不是矩陣 _LightMatrix0。但是,投影儀空間中的座標與光線空間中的座標非常類似——除了產生的 和 座標在正確範圍內;因此,我們不必擔心新增 0.5。但是,我們必須執行除以 座標的操作(如投影紋理對映中總是做的那樣);透過顯式地將 和 除以 還是透過使用 tex2Dproj。
行 ZWrite Off 確保我們不會改變深度緩衝區,因為我們只是將光線新增到已經光柵化的網格。Offset -1, -1 稍微改變了深度,以假裝我們稍微位於網格的前面,我們正在向其新增光線。這有助於確保我們現在光柵化的任何內容都不會被該網格遮擋。(當你複製貼上下面的程式碼時,一些編輯器會在第一個 “-” 之後新增一個空格字元(“ ”),這會產生語法錯誤。只需刪除該空格字元。)
Shader "Cg projector shader for adding light" {
Properties {
_ShadowTex ("Projected Image", 2D) = "white" {}
}
SubShader {
Pass {
Blend One One
// add color of _ShadowTex to the color in the framebuffer
ZWrite Off // don't change depths
Offset -1, -1 // avoid depth fighting (should be "Offset -1, -1")
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// User-specified properties
uniform sampler2D _ShadowTex;
// Projector-specific uniforms
uniform float4x4 unity_Projector; // transformation matrix
// from object space to projector space
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 posProj : TEXCOORD0;
// position in projector space
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.posProj = mul(unity_Projector, input.vertex);
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
if (input.posProj.w > 0.0) // in front of projector?
{
return tex2D(_ShadowTex ,
input.posProj.xy / input.posProj.w);
// alternatively: return tex2Dproj(
// _ShadowTex, input.posProj);
}
else // behind projector
{
return float4(0.0, 0.0, 0.0, 0.0);
}
}
ENDCG
}
}
Fallback "Projector/Light"
}
請注意,我們必須測試 是否為正值(即片段位於投影儀的前面,而不是後面)。如果沒有此測試,投影儀也會向它後面的物體新增光線。此外,紋理影像必須是正方形的,並且通常最好使用紋理座標設定為鉗制的紋理。
以防你好奇:紋理的著色器屬性稱為 _ShadowTex,以便與投影儀的內建著色器相容。
如 “Cookie”部分 中所述,投影紋理對映有時會伴隨一個令人不快的副作用:在投影的邊緣,GPU 使用高 mip 級別,這會導致可見的邊界(特別是對於紋理座標被鉗制的紋理對映)。避免此問題的最簡單方法是停用紋理影像的 mip 對映:在 **Project Window** 中找到並選擇紋理影像;然後在 **Inspector Window** 中將 **Texture Type** 設定為 **Advanced**,並取消選中 **Generate Mip Maps**。不要忘記單擊 **Apply** 按鈕。

建立用於調製顏色的投影儀的基本步驟與上面相同。唯一的區別是著色器程式碼。以下示例透過衰減顏色(特別是地板的顏色)來新增陰影。請注意,在實際應用中,陰影投射者的顏色不應衰減。這可以透過將陰影投射者分配給特定層(在遊戲物件的檢查器視窗中)並在投影儀的檢查器視窗中忽略層下指定此層來實現。
為了使陰影具有特定的形狀,我們使用紋理影像的 Alpha 分量來確定陰影的深淺。(因此,我們可以使用標準資源中燈光用的 Cookie 紋理。)為了在幀緩衝區中衰減顏色,我們應該用 1 減去 Alpha(即 Alpha 等於 1 時的因子 0)來乘以它。因此,適當的混合方程是
混合 零 一減去源 Alpha
零表示我們不新增任何光。即使陰影太暗,也不應新增光;相反,應在片段著色器中降低 Alpha 分量,例如,透過將其乘以小於 1 的因子。為了獨立地調製幀緩衝區中的顏色分量,我們需要混合 零 源顏色或混合 零 一減去源顏色。
不同的混合方程實際上是著色器程式碼中與新增光版本相比的唯一變化。
Shader "Cg projector shader for drop shadows" {
Properties {
_ShadowTex ("Projected Image", 2D) = "white" {}
}
SubShader {
Pass {
Blend Zero OneMinusSrcAlpha // attenuate color in framebuffer
// by 1 minus alpha of _ShadowTex
ZWrite Off // don't change depths
Offset -1, -1 // avoid depth fighting (should be "Offset -1, -1")
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// User-specified properties
uniform sampler2D _ShadowTex;
// Projector-specific uniforms
uniform float4x4 unity_Projector; // transformation matrix
// from object space to projector space
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 posProj : TEXCOORD0;
// position in projector space
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.posProj = mul(unity_Projector, input.vertex);
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
if (input.posProj.w > 0.0) // in front of projector?
{
return tex2D(_ShadowTex ,
input.posProj.xy / input.posProj.w);
// alternatively: return tex2Dproj(
// _ShadowTex, input.posProj);
}
else // behind projector
{
return float4(0.0, 0.0, 0.0, 0.0);
}
}
ENDCG
}
}
Fallback "Projector/Light"
}
恭喜,本教程到此結束。我們已經瞭解了
- Unity 投影儀的工作原理。
- 如何實現一個著色器,用於將光新增到投影儀上的物件。
- 如何實現一個著色器,用於衰減投影儀上的物件的顏色。
如果你還想了解更多
- 關於光空間(與投影空間非常相似),你應該閱讀“Cookies”部分。
- 關於紋理對映,特別是 Alpha 紋理貼圖,你應該閱讀“透明紋理”部分。
- 關於固定功能 OpenGL 中的投影紋理對映,你可以閱讀 NVIDIA 的白皮書“投影紋理對映”,作者 Cass Everitt(可線上獲得線上)。
- 關於 Unity 投影儀,你應該閱讀Unity 關於投影儀的文件以及 Unity 的“標準資源”中的示例(可在 Asset Store 獲取)在“標準資源”>“效果”>“投影儀”>“著色器”。