shader篇-阴影
标签(空格分隔): shader
- shader篇-阴影
- 简介
- 投射阴影实现
- 接受阴影
- Unity统一管理光照衰减和阴影
- 透明度物体的阴影
简介
阴影的实现需要2个过程:
一、接受阴影的物体,需要在shader中采样阴影映射纹理,并把采样后结果与光照结果相乘获得阴影效果。
二、一个物体如果想投射阴影,就需要将该物体加入光照映射纹理的计算中,以便被采样时可以获取该物体相关信息。Unity Shader中该过程是又LightgMode 为ShadeowCaster的Pass实现。
投射阴影实现
Unity中,是否让物体投射或接受阴影,是由mesh renderer组件的Cast Shadows和Receive Shadows的设置来实现
不过即便没有设置,如果fallback能调用含LightMode 为ShadowCaster的Pass时,一样能投射阴影,比如说 Fallback “Specualr”,通过回调内置的Specular可以间接调用含有LightMode 为ShadowCaster的Pass。
接受阴影
编写接受阴影的shader时需要#include “AutoLight.cginc”,计算阴影的宏都是来自这个文件。
配置
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
注意:为保证内置宏运行正确,a2v结构体顶点坐标变量名必须
顶点着色器
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// 计算阴影值
TRANSFER_SHADOW(o);
return o;
}
之后,我们使用内置宏SHADOW_ATTENUATION在片元着色器中计算阴影值
Unity统一管理光照衰减和阴影
unity内置了一个宏计算光照衰减和阴影UNITY_LIGHT_ATTENUATION
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4((diffuse + specular) * atten, 1.0);
}
这种内置宏可以简化我们某个工作
透明度物体的阴影
对于大多数不透明物体,fallback设为VertexLit就可以得到正确的阴影,但不透明物体就要小心了。
配置
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
SubShader {
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass {
Tags { "LightMode"="ForwardBase" }
Cull Off
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
SHADOW_COORDS(3)
};
注意:SHADOW_COORDS的参数是3,意味着它将占有第四个插值寄存器TEXCOORD3
FallBack "Transparent/Cutout/VertexLit"
}
还有fallback,这一次不能间接调用Vertexlit了,我们需要直接调用Transparent/Cutout/VertexLit,只有这样才能调用含透明度测试的shader
注意:Unity内置的半透明shader都不会添加阴影,因为半透明物体关闭了深度写入,必须处理好渲染顺序,否则阴影处理会非常复杂