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

不透明物体的阴影

一、物体没有阴影的原因

1.不投射阴影

该物体材质的 Shader 中没有 LightMode 为 ShaderCaster 的 Pass,无法进行阴影映射纹理的计算

2.不接收阴影的原因

该物体材质的 Shader 中没有对阴影映射相关纹理采样,没有进行阴影相关颜色的计算

二、物体投射阴影

1.主要思路

Unity 会寻找 LightMode 为 ShaderCaster 的 Pass 来进行处理,如果该 Shader 没有该 Pass,会在它 FallBack 指定的 Shader 中寻找,直到找到为止 注意: 由于投射阴影相关的代码较为通用 因此建议大家不用自己去实现相关 Shader 代码 直接通过 FallBack "Specular" 调用 Unity 中默认 Shader 中的相关代码即可

2.三个关键宏

这三个宏存储于#include "UnityCG.cginc"

  1. V2F_ShADOW_CASTER 顶点到片元着色器阴影投射结构体数据宏 这个宏定义了一些标准的成员变量 这些变量用于在阴影投射路径中传递顶点数据到片元着色器 主要在结构体中使用
  2. TRANSFAR_SHADOW_CASTER_NORMALOFFSET 转移阴影投射器法线偏移宏 用于在顶点着色器中计算和传递阴影投射所需的变量 主要做了:
    1. 将对象空间的顶点位置转换到裁剪空间中
    2. 考虑法线偏移,以减轻阴影失真问题,尤其指在处理子阴影时
    3. 传递顶点的投影空间位置,用于后续的阴影计算 主要在顶点着色器中使用
  3. SHADOW_CASTER_FRAGMENT 阴影投射片元宏 将深度值写入到阴影映射纹理中 主要在片元着色器中使用

3.示例代码

注意:

  1. 这里只提供对应的 Pass,其他 Pass 可以自行添加到 SubShader 中使用
  2. 需要使用编译指令#pragma multi_compile_shadowcaster
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
//该Pass主要是用于进行阴影投影 主要是用来计算阴影映射投影的
Pass
{
Tags{"LightMode" = "ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//该编译指令是告诉Unity编译器生成多个着色器变体
//用于支持不同类型的阴影(SM,SSM等)
//可以确保着色器能够在所有可能的阴影投射模式下正确渲染
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"//这个内置文件包含了关键的阴影计算相关的
struct v2f
{
//顶点到片元着色器 阴影投影结构体的数据宏
//这个宏定义了一些标准的成员变量,用于在阴影投射路径中传递顶点到片元着色器
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f data;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递投影所需的变量
//主要做了
//1. 将对象空间的顶点位置转化到裁剪空间
//2. 考虑法线偏移,以减轻阴影失帧问题,尤其是在处理自阴影时
//3. 传递顶点的投影空间位置,用于后续的阴影计算
TRANSFER_SHADOW_CASTER_NORMALOFFSET(data);
return data;
}
float4 frag(v2f i):SV_TARGET
{
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}

三、物体接收阴影

1.主要思路

  1. 在顶点着色器中进行顶点坐标转换(将顶点坐标 转换为 阴影映射纹理坐标)
  2. 在片元着色器中使用阴影映射纹理坐标在阴影映射纹理中进行采样 通过得到的深度值判断片元(像素)是否在阴影中,以计算出阴影衰减值
  3. 将采样结果参与到最终的颜色计算中

2.三个关键宏

1.)Base Pass 中

  1. 引用内置文件#include "AutoLight.cginc" 该内置文件中,有用于计算时所需要的三剑客

  2. 需要使用编译指令#pragma multi_compile_fwdbase

  3. SHADOW_COORDS————阴影坐标宏 该宏在 v2f 结构体中(即:顶点着色器返回值)中使用 本质上是声明了一个用于对阴影纹理进行采样的坐标 内部实际上是声明了一个名为_ShadowCoord 的阴影纹理 注意点: 使用时 SHADOW_COORDS(2) 传入参数 2 表示需要时下一个可用的插值寄存的索引值

  4. TRANSFER_SHADOW 该宏在顶点着色器中使用,传入对应的 v2f 结构体 该宏会在内部自己判断该使用的 SM、还是 SMM 阴影映射技术 最终目的就是将顶点进行坐标转化并存储到_ShadowCoord 阴影纹理坐标变量中 注意点:

    1. 该宏所使用的 顶点着色器中传入的结构体 该结构体中顶点的命名必须是 vertex
    2. 该宏所的 v2f 结构体 该结构体中顶点位置的命名必须是 pos
  5. SHADOW_ATTENUATION 该宏在片元着色器中调用,传入对应的 v2f 结构体对象 该宏会在内部利用 v2f 中的 阴影纹理坐标变量(ShadowCoord)对相关纹理进行采样 将采样得到的深度值进行比较,以计算出一个 fixed3 的阴影衰减值 我们只需要使用它返回的结果和 (漫反射+高光反射) 的结果相乘即可

3.实例代码

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
//BasePass 基础渲染通道
Pass
{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//保证我们在 Shader 中使用光照衰减等光照变量可以被正确赋值,并帮助我们编译 BasePass 中的所有变体
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
//该内置文件 其中有计算阴影会用到的三个内置宏
#include "AutoLight.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float3 normal:NORMAL;
float3 wPos:TEXCOORD0;
//阴影坐标宏 主要用于存储阴影纹理坐标
SHADOW_COORDS(2)
};
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;
//计算阴影映射纹理坐标 他会在内部去将顶点坐标转换 然后将其存入 v2f中的SHADOW_COORDS
TRANSFER_SHADOW(data);
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);

