Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

多种光源

一、Shader 开发中常用的光源属性

Unity 中支持四种光源类型 Snipaste_2024-09-21_21-10-22.png

Shader 开发中常用的光源属性:

  • 位置
  • 方向
  • 颜色
  • 强度
  • 衰减

二、逐个分析各种光源在 Shader 当中的作用

1.Directional 方向光

  1. 充当角色:太阳
  2. 照射范围:无限制
  3. 特点:
    1. 不存在固定属性
    2. 重要属性只有方向(通过 Tramsform 的 Rotation 属性来改变方向)
    3. 它到场景中所有点的方向都一样
    4. 由于没有位置,因此没有衰减的概念,即光的强度不会随着距离而发生变化

2.点光源

  1. 充当角色:灯泡、蜡烛等
  2. 照射范围:有限
  3. 特点:
    1. 它的光是由一个点发出,向西面八方衍生的光
    2. 它的参数范围由 Range 来决定
    3. 它的位置由 Transform 中的 Position 决定
    4. 它存在衰减,随着物体离光源距离决定衰减强度

2.聚光灯

  1. 充当角色:探照灯、手电筒等
  2. 照射范围:有限
  3. 特点:
    1. 它的光的范围由空间中的一块锥形区域决定的
    2. 它的参数范围由 Range 和 SpotAngle 来决定
    3. 它的位置由 Transform 中的 Position 决定
    4. 它存在衰减,随着物体离聚光灯距离决定衰减强度 但是它相对点光源的计算公式更加复杂,因为需要该点是否在锥型范围内

3.Area 面光源

因为该光源需要 Bake(烘焙),所以在 Shader 中不讨论

三、如何 Shader 中判断光源类型

1.前向渲染路径中我们主要关注处理什么样的内容?

两个 Pass

  1. Base Pass(基础渲染通道,每个光源只会计算一次) 只需要处理一个逐像素平行光源(一般场景中最亮会自动赋值对应变量) 其他的中质量光源(逐顶点)、低质量光源(SH),Unity 会帮助我们处理
  2. Additional Pass(附加渲染通道) 除了最亮的平行光、 其他高质量的光源(可能是平行光、点光源、聚光灯)都会掉用一次该 Pass 进行计算

因此:我们一般需要在 Additional Pass 中判断光源的类型来区分处理部分逻辑

2.具体实现

Unity 中提供了三个重要的宏 用于在编译时根据条件判断 来包含或者排除不同代码块,实现条件编译

  • _DIRECTIONAL_LIGHT 平行光
  • _POINT_LIGHT 点光源
  • _SPOT_LIGHT LIGHT 聚光灯

通过宏配合 UnityShader 中的条件编译预处理指令 即可包含或者排除不同代码块

1
2
3
4
5
6
7
8
9
#if defined(_DIRECTIONAL_LIGHT)
//平行光逻辑
#elif defined(_POINT_LIGHT)
//点光源逻辑
#elif defined(_SPOT_LIGHT)
//聚光灯逻辑
#else
//其他逻辑
#endif

Unity 底层就会根据该条件编译指令 生成多个 Shader Variants(着色器变体) 这些 Variants 变体共享相同的核心代码 但是根据这些条件编译的选择包含不同的代码块

