跳至內容

Cg 程式設計/Unity/平面陰影

來自 Wikibooks,開放世界中的開放書籍
一個帶有投影陰影的卡通人物。

本教程涵蓋了將陰影投影到平面上

它不基於任何特定的教程;但是,瞭解“頂點變換”部分是有用的。

將硬陰影投影到平面上

[編輯 | 編輯原始碼]

即時計算逼真的陰影是困難的。但是,某些情況要容易得多。將硬陰影(即沒有半影的陰影;參見“球體的軟陰影”部分)投影到平面上就是其中一種情況。這個想法是透過渲染投射陰影的物體來渲染陰影,該物體的顏色為陰影的顏色,並且頂點被投影到接收陰影的平面正上方。

P在方向L上投影到平面的示意圖,在平面的座標系中。

將物體投影到平面上

[編輯 | 編輯原始碼]

為了渲染投影陰影,我們必須將物體投影到平面上。為了指定平面,我們將使用預設平面遊戲物件的區域性座標系。因此,我們可以透過編輯平面物件輕鬆修改平面的位置和方向。在該遊戲物件的座標系中,實際平面只是 平面,該平面由 軸跨越。

在頂點著色器中投影物體意味著投影每個頂點。這可以透過類似於“頂點變換”部分中討論的投影矩陣來完成。但是,這些矩陣計算和除錯起來有些困難。因此,我們將採用另一種方法,使用一些向量算術來計算投影。左側的示意圖顯示了點P在光方向L上投影到接收陰影的平面上的情況。(注意,向量L與通常用於光照計算的光向量方向相反。)為了將點P移動到平面,我們添加了L的縮放版本。縮放因子原來是P到平面的距離除以L在平面法線方向上的長度(因為類似於灰色線所示的三角形)。在平面的座標系中,法線向量只是 軸,我們也可以使用點P 座標除以向量L的負 座標的比率。

因此,頂點著色器可能如下所示

         uniform float4x4 _World2Receiver; // transformation from 
            // world coordinates to the coordinate system of the plane

         [...]

         float4 vert(float4 vertexPos : POSITION) : SV_POSITION
         {
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
 
            float4 lightDirection;
            if (0.0 != _WorldSpaceLightPos0.w) 
            {
               // point or spot light
               lightDirection = normalize(
                  mul(modelMatrix, vertexPos - _WorldSpaceLightPos0));
            } 
            else 
            {
               // directional light
               lightDirection = -normalize(_WorldSpaceLightPos0); 
            }
 
            float4 vertexInWorldSpace = mul(modelMatrix, vertexPos);
            float distanceOfVertex = 
               mul(_World2Receiver, vertexInWorldSpace).y 
               // = height over plane 
            float lengthOfLightDirectionInY = 
               mul(_World2Receiver, lightDirection).y 
               // = length in y direction
 
           lightDirection = lightDirection 
               * (distanceOfVertex / (-lengthOfLightDirectionInY));
 
            return mul(UNITY_MATRIX_VP, 
               vertexInWorldSpace + lightDirection));
         }

統一變數_World2Receiver最好使用一個小的 C# 指令碼來設定,該指令碼應附加到投射陰影的物體上

using UnityEngine;

[ExecuteInEditMode, RequireComponent(typeof(Renderer))]
public class SetWorld2Receiver : MonoBehaviour {
	public GameObject plane;
	Renderer rend;
	Renderer planeRend;
	void Start() {
		rend = GetComponent<Renderer>();
	}

	// Update is called once per frame
	void Update () {
		if (plane != null) {
			planeRend = plane.GetComponent<Renderer>();
			rend.sharedMaterial.SetMatrix("_World2Receiver", 
				planeRend.worldToLocalMatrix);
		}
	}
}

該指令碼(應命名為SetWorld2Receiver)要求使用者指定接收陰影的平面物件,並相應地設定統一變數_World2Receiver

完整的著色器程式碼

[編輯 | 編輯原始碼]

對於完整的著色器程式碼,我們透過注意到矩陣向量積的 座標只是矩陣的第二行(即從 0 開始時的第一行)與向量的點積來提高效能。此外,我們透過在頂點位於平面下方時或光向上照射時不移動頂點來提高魯棒性。此外,我們嘗試透過以下指令確保陰影在平面的頂部

偏移 -1.0,-2.0

這會稍微降低光柵化三角形的深度,以使它們始終遮擋其他深度大致相同的三角形。

著色器的第一遍渲染投射陰影的物體,而第二遍渲染投影陰影。在實際應用中,第一遍可以用一個或多個遍來代替,以計算投射陰影的物體的照明。

