阴影投射的两个要点:
1、接收其他物体阴影的投射
点A在相机的深度值设为 Z1 ,点A在阴影纹理的深度值为Z2 ,如果Z1 >Z2 ,则该点处于相机的可见范围并且处于阴影之中,即该点看得到阴影
2、向其他物体投射阴影
Unity会在当前物体的Shader中检查:①是否有{“LightMode” = “ShadowCast”,有的话则可以投射阴影。②没有ShadowCast则在Fallback指定的shader中继续寻找。 如果上述两种情况均没有则不向其他物体投射阴影。
代码1: 这段代码是之前 写漫反射时候的代码,唯一不同的是在末尾加了 Fallback"Specular"
Shader "ShaderPath/ShadowShader"//shader的选择路径
{
Properties//该Shader可控的属性
{
_DiffuseColor ("DiffuseColor",Color) = (1,1,1,0)//漫反射的主色调
}
SubShader//子着色器
{
// 以下均为默认值,详情可查看以往博客
Cull Back ZWrite On ZTest LEqual
Pass
{
Tags {"LightMode" = "ForwardBase"}
//与ENDCG相照应,将CG代码包裹
CGPROGRAM
//顶点函数定义
#pragma vertex vert
//片元函数定义
#pragma fragment frag
//引入必要的Unity库 如下面的UnityObjectToClipPos 就是库中函数
#include "UnityCG.cginc"
//引入光照库 _LightColor0需要用
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;//每个顶点结构体必须有的
float3 normal : NORMAL;//定义法线
};
struct v2f
{
fixed3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float4 pos : SV_POSITION;//每个片元结构体必须有的
};
fixed4 _DiffuseColor;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);//把顶点从模型空间转换到剪裁空间
o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));//把法线从模型空间转换到世界空间
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;//模型坐标转到世界坐标
return o;
}
fixed4 frag (v2f i) : SV_Target//返回一个RGBA到模型上
{
fixed3 lightDir = UnityWorldSpaceLightDir(i.worldPos);//获取光源在世界空间下的方向(光源发射出来的方向)
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor * (1+dot(lightDir,i.worldNormal))/2;
return fixed4(diffuse,1);
}
ENDCG
}
}
Fallback"Specular"
}
运行上面代码可以观察到如下结果:我们此时用的是Plane模型(这个是背面剪裁的,也就是从Plane的背面看过去是完全不显示的,这句话后面有用)
可看到产生了阴影,因为Specluar里的Fallback “Legacy Shaders/VertexLit” 面含有ShadowCast的Pass,代码如下,PS如果去掉Fallback阴影就消失啦!
Pass {
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_shadowcaster
#pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
#include "UnityCG.cginc"
struct v2f {
V2F_SHADOW_CASTER;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert( appdata_base v )
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
float4 frag( v2f i ) : SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
以上代码看不懂没关系,因为有很多我们没用过的宏,只要知道这个可以实现阴影即可
官方原Shader代码的下载地址,就是内置Shader源码 下图是Plane正面是朝向光源方向的,此时是会产生阴影的
当我们把Plane反向,会看到阴影消失了,因为该物体是背面剪裁的,Unity默认的投影方式(Cast Shadow)是On,因此被剪裁的部分是没有阴影的,此时只要根据下面图将Cast Shadow设为Two Sided就可以看到阴影
让物体自身能接受阴影
Shader "ShaderPath/ShadowShader"//shader的选择路径
{
Properties//该Shader可控的属性
{
_DiffuseColor ("DiffuseColor",Color) = (1,1,1,0)//漫反射的主色调
}
SubShader//子着色器
{
// 以下均为默认值,详情可查看以往博客
Cull Back ZWrite On ZTest LEqual
Pass
{
Tags {"LightMode" = "ForwardBase" }
//与ENDCG相照应,将CG代码包裹
CGPROGRAM
//顶点函数定义
#pragma vertex vert
//片元函数定义
#pragma fragment frag
// 必须一定要引入这句编译 不然得不到正确的光照信息,会导致阴影不出现
#pragma multi_compile_fwdbase
//引入必要的Unity库 如下面的UnityObjectToClipPos 就是库中函数
#include "UnityCG.cginc"
//引入光照库 _LightColor0需要用
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;//每个顶点结构体必须有的
float3 normal : NORMAL;//定义法线
};
struct v2f
{
fixed3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2) //因为上面用到的最后一个寄存器是TEXCOORD1,因此这里填2,此处是声明一个对阴影纹理采样的坐标
float4 pos : SV_POSITION;//每个片元结构体必须有的
};
fixed4 _DiffuseColor;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);//把顶点从模型空间转换到剪裁空间
o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));//把法线从模型空间转换到世界空间
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;//模型坐标转到世界坐标
TRANSFER_SHADOW(o); //计算在顶点中声明的阴影纹理的坐标
return o;
}
fixed4 frag (v2f i) : SV_Target//返回一个RGBA到模型上
{
fixed3 lightDir = UnityWorldSpaceLightDir(i.worldPos);//获取光源在世界空间下的方向(光源发射出来的方向)
fixed shadow = SHADOW_ATTENUATION(i);//计算阴影值
fixed3 diffuse = _LightColor0.rgb * _DiffuseColor * (1+dot(lightDir,i.worldNormal))/2;
return fixed4(diffuse *shadow,1);
}
ENDCG
}
}
Fallback "Specular"
}
**关键代码有四句:**并且都已经在代码中做了注释
1、#pragma multi_compile_fwdbase //确保引入正确的光照信息
2、SHADOW_COORDS(2) //声明阴影纹理的采样坐标
3、TRANSFER_SHADOW(o); //计算阴影纹理的坐标
4、fixed shadow = SHADOW_ATTENUATION(i); //计算阴影值
上述过程其实和对一张图片进行纹理采样是一样的,且听我慢慢说来
2~4步骤可以跟下面三步一一对应,只是此时的阴影纹理是Unity自己生成的不是我们赋值的
1、float2 uv : TEXCOORD3;//用于存储纹理信息
2、o.uv = TRANSFORM_TEX(v.texcoord, _MainTex) ;//计算坐标值
3、fixed3 albedo = tex2D(_MainTex,i.uv).rgb //计算采样后的颜色信息