1. 光源类型
Unity中支持四种类型的光源,分别是:平行光、点光源、聚光灯和面光源。
平行光——Directional Light
常被用做太阳的功能,各向光照均匀,不存在衰减,光照角度由光源自身旋转决定(而非光源与物体的相对位置),因此只关心光源方向(旋转),不关心光源位置
在平行光的Inpector面板中
- Type为Directional
- Color控制光源颜色
- Instensity控制光源强度

点光源——Point Light
以光源为中心,照亮一定半径的球形范围,各向光照均匀,且光照强度存在距离衰减
在点光源的Inpector面板中
- Type为Point
- Range控制范围球半径
- Color控制光源颜色
- Instensity控制光源强度

聚光灯——Spot Light
光照范围为一个以光源为顶点,以光源朝向为中心轴,按照光源指定角度形成的圆锥体,光照强度存在距离衰减,也会随光照方向与中心轴的角度变大而衰减
在聚光灯的Inpector面板中
- Type为Spot
- Range控制圆锥半径(远近)
- Spot Angle控制圆锥张角
- Color控制光源颜色
- Instensity控制光源强度

面光源——Area Light
只用于烘焙,这里不作讨论
对比
几种光源的对比可以参照下表:
| 光源类型 | 范围 | 位置 | 方向 | 颜色 | 强度 | 衰减 |
|---|---|---|---|---|---|---|
| 平行光 | 整个场景 | 不关心位置 | Rotation决定光照方向 | Color控制颜色 | Instensity 控制强度 | 无衰减 |
| 点光源 | 以光源为中心,Range为半径的球体 | Position决定位置 | 光源与物体的连线决定光照方向 | Color控制颜色 | Instensity 控制强度 | 距离相关 |
| 聚光灯 | 以光源为顶点,光源朝向为中心轴,SpotAngle为张角、Range为边长的圆锥 | Position决定位置 | 光源与物体的连线决定光照方向 | Color控制颜色 | Instensity 控制强度 | 距离相关 |
2. 在Unity中获取光源属性
判断光源类型
Unity中提供了各种内置的宏,可以用来判断当前所处理的光源类型,具体使用如下:
#ifdef USING_DIRECTIONAL_LIGHT
//当前处理的光源类型为平行光
#else
#if defined(POINT)
//当前处理的光源类型为点光源
#elif defined(SPOT)
//当前处理的光源类型为聚光灯
#else
//其他
#endif
#endif
获得光源方向/位置
- 对于平行光,不关心光源位置信息,此时 _WorldSpaceLightPos0.xyz 存储的就是光源的方向
- 对于非平行光,_WorldSpaceLightPos0.xyz 中存储的是光源在世界空间中的位置,可以通过光源位置减去照射目标位置计算获得光源位置:_WorldSpaceLightPos0.xyz - i.worldPos.xyz
- 均需要手动归一化
获得光源颜色/强度
- _LightColor0 可以获取当前处理光源的颜色和强度
- 其值已经是颜色和强度相乘后的值,包含了颜色和强度两方面的综合信息,不需要再手动处理
- Clight = _LightColor0.rgb
获得光源衰减
光照衰减对漫反射和高光反射的效果都会产生影响,因此在计算时,除了我们使用的光照模型的相关计算(就像之前的Shader里),还需要乘以衰减系数 atten
平行光由于不存在衰减,atten 始终为1
非平行光的衰减与光源到物体的距离有关,为了避免在计算时进行复杂的开平方等操作,Unity内置了光照衰减纹理(行,就叫这么个名吧)提供光照衰减信息。通过对光照衰减纹理进行采样即可获得所需的衰减系数。
在不同的光源类型下,衰减存储的贴图、如何进行采样均有不同,具体参照以下示例:
#ifdef USING_DIRECTIONAL_LIGHT
//平行光没有衰减,固定为1
atten = 1;
#else
#if defined(POINT)
float3 _pos = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
//点光源的衰减存储在_LightTexture0纹理中,通过光源空间下点到光源距离的平方构建一个二维采样坐标,对纹理进行采样
//不同平台下,光照衰减可能会被存储到纹理的不同颜色通道中,一般PC和主机平台为R通道,移动平台为A通道
//UNITY_ATTEN_CHANNEL宏可以根据当前平台自动返回对应的颜色通道
atten = tex2D(_LightTexture0, dot(_pos, _pos).rr).UNITY_ATTEN_CHANNEL;
#elif defined(spot)
float4 _pos = mul(_LightMatrix0, float4(i.worldPos, 1));
//聚光灯的距离衰减存储在_lightTextureB0纹理中,可以通过和点光源一样的方式采样距离衰减
fixed _atten_1 = tex2D(_lightTextureB0, dot(_pos, _pos).rr).UNITY_ATTEN_CHANNEL;
//聚光灯的角度衰减存储在_lightTexture0纹理中,通过NDC坐标的XY值进行采样(上移0.5)
fixed _atten_2 = tex2D(_lightTexture0, _pos.xy/_pos.w + 0.5).w;
atten = (_pos.z > 0) * _atten_1 * _atten_2;
#else
atten = 1;
#endif
#endif
3. 多光源前向渲染示例
- 创建 Chapter_9_Forward_Mat 作为测试材质
- 创建 Chapter_9_Forward_Shader 作为测试shader,并赋给 Chapter_9_Forward_Mat 材质
- 场景中创建一个用于测试的胶囊体,将 Chapter_9_Forward_Mat 材质赋给胶囊体
- 场景中添加一盏平行光,设置平行光的颜色为黄色,并调整平行光角度
- 场景中添加一盏聚光灯,设置聚光灯的颜色为蓝色,并调整聚光灯的角度和半径,使聚光灯能够照射到胶囊体
- 场景中添加一盏点光源,设置点光源的颜色为红色,并调整点光源的半径,使聚光灯能够照射到胶囊体
Shader中包含两个Pass:
第一个Pass设置标签为前向渲染的 BasePass
Tags{ "LightMode" = "ForwardBase"}
并添加编译指令:
#pragma multi_compile_fwdbase
第二个Pass设置标签为前向渲染的 Additional Pass
Tags { "LightMode" = "ForwardAdd" }
并添加编译指令:
#pragma multi_compile_fwdadd
为了能够正常使用 _LightMatrix0 、USING_DIRECTIONAL_LIGHT 等内置变量和内置宏,需要增加引用文件:
#include "AutoLight.cginc"
为了使 Additioanl Pass 的光照效果叠加到 Base Pass,而非覆盖,需要设置混合模式:
Blend One One
在片元着色其中,除了上文所说的取关照衰减之外,在计算光源方向时,也需要对平行光和非平行光进行判断(虽然本次的例子中 Additional Pass 不会出现平行光)
#ifdef USING_DIRECTIONAL_LIGHT
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
#else
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
#endif
最终Shader如下:
// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'
Shader "MyShader/Chapter_9/Chapter_9_Forward_Shader"
{
Properties
{
_Color("MainColor", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(1, 50)) = 16
}
Subshader
{
Pass
{
Tags{ "LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
};
fixed4 _Color;
fixed4 _Specular;
half _Gloss;
v2f vert(a2v v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
float3 _worldNormal = normalize(i.worldNormal);
fixed3 _diffuse = _LightColor0.rgb * _Color.xyz * saturate(dot(_worldLight, _worldNormal));
float3 _worldView = normalize(UnityWorldSpaceViewDir(i.worldPos));
float3 _h = normalize(_worldLight + _worldView);
fixed3 _specular = _LightColor0.rgb * _Specular.xyz * pow(saturate(dot(_worldNormal, _h)), _Gloss);
return fixed4(_ambient + _diffuse + _specular, 1);
}
ENDCG
}
Pass
{
Tags { "LightMode" = "ForwardAdd" }
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdadd
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
fixed4 _Color;
fixed4 _Specular;
half _Gloss;
v2f vert(a2v v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed atten;
#ifdef USING_DIRECTIONAL_LIGHT
atten = 1;
#else
#if defined(POINT)
float3 _pos = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
atten = tex2D(_LightTexture0, dot(_pos, _pos).rr).UNITY_ATTEN_CHANNEL;
#elif defined(SPOT)
float4 _pos = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed _atten_1 = tex2D(_LightTextureB0, dot(_pos, _pos).rr).UNITY_ATTEN_CHANNEL;
fixed _atten_2 = tex2D(_LightTexture0, _pos.xy/_pos.w + 0.5).w;
atten = (_pos.z > 0) * _atten_1 * _atten_2;
#else
atten = 1;
#endif
#endif
float3 _worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
#else
float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
#endif
fixed3 _diffuse = _LightColor0.rgb * _Color.xyz * saturate(dot(_worldNormal, _worldLight));
float3 _worldView = UnityWorldSpaceViewDir(i.worldPos);
float3 _h = normalize(_worldView + _worldLight);
fixed3 _specular = _LightColor0.rgb * _Color.xyz * pow(saturate(dot(_worldNormal, _h)), _Gloss);
return fixed4((_diffuse + _specular) * atten, 1);
}
ENDCG
}
}
}
效果如下:

在 Frame Debug 面板中,可以看到此时通过3个 Draw Call 对胶囊体进行了绘制:
- 首先执行第0号Pass,也就是我们定义的Base Pass,处理了平行光的光照

- 其次执行第1号Pass,也就是我们定义的Addition Pass,处理了聚光灯的光照(测试时手动调高了聚光灯的强度)

- 最后又再次执行第1号Pass,处理了点光源的光照

还可以调整光源的Render Mode设置或者Project Settings里逐像素光源的最大数量,根据 Frame Debug 面板信息观察渲染过程的变化,就不一一截图了。
&spm=1001.2101.3001.5002&articleId=139050455&d=1&t=3&u=ece43bddd02f42bf94f81c6f91699ce6)
5685

被折叠的 条评论
为什么被折叠?



