20.标准光照着色器-标准漫反射
20.1 知识点
什么是标准漫反射 Shader
学习了光源和阴影的主要知识点后(包括多光源、阴影、光照衰减等),已经可以在 Shader 中处理光和阴影的效果了。
现在结合所学知识,可以实现一个标准的漫反射 Shader:
- 基于 Phong 光照模型(去掉高光反射)
- 带有法线(通过世界空间计算,全局效果更准确)
- 支持多光源和阴影
所谓“标准”,其实只是一个常用 Shader 而已。
制作常用漫反射 Shader
主要步骤
- 新建一个 Shader 文件,命名为
Lesson20_BumpedDiffuse
。 - 从 Shader开发入门中 的
Lesson30_Bump_Texture_World_High_Performance.shader
,粘贴到新建文件中。 - 添加渲染标签
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
,设置渲染类型为不透明,渲染队列为几何队列。 - 删除高光反射相关代码。
- 添加阴影和衰减相关代码。
- 添加附加渲染通道。
- 添加
FallBack "Diffuse"
。
新建shader,取名为Lesson20_BumpedDiffuse凹凸漫反射
Shader "Unlit/Lesson20_BumpedDiffuse"
{
}
复制 Shader开发入门中 Lesson30_Bump_Texture_World_High_Performance.shader,粘贴到新建文件中
Shader "Unlit/Lesson20_BumpedDiffuse"
{
Properties
{
_MainColor("MainColor", Color) = (1,1,1,1)// 漫反射颜色
_MainTex("MainTex", 2D) = ""{}// 单张纹理
_BumpMap("BumpMap", 2D) = ""{}// 法线纹理
_BumpScale("BumpScale", Range(0,1)) = 1// 凹凸程度
_SpecularColor("SpecularColor", Color) = (1,1,1,1)// 高光反射颜色
_SpecularNum("SpecularNum", Range(0,20)) = 18// 光泽度
}
SubShader
{
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度
struct v2f
{
//裁剪空间下坐标
float4 pos:SV_POSITION;
//纹理uv
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//float2 uvTex:TEXCOORD0;//颜色纹理
//float2 uvBump:TEXCOORD1;//法线纹理
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0; //纹理变量
//顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
//float3 worldPos:TEXCOORD1;
//切线 到 世界空间的 变换矩阵
//float3x3 rotation:TEXCOORD2;
//代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
float4 TtoW0:TEXCOORD1;
float4 TtoW1:TEXCOORD2;
float4 TtoW2:TEXCOORD3;
};
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
//v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
float3 worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
//把模型空间下的法线、切线转换到世界空间下
float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
//cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;
//这个就是我们 切线空间到世界空间的 转换矩阵
// | | |
// 切线 副切线 法线
// | | |
//v2f.rotation = float3x3( worldTangent.x, worldBinormal.x, worldNormal.x,
// worldTangent.y, worldBinormal.y, worldNormal.y,
// worldTangent.z, worldBinormal.z, worldNormal.z);
v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//世界空间下光的方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//世界空间下视角方向
// fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//纹理采样函数tex2D
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//把计算完毕后的切线空间下的法线转换到世界空间下
// float3 worldNormal = mul(v2f.rotation, tangentNormal);
//以下两种写法等价 但是可以一步到位
//float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
//float3 worldNormal = mul(rotation, tangentNormal);
//本质 就是在进行矩阵运算
float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
dot(v2f.TtoW2.xyz, tangentNormal));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));
//半角向量 = 视角方向 + 光的方向
float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
//高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
max(0, dot(worldNormal, halfA)), _SpecularNum);
//布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
return fixed4(color.rgb, 1);
}
ENDCG
}
}
}
加入渲染标签Tags { “RenderType”=”Opaque” “Queue”=”Geometry”},渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
Shader "Unlit/Lesson20_BumpedDiffuse"
{
Properties
{
//...
}
SubShader
{
// 加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
// 渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
Tags
{
"RenderType"="Opaque" "Queue"="Geometry"
}
Pass
{
//...
}
}
}
删除高光反射相关属性,片元函数还有用到_SpecularColor和_SpecularNum的等一下删
Shader "Unlit/Lesson20_BumpedDiffuse"
{
Properties
{
//...
// _SpecularColor("SpecularColor", Color) = (1,1,1,1)// 高光反射颜色
// _SpecularNum("SpecularNum", Range(0,20)) = 18// 光泽度
}
SubShader
{
// 加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
// 渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
Tags
{
"RenderType"="Opaque" "Queue"="Geometry"
}
Pass
{
//...
// float4 _SpecularColor; //高光颜色
// fixed _SpecularNum; //光泽度
//...
}
}
}
添加multi_compile_fwdbase指令和AutoLight内置文件
#pragma vertex vert
#pragma fragment frag
// 用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
//使用阴影宏内置文件
#include "AutoLight.cginc"
v2f结构体添加阴影坐标宏SHADOW_COORDS
struct v2f
{
//裁剪空间下坐标
float4 pos:SV_POSITION;
//纹理uv
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//float2 uvTex:TEXCOORD0;//颜色纹理
//float2 uvBump:TEXCOORD1;//法线纹理
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0; //纹理变量
//顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
//float3 worldPos:TEXCOORD1;
//切线 到 世界空间的 变换矩阵
//float3x3 rotation:TEXCOORD2;
//代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
float4 TtoW0:TEXCOORD1;
float4 TtoW1:TEXCOORD2;
float4 TtoW2:TEXCOORD3;
//阴影坐标宏
//n为下一个可用的插值寄存器的索引值(结构体前面有几个TEXCOORD就填几)
//比如这里前面有四个
SHADOW_COORDS(4)
};
顶点函数添加坐标转换宏TRANSFER_SHADOW
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
//v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
float3 worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
//把模型空间下的法线、切线转换到世界空间下
float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
//cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;
//这个就是我们 切线空间到世界空间的 转换矩阵
// | | |
// 切线 副切线 法线
// | | |
//v2f.rotation = float3x3( worldTangent.x, worldBinormal.x, worldNormal.x,
// worldTangent.y, worldBinormal.y, worldNormal.y,
// worldTangent.z, worldBinormal.z, worldNormal.z);
v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
//坐标转换宏
TRANSFER_SHADOW(v2f)
return v2f;
}
片元函数删除高光相关,加上计算光照衰减和阴影衰减的宏UNITY_LIGHT_ATTENUATION,最后计算时乘上衰减值
fixed4 frag(v2f v2f) : SV_Target
{
//世界空间下光的方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//世界空间下视角方向
// fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//纹理采样函数tex2D
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//把计算完毕后的切线空间下的法线转换到世界空间下
// float3 worldNormal = mul(v2f.rotation, tangentNormal);
//以下两种写法等价 但是可以一步到位
//float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
//float3 worldNormal = mul(rotation, tangentNormal);
//本质 就是在进行矩阵运算
float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
dot(v2f.TtoW2.xyz, tangentNormal));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));
//不使用高光了
// //半角向量 = 视角方向 + 光的方向
// float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
//
// //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
// fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
// max(0, dot(worldNormal, halfA)), _SpecularNum);
//计算光照衰减和阴影衰减的宏
UNITY_LIGHT_ATTENUATION(atten, v2f, worldPos);
// //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
// fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
//布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色*衰减值
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor*atten;
return fixed4(color.rgb, 1);
}
加入附加渲染通道,复制基础渲染通道。把LightMode改成ForwardAdd。添加Blend One One让线性减淡的效果 进行 光照颜色混合。把预编译指令改成multi_compile_fwdadd_fullshadows让Unity生成多个包括支持和不支持阴影的Shader变体,从而为额外的逐像素光源计算阴影,并传递给Shader。之前需要我们手动考虑不同光源的情况,现在学了UNITY_LIGHT_ATTENUATION宏会在内部帮我们计算。
Shader "Unlit/Lesson20_BumpedDiffuse"
{
Properties
{
//...
}
SubShader
{
// 加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
// 渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
Tags
{
"RenderType"="Opaque" "Queue"="Geometry"
}
//Base Pass
Pass
{
//...
}
//Additional Pass
Pass
{
//设置我们的光照模式 ForwardAdd这种向前渲染模式 主要是用来处理 附加光照渲染的
Tags
{
"LightMode"="ForwardAdd"
}
//线性减淡的效果 进行 光照颜色混合
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//这个编译指令让Unity生成多个包括支持和不支持阴影的Shader变体
//从而为额外的逐像素光源计算阴影,并传递给Shader了
#pragma multi_compile_fwdadd_fullshadows
#include "UnityCG.cginc"
#include "Lighting.cginc"
//使用阴影宏内置文件
#include "AutoLight.cginc"
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
// float4 _SpecularColor; //高光颜色
// fixed _SpecularNum; //光泽度
struct v2f
{
//裁剪空间下坐标
float4 pos:SV_POSITION;
//纹理uv
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//float2 uvTex:TEXCOORD0;//颜色纹理
//float2 uvBump:TEXCOORD1;//法线纹理
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0; //纹理变量
//顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
//float3 worldPos:TEXCOORD1;
//切线 到 世界空间的 变换矩阵
//float3x3 rotation:TEXCOORD2;
//代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
float4 TtoW0:TEXCOORD1;
float4 TtoW1:TEXCOORD2;
float4 TtoW2:TEXCOORD3;
//阴影坐标宏
//n为下一个可用的插值寄存器的索引值(结构体前面有几个TEXCOORD就填几)
//比如这里前面有四个
SHADOW_COORDS(4)
};
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
//v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
float3 worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
//把模型空间下的法线、切线转换到世界空间下
float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
//cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;
//这个就是我们 切线空间到世界空间的 转换矩阵
// | | |
// 切线 副切线 法线
// | | |
//v2f.rotation = float3x3( worldTangent.x, worldBinormal.x, worldNormal.x,
// worldTangent.y, worldBinormal.y, worldNormal.y,
// worldTangent.z, worldBinormal.z, worldNormal.z);
v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
//坐标转换宏
TRANSFER_SHADOW(v2f)
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//世界空间下光的方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//世界空间下视角方向
// fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//纹理采样函数tex2D
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//把计算完毕后的切线空间下的法线转换到世界空间下
// float3 worldNormal = mul(v2f.rotation, tangentNormal);
//以下两种写法等价 但是可以一步到位
//float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
//float3 worldNormal = mul(rotation, tangentNormal);
//本质 就是在进行矩阵运算
float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
dot(v2f.TtoW2.xyz, tangentNormal));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));
//不使用高光了
// //半角向量 = 视角方向 + 光的方向
// float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
//
// //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
// fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
// max(0, dot(worldNormal, halfA)), _SpecularNum);
//计算光照衰减和阴影衰减的宏
UNITY_LIGHT_ATTENUATION(atten, v2f, worldPos);
// //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
// fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
//布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色*衰减值
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor*atten;
return fixed4(color.rgb, 1);
}
ENDCG
}
}
}
注意附加渲染通道的参数名要改成v,否则可能出现Shader error in ‘Unlit/Lesson20_BumpedDiffuse’: undeclared identifier ‘v’ at line 290 (on d3d11)这样的报错。为了统一期间基础通道也改一下参数名为v
v2f vert(appdata_full v)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(v.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
//v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
//把模型空间下的法线、切线转换到世界空间下
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
//cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
//这个就是我们 切线空间到世界空间的 转换矩阵
// | | |
// 切线 副切线 法线
// | | |
//v2f.rotation = float3x3( worldTangent.x, worldBinormal.x, worldNormal.x,
// worldTangent.y, worldBinormal.y, worldNormal.y,
// worldTangent.z, worldBinormal.z, worldNormal.z);
v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
//坐标转换宏
TRANSFER_SHADOW(v2f)
return v2f;
}
加入FallBac “Diffuse”,这Unity自带的漫反射Shader,里面实现了对应投射阴影的Pass
//Unity自带的漫反射Shader,里面实现了对应投射阴影的Pass
Fallback "Diffuse"
创建材质,可以看到可以接收和投放多光源阴影。我们成功实现了常见的带法线、漫反射、有阴影且能收到多光源影响的常用shader
20.2 知识点代码
Lesson20_标准光照着色器_标准漫反射.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson20_标准光照着色器_标准漫反射 : MonoBehaviour
{
void Start()
{
#region 知识点一 什么是标准漫反射Shader?
//目前我们已经完成了光源和阴影的主要知识点学习
//我们学习了多光源、阴影、光照衰减等等知识
//已经可以在Shader中处理光和阴影相关的效果了
//那么我们将结合所学的知识实现一个标准的漫反射Shader
//该Shader其实就是一个
//带有法线(世界空间中计算-全局效果的表现更准确)的基于Phong光照模型(去掉高光反射)的
//支持多光源和阴影的Shader
//说是标准,其实就是一个常用Shader而已
#endregion
#region 知识点二 制作常用漫反射Shader
//1.新建一个Shader,取名叫Lesson20_BumpedDiffuse(凹凸漫反射)
//2.复制 Shader开发入门中 Lesson30_Bump_Texture_World_High_Performance.shader,粘贴到新建文件中
//3.加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
// 渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
//4.删除高光反射相关代码
//5.加入阴影、衰减相关代码
//6.加入附加渲染通道
//7.加入FallBac "Diffuse"
#endregion
}
}
Lesson20_BumpedDiffuse.shader
Shader "Unlit/Lesson20_BumpedDiffuse"
{
Properties
{
_MainColor("MainColor", Color) = (1,1,1,1)// 漫反射颜色
_MainTex("MainTex", 2D) = ""{}// 单张纹理
_BumpMap("BumpMap", 2D) = ""{}// 法线纹理
_BumpScale("BumpScale", Range(0,1)) = 1// 凹凸程度
// _SpecularColor("SpecularColor", Color) = (1,1,1,1)// 高光反射颜色
// _SpecularNum("SpecularNum", Range(0,20)) = 18// 光泽度
}
SubShader
{
// 加入渲染标签Tags { "RenderType"="Opaque" "Queue"="Geometry"}
// 渲染类型设置为不透明的、渲染队列设置为几何队列(不透明的几何体通常使用该队列)
Tags
{
"RenderType"="Opaque" "Queue"="Geometry"
}
//Base Pass
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
//使用阴影宏内置文件
#include "AutoLight.cginc"
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
// float4 _SpecularColor; //高光颜色
// fixed _SpecularNum; //光泽度
struct v2f
{
//裁剪空间下坐标
float4 pos:SV_POSITION;
//纹理uv
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//float2 uvTex:TEXCOORD0;//颜色纹理
//float2 uvBump:TEXCOORD1;//法线纹理
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0; //纹理变量
//顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
//float3 worldPos:TEXCOORD1;
//切线 到 世界空间的 变换矩阵
//float3x3 rotation:TEXCOORD2;
//代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
float4 TtoW0:TEXCOORD1;
float4 TtoW1:TEXCOORD2;
float4 TtoW2:TEXCOORD3;
//阴影坐标宏
//n为下一个可用的插值寄存器的索引值(结构体前面有几个TEXCOORD就填几)
//比如这里前面有四个
SHADOW_COORDS(4)
};
v2f vert(appdata_full v)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(v.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
//v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
//把模型空间下的法线、切线转换到世界空间下
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
//cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
//这个就是我们 切线空间到世界空间的 转换矩阵
// | | |
// 切线 副切线 法线
// | | |
//v2f.rotation = float3x3( worldTangent.x, worldBinormal.x, worldNormal.x,
// worldTangent.y, worldBinormal.y, worldNormal.y,
// worldTangent.z, worldBinormal.z, worldNormal.z);
v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
//坐标转换宏
TRANSFER_SHADOW(v2f)
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//世界空间下光的方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//世界空间下视角方向
// fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//纹理采样函数tex2D
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//把计算完毕后的切线空间下的法线转换到世界空间下
// float3 worldNormal = mul(v2f.rotation, tangentNormal);
//以下两种写法等价 但是可以一步到位
//float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
//float3 worldNormal = mul(rotation, tangentNormal);
//本质 就是在进行矩阵运算
float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
dot(v2f.TtoW2.xyz, tangentNormal));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));
//不使用高光了
// //半角向量 = 视角方向 + 光的方向
// float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
//
// //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
// fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
// max(0, dot(worldNormal, halfA)), _SpecularNum);
//计算光照衰减和阴影衰减的宏
UNITY_LIGHT_ATTENUATION(atten, v2f, worldPos);
// //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
// fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
//布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色*衰减值
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten;
return fixed4(color.rgb, 1);
}
ENDCG
}
//Additional Pass
Pass
{
//设置我们的光照模式 ForwardAdd这种向前渲染模式 主要是用来处理 附加光照渲染的
Tags
{
"LightMode"="ForwardAdd"
}
//线性减淡的效果 进行 光照颜色混合
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//这个编译指令让Unity生成多个包括支持和不支持阴影的Shader变体
//从而为额外的逐像素光源计算阴影,并传递给Shader了
#pragma multi_compile_fwdadd_fullshadows
#include "UnityCG.cginc"
#include "Lighting.cginc"
//使用阴影宏内置文件
#include "AutoLight.cginc"
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
// float4 _SpecularColor; //高光颜色
// fixed _SpecularNum; //光泽度
struct v2f
{
//裁剪空间下坐标
float4 pos:SV_POSITION;
//纹理uv
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//float2 uvTex:TEXCOORD0;//颜色纹理
//float2 uvBump:TEXCOORD1;//法线纹理
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0; //纹理变量
//顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
//float3 worldPos:TEXCOORD1;
//切线 到 世界空间的 变换矩阵
//float3x3 rotation:TEXCOORD2;
//代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
float4 TtoW0:TEXCOORD1;
float4 TtoW1:TEXCOORD2;
float4 TtoW2:TEXCOORD3;
//阴影坐标宏
//n为下一个可用的插值寄存器的索引值(结构体前面有几个TEXCOORD就填几)
//比如这里前面有四个
SHADOW_COORDS(4)
};
v2f vert(appdata_full v)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(v.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
//v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
//把模型空间下的法线、切线转换到世界空间下
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
//cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * v.tangent.w;
//这个就是我们 切线空间到世界空间的 转换矩阵
// | | |
// 切线 副切线 法线
// | | |
//v2f.rotation = float3x3( worldTangent.x, worldBinormal.x, worldNormal.x,
// worldTangent.y, worldBinormal.y, worldNormal.y,
// worldTangent.z, worldBinormal.z, worldNormal.z);
v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
//坐标转换宏
TRANSFER_SHADOW(v2f)
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//世界空间下光的方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//世界空间下视角方向
// fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//纹理采样函数tex2D
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//把计算完毕后的切线空间下的法线转换到世界空间下
// float3 worldNormal = mul(v2f.rotation, tangentNormal);
//以下两种写法等价 但是可以一步到位
//float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
//float3 worldNormal = mul(rotation, tangentNormal);
//本质 就是在进行矩阵运算
float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
dot(v2f.TtoW2.xyz, tangentNormal));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));
//不使用高光了
// //半角向量 = 视角方向 + 光的方向
// float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
//
// //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
// fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
// max(0, dot(worldNormal, halfA)), _SpecularNum);
//计算光照衰减和阴影衰减的宏
UNITY_LIGHT_ATTENUATION(atten, v2f, worldPos);
// //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
// fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
//布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色*衰减值
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten;
return fixed4(color.rgb, 1);
}
ENDCG
}
}
//Unity自带的漫反射Shader,里面实现了对应投射阴影的Pass
Fallback "Diffuse"
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com