Cg 程式設計/Unity/多重燈光
本教程涵蓋了單遍多重光源照明。特別是,它涵蓋了 Unity 所謂的 ForwardBase 通道中的“頂點燈光”。
本教程是 “平滑鏡面高光”部分 的擴充套件。如果你還沒有閱讀過該教程,你應該先閱讀它。
如 “漫反射”部分 所述,Unity 的正向渲染路徑使用單獨的通道來處理最重要的光源。這些被稱為“畫素燈光”,因為內建著色器使用每畫素照明來渲染它們。所有將渲染模式設定為重要的光源都將作為畫素燈光渲染。如果質量專案設定的畫素燈光數量允許使用更多畫素燈光,那麼一些將渲染模式設定為自動的光源也會作為畫素燈光渲染。其他光源會發生什麼?Unity 的內建著色器將另外四個燈光作為頂點燈光渲染到 ForwardBase 通道中。顧名思義,內建著色器使用每頂點照明來渲染這些燈光。這就是本教程所要講的內容。(進一步的燈光將透過球諧照明來近似,這裡不討論。)
在 ForwardBase 通道中,四個頂點燈光(即它們的位置和顏色)由以下內建制服指定(參見 Unity 內建著色器變數的文件)
// Built-in uniforms for "vertex lights"
uniform half4 unity_LightColor[4];
// array of the colors of the 4 light sources
uniform float4 unity_4LightPosX0;
// x coordinates of the 4 light sources in world space
uniform float4 unity_4LightPosY0;
// y coordinates of the 4 light sources in world space
uniform float4 unity_4LightPosZ0;
// z coordinates of the 4 light sources in world space
uniform float4 unity_4LightAtten0;
// scale factors for attenuation with squared distance
我們遵循 Unity 的內建著色器,只計算頂點燈光(假設它們是點光源)的漫反射,使用每頂點照明。這可以使用頂點著色器中的以下 for 迴圈來計算
// Diffuse reflection by four "vertex lights"
float3 vertexLighting = float3(0.0, 0.0, 0.0);
#ifdef VERTEXLIGHT_ON
for (int index = 0; index < 4; index++)
{
float4 lightPosition = float4(unity_4LightPosX0[index],
unity_4LightPosY0[index],
unity_4LightPosZ0[index], 1.0);
float3 vertexToLightSource =
lightPosition.xyz - output.posWorld.xyz;
float3 lightDirection = normalize(vertexToLightSource);
float squaredDistance =
dot(vertexToLightSource, vertexToLightSource);
float attenuation = 1.0 / (1.0 +
unity_4LightAtten0[index] * squaredDistance);
float3 diffuseReflection = attenuation
* unity_LightColor[index].rgb * _Color.rgb
* max(0.0, dot(output.normalDir, lightDirection));
vertexLighting = vertexLighting + diffuseReflection;
}
#endif
所有頂點燈光的總漫反射照明在 vertexLighting 中累加,透過將其初始化為黑色,然後在 for 迴圈結束時將每個頂點燈光的漫反射新增到 vertexLighting 的先前值。for 迴圈對於任何 C/C++/Java/JavaScript 程式設計師來說應該很熟悉。請注意,Cg 中的 for 迴圈對於某些 GPU 來說是嚴重受限的;特別是限制(這裡:0 和 4)對於某些 GPU 來說必須是常量,也就是說,你甚至不能使用制服來確定限制。(技術原因是,為了“展開”迴圈,必須在編譯時知道限制。)
這或多或少是 Unity 內建著色器中頂點燈光的計算方式。但是,請記住,沒有什麼能阻止你使用這些“頂點燈光”來計算鏡面反射或每畫素照明。
在 “平滑鏡面高光”部分 的著色器程式碼上下文中,完整著色器程式碼為
Shader "Cg per-pixel lighting with vertex lights" {
Properties {
_Color ("Diffuse Material Color", Color) = (1,1,1,1)
_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
_Shininess ("Shininess", Float) = 10
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" } // pass for
// 4 vertex lights, ambient light & first pixel light
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")
// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
float3 vertexLighting : TEXCOORD2;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
float4x4 modelMatrix = unity_ObjectToWorld;
float4x4 modelMatrixInverse = unity_WorldToObject;
output.posWorld = mul(modelMatrix, input.vertex);
output.normalDir = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
output.pos = UnityObjectToClipPos(input.vertex);
// Diffuse reflection by four "vertex lights"
output.vertexLighting = float3(0.0, 0.0, 0.0);
#ifdef VERTEXLIGHT_ON
for (int index = 0; index < 4; index++)
{
float4 lightPosition = float4(unity_4LightPosX0[index],
unity_4LightPosY0[index],
unity_4LightPosZ0[index], 1.0);
float3 vertexToLightSource =
lightPosition.xyz - output.posWorld.xyz;
float3 lightDirection = normalize(vertexToLightSource);
float squaredDistance =
dot(vertexToLightSource, vertexToLightSource);
float attenuation = 1.0 / (1.0 +
unity_4LightAtten0[index] * squaredDistance);
float3 diffuseReflection = attenuation
* unity_LightColor[index].rgb * _Color.rgb
* max(0.0, dot(output.normalDir, lightDirection));
output.vertexLighting =
output.vertexLighting + diffuseReflection;
}
#endif
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float3 normalDirection = normalize(input.normalDir);
float3 viewDirection = normalize(
_WorldSpaceCameraPos - input.posWorld.xyz);
float3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection =
normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource =
_WorldSpaceLightPos0.xyz - input.posWorld.xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
float3 ambientLighting =
UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* max(0.0, dot(normalDirection, lightDirection));
float3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
return float4(input.vertexLighting + ambientLighting
+ diffuseReflection + specularReflection, 1.0);
}
ENDCG
}
Pass {
Tags { "LightMode" = "ForwardAdd" }
// pass for additional light sources
Blend One One // additive blending
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _LightColor0;
// color of light source (from "Lighting.cginc")
// User-specified properties
uniform float4 _Color;
uniform float4 _SpecColor;
uniform float _Shininess;
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct vertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
};
vertexOutput vert(vertexInput input)
{
vertexOutput output;
float4x4 modelMatrix = unity_ObjectToWorld;
float4x4 modelMatrixInverse = unity_WorldToObject;
output.posWorld = mul(modelMatrix, input.vertex);
output.normalDir = normalize(
mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float3 normalDirection = normalize(input.normalDir);
float3 viewDirection = normalize(
_WorldSpaceCameraPos.xyz - input.posWorld.xyz);
float3 lightDirection;
float attenuation;
if (0.0 == _WorldSpaceLightPos0.w) // directional light?
{
attenuation = 1.0; // no attenuation
lightDirection =
normalize(_WorldSpaceLightPos0.xyz);
}
else // point or spot light
{
float3 vertexToLightSource =
_WorldSpaceLightPos0.xyz - input.posWorld.xyz;
float distance = length(vertexToLightSource);
attenuation = 1.0 / distance; // linear attenuation
lightDirection = normalize(vertexToLightSource);
}
float3 diffuseReflection =
attenuation * _LightColor0.rgb * _Color.rgb
* max(0.0, dot(normalDirection, lightDirection));
float3 specularReflection;
if (dot(normalDirection, lightDirection) < 0.0)
// light source on the wrong side?
{
specularReflection = float3(0.0, 0.0, 0.0);
// no specular reflection
}
else // light source on the right side
{
specularReflection = attenuation * _LightColor0.rgb
* _SpecColor.rgb * pow(max(0.0, dot(
reflect(-lightDirection, normalDirection),
viewDirection)), _Shininess);
}
return float4(diffuseReflection
+ specularReflection, 1.0);
// no ambient lighting in this pass
}
ENDCG
}
}
Fallback "Specular"
}
使用 #pragma multi_compile_fwdbase 和 #ifdef VERTEXLIGHT_ON ... #endif 是必要的,以確保當 Unity 不提供資料時,不會計算頂點照明;另見 Unity 關於 multi_compile 指令的文件。
恭喜你,你已經完成了本教程。我們已經看到了
- Unity 的頂點燈光是如何指定的。
- 如何在 Cg 中使用 for 迴圈來計算單遍多重燈光的照明。
如果你還想了解更多
- 關於著色器程式碼的其他部分,你應該閱讀 “平滑鏡面高光”部分。
- 關於 Unity 的正向渲染路徑以及
ForwardBase通道中計算的內容,你應該閱讀 Unity 關於正向渲染的參考。