zoukankan      html  css  js  c++  java
  • 翻译12 Unity 半透明阴影

    支持镂空阴影
    噪声
    粗略的半透明阴影
    镂空阴影和半透明阴影之间切换

    使用Unity 5.6.6f1

    1 镂空阴影

    翻译11介绍了镂空渲染,可能也注意到了,物体的投射的阴影是物体本身的形状,跟镂空形状完全不一致。这是因为我们之前的Shader投射阴影只是简单的采样了光的方向和到达物体表面的距离,没有区分表面形状。

    image

    图1.1 不符合现实的半透明阴影


    1.1 重写阴影

    为了将透明度考虑在内,需要在阴影投射pass通道访问alpha值。这意味着我们要采样albedo纹理。然而,当仅用opaque渲染模式时就不需要采样,为此需要适配Shader变体。

    现在的Shader已经集成了两个变体阴影,一是针对PointLight立方体阴影,二是针对其他类型灯光。现在增加第三个变体。改造一下之前的Shadow.cginc-略

    1.2 裁剪阴影片段

    就像渲染镂空效果时,阴影的镂空也根据alpha值来决定是否丢弃片段。因此算法上大致相似,必要变量也相似。拷贝tint、albedo、renderingsettings。

    #include "UnityCG.cginc"
    
    float4 _Tint;
    sampler2D _MainTex;
    float4 _MainTex_ST, _AlphaCutOff;

    当使用Cutout渲染模式就需要采样albedo纹理,但是只有当不使用albedo纹理alpha值作为平滑度时才能这样做(之前是把albedo`s alpha作为了平滑值,避免冲突)。如果满足上述条件,就可把uv坐标传递给片元程序。

    写一个宏_SHADOW_NEED_UV

    #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坐标时就采样

    #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

    image

    图1.2 镂空阴影

    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,还不能正确投射半透明阴影。继续!

    2.1 抖动阴影

            阴影贴图包含了光线到表面的距离。光线阻挡结果信息存在阴影贴图,存储的结果是:0或1。因此是没有办法指定光被半透明表面部分阻挡
            目前能做到的就是将阴影表面的一部分剪掉(镂空)。这就是为镂空阴影所做的。但是,除了基于阈值进行裁剪外,我们还可以裁剪片段。例如,如果一个表面让一半的光通过,并通过使用特定抖动纹理裁剪所有其他片段。就可以把生成的阴影显示为完整阴影的一半。
    依靠纹理的alpha值,我们可以使用带有更多或更少孔的图案,就不会出现重复的模式。而且,如果我们混合使用这些模式,则可以对阴影的密度进行平滑过渡。基本上,我们仅使用两种状态来近似渐变。这种技术被称为抖动。
            Unity包含一个抖动模式图集,我们可以使用。它包含16种4×4像素的不同图案。它从一个完全空的模式开始。每个连续的模式填充一个额外的像素,直到有七个像素被填充。然后模式被反转和反转,直到所有像素都被填充。

    2.2 VPOS

           要对我们的阴影应用抖动模式,我们必须对其进行采样。 不能使用网格的UV坐标,因为它们在阴影空间中不一致。 相反,我们需要使用片段的屏幕空间坐标。 阴影是从光的视角角度渲染贴图的,这会使图案与阴影贴图对齐。

            片段的屏幕空间坐标可以在片元程序中访问,方法是添加一个带有VPOS语义的参数。这些坐标不是由顶点程序显式输出的,但GPU可以程序用使用它们。

            然而不幸的是,VPOS和SV_POSITION语义,在一些平台上,它们最终可能会映射到相同的语义位置。所以我们不能同时在我们的结构体中使用它们。幸运的是,我们只需要在顶点程序中使用SV_POSITION,而VPOS只需要在fragment程序中使用。所以分别为Vertex和Fragment程序定义一个单独的结构体。

    2.3 抖动处理

            要访问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))

    image image

    图2.1 Dither抖动纹理 0.0625 vs. 0.9375

    2.4 近似半透明

           根据tex3D采样函数,可知Z坐标值决定是否要渲染片元。那么只需将该片元的alpha值与最高LOD层级(15/16=0.9375)相乘,得出该片元alpha所在的LOD层级。

    tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.1, alpha * 0.9375))

    image

    图2.2 近似模拟半透

            抖动纹理密度大小可以缩放VPOS指定的纹理坐标实现,Unity使用了0.25值来缩放。

    tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.25, alpha * 0.9375))

    image

    图2.3 缩放后

             缩放后效果还行但不完美,这取决于抖动纹理的分辨率。越高的分辨率填充的越密集,效果越好。同时,抖动纹理在Hard和Soft阴影模式下的效果也不相同

    image image

    图2.4 soft vs. hard

            这个阴影模式在物体移动时有一个视觉错误,整个阴影都在动。密集恐惧。

    dither_pattern

    图2.5 抖动


    3 组合剪影与淡出效果

            可能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

           上面这段代码加入后,会自动切换为裁剪阴影。

    3.1 启用自定义关键字

    3.2 合并显示

            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);
                    }
                }
            }

    image

  • 相关阅读:
    20200630(A--E)题解 by 王文硕
    20200629(A--E)题解 by 章思航
    GC垃圾回收
    Codeforces Round #629 (Div. 3) A、B、C
    AtomicInteger的Increment方法的自己实现。
    两个线程,一个输出字母一个输出数字,输出A1B2C3....Z26
    NIO记录
    mysql优化相关
    一些Nginx的Linux命令和conf配置文件
    docker记录
  • 原文地址:https://www.cnblogs.com/baolong-chen/p/12664143.html
Copyright © 2011-2022 走看看