这个示例Shader支持全局光照(GI),光照处理(Base Pass & Additional Pass)、阴影处理(投射&接收)、Lightmap、雾效
// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'
Shader "Custom/ReceiveShadowsTest"
{
Properties
{
[Header(Main Texture)]
_MainTex ("Base (RGB)", 2D) = "white" {}
[Space]
//对纹理调色
_Color ("Color", Color) = (1,1,1,1)
//高光颜色
_Specular ("Specular", Color) = (1,1,1,1)
//控制高光亮度(值越小越亮)
//[PowerSlider(2)] //定义滑条的值以指数曲线变化,参数定义了底数
_Gloss ("Specular Power", Range(0, 30)) = 1
//用个Toggle开关来控制是否显示光照图
//[Toggle] //会隐式定义一个_USELIGHTMAP_ON开关 (大写属性名_ON)
[Toggle(USE_LIGHTMAP)] //显示指定开关名称
_UseLightmap ("Use Lightmap", Float) = 0
[Space(5)]
[Header(More Settings)]
//枚举
//渲染状态枚举只能使用UnityEngine.Rendering空间下的定义
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend Mode", Float) = 1
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend Mode", Float) = 1
[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 1
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 2
//需要在SubShader中声明
//#pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY
//keyword命名规则为: 大写属性名_枚举名
//[KeywordEnum(None, Add, Multiply)] _Overlay ("Overlay mode", Float) = 0
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
ZTest [_ZTest]
Cull [_Cull]
// 这个Pass同时处理未烘焙&已烘焙的物体显示
Pass {
//ForwardBase说明:
//ambient, main directional light, vertex/SH lights and lightmaps are applied
//只会接收环境光,最亮的平行光,逐顶点/球谐光以及光照图
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
//确保可以正常访问光照变量的值
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
//计算阴影所用到的宏都在Autolight.cginc文件中
#include "Autolight.cginc"
//shader_feature这个定义的keyword无法通过脚本进行控制,只能通过Shader面板控制
#pragma shader_feature USE_LIGHTMAP
//multi_compile这个定义的keyword可以通过脚本设置开启或关闭
//Material.EnableKeyword()、Material.DisableKeyword()
//Shader.EnableKeyword()、Shader.DisableKeyword()
//定义keyword,一个keyword会生成一个shader变种
//#pragma multi_compile __ USE_LIGHTMAP
//开启雾效
#pragma multi_compile_fog
#define USING_FOG (defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2))
struct appdata
{
//模型空间坐标
//TRANSFER_SHADOW宏用到了这个变量名
float3 vertex : POSITION;
//模型空间法线
float3 normal : NORMAL;
//主纹理uv坐标
float2 uv : TEXCOORD0;
//光照图uv坐标
float2 uv1 : TEXCOORD1;
};
struct v2f
{
//裁剪空间坐标
float4 pos : SV_POSITION;
//世界空间坐标
float3 worldPosition : TEXCOORD0;
//世界空间法线
float3 worldNormal : TEXCOORD1;
//为了节约寄存器,主纹理uv坐标放到xy上,光照图uv坐标放到zw上
float4 uv : TEXCOORD2;
//这个宏的作用很简单,就是声明一个用于对阴影纹理采样的坐标。
//需要注意的是,这个宏的参数需要是下一个可用的插值寄存器的索引值
SHADOW_COORDS(3)
//雾
#if USING_FOG
fixed fog : TEXCOORD5;
#endif
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
//模型坐标转裁剪空间坐标
o.pos = UnityObjectToClipPos(v.vertex);
//模型空间坐标转到世界空间坐标
o.worldPosition = mul(unity_ObjectToWorld, v.vertex).xyz;
//将法线从模型空间转到世界空间
o.worldNormal = UnityObjectToWorldNormal(v.normal);
//计算主纹理的uv坐标(考虑 缩放&平移)
o.uv.xy = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
#if USE_LIGHTMAP
//计算光照图的uv坐标(考虑 缩放&平移)
o.uv.zw = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
#else
//在顶点着色器返回之前添加这个宏.
//这个宏用于在顶点着色器中计算上一步中声明的阴影纹理坐标
TRANSFER_SHADOW(o);
#endif
//雾
#if USING_FOG
float3 eyePos = UnityObjectToViewPos(v.vertex);
float fogCoord = length(eyePos.xyz);
UNITY_CALC_FOG_FACTOR_RAW(fogCoord);
o.fog = saturate(unityFogFactor);
#endif
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col;
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//用纹理颜色作为漫反射颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color;
#if USE_LIGHTMAP
//光照图采样
half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv.zw);
fixed3 bakedColor = DecodeLightmap(bakedColorTex);
col = fixed4(ambient + albedo * bakedColor, 1.0);
#else
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPosition));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition));
fixed3 halfDir = normalize(worldLightDir + viewDir);
//_LightColor0记录了最亮的平行光颜色
//漫反射光
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
//高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
//对于Base Pass来说,它处理的逐像素光源类型一定是平行光。
//由于平行光可以认为是没有衰减的,因此这里我们直接令衰减值为1.0
//衰减值
fixed atten = 1.0;
//计算阴影值
//如果关闭了阴影,SHADOW_COORDS和TRANSFER_SHADOW没有任何作用
//如果关闭了阴影,SHADOW_ATTENUATION会返回1
fixed shadow = SHADOW_ATTENUATION(i);//对阴影纹理采样
col = fixed4(ambient + (diffuse + specular) * shadow * atten, 1.0);
#endif
//雾
#if USING_FOG
col.rgb = lerp(unity_FogColor.rgb, col.rgb, i.fog);
#endif
return col;
}
ENDCG
}
Pass {
//ForwardAdd说明: 与ForwardBase中的处理没多大区别,只是要判断下光源类型
//为场景中其他逐像素光源定义Additional Pass
Tags { "LightMode"="ForwardAdd" }
//与缓冲区中的ForwardBase处理结果混合
Blend One One
CGPROGRAM
//multi_compile_fwdadd指令可以保证我们在Additional Pass中访问到正确的光照变量
//获得正确的光照变量,不处理阴影
#pragma multi_compile_fwdadd
//multi_compile_fwdadd_fullshadows指令可以保证我们在Additional Pass中访问到正确的光照变量
//获得正确的光照变量,要处理阴影
//#pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "Autolight.cginc"
#pragma shader_feature USE_LIGHTMAP
struct appdata
{
//模型空间坐标
//TRANSFER_SHADOW宏用到了这个变量名
float3 vertex : POSITION;
//模型空间法线
float3 normal : NORMAL;
//主纹理uv坐标
float2 uv : TEXCOORD0;
};
struct v2f
{
//裁剪空间坐标
float4 pos : SV_POSITION;
//世界空间坐标
float3 worldPosition : TEXCOORD0;
//世界空间法线
float3 worldNormal : TEXCOORD1;
//主纹理uv坐标
float2 uv : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
//模型坐标转裁剪空间坐标
o.pos = UnityObjectToClipPos(v.vertex);
//模型空间坐标转到世界空间坐标
o.worldPosition = mul(unity_ObjectToWorld, v.vertex).xyz;
//将法线从模型空间转到世界空间
o.worldNormal = UnityObjectToWorldNormal(v.normal);
//计算主纹理的uv坐标(考虑 缩放&平移)
o.uv = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col;
#if USE_LIGHTMAP
col = fixed4(0, 0, 0, 0);
#else
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPosition));
//-----处理不同光源的方向-----//
#ifdef USING_DIRECTIONAL_LIGHT
//平行光
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
//点光源 或 聚光灯
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
#endif
fixed3 halfDir = normalize(worldLightDir + viewDir);
//-----处理不同光源的衰减-----//
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;//衰减值
#else
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPosition, 1)).xyz;
//尽管我们可以使用数学表达式来计算给定点相对于点光源或聚光灯的衰减,
//但这些计算往往涉及开根号、除法等计算量相对较大的操作,因此Unity选择了使用一张纹理作为
//查找表(Lookup Table, LUT),以在片元着色器中得到光源的衰减。我们首先得到光源空间下的坐标,
//然后使用该坐标对衰减纹理进行采样得到衰减值。
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
//用纹理颜色作为漫反射颜色
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color;
//漫反射光
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
//高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
col = fixed4((diffuse + specular) * atten, 1.0);
#endif
return col;
}
ENDCG
}
// 投射阴影Pass
// 没这个Pass物体无法向其他物体投射阴影
Pass
{
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
//开启深度写入
ZWrite On
//开启深度测试(<=)
ZTest LEqual
//关闭裁剪
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
//开启阴影投射
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f {
V2F_SHADOW_CASTER;
};
v2f vert( appdata_base v )
{
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
float4 frag( v2f i ) : SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
}
效果截图
官方内置编译命令说明