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

本教程涵蓋將陰影投影到平面上。
本教程沒有基於任何特定教程;但是,對“頂點變換”部分有一定的瞭解將會有所幫助。
即時計算逼真的陰影很困難。但是,有些情況要容易得多。將硬陰影(即沒有半影的陰影;參見“球體的軟陰影”部分)投影到平面上就是其中一種情況。其思想是透過將投射陰影的物體渲染成陰影的顏色,並將頂點投影到接收陰影的平面正上方來渲染陰影。

為了渲染投影的陰影,我們必須將物體投影到平面上。為了指定平面,我們將使用預設平面遊戲物件的本地座標系。因此,我們可以透過編輯平面物件輕鬆地修改平面的位置和方向。在該遊戲物件的座標系中,實際的平面只是 平面,它由 和 軸所跨越。
在頂點著色器中投影物體意味著投影每個頂點。這可以使用類似於“頂點變換”部分中討論的投影矩陣來完成。但是,這些矩陣計算和除錯起來有些困難。因此,我們將採取另一種方法,使用一些向量運算來計算投影。左側的圖示顯示了點P在光方向L上投影到接收陰影的平面。 (注意向量L與通常在光照計算中使用的光向量方向相反。)為了將點P移動到平面上,我們添加了L的縮放版本。縮放因子恰好是P到平面的距離除以L在平面法向量方向上的長度(由於類似三角形,如灰色線所示)。在平面的座標系中,法向量只是 軸,我們也可以使用點P的 座標除以向量L的負 座標的比率。
因此,頂點著色器可能如下所示
GLSLPROGRAM
// User-specified uniforms
uniform mat4 _World2Receiver; // transformation from
// world coordinates to the coordinate system of the plane
// The following built-in uniforms
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 unity_Scale; // w = 1/uniform scale;
// should be multiplied to _World2Object
uniform vec4 _WorldSpaceLightPos0;
// position or direction of light source
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object * unity_Scale.w;
modelMatrixInverse[3][3] = 1.0;
mat4 viewMatrix = gl_ModelViewMatrix * modelMatrixInverse;
vec4 lightDirection;
if (0.0 != _WorldSpaceLightPos0.w) // point or spot light?
{
lightDirection = normalize(
modelMatrix * gl_Vertex - _WorldSpaceLightPos0);
}
else // directional light
{
lightDirection = -normalize(_WorldSpaceLightPos0);
}
vec4 vertexInWorldSpace = modelMatrix * gl_Vertex;
float distanceOfVertex =
(_World2Receiver * vertexInWorldSpace).y;
// = height over plane
float lengthOfLightDirectionInY =
(_World2Receiver * lightDirection).y;
// = length in y direction
lightDirection = lightDirection
* (distanceOfVertex / (-lengthOfLightDirectionInY));
gl_Position = gl_ProjectionMatrix * (viewMatrix
* (vertexInWorldSpace + lightDirection));
}
#endif
...
統一變數_World2Receiver最好藉助一個小指令碼設定,該指令碼應該附加到投射陰影的物體上
@script ExecuteInEditMode()
public var plane : GameObject;
function Update ()
{
if (null != plane)
{
renderer.sharedMaterial.SetMatrix("_World2Receiver",
plane.renderer.worldToLocalMatrix);
}
}
該指令碼要求使用者指定接收陰影的平面物件,並相應地設定統一變數_World2Receiver。
對於完整的著色器程式碼,我們透過注意到 矩陣-向量積的座標只是矩陣的第二行(即從 0 開始的第一行)與向量的點積來提高效能。此外,我們透過在頂點在平面下方或光向上照射時不移動頂點來提高魯棒性。此外,我們嘗試透過以下指令確保陰影位於平面上方
偏移 -1.0,-2.0
這會稍微降低光柵化三角形的深度,以便它們始終遮擋大約相同深度的其他三角形。
著色器的第一遍渲染投射陰影的物體,而第二遍渲染投影的陰影。在實際應用中,第一遍可以用一個或多個遍來計算投射陰影的物體的光照來代替。
Shader "GLSL 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
GLSLPROGRAM
// User-specified properties
uniform vec4 _Color;
#ifdef VERTEX
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = _Color;
}
#endif
ENDGLSL
}
Pass {
Tags { "LightMode" = "ForwardBase" }
// rendering of projected shadow
Offset -1.0, -2.0
// make sure shadow polygons are on top of shadow receiver
GLSLPROGRAM
// User-specified uniforms
uniform vec4 _ShadowColor;
uniform mat4 _World2Receiver; // set by script
// The following built-in uniforms )
// are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform mat4 _Object2World; // model matrix
uniform mat4 _World2Object; // inverse model matrix
uniform vec4 unity_Scale; // w = 1/uniform scale;
// should be multiplied to _World2Object
uniform vec4 _WorldSpaceLightPos0;
// position or direction of light source
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
mat4 modelMatrixInverse = _World2Object * unity_Scale.w;
modelMatrixInverse[3][3] = 1.0;
mat4 viewMatrix = gl_ModelViewMatrix * modelMatrixInverse;
vec4 lightDirection;
if (0.0 != _WorldSpaceLightPos0.w)
{
// point or spot light
lightDirection = normalize(
modelMatrix * gl_Vertex - _WorldSpaceLightPos0);
}
else
{
// directional light
lightDirection = -normalize(_WorldSpaceLightPos0);
}
vec4 vertexInWorldSpace = modelMatrix * gl_Vertex;
vec4 world2ReceiverRow1 =
vec4(_World2Receiver[0][1], _World2Receiver[1][1],
_World2Receiver[2][1], _World2Receiver[3][1]);
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 = vec4(0.0, 0.0, 0.0, 0.0);
// don't move vertex
}
gl_Position = gl_ProjectionMatrix * (viewMatrix
* (vertexInWorldSpace + lightDirection));
}
#endif
#ifdef FRAGMENT
void main()
{
gl_FragColor = _ShadowColor;
}
#endif
ENDGLSL
}
}
}
有一些地方可以改進,特別是在片段著色器中
- 可以使用在“切除”部分中討論過的
discard指令來刪除陰影片段,這些片段位於矩形平面物件之外。 - 如果平面是紋理化的,則可以透過僅使用區域性頂點座標進行紋理查詢(也在平面物件的著色器中)並將平面的紋理指定為投射陰影的物體的著色器屬性來整合這種紋理化。
- 可以透過在本著色器中計算平面的光照並根據投射陰影的物體表面法向量與光方向的角度對其進行衰減來模擬軟陰影,類似於“輪廓增強”部分中的方法。
恭喜,本教程到此結束。我們已經瞭解了
- 如何將頂點沿光方向投影到平面上。
- 如何實現這種技術將陰影投影到平面上。
如果你想了解更多
- 關於模型變換、檢視變換和投影變換,你應該閱讀 “頂點變換”章節 的描述。
- 關於設定投影矩陣來投影陰影,你可以閱讀 Tom McReynolds 組織的 SIGGRAPH '98 課程“使用 OpenGL 的高階圖形程式設計技術”的第 9.4.1 節,該課程可以在 網上 找到。