支持镂空阴影
噪声
粗略的半透明阴影
镂空阴影和半透明阴影之间切换算法
使用Unity 5.6.6f1函数
翻译11介绍了镂空渲染,可能也注意到了,物体的投射的阴影是物体自己的形状,跟镂空形状彻底不一致。这是由于咱们以前的Shader投射阴影只是简单的采样了光的方向和到达物体表面的距离,没有区分表面形状。spa
图1.1 不符合现实的半透明阴影翻译
为了将透明度考虑在内,须要在阴影投射pass通道访问alpha值。这意味着咱们要采样albedo纹理。然而,当仅用opaque渲染模式时就不须要采样,为此须要适配Shader变体。code
如今的Shader已经集成了两个变体阴影,一是针对PointLight立方体阴影,二是针对其余类型灯光。如今增长第三个变体。改造一下以前的Shadow.cginc-略blog
就像渲染镂空效果时,阴影的镂空也根据alpha值来决定是否丢弃片断。所以算法上大体类似,必要变量也类似。拷贝tint、albedo、renderingsettings。ip
#include "UnityCG.cginc" float4 _Tint; sampler2D _MainTex; float4 _MainTex_ST, _AlphaCutOff;
当使用Cutout渲染模式就须要采样albedo纹理,可是只有当不使用albedo纹理alpha值做为平滑度时才能这样作(以前是把albedo`s alpha做为了平滑值,避免冲突)。若是知足上述条件,就可把uv坐标传递给片元程序。get
写一个宏_SHADOW_NEED_UV it
#if defined(_RENDERING_CUTOUT) && !defined(_SMOOTHNESS_ALBEDO)
#define _SHADOW_NEED_UV 1
#endif
struct Interplotars { float4 position : SV_POSITION; #if defined(_SHADOW_NEED_UV) float2 uv : TEXCOORD0; #endif #if defined(SHADOWS_CUBE) float3 lightVec : TEXCOORD1; #endif };
实现宏_SHADOW_NEED_UV ,须要传递uv坐标时就采样io
#if defined(_SHADOW_NEED_UV)
i.uv = TRANSFORM_TEX(v.uv, _MainTex);
#endif
拷贝GetAlpha函数,把厉害的宏替换为_SHADOW_NEED_UV
//采样alpha float GetAlpha(Interpolators i) { float alpha = _Tint.a; #if defined(_SHADOW_NEED_UV) alpha *= tex2D(_MainTex, i.uv.xy).a; #endif return alpha; }
如今能够在fragment程序中获取alpha值,而后在Cutout渲染模式下使用clip裁切。
float alpha = GetAlpha(i); #if defined(_RENDERING_CUTOUT) clip(alpha - _AlphaCutOff); #endif
图1.2 镂空阴影
为了同时支持Fade和Transparent渲染模式的阴影,必须将其关键字添加到shadowCaster通道的着色器钟并定义ShaderFeature。
#pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE _RENDERING_TRANSPARENT
在Fade和Transparent模式下阴影也应该是半透明的,定义宏SHADOWS_SEMITRANSPARENT
#if defined(_RENDERING_FADE) || defined(_RENDERING_TRANSPARENT)
#define SHADOWS_SEMITRANSPARENT 1
#endif
再次调整Shadeow_need_uv
#if SHADOWS_SEMITRANSPARENT || defined(_RENDERING_CUTOUT) #if !defined(_SMOOTHNESS_ALBEDO) #define SHADOWS_NEED_UV 1 #endif #endif
这个时候因为是使用了Clip,还不能正确投射半透明阴影。继续!
阴影贴图包含了光线到表面的距离。光线阻挡结果信息存在阴影贴图,存储的结果是:0或1。所以是没有办法指定光被半透明表面部分阻挡。
目前能作到的就是将阴影表面的一部分剪掉(镂空)。这就是为镂空阴影所作的。可是,除了基于阈值进行裁剪外,咱们还能够裁剪片断。例如,若是一个表面让一半的光经过,并经过使用特定抖动纹理裁剪全部其余片断。就能够把生成的阴影显示为完整阴影的一半。
依靠纹理的alpha值,咱们可使用带有更多或更少孔的图案,就不会出现重复的模式。并且,若是咱们混合使用这些模式,则能够对阴影的密度进行平滑过渡。基本上,咱们仅使用两种状态来近似渐变。这种技术被称为抖动。
Unity包含一个抖动模式图集,咱们可使用。它包含16种4×4像素的不一样图案。它从一个彻底空的模式开始。每一个连续的模式填充一个额外的像素,直到有七个像素被填充。而后模式被反转和反转,直到全部像素都被填充。
要对咱们的阴影应用抖动模式,咱们必须对其进行采样。 不能使用网格的UV坐标,由于它们在阴影空间中不一致。 相反,咱们须要使用片断的屏幕空间坐标。 阴影是从光的视角角度渲染贴图的,这会使图案与阴影贴图对齐。
片断的屏幕空间坐标能够在片元程序中访问,方法是添加一个带有VPOS语义的参数。这些坐标不是由顶点程序显式输出的,但GPU能够程序用使用它们。
然而不幸的是,VPOS和SV_POSITION语义,在一些平台上,它们最终可能会映射到相同的语义位置。因此咱们不能同时在咱们的结构体中使用它们。幸运的是,咱们只须要在顶点程序中使用SV_POSITION,而VPOS只须要在fragment程序中使用。因此分别为Vertex和Fragment程序定义一个单独的结构体。
要访问Unity的抖动模式纹理,须要增长变量_DitherMaskLOD,不一样模式存储在3D纹理的不一样layer中,必须使用sampler3D声明。
sampler3D _DitherMaskLOD;
若是须要半透明阴影,就在片元程序采样该纹理。经过tex3D采样,参数是_DitherMaskLOD和一个3D坐标。因为该纹理有16种模式,模式选择由Z坐标决定,Z的范围是0-1。以0.0625增量步进。
#if SHADOWS_SEMITRANSPERANT tex3D(_DitherMaskLOD, float3(i.vpos.xy, 0.0625)); #endif
采样该纹理的目的是取得改纹理的alpha通道,再使用它减去一个较小值,而后用clip裁剪掉
#if SHADOWS_SEMITRANSPERANT float dither = tex3D(_DitherMaskLOD, float3(i.vpos.xy, 0.0625)).a;
clip(dither – 0.01); #endif
要真正看到该模式纹理显示,须要调整显示密度大小,这能够经过将纹理的坐标位置乘以0.01来实现的。聚光灯的阴影下观察它。Z值从0-1,纹理将从不渲染到彻底渲染。
tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.1, 0.0625))
图2.1 Dither抖动纹理 0.0625 vs. 0.9375
根据tex3D采样函数,可知Z坐标值决定是否要渲染片元。那么只需将该片元的alpha值与最高LOD层级(15/16=0.9375)相乘,得出该片元alpha所在的LOD层级。
tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.1, alpha * 0.9375))
图2.2 近似模拟半透
抖动纹理密度大小能够缩放VPOS指定的纹理坐标实现,Unity使用了0.25值来缩放。
tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.25, alpha * 0.9375))
图2.3 缩放后
缩放后效果还行但不完美,这取决于抖动纹理的分辨率。越高的分辨率填充的越密集,效果越好。同时,抖动纹理在Hard和Soft阴影模式下的效果也不相同
图2.4 soft vs. hard
这个阴影模式在物体移动时有一个视觉错误,整个阴影都在动。密集恐惧。
图2.5 抖动
可能Dither模式太难堪了,咱们可能既想要投射阴影,也要半透明的效果。这就能够组合Cutout与Fade渲染。在阴影pass中增长一个新的关键字:
_SEMITRANSPARENT_SHADOWS。若没有启用淡出半透阴影,回退到裁剪阴影。
#pragma shader_feature _ _SEMITRANSPARENT_SHADOWS #if defined(_RENDERING_FADE) || defined(_RENDERING_TRANSPARENT) //#define SHADOWS_SEMITRANSPARENT 1 #if _SEMITRANSPARENT_SHADOWS #define SHADOWS_SEMITRANSPARENT 1
#elif #define _RENDERING_CUTOUT
#endif #endif
上面这段代码加入后,会自动切换为裁剪阴影。
void DoSimetransparentShadow(RenderMode mode) { if (mode == RenderMode.Fade || mode == RenderMode.Transparent) { EditorGUI.BeginChangeCheck(); bool enable = EditorGUILayout.Toggle ( new GUIContent("Semitransparent Shadow"), IsKeyEnable("_SEMITRANSPARENT_SHADOWS") ); if(!enable) isShowCutoffAlpha = true; if (EditorGUI.EndChangeCheck()) { SetKeyword("_SEMITRANSPARENT_SHADOWS", enable); } } }