Shader Variants 着色器变体的概念: 通过条件编译指令(#if、#elif、#else、#endif) 根据不同的配置选项生成多个版本的 Shader 这些不同版本的 Shader 称为 Shader Variants。

4.实现步骤

  1. 实现 Blinn-Phong 光照 模型的逐片元光照模型
  2. 其中已经存在的 Pass 就是我们的 BasePass(基础渲染通道) 添加一个编译指令#Progma multi_compile_fwdbase 保证我们在 Shader 中使用光照衰减等光照变量可以被正确赋值,并帮助我们编译 BasePass 中的所有变体
  3. 复制 BasePass,基于它我们来修改成 AddtionalPass(附加渲染通道)
    1. LightMode 改为 ForwordAdd
    2. 加入混合指令 Blend One One 表示开启混合 线性减淡的效果
    3. 添加一个编译指令#Progma multi_compile_fwdadd,保证我们在 Shader 中使用光照衰减等光照变量可以被正确赋值,并帮助我们编译 AddtionalPass 中的所有变体
    4. 修改相关代码,基于不同的光照类型来进行计算衰减值
      1. 光的方向计算方向修改
      2. 基于不同的光照类型计算衰减值

5.示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
Shader "Unlit/ForwardLighting"
{
Properties
{
_MainColor("MainColor",Color) = (1,1,1,1)
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
_SpecularNum("SpecularNum",Range(0,20)) = 0.5
}
SubShader
{
//BasePass 基础渲染通道
Pass
{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//保证我们在 Shader 中使用光照衰减等光照变量可以被正确赋值,并帮助我们编译 BasePass 中的所有变体
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float3 normal:NORMAL;
float3 wPos:TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.normal = UnityObjectToWorldNormal(v.normal);
data.wPos = mul(UNITY_MATRIX_M,v.vertex).xyz;
return data;
}


float3 _MainColor;
fixed3 GetLambertFColor(in float3 normal)
{
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 color = _MainColor.rgb * _LightColor0.rgb * max(0,dot(lightDir,normal));
return color;
}


float3 _SpecularColor;
float _SpecularNum;
fixed3 GetBlinnSpecularColor(in float3 wPos,in float3 normal)
{
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz-wPos);

float3 lightDir = normalize(_WorldSpaceLightPos0).xyz;

float3 halfA = normalize(viewDir + lightDir);

fixed3 color = _LightColor0.rgb * _SpecularColor.rgb *pow(max(0,dot(halfA,normal)),_SpecularNum);
return color;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 lambertColor = GetLambertFColor(i.normal);
fixed3 SpecularColor = GetBlinnSpecularColor(i.wPos,i.normal);

//衰减值
fixed attenuation = 1;
//衰减值 会和 漫反射颜射+高光反射颜色 后 再进行乘法运算
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + SpecularColor)*attenuation;
return fixed4(color.rgb , 1);
}
ENDCG
}
//AddionalPass 附加渲染通道
Pass
{
Tags {"LightMode"="ForwardAdd"}
//使用 线性减淡的效果 进行 光照颜色混合
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//保证我们在 Shader 中使用光照衰减等光照变量可以被正确赋值,并帮助我们编译 AddtionalPass 中的所有变体
#pragma multi_compile_fwdadd
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float3 normal:NORMAL;
float3 wPos:TEXCOORD0;
};
float3 _MainColor;
float3 _SpecularColor;
float _SpecularNum;
v2f vert (appdata_base v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.normal = UnityObjectToWorldNormal(v.normal);
data.wPos = mul(UNITY_MATRIX_M,v.vertex).xyz;
return data;
}

fixed4 frag (v2f i) : SV_Target
{
//兰伯特漫反射
//漫反射颜色 = 光颜色 * 属性中的漫反射颜色 * max(0,dot(世界坐标系下的法线,世界坐标系下的光的方向));
//获取法线
fixed3 worldNormal = normalize(i.normal);
//获取光的方向
#if defined(_DIRECTIONAL_LIGHT)
//平行光 的方向就是 它的位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
//聚光灯和点光源的方向 光的位置 - 顶点位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.wPos.xyz);
#endif
fixed3 diffuse = _LightColor0.rgb * _MainColor.rgb * max(0,dot(worldNormal,worldLightDir));

//BlinnPhong模型
//高光反射颜色 = 光源色 * 属性中的高光颜色 * pow(max(0,dot(世界坐标下的法线,世界坐标系下的半角向量)),光泽度)
//视角方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.wPos.xyz);
//半角方向向量
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(worldNormal,halfDir)),_SpecularNum);

//衰减值
#if defined(_Direction_LIGHT)
fixed atten = 1;
#elif defined(_Point_LIGHT)
//将世界坐标系下的顶点 转到 光源坐标系下
float3 lightCoord = mul(unity_worldToLight,float4(i.wPos,1)).xyz;
//利用这坐标得到距离的平方 再在光源纹理中映射得到衰减值
fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
#elif defined(_SPOT_LIGHT)
//将世界坐标系下的顶点 转到 光源坐标系下 聚光灯需要w进行计算
float4 lightCoord = mul(unity_worldToLight,float4(i.wPos,1));
fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
tex2D(_LIghtTexture0,lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
tex2D(_LightTextureB0,dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;// 距离的平方采样
#else
fixed atten = 1;
#endif

//在附加渲染通道中 不需要在加上环境光颜色了 因为它只需要计算一次 在BasePass中计算即可
return fixed4((diffuse + specular) * atten, 1);
}
ENDCG
}
}
}

评论