Unity3D 阴影和深度纹理总结

原创 2017年08月28日 14:43:16

首先介绍一下,阴影的生成,阴影一般是在屏幕空间生成的,利用Unity3D引擎实现阴影的绘制,首先要明白其实现原理,Unity5的Demo已经为我们提供了代码实现,先介绍一下阴影的视线原理:

屏幕空间的阴影实现主要有这么几个步骤:

1、首先得到从当前摄像机处观察到的深度纹理。在延迟渲染里这张深度图本来就有,如果是前向渲染的话就需要把场景整个渲染一遍,把深度渲染

到深度图中。

2、然后再从光源出发得到从该光源处观察到的深度纹理,也被称为这个光源的ShadowMap

3、然后在屏幕空间做一次阴影收集计算(Shadows Collector),这次计算会得到一张屏幕空间阴影纹理,也就是说这张图里面需要有阴影的部分

已经显示在图上了。这个过程概括来说就是把每一个像素根据它在摄像机深度纹理中的深度值得到世界空间坐标,再把它的坐标从世界空间转换到

光源空间中,和光源的ShadowMap里面的深度值对比,如果大于ShadowMap中的深度距离,那么就说明光源无法照到,在阴影内。

4、最后,在正常渲染物体为它计算阴影的时候,只需要按照当前处理的fragment在屏幕空间中的位置对步骤3得到的屏幕空间阴影图采样就可以了。


它的具体实现Shader名字是:

Hidden/Internal-ScreenSpaceShadows(在DefaultResourcesExtra/Internal-ScreenSpaceShadows.shader)。这个Pass在不同的Unity版本

里是不一样的,比如在Unity 5.3里面就是Internal-PrePassCollectShadows(在DefaultResources/Internal-PrePassCollectShadows.shader),看的时候还是要全局搜索确定下。

主要思路就是从摄像机的深度纹理里采样得到该fragment的深度值,然后利用矩阵变换计算得到该点

对应的世界空间的世界坐标(利用CameraToWorld矩阵),然后再变换到光源空间下的坐标(利用WorldToShadow矩阵),最后拿

这个坐标对光源的ShadowMap采样计算阴影。


通常的做法其实都可以满足需求,最重要的是遇到一些特殊情况比如材质是透明或者是半透明的,这个就需要特殊处理了。
在Unity自带的Shader:Standard Shader中计算阴影的Pass:
//  Shadow rendering pass
Pass {
    Name "ShadowCaster"
    Tags { "LightMode" = "ShadowCaster" }

    ZWrite On ZTest LEqual

    CGPROGRAM
    #pragma target 3.0
    // TEMPORARY: GLES2.0 temporarily disabled to prevent errors spam on devices without textureCubeLodEXT
    #pragma exclude_renderers gles

    #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
    #pragma multi_compile_shadowcaster

    #pragma vertex vertShadowCaster
    #pragma fragment fragShadowCaster

    #include "UnityStandardShadow.cginc"

    ENDCG
}

可以发现也是和我们一样定义了LightMode为ShadowCaster的Pass。_ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON这些keyword就是为半透明阴影效果定义的。再看fragShadowCaster的实现(在UnityStandardShadow.cginc中定义):

half4 fragShadowCaster (
    #ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
    VertexOutputShadowCaster i
    #endif
    #ifdef UNITY_STANDARD_USE_DITHER_MASK
    , UNITY_VPOS_TYPE vpos : VPOS
    #endif
    ) : SV_Target
{
    #if defined(UNITY_STANDARD_USE_SHADOW_UVS)
        half alpha = tex2D(_MainTex, i.tex).a * _Color.a;
        #if defined(_ALPHATEST_ON)
            clip (alpha - _Cutoff);
        #endif
        #if defined(_ALPHABLEND_ON) || defined(_ALPHAPREMULTIPLY_ON)
            #if defined(UNITY_STANDARD_USE_DITHER_MASK)
                // Use dither mask for alpha blended shadows, based on pixel position xy
                // and alpha level. Our dither texture is 4x4x16.
                half alphaRef = tex3D(_DitherMaskLOD, float3(vpos.xy*0.25,alpha*0.9375)).a;
                clip (alphaRef - 0.01);
            #else
                clip (alpha - _Cutoff);
            #endif
        #endif
    #endif // #if defined(UNITY_STANDARD_USE_SHADOW_UVS)

    SHADOW_CASTER_FRAGMENT(i)
}   

代码也很简单,我们需要关注的就是tex3D(_DitherMaskLOD, float3(vpos.xy_0.25,alpha_0.9375)).a这一句。可以看出来,它是对一张

3D的dither纹理采样,这张纹理的z方向对应了透明度值。再使用clip对dither后的结果进行cutout,造成一种半透明阴影的假象。

说得简单点,这种Dither技术其实就是利用人眼的特性,我们看到的其实是有很多细小孔洞的阴影,由于这些空洞的大小和密度变化,

在人眼看来就像是半透明了一样,但实际上真正的阴影颜色是没有变的。总结其实就是,dither texture + cutout。

