Contents
本教程是一个Pass包含了多个光源进行光照。特别的,它包含了在ForwardBase通道中Unity的所谓的”顶点灯“。
多个光照在一个Pass中
如“Diffuse Reflection”一节中所述,Unity前向渲染路径对最重要的光源使用单独的通道。之所以将他们称为“像素灯”,是因为内置的着色器使用逐像素光照对其进行渲染。所有Render Mode
设置为Important
的所有光源都被渲染为像素光 。如果项目设置的Quality
中的Pixel Light Count
允许更多的光照,则某些Render Mode
设置为Auto
的光源也将被渲染为像素光。其他光源会发生什么呢?Unity的内置着色器在ForwardBase传递中将四个额外的灯光渲染为顶点灯光。 顾名思义,内置着色器使用逐顶点照明来渲染这些灯光。 这就是本教程的内容。 (更多的光是通过球谐光照明得到的,此处并未涉及。)
在ForwardBase通道中,四个顶点光(即它们的位置和颜色)由以下内置uniform指定(请参见Unity的内置着色器变量文档):
// 内置uniform的顶点光
uniform half4 unity_LightColor[4];
// 四种光源颜色队列
uniform float4 unity_4LightPosX0;
//世界空间中4个光源的x坐标
uniform float4 unity_4LightPosY0;
// 世界空间中4个光源的y坐标
uniform float4 unity_4LightPosZ0;
// 世界空间中4个光源的z坐标
uniform float4 unity_4LightAtten0;
// 距离平方衰减的比例因子
我们遵循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
通过将所有顶点光的总漫射照明初始化为黑色,然后将每个顶点光的漫反射添加到for循环末尾的vertexLighting的先前值,将总漫反射光累积在vertexLighting
中。 请注意,对于某些GPU,Cg中的for循环受到严格限制。特别是对于某些GPU,限制(此处为0和4)必须为常数,即您甚至无法使用制服来确定限制。 (技术原因是必须在编译时知道限制,才能“展开”循环。)
这或多或少是在Unity的内置着色器中计算顶点光的方式。 但是,请记住,没有什么会阻止您使用这些“顶点光”来计算镜面反射或逐像素照明。
完整Shader代码
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的顶点灯。
- 如何在Cg中使用for循环来一次计算多个灯的照度。