【Unity Shader】Shadow Caster、RenderType和_CameraDepthTexture
当我们制作某些屏幕特效时,需要取到屏幕的深度图或法线图,比如ssao,景深等,另外像是制作软粒子shader,体积雾等也需要取到深度图,以计算深度差等。unity提供了两个内置的纹理_CameraDepthTexture和_CameraDepthNormalsTexture,使用时只需声明sampler2D _CameraDepthTexture、sampler2D _CameraDepthNormalsTexture,并修改需要渲染出深度图的Camera的depthTextureMode为Depth或DepthNormals就行了。
其中_CameraDepthTexture存储的是屏幕深度信息,而_CameraDepthNormalsTexture存储的还包括法线信息。
而接下来我需要研究的问题是,究竟怎样的物体会被渲染到_CameraDepthTexture这张图里,以及会以什么样的方式渲染(Unity中_CameraDepthNormalsTexture的渲染已经在上一篇文章中解释过了,链接:http://blog.csdn.net/mobilebbki399/article/details/50512059)。
研究这个的意义,我们可以想象,如果写了一个shader,其blend为srcalpha onesrcalpha,也就是普通的transparent效果,并且我们可以通过属性去修改它的alpha,但是我们将RenderType改成Opaque,那么这个物体会以实心的形式渲染到_CameraDepthNormalsTexture(5.x中_CameraDepthTexture不再由RenderType替代渲染得到)。
举个例子,注意下面两张图:
其中第一张图中黄色方块的shader中使用了blend srcalpha onesrcalpha,并给予一个_Alpha属性控制其alpha,也就是普通的transparent shader,区别就是其RenderType为Opaque,然后使用了一个景深屏幕特效,其实现屏幕特效的shader中用_CameraDepthNormalsTexture来解码得到的深度对原始图像和模糊后图像插值(4.x的版本应该用_CameraDepthTexture也一样有效),可以看到当把黄色方块的_Alpha改成0,但因为其RenderType为Opaque,也就是会渲染到_CameraDepthNormalsTexture,导致出现如第二幅图中的情况。
注意尽管_CameraDepthTexture里存放的是深度信息,但这和深度缓存还是不同的,我们知道在shader中用zwrite off可以关闭写入深度缓存,但这不表示关闭写入_CameraDepthTexture,_CameraDepthTexture本身是渲染得到的屏幕纹理,和zwrite并没有直接的联系。
之前的文章中有提过,在unity5.x新特性中可以看到:
我们可以尝试在unity4.x中编写shader,写一个最简单的vertex&fragment shader,并删除最后的FallBack,将RenderType改成Opaque,挂在场景中的一个物体上,然后写一个屏幕特效shader,这个shader只输出_CameraDepthTexture,并编写使用屏幕特效的脚本挂到摄像机上(注意记得将该Camera的DepthTextureMode改成Depth),然后可以看到Game视图输出的效果如下,可以看到这个shader成功被写入_CameraDepthTexture。
然后我们切换到unity5.x,编写同样的shader(unity5.x中直接右键创建Unlit shader就行了),然后同样输出_CameraDepthTexture,可以看到效果:
说明这个shader没有被成功写入_CameraDepthTexture,我们修改一下屏幕特效shader,将其输出_CameraDepthNormalsTexture,可以看到效果:
可以看到_CameraDepthNormalsTexture中存在这个物体,也就是说Unity5.x中_CameraDepthNormalsTexture是通过RenderType进行替代渲染得到的,上一篇文章已经证明过了,而_CameraDepthTexture在unity5.x中不再和RenderType有直接关系了。
根据unity5.0新特性描述我们已经知道,5.0中_CameraDepthTexture的渲染是通过同一个shader中的Shadow Caster Pass渲染得到的。
我们修改在5.x中创建的Unlit Shader,在其最后加入FallBack "Diffuse",然后再次运行场景观察_CameraDepthTexture的输出情况,效果如下:
可以看到_CameraDepthTexture中成功渲染出该物体,并且注意此时如果开启场景中灯光的阴影,这个Unlit Shader的物体会投射阴影,那么FallBack "Diffuse"究竟有什么玄机呢,先来看看Unity 官网上关于shader中投射阴影的描述:
In order to cast shadows, a shader has to have a ShadowCaster
pass type in any of its subshaders or any fallback. The ShadowCaster pass is used to render the object into the shadowmap, and typically it is fairly simple - the vertex shader only needs to evaluate the vertex position, and the fragment shader pretty much does not do anything. The shadowmap is only the depth buffer, so even the color output by the fragment shader does not really matter.
也就是说产生阴影首先要包含LightMode=ShadowCaster的Pass或者FallBack一个包含该Pass的shader,这个Pass实际上就是为了渲染到一张shadowmap,而实际上我们加入的FallBack语句返回的Diffuse shader,该shader会Fall Back一个Legacy Shaders/VertexLit,在其中能找到这个Pass,事实上Unity 官网上能看到其提供了一种产生实心阴影的普遍方式:
UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
就是在你的shader中使用Legacy Shaders/VertexLit这个shader中的ShadowCaster的Pass,之所以这样用,因为绝大多数不透明物体产生的阴影都是相同的,即都是实心的,因此使用一个相同的ShaderCaster Pass即可,除非遇到有修改过顶点函数,比如顶点挤出或是扭曲等,亦或是使用过clip裁剪像素的shader,比如溶解shader,其投射的阴影需要重新编写LightMode=ShadowCaster的Pass。
接下来我们尝试自己编写ShadowCaster,观察其对_CameraDepthTexture的影响,在原来的Unlit Shader基础上我们删除添加的FallBack,并添加LightMode=ShadowCaster的Pass内容如下:
Pass {
Tags { "LightMode"="ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
sampler2D _Shadow;
struct v2f{
V2F_SHADOW_CASTER;
float2 uv:TEXCOORD2;
};
v2f vert(appdata_base v){
v2f o;
o.uv = v.texcoord.xy;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
return o;
}
float4 frag( v2f i ) : SV_Target
{
fixed alpha = tex2D(_Shadow, i.uv).a;
clip(alpha - 0.5);
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
然后我们看效果,可以看到投射的阴影变成镂空效果了,尽管物体本身还是实心的,然后再观察_CameraDepthTexture,看到深度图的效果也是镂空的了,最后再试着输出_CameraDepthNormalsTexture,看到物体还是实心的,因为_CameraDepthNormalsTexture的渲染是依据RenderType的,而_CameraDepthTexture的渲染在5.x中是依据ShadowCaster Pass的。
所以,在Unity5.x中_CameraDepthTexture的渲染是通过ShodowCaster Pass的。
最后总结一下:简单的说,我们在编写某些shader(如景深,软粒子,ssao等),需要取到屏幕深度图或法线图信息,而为了保证这两张纹理中有正确的数据,需要给shader使用正确的rendertype,比如当你的shader使用了clip进行像素裁剪,那么RenderType应该是TransparentCutOut,unity在渲染_CameraDepthNormalsTexture的时候会使用内置的一个Camera-DepthNormalTexture.shader中的RenderType同样是TransparentCutOut的Pass来替代这个shader,那么渲染进入_CameraDepthNormalsTexture的内容同样是clip过的而不是实心的,同样如果你的shader使用了blend制作了半透明效果,应该给RenderType设置为Transparent,由于Camera-DepthNormalTexture.shader中没有RenderType为Transparent的Pass,这个shader将不会渲染到_CameraDepthNormalsTexture中,同样对于_CameraDepthTexture,5.x中需要编写LightMode=ShadowCaster的Pass来将物体渲染到_CameraDepthTexture(4.x以及之前是通过RenderType来渲染_CameraDepthTexture和_CameraDepthNormalsTexture,到了5.x,unity将_CameraDepthTexture的渲染交给shadowcaster pass而不是RenderType)。