US3S7L1——标准漫反射Shader
US3S7L1——标准漫反射Shader
标准漫反射Shader
目前我们已经完成了光源和阴影的主要知识点学习,包括多光源、阴影、光照衰减等等知识
已经可以在 Shader 中处理光和阴影相关的效果了,那么我们将结合所学的知识实现一个标准的漫反射 Shader
该 Shader 其实就是一个带有法线(世界空间中计算,因为全局效果的表现更准确,详见:US3S2L7——世界空间下计算法线纹理贴图)
的基于 Phong 光照模型(去掉高光反射)的支持多光源和阴影的Shader
说是标准,其实就是一个常用 Shader 而已
制作常用漫反射Shader
-
新建一个 Shader,取名叫
BumpedDiffuse
(凹凸漫反射) -
复用 世界空间下计算法线纹理贴图的 中 Shader 代码,粘贴到新建文件中
具体代码,详见:US3S2L7——世界空间下计算法线纹理贴图
-
加入渲染标签
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
1
2
3
4
5SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }
Pass {/*...*/}
} -
删除高光反射相关代码
-
加入阴影、衰减相关代码
1
2
3
4
5
6
7
8
9struct v2f
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; // 使用float4同时存储主要纹理的uv(xy存储)和法线纹理的uv(zw存储)
float4 tangentToWorld0: TEXCOORD1; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第一行
float4 tangentToWorld1: TEXCOORD2; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第二行
float4 tangentToWorld2: TEXCOORD3; // 它用来存储变换矩阵和顶点相对于世界坐标的位置的第三行
SHADOW_COORDS(4) // 阴影坐标变量宏,因为前面有4个TEXCOORD变量,故这里参数填写4
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24v2f vert (appdata_full v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex); //计算裁剪空间下顶点坐标
// 分别计算主纹理和法线纹理的缩放平移
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// 得到世界空间下的顶点位置,用于之后在片元中计算视角方向(基于世界空间下)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
// 将模型空间下的法线、切线转换到世界空间下,并计算世界空间下的副切线
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
// 将切线空间到世界空间的转换矩阵,以及世界坐标存储到三个贴图变量内
data.tangentToWorld0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
data.tangentToWorld1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
data.tangentToWorld2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
// 阴影坐标转换宏
TRANSFER_SHADOW(data)
return data;
}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
29fixed4 frag (v2f i) : SV_Target
{
// 计算世界空间下光的方向和视角方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 worldPos = float3(i.tangentToWorld0.w, i.tangentToWorld1.w, i.tangentToWorld2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 通过法线纹理采样并解压缩,再乘以凹凸系数,得到切线空间下法线数据
float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 将切线空间下法线数据转换到世界空间下
float3 worldNormal = float3(
dot(i.tangentToWorld0.xyz, tangentNormal),
dot(i.tangentToWorld1.xyz, tangentNormal),
dot(i.tangentToWorld2.xyz, tangentNormal)
);
// 根据以上数据计算光照模型颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _MainColor.rgb; //反射率
// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, lightDir));
// 计算光照和阴影综合衰减值
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
// 最终颜色
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten;
return fixed4(color.rgb, 1);
} -
加入附加渲染通道,实现多光源效果
可以直接复用 Bass Pass 的内容,只是需要修改
LightMode
和编译指令,再加上线性混合,同时注意修改光源方向相关计算代码并删除环境光的叠加1
2
3
4
5
6
7
8
9
10
11
12
13// 附加渲染通道 Additional Pass
Pass
{
Tags { "LightMode" = "ForwardAdd" }
Blend One One
CGPROGRAM
//...
ENDCG
}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
33fixed4 frag (v2f i) : SV_Target
{
// 计算世界空间下光的方向和视角方向
float3 worldPos = float3(i.tangentToWorld0.w, i.tangentToWorld1.w, i.tangentToWorld2.w);
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); // 平行光的光源方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - worldPos); // 非平行光的光源方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 通过法线纹理采样并解压缩,再乘以凹凸系数,得到切线空间下法线数据
float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 将切线空间下法线数据转换到世界空间下
float3 worldNormal = float3(
dot(i.tangentToWorld0.xyz, tangentNormal),
dot(i.tangentToWorld1.xyz, tangentNormal),
dot(i.tangentToWorld2.xyz, tangentNormal)
);
// 根据以上数据计算光照模型颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy) * _Color.rgb; //反射率
// 漫反射光照计算:这里需要使用已经计算完毕的切线数据和光照方向
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, lightDir));
// 计算光照和阴影综合衰减值
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
// 最终颜色
fixed3 color = lambertColor * atten;
return fixed4(color.rgb, 1);
} -
加入
FallBack "Diffuse"
,实现阴影投射1
Fallback "Diffuse"
显示效果:
可以看到,这个 Shader 拥有法线带来的凹凸效果,拥有多光源的光照效果,同时可以接收并投射阴影
完整 Shader 代码如下:
1 | Shader "TeachShader/BumpedDiffuse" |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 文KRIFE齐的博客!