zoukankan      html  css  js  c++  java
  • 翻译11 Unity 透明渲染

    剪纸镂空shader
    渲染队列
    半透明材质
    合并反射和透明

    11之前的shader仅能支持不透明渲染,现在增加透明渲染。

    1 Cutout-镂空渲染

    要创建透明的材质,首先要了解每个片元的透明度。透明度信息存储在颜色的alpha通道,在我们的shader里是主纹理的alpha通道和调色值的alpha通道。

    1.1 确定Alpha值

    获取alpha值

    //采样alpha
    float GetAlpha(Interpolators i){
        return _Tint.a * tex2D(_MainTex, i.uv.xy).a;
    }

    但是前面讲了SMOOTHNESS_ALBEDO值可能被用来确定平滑度,所以要在该值不被使用时才能采样该纹理的alpha值,避免错误

    //采样alpha
    float GetAlpha(Interpolators i){
        float alpha = _Tint.a;
    #if !defined(_SMOOTHNESS_ALBEDO)
        alpha *= tex2D(_MainTex, i.uv.xy).a;
    #endif
        return alpha;
    }

    1.2 剪切镂空

    在不透明的材质,每个通过深度测试的片元都会被渲染,所有的片元都是不透明的并且写入深度缓冲。所以实现透明最简单快捷的方法是:在深度测试时,要么它完全不透明,要么它完全透明。如果它是透明就不渲染。

    CG提供了一个Clip函数,若参数<0,那么该像素所在的片元整个就会被丢弃(粗暴处理),同时不会写入深度缓冲。可以将得到的Alpha减去一个给定值(视效果)来决定是否渲染。

    float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
        float alpha = GetAlpha(i);
        clip(alpha - 0.5);
        //...
    }

    image

    图1.1 Transparency

    1.3 自定义裁剪范围

    1.2中给定0.5不太灵活,我们需要一个任意值来代替。

    Properties {
        …
        _AlphaCutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
    }
    
    float _AlphaCutoff;
    //…
    float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
        float alpha = GetAlpha(i);
        clip(alpha - _AlphaCutoff);
        //…
    }

    DX11汇编

       0: sample r0.xyzw, v1.xyxx, t0.xyzw, s2//采样
       1: mul r0.xyz, r0.xyzx, cb0[6].xyzx      //r0 * cb0[6]
       2: mad r0.w, cb0[6].w, r0.w, -cb0[8].y //cb0[6] * r0 + (-cb0[8])
       3: lt r0.w, r0.w, l(0.000000)      //浮点比较:r0.w < 0 ?  0xFFFFFFFF : 0x0000000
       4: discard_nz r0.w

    1.4 渲染模式

    Clip性能问题:桌面GPU尚可,但是对移动GPU来讲性能就很糟糕。而且对于不透明物体不需要执行该函数。增加预定义的关键字

    void SetRenderMode()
    {
        RenderMode mode = RenderMode.Opaque;
        if (IsKeyEnable("_RENDER_CUTOUT"))
        {
               mode = RenderMode.Cutout;
        }
           EditorGUI.BeginChangeCheck();
        GUIContent gc = new GUIContent("RenderMode");
        mode = (RenderMode)EditorGUILayout.EnumPopup(gc, mode);
        if (EditorGUI.EndChangeCheck())
        {
            SetKeyword("_RENDER_CUTOUT", mode == RenderMode.Cutout);
        }
    }
    float alpha = GetAlpha(i);
    #if defined(_RENDERING_CUTOUT)
        clip(alpha - _AlphaCutoff);
    #endif

    1.5 渲染队列

    UnityShader提供了队列Queue标签

        //Determine in which order objects are renderered.
        public enum RenderQueue
        {
            //This render queue is rendered before any others.
            Background = 1000,
            //Opaque geometry uses this queue.
            Geometry = 2000,
            //Alpha tested geometry uses this queue.
            AlphaTest = 2450,
            //Last render queue that is considered "opaque".
            GeometryLast = 2500,
            //This render queue is rendered after Geometry and AlphaTest, in back-to-front
            //order.
            Transparent = 3000,
            //This render queue is meant for overlay effects.
            Overlay = 4000
        }

    我们可以用自定义UI来决定渲染队列

    if (EditorGUI.EndChangeCheck())
    {
        SetKeyword("_RENDER_CUTOUT", mode == RenderMode.Cutout);
        RenderQueue queue = mode == RenderMode.Opaque? RenderQueue.Geometry:RenderQueue.AlphaTest;
        targetMaterial.renderQueue = (int)queue;
    }

    1.6 渲染类型标签

    标签在用摄像机的replacement shaders时非常有用,可以自定义渲染效果。

    string renderType = mode == RenderMode.Opaque ? "" : "TransparentCutout";
    targetMaterial.SetOverrideTag("RenderType", renderType);

    2 半透明-淡入淡出渲染

    Cutout渲染局限性
        1是对每一个像素计算,不满足alpha值的片元整个会被丢弃
        2是渲染结果要么全不透明要么全透明
        3是在不透明和透明区间没有过渡,硬边严重
        4是镂空边缘锯齿严重

    为了解决上述问题,Unity自带的StandardShader使用了一种Fade模式。增加关键字_RENDERING_FADE略。

    2.1 渲染设置

    Fade模式有它自己的渲染队列和渲染类型。Queue始值是3000,标识透明物体。RenderType是Transparent.

    替换SetRenderMode函数,定义一个通用的渲染设置

    class RenderingSettings
    {
        public RenderQueue Queue;
        public string RenderType;
        public static RenderingSettings[] modes =
        {
            new RenderingSettings(){Queue = RenderQueue.Geometry, RenderType = ""},
            new RenderingSettings() { Queue = RenderQueue.AlphaTest, RenderType = "TransparentCutout"},
            new RenderingSettings() { Queue = RenderQueue.Transparent, RenderType = "Transparent"}
        };
    }
    void SetRenderMode()
    {
        RenderMode mode = RenderMode.Opaque;
        if (IsKeyEnable("_RENDERING_CUTOUT"))
        {
            mode = RenderMode.Cutout;
            AlphaCutOffShow();
        }else if (IsKeyEnable("_RENDERING_FADE"))
           {
            mode = RenderMode.Fade;
        }
        EditorGUI.BeginChangeCheck();
        GUIContent gc = new GUIContent("RenderMode");
        mode = (RenderMode)EditorGUILayout.EnumPopup(gc, mode);
        if (EditorGUI.EndChangeCheck())
        {
            SetKeyword("_RENDERING_CUTOUT", mode == RenderMode.Cutout);
            SetKeyword("_RENDERING_FADE", mode == RenderMode.Fade);
            //RenderQueue queue = mode == RenderMode.Opaque? RenderQueue.Geometry:RenderQueue.AlphaTest;
            //string renderType = mode == RenderMode.Opaque ? "" : "TransparentCutout";
            RenderingSettings settings = RenderingSettings.modes[(int)mode];
            targetMaterial.renderQueue = (int)settings.Queue;
            targetMaterial.SetOverrideTag("RenderType", settings.RenderType);
        }
    }

    2.2 渲染透明物体

    打开FrameDebugger观察,选择Fade模式,然后对比前后的变化:
        当使用Opaque或Cutout模式,使用该材质的对象通过Render.OpaqueGeometry渲染。
        当使用Fade模式,对象通过Render.TransparentGeometry渲染。

    image image

    图2.1 Opaque vs Transparent

    注意到图2.1右侧图片,也调用了Render.OpaqueGeometry方法。注意这个调用先后顺序,Opaque和Cutout先被渲染,Transparent后渲染。因此该渲染顺序,确保了半透明物体不会在它们之后渲染。

    2.3 Blending Fragments - 融合

    公式:Blend src dst
    举例:源:透明度为a,颜色值Ac;目标颜色为Bc.
    混合后颜色 = Ac*a + (1-a)*Bc

    实现Fade渲染模式。用两个关键字在base和additive pass支持三种渲染模式。

    #pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE

    在Fade模式下,我们需要对当前片元的颜色与colorBuffer已存在的颜色融合。这一步虽在GPU完成,但需要现在Fragment程序预先计算提供。

    为了创建一个透明效果,我们需要使用不同于Opaque和Cutout的融合模式。例如在additive Pass,把新颜色加到现有的颜色。然而不能简单的相加在一起,这个多通道融合需要取决于alpha值:
        当alpha为1,物体将会渲染成完全不透明。对于上述举例,通常在basePass使用Blend One Zero,在additivePass使用Blend One One;
        当alpha为0,物体将会渲染成完全透明。这两个pass的融合只能使用Blend Zero One;
        当alpha为0.25,使用Blend 0.25 0.75 和 Blend 0.25 One;
        以此类推…

    为了达成上述效果,可以使用SrcAlpha和OneMinusSrcAlpha关键字

    image图2.2 半透明效果

    这两个关键字只是对Fade渲染半透明效果的近似估计,我们也可以自定义属性变量代替。

    _SrcBlend("SrcBlend", float) = 1
    _DstBlend("DstBlend", float) = 0

    由于这两个属性依赖渲染模式,因此不能直接显示在Inspector面板。

    [HideInInspector] _SrcBlend("SrcBlend", float) = 1
    [HideInInspector] _DstBlend("DstBlend", float) = 0
    

    使用自定义的属性,必须放入中括号,这是旧Shader语法要求。

    Blend [_SrcBlend] [_DstBlend]

    为了能控制这些变量,在RenderingSettings增加两个变量

    public static RenderingSettings[] modes = {
        new RenderingSettings() {
            queue = RenderQueue.Geometry,
            renderType = "",
            srcBlend = BlendMode.One,
            dstBlend = BlendMode.Zero
        },
        new RenderingSettings() {
            queue = RenderQueue.AlphaTest,
            renderType = "TransparentCutout",
            srcBlend = BlendMode.One,
            dstBlend = BlendMode.Zero
        },
        new RenderingSettings() {
            queue = RenderQueue.Transparent,
            renderType = "Transparent",
            srcBlend = BlendMode.SrcAlpha,
            dstBlend = BlendMode.OneMinusSrcAlpha
        }
    };

    通过Material.SetInt函数直接为_SrcBlend_DstBlend指定值

    targetMaterial.SetInt("_SrcBlend", (int)settings.SrcBlend);
    targetMaterial.SetInt("_DstBlend", (int)settings.DstBlend);

    2.4 深度冲突问题

    把两个半透明物体一高一低放置在一起(不是重叠但很接近),互相重合区域会出现一个遮挡问题。不能透过透明区域看见另一个物体。

    image

    图2.3 奇怪现象

    Unity首先试图绘制距离摄像机最近的物体,这是对重叠几何绘制最有效的方法。不幸的是它对半透明物体失效了。解决方法就是反过来绘制,最远的物体先绘制写入深度缓冲,最近的物体后绘制与深度缓冲比较。这也是绘制半透明物体代价更昂贵的原因。

    为了确定几何图形的绘制顺序,Unity使用了物体的中心位置。这对于相距很远的小物体来说很有效。但是对于较大物体,或者是紧靠在一起的平面物体,它就不好使了。在这种情况下,当你改变视角时,绘制顺序会突然变化。这可能会导致重叠的半透明物体外观的突然变化。

    无法绕过此限制,尤其是在绘制相交几何时。  但是在我们的例子中,某些绘制顺序产生了明显错误结果。 发生这种错误是因为我们的着色器仍会写入深度缓冲区。 深度缓冲区是二进制数,并不关心透明度。 如果片段没有被裁剪,其深度最终将写入缓冲区。 由于半透明对象的绘制顺序并不完美,若开启深度写入,不可见物体的深度值最终可能会覆盖可见的物体的深度值。 因此在使用Fade渲染模式时,我们必须禁用对深度缓冲区的写入。

    2.5 ZWrite 控制

    类似融合自定义属性,ZWrite模式也可自定义

    [HideInInspector] _SrcBlend ("_SrcBlend", Float) = 1
    [HideInInspector] _DstBlend ("_DstBlend", Float) = 0
    [HideInInspector] _ZWrite ("_ZWrite", Float) = 1
    …
    Blend [_SrcBlend] [_DstBlend]
    ZWrite [_ZWrite]

    GUI拓展略

    image

    图2.4 重合正确

    3 全透明渲染-逼真的材质

    Fade缺点:
        我们创建的半透明渲染模式会根据物体alpha值淡出显示。 请注意,物体颜色的显示呈褪色。 它的漫反射和镜面反射以及高光都会被淡化了。 这就是为什么它被称为淡入淡出模式。
        Fade模式适用于大多数效果,但它不能正确渲染一个半透明的固体物体。例如玻璃。

    Transparent优点:
        玻璃材质实际上是完全透明的,并且也有清晰的高光和反射光,反射光也会加入到其他任何经过的光的路径钟参与着色。


    image

    图3.1 红色过渡趋势

    enum RenderingMode {
        Opaque, Cutout, Fade, Transparent
    }

    设置Transparent模式与Fade相同,不管Alpha值是多少我们必须添加反射。因此源混合模式必须为1而不能依赖Alpha值。然后增加关键字_RENDERING_TRANSPARENT.

    3.1 预乘Alpha

    预乘Alpha:纹理的RGB通道分别预乘Alpha得到新的颜色,然后就可以不需要Alpha通道。

    优点
        它们可存储与RGB通道相关联的不同Alpha值,这样可以使用相同数据来实现多种组合效果。
        少计算一次乘法:混合后颜色 = Ac*a + (1-a)*Bc => Ac` + (1-a)*Bc.提交效率
        在有透明像素的边缘进行正确插值:如果RGB不预乘Alpha,插值时的权重过大,边缘会产生奇怪的颜色。例如出现黑边问题

    缺点:
        RGB预乘后,RGB每个通道值会变小,颜色变暗
        纹理精度下降。每个通道都乘以了alpha因子
        不能直观知道原RGB颜色值

    3.2 调节Alpha

    如果一个物体既透明又反光,我们就能看到它背后的东西,也能看到反射回来的东西。这对物体的两面观察都成立。但是同样的光既不能被反射也不能穿过物体。这又是一个能量守恒的问题。所以一个物体的反射性越强,穿过它的光就越少,不管它固有的透明度如何。

    表面没有反射,它的alpha值是0。当反射所有的光时,它的alpha实际上变成了1。当我们在Fragment Program中确定反射率时,我们可以使用它来调整alpha值。给定初始alpha和反射率:

    调节alpha = 1 – (1 - α)(1 - r)

    (1 – r)在Shader定义为oneMinusReflectivity

    调节alpha = 1 – oneMinusReflectivity + α * oneMinusReflectivity

    #if  defined(_RENDERING_TRANSPARENT)
        albedo *= alpha;
        alpha = 1 - oneMinusReflectivity + alpha * oneMinusReflectivity;
    #endif
    效果更暗了。

  • 相关阅读:
    [翻译]ASP.NET MVC 3 开发的20个秘诀(六)[20 Recipes for Programming MVC 3]:找回忘记的密码
    No.9425 [翻译]开发之前设计先行
    [翻译]ASP.NET MVC 3 开发的20个秘诀(五)[20 Recipes for Programming MVC 3]:发送欢迎邮件
    内联(inline)函数与虚函数(virtual)的讨论
    内联函数、模板、头文件杂记(转)
    gdb使用整理
    data structure and algorithm analysis上一点摘录
    lsof by example 例子
    linux core文件机制
    转一篇shared_ptr的小文
  • 原文地址:https://www.cnblogs.com/baolong-chen/p/12353282.html
Copyright © 2011-2022 走看看