//得到阴影衰减值 之后与漫反射和高光反射颜色进行乘法运算即可
fixed3 shadowAtten = SHADOW_ATTENUATION(i);

//衰减值
fixed attenuation = 1;
//衰减值 会和 漫反射颜射+高光反射颜色 后 再进行乘法运算
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + SpecularColor)* attenuation* shadowAtten;
return fixed4(color.rgb , 1);
}
ENDCG
}

四、光照衰减和阴影的综合实现

1.主要原因

光照值衰减和接受阴影相关计算是类似的 都是通过计算出一个衰减值,参与到颜色计算 都是用(漫反射+高光反射)的结果乘以对应的衰减值

所以 Unity 中专门处理这两个的宏 来专门处理光照衰减和阴影衰减的计算

2.实现思路

1.)Base Pass 中

  1. 引用内置文件#include "AutoLight.cginc" 该内置文件中,有用于计算时所需要的三剑客

  2. 需要使用编译指令#pragma multi_compile_fwdbase

  3. SHADOW_COORDS————阴影坐标宏 该宏在 v2f 结构体中(即:顶点着色器返回值)中使用 本质上是声明了一个用于对阴影纹理进行采样的坐标 内部实际上是声明了一个名为_ShadowCoord 的阴影纹理 注意点: 使用时 SHADOW_COORDS(2) 传入参数 2 表示需要时下一个可用的插值寄存的索引值

  4. TRANSFER_SHADOW 该宏在顶点着色器中使用,传入对应的 v2f 结构体 该宏会在内部自己判断该使用的 SM、还是 SMM 阴影映射技术 最终目的就是将顶点进行坐标转化并存储到_ShadowCoord 阴影纹理坐标变量中 注意点:

    1. 该宏所使用的 顶点着色器中传入的结构体 该结构体中顶点的命名必须是 vertex
    2. 该宏所的 v2f 结构体 该结构体中顶点位置的命名必须是 pos
  5. UNITY_LIGHT_ATTENUATION 该宏在顶点着色器中使用,需要传入三个参数, 参数一:最终计算出的衰减值的变量名 参数二:片元着色器中的 v2f 的结构体对象 参数三:顶点相对世界坐标系的位置

2.)Addtional Pass 中

  1. 引用内置文件#include "AutoLight.cginc" 该内置文件中,有用于计算时所需要的三剑客

  2. 需要使用编译指令#pragma multi_compile_fwdbase_fullshadows

  3. SHADOW_COORDS————阴影坐标宏 该宏在 v2f 结构体中(即:顶点着色器返回值)中使用 本质上是声明了一个用于对阴影纹理进行采样的坐标 内部实际上是声明了一个名为_ShadowCoord 的阴影纹理 注意点: 使用时 SHADOW_COORDS(2) 传入参数 2 表示需要时下一个可用的插值寄存的索引值

  4. TRANSFER_SHADOW 该宏在顶点着色器中使用,传入对应的 v2f 结构体 该宏会在内部自己判断该使用的 SM、还是 SMM 阴影映射技术 最终目的就是将顶点进行坐标转化并存储到_ShadowCoord 阴影纹理坐标变量中 注意点:

    1. 该宏所使用的 顶点着色器中传入的结构体 该结构体中顶点的命名必须是 vertex
    2. 该宏所的 v2f 结构体 该结构体中顶点位置的命名必须是 pos
  5. UNITY_LIGHT_ATTENUATION 该宏在顶点着色器中使用,需要传入三个参数, 参数一:最终计算出的衰减值的变量名 参数二:片元着色器中的 v2f 的结构体对象 参数三:顶点相对世界坐标系的位置

3.实例代码

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
154
155
156
157
158
159
160
Shader "Unlit/ShadowAttenuation"
{
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"
//该内置文件 其中有计算阴影会用到的三个内置宏
#include "AutoLight.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float3 normal:NORMAL;
float3 wPos:TEXCOORD0;
//阴影坐标宏 主要用于存储阴影纹理坐标
SHADOW_COORDS(2)
};
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;
//计算阴影映射纹理坐标 他会在内部去将顶点坐标转换 然后将其存入 v2f中的SHADOW_COORDS
TRANSFER_SHADOW(data);
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);

//得到阴影衰减值 之后与漫反射和高光反射颜色进行乘法运算即可
//fixed3 shadowAtten = SHADOW_ATTENUATION(i);

//衰减值
//fixed attenuation = 1;

//利用灯光衰减和阴影衰减计算宏统一计算
UNITY_LIGHT_ATTENUATION(attenuation,i,i.wPos)

//衰减值 会和 漫反射颜射+高光反射颜色 后 再进行乘法运算
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
#pragma multi_compile_fwdadd_fullshadows

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float3 normal:NORMAL;
float3 wPos:TEXCOORD0;
//阴影坐标宏 主要用于存储阴影纹理坐标
SHADOW_COORDS(2)
};
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;
//计算阴影映射纹理坐标 他会在内部去将顶点坐标转换 然后将其存入 v2f中的SHADOW_COORDS
TRANSFER_SHADOW(data);
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);
//利用灯光衰减和阴影衰减计算宏统一计算
UNITY_LIGHT_ATTENUATION(attenuation,i,i.wPos)

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


}

Fallback "Specular"
}

评论