Shader "Cg planar shadow" {
   Properties {
      _Color ("Object's Color", Color) = (0,1,0,1)
      _ShadowColor ("Shadow's Color", Color) = (0,0,0,1)
   }
   SubShader {
      Pass {      
         Tags { "LightMode" = "ForwardBase" } // rendering of object
 
         CGPROGRAM
 
         #pragma vertex vert 
         #pragma fragment frag
 
         // User-specified properties
         uniform float4 _Color; 
 
         float4 vert(float4 vertexPos : POSITION) : SV_POSITION 
         {
            return mul(UNITY_MATRIX_MVP, vertexPos);
         }
 
         float4 frag(void) : COLOR
         {
            return _Color; 
         }
 
         ENDCG 
      }
 
      Pass {   
         Tags { "LightMode" = "ForwardBase" } 
            // rendering of projected shadow
         Offset -1.0, -2.0 
            // make sure shadow polygons are on top of shadow receiver
 
         CGPROGRAM
 
         #pragma vertex vert 
         #pragma fragment frag
 
         #include "UnityCG.cginc"
 
         // User-specified uniforms
         uniform float4 _ShadowColor;
         uniform float4x4 _World2Receiver; // transformation from 
            // world coordinates to the coordinate system of the plane
 
         float4 vert(float4 vertexPos : POSITION) : SV_POSITION
         {
            float4x4 modelMatrix = unity_ObjectToWorld;
            float4x4 modelMatrixInverse = unity_WorldToObject; 
            float4x4 viewMatrix = 
               mul(UNITY_MATRIX_MV, modelMatrixInverse);
 
            float4 lightDirection;
            if (0.0 != _WorldSpaceLightPos0.w) 
            {
               // point or spot light
               lightDirection = normalize(
                  mul(modelMatrix, vertexPos - _WorldSpaceLightPos0));
            } 
            else 
            {
               // directional light
               lightDirection = -normalize(_WorldSpaceLightPos0); 
            }
 
            float4 vertexInWorldSpace = mul(modelMatrix, vertexPos);
            float4 world2ReceiverRow1 = 
               float4(_World2Receiver[1][0], _World2Receiver[1][1], 
               _World2Receiver[1][2], _World2Receiver[1][3]);
            float distanceOfVertex = 
               dot(world2ReceiverRow1, vertexInWorldSpace); 
               // = (_World2Receiver * vertexInWorldSpace).y 
               // = height over plane 
            float lengthOfLightDirectionInY = 
               dot(world2ReceiverRow1, lightDirection); 
               // = (_World2Receiver * lightDirection).y 
               // = length in y direction
 
            if (distanceOfVertex > 0.0 && lengthOfLightDirectionInY < 0.0)
            {
               lightDirection = lightDirection 
                  * (distanceOfVertex / (-lengthOfLightDirectionInY));
            }
            else
            {
               lightDirection = float4(0.0, 0.0, 0.0, 0.0); 
                  // don't move vertex
            }
 
            return mul(UNITY_MATRIX_VP,  
               vertexInWorldSpace + lightDirection);
         }
 
         float4 frag(void) : COLOR 
         {
            return _ShadowColor;
         }
 
         ENDCG 
      }
   }
}

片段著色器的進一步改進

[編輯 | 編輯原始碼]

有一些東西可以改進,特別是在片段著色器中

  • 可以使用discard指令刪除位於矩形平面物件之外的陰影片段,該指令在“切口”部分中進行了討論。
  • 如果平面是紋理化的,則可以透過僅使用區域性頂點座標進行紋理查詢(也在平面物件的著色器中)並將平面的紋理指定為投射陰影的物體的著色器屬性來整合此紋理化。
  • 可以透過在此著色器中計算平面的照明並根據投射陰影的物體的表面法線向量與光方向的角度對其進行衰減來偽造軟陰影,這類似於“輪廓增強”部分中的方法。

恭喜,本教程到此結束。我們已經看到

  • 如何將頂點沿光方向投影到平面上。
  • 如何實現此技術將陰影投影到平面上。

進一步閱讀

[編輯 | 編輯原始碼]

如果您還想了解更多

  • 關於模型變換、檢視變換和投影,您應該閱讀“頂點變換”部分中的描述。
  • 關於設定投影矩陣以投影陰影,你可以閱讀 Tom McReynolds 組織的 SIGGRAPH '98 課程“使用 OpenGL 的高階圖形程式設計技術”的第 9.4.1 節,該課程可以 線上 獲取。

< Cg 程式設計/Unity

除非另有說明,本頁面上的所有示例原始碼均為公有領域。
華夏公益教科書