如果我们要自己在shader中实现这种效果,也可以仿照Standard Shader的做法,对_DitherMaskLOD这张纹理进行采样。例如,

在Shader中添加自定义的ShadowCaster Pass:

Pass{
    Name "ShadowCaster"
    Tags{ "LightMode" = "ShadowCaster" }

    ZWrite On ZTest LEqual

    CGPROGRAM
    #pragma target 3.0

    #pragma vertex vert
    #pragma fragment frag

    #pragma multi_compile_shadowcaster

    #define UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
    #define UNITY_STANDARD_USE_DITHER_MASK
    #define UNITY_STANDARD_USE_SHADOW_UVS

    #include "UnityStandardShadow.cginc"

    fixed _AlphaScale;

    struct VertexOutput
    {
        V2F_SHADOW_CASTER_NOPOS
        float2 tex : TEXCOORD1;
    };

    void vert(VertexInput v, out VertexOutput o, out float4 opos : SV_POSITION)
    {
        TRANSFER_SHADOW_CASTER_NOPOS(o,opos)
        o.tex = v.uv0;
    }

    half4 frag(VertexOutput i, UNITY_VPOS_TYPE vpos : VPOS) : SV_Target
    {
        half alpha = tex2D(_MainTex, i.tex).a * _AlphaScale;

        half alphaRef = tex3D(_DitherMaskLOD, float3(vpos.xy*0.25,alpha*0.9375)).a;
        clip(alphaRef - 0.01);

        SHADOW_CASTER_FRAGMENT(i)
    }

    ENDCG
}

同时禁用其他Fallback,就可以得到“半透明”阴影的效果了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

Unity 3D中的阴影设置

在Unity 3D中,经常需要用到光照阴影,即Directional Light的Shadow,Shadow分为Hard Shadow和Soft Shadow。区别是Soft Shadow的阴影边缘比...
  • fang_tang_
  • fang_tang_
  • 2014年12月12日 17:55
  • 1457

王者荣耀的实时阴影及其原理

王者荣耀的实时阴影及其原理
  • lj820348789
  • lj820348789
  • 2017年02月27日 14:44
  • 2098

Unity局部高效实时阴影的思考和实现

无意间看到一篇文章,说是Unity5 demo中为了实现角色的良好阴影,单独给角色设计了一个角色阴影系统。而且使用的是比较老的技术,但效果很好。其实在很多时候,我们需要的并不是万能的阴影光照系统,而是...
  • yxriyin
  • yxriyin
  • 2015年12月03日 18:00
  • 5879

unity3D阴影手册

  • 2015年08月13日 22:50
  • 52KB
  • 下载

OpenGL: 深度阴影的原理

深度阴影的原理深度阴影纹理的原理:分三步, 第一步, 生成一个深度纹理. 首先将观察点移动到光源位置, 而后绘制场景, 将得到的场景帧缓存中的深度值写入纹理中第 二步, 绘制场景的时候使用纹理生成方式...
  • Augusdi
  • Augusdi
  • 2014年04月15日 16:35
  • 3152

Shader山下(二十四)相机深度纹理

相机可以生成深度和深度法线纹理,以及运动向量纹理(需要Unity5.4以上)。我们可以使用这些纹理来实现屏幕后处理效果。 当你需要某种纹理的时候,可以为Camera的depthTextureMode添...
  • ecidevilin
  • ecidevilin
  • 2016年11月09日 21:48
  • 1781

Unity3D游戏制作(三)——移动平台上的角色阴影制作

本系列文章由 Amazonzx 编写,欢迎转载,转载请注明出处。 http://blog.csdn.net/amazonzx/article/details/7973740   本文将重点介绍两...
  • Amazonzx
  • Amazonzx
  • 2012年09月13日 10:21
  • 30517

纹理阴影实现笔记

ogre纹理阴影实现的三种方法 ogre包含两种阴影实现技术,阴影体与阴影纹理. 阴影体需要主cpu计算出物体的轮廓,同时生成更多的多边形,会降低帧率 并且在骨骼蒙皮时,会存在问题(如果采用硬蒙皮...
  • lsccsl
  • lsccsl
  • 2013年12月31日 16:16
  • 1967

深度纹理的使用

在D3D中创建一个深度纹理,并传入片段程序。主要步骤如下:         1、创建一个深度纹理         //创建深度纹理 //1 获取后台缓存信息 IDirect3DSurface9...
  • a812073479
  • a812073479
  • 2015年10月17日 22:21
  • 1082

OpenGL阴影贴图

阴影贴图算法综述: 对于阴影场景的渲染需要两个步骤来完成。第一步是产生阴影图本身,第二步是将阴影图应用到场景中。根据实现方式以及光源数目的不同,阴影场景渲染过程可能需要两个或者更多的绘制过程。 ...
  • wang15061955806
  • wang15061955806
  • 2016年08月03日 10:40
  • 703
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Unity3D 阴影和深度纹理总结
举报原因:
原因补充:

(最多只允许输入30个字)