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 关于正向渲染的参考。