zoukankan      html  css  js  c++  java
  • 翻译14 Fog

    应用雾到游戏对象
    基于距离或深度的雾
    支持deferred fog

    1 Forward Fog

    在14之前,一直假定着光线在真空中传播,在真空中可能是精确的。但是当光线穿过大气或水就不一样了,光线在击中物体表面时会发生被吸收、散射和反射。

    一个精确的大气干扰光线渲染将需要及其昂贵的体积测量方法,那是大多数现代GPU负担不起的。相反,勉强采用一些常量雾参数近似模拟。

    1.1 Standard Fog

    Unity光照设置包含了场景雾设置选项,默认是不启用。启用后,默认是灰色雾。Unity自带的雾只适用于使用了Forward渲染路径的物体。若激活Deferred path,提示:

    image

    图1 deferred 提示

    image

    图2 不明显的雾

    1.2 Linear Fog

    图2不明显,是因为Fog color灰色雾将散射和反射更多的光线,吸收较少。把Fog Color改为纯黑色试试

    image

     image

    图3 linear fog

    雾的浓度是随视距线性增长的,在视距开头正常显示,超过这个距离就只有雾的颜色可见。

    线性雾公式

    image

    c 是雾坐标;
    S 是视距起始距离;
    E 是视距终止距离;
    f值 被限定在[0, 1]范围,被用在雾和物体着色之间插值。

    最终计算在fragment color着色到物体对象上,雾不会影响到skybox

    1.3 Exponential Fog

    更接近真实感的雾

    image 

    image

    图4 指数雾

    指数雾公式

    imageimage

    d 是fog的密度因子;
    c 是距离因子。

    1.4 Exponential Squared Fog

    image

    image

    图5 指数平方雾

    指数平方雾公式

    image image

    1.5 Adding Fog

    增加Fog到自己的shader中,增加Fog需要使用内置关键字:multi_compile_fog指令。该指令会额外增加:FOG_LINEAR、FOG_EXP、FOG_EXP2变体。

    #pragma multi_compile_fog

    新增ApplyFog()函数,用于在Fragment计算最终着色:获取当前颜色和插值数据作为参数,返回最终颜色。

    计算步骤:
    任何雾公式都是基于视距的,首先计算出视距值备用;
    然后使用UnityCG.cginc宏UNITY_CALC_FOG_FACTOR_RAW根据具体雾公式计算出雾因子。
    最后根据雾因子,在fog_color和当前color取插值返回。

    float4 ApplyFOG(float4 color, Interpolators i)
    {
        float viewDistance = length(_WorldSpaceCameraPos - i.worldPos);
        UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
        return learp(unity_FogColor, color, unityFogFactor);
    }
    //宏UNITY_CALC_FOG_FACTOR_RAW
    #if defined(FOG_LINEAR)
        // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
        #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
    #elif defined(FOG_EXP)
        // factor = exp(-density*z)
        #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
    #elif defined(FOG_EXP2)
        // factor = exp(-(density*z)^2)
        #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
    #else
        #define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
    #endif
    
    //宏UNITY_CALC_FOG_FACTOR
    #define UNITY_CALC_FOG_FACTOR(coord) UNITY_CALC_FOG_FACTOR_RAW(UNITY_Z_0_FAR_FROM_CLIPSPACE(coord))
    
    //unity_FogParams 定义在ShaderVariables
    // x = density / sqrt(ln(2)), useful for Exp2 mode
    // y = density / ln(2), useful for Exp mode
    // z = -1/(end-start), useful for Linear mode
    // w = end/(end-start), useful for Linear mode
    float4 unity_FogParams;
    View Code

    注意雾因子必须限定在[0,1]

    return learp(unity_FogColor, color, saturate(unityFogFactor));

    同时雾也不能影响Alpha值

    color.rgb = learp(unity_FogColor.rgb, color.rgb, saturate(unityFogFactor));
    

    image

    图6 linear:standard vs. mine

    image

    图7 exp:standard vs. mine

    image

    图8 exp2:standard vs. mine

    1.6 Depth-Based Fog

    增加深度雾支持。与Standard Shader不同的原因是计算fog坐标方法不同。虽然使用world-space视图距离是有意义的,但标准着色器使用裁剪空间深度值。因此视角不影响雾坐标。此外,在某些情况下,距离是受相机的近裁切面距离的影响,这将把雾推开一点。

    image

    图9 深度 (三角) vs. 距离(园)

    基于深度代替距离的优点是:不必计算平方根,计算速度更快,适用于非真实渲染。缺点是:忽略视角,也即相机以原点旋转会影响雾密度,因为旋转时密度会改变。

    image

    图10 红到蓝旋转,深度改变密度

    支持depth-based深度雾 ,必须把clip-pass裁剪空间深度值传递到片元函数。定义一个关键字:FOG_DEPTH.

    #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
        #define FOG_DEPTH 1
    #endif

    由于需要多存储一个z值,但又不能新增一个独立的变量,就把worldPos改为float4

    #if defined(FOG_DEPTH)
        float4 worldPos : TEXCOORD4;
    #else
        float3 worldPos : TEXCOORD4;
    #endif

    然后要替换i.worldPos的所有用法为i.worldPos.xyz。将剪贴空间深度值赋给i.worldPos.w,在fragment传递给viewDistance。它只是齐次剪贴空间位置的Z坐标,所以在它被转换为0-1范围内的值之前。

    image

    图11 incrrect

    image

    图12 正确

    不正确的原因:可能会有反向裁剪空间Z的情况,需要转换。

    #if defined(UNITY_REVERSED_Z)
        //D3d with reversed Z =>
        //z clip range is [near, 0] -> remapping to [0, far]
        //max is required to protect ourselves from near plane not being
        //correct/meaningfull in case of oblique matrices.
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) 
            max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)
    #elif UNITY_UV_STARTS_AT_TOP
        //D3d without reversed z => z clip range is [0, far] -> nothing to do
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
    #else
        //Opengl => z clip range is [-near, far] -> should remap in theory
        //but dont do it in practice to save some perf (range is close enought)
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
    #endif
    
    #define UNITY_CALC_FOG_FACTOR(coord) 
        UNITY_CALC_FOG_FACTOR_RAW(UNITY_Z_0_FAR_FROM_CLIPSPACE(coord))
    View Code

    1.7 Clip-Space Depth or World-Space Distance

    增加双支持!FOG_DISTANCE 和 FOG_DEPTH。用宏代替feature指令,仿照BINORMAL_PER_FRAGMENT定义FOG_DISTANCE,默认就是它。

    CGINCLUDE
        #define BINORMAL_PER_FRAGMENT
        #define FOG_DISTANCE
    ENDCG
    //在shader中,要切换到基于距离的雾,如果FOG_DISTANCE已经被定义,我们要做的就是去掉FOG_DEPTH的定义。
    #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
        #if !defined(FOG_DISTANCE)
            #define FOG_DEPTH 1
        #endif
    #endif

    1.8 Disabling Fog

    增加支持禁用。只在需要时使用雾,增加FOG_ON宏

    #if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
        #if !defined(FOG_DISTANCE)
            #define FOG_DEPTH 1
        #endif
        #define FOG_ON 1
    #endif
    
    float4 ApplyFog (float4 color, Interpolators i) {
        #if FOG_ON
            float viewDistance = length(_WorldSpaceCameraPos - i.worldPos.xyz);
            #if FOG_DEPTH
                viewDistance = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.worldPos.w);
            #endif
            UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
            color.rgb = lerp(unity_FogColor.rgb, color.rgb, saturate(unityFogFactor));
        #endif
        return color;
    }

    1.9 Multiple Lights

    增加支持多光源。但是变得更亮了,这是因为每个光的颜色都叠加到了雾色之上,所以黑色雾是没问题。

    image

    图13 太亮了

    解决办法就是:对additive pass使用黑色雾,这样就会淡化一部分颜色。

    float3 fogColor = 0;
    #if defined(FORWARD_BASE_PASS)
        fogColor = unity_FogColor.rgb;
    #endif
    color.rgb = lerp(fogColor, color.rgb, saturate(unityFogFactor));

    image

    2 Deferred Fog

    deferred路径没有雾,这是因为所有的光照计算完成后,才会计算雾。为了能够在deferred渲染雾,见2.1

    2.1 Image Effects

    要增加雾渲染,需要等所有光照计算直到它们完成后,在其他pass再次渲染雾。该pass不在shader内部,属于屏幕ImageEffects(后处理)阶段。

    [ExecuteInEditMode]
    public class DeferredFogRender : MonoBehaviour
    {
        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
        }
    }

    这是增加了一个全屏后处理pass。如果有多个这样实现了OnRenderImage脚本,将会按顺序依次执行。

    OnRenderImage(RenderTexture source, RenderTexture destination)两个参数:
    source        是已计算好最终颜色
    destination 输出雾。若为空直接进入帧缓冲区

    方法内部必须调用Graphics.Blit函数,它会画一个全屏面片输出到Destination

    void OnRenderImage (RenderTexture source, RenderTexture destination) {
        Graphics.Blit(source, destination);
    }

    image

    图14 后处理pass

    2.2 Fog Shader

    2.1只是做了简单的拷贝,没什么用。必须要新建一个处理sourceTexture的shader来渲染雾。基本框架:

    Shader "Custom/MyDeferredFog"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
        }
        SubShader
        {
            // No culling or depth
            Cull Off
            ZWrite Off
            ZTest Always
            Pass
            {
            }
        }
    }

    然后用后处理脚本需要引用该Shader

    Pass
    {
        CGPROGRAM
        #pragma vertex   VertexProgram
        #pragma fragment FragmentProgram
        #pragma multi_compile_fog
        #include "UnityCG.cginc"
        sampler2D _MainTex;
        struct modelData
        {
            float4 vertex : POSITION;
            float2 uv      : TEXCOORD0;
        };
        struct Interpolarters
        {
            float4 position : SV_POSITION;
            float2 uv        : TEXCOORD0;
        };
        Interpolarters VertexProgram(modelData m) {
            Interpolarters i;
            i.position = UnityObjectToClipPos(m.vertex);
            i.uv = m.uv;
            return i;
        }
        float4 FragmentProgram(Interpolarters i) :SV_Target
        {
            float3 sourceColor = tex2D(_MainTex, i.uv).rgb;
            return float4(sourceColor, 1);
        }
        ENDCG
    }


    2.3 Depth-Based Fog

    增加深度雾。Unity自带深度buffer变量_CameraDepthTexture, 然后使用指令SAMPLE_DEPTH_TEXTURE采样深度:

    UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);

    float4 FragmentProgram(Interpolarters i) :SV_Target
    {
        float depth = SAMPLE_DEPTHE_TEXTURE(_CameraDepthTexture, i.uv);
        float3 sourceColor  = tex2D(_MainTex, i.uv).rgb;
        return float4(sourceColor, 1);
    }

    !首先。可以使用在UnityCG中定义的Linear01Depth函数将其转换为一个线性范围。这是因为从深度缓冲区得到原始数据后,需要从齐次坐标转换为[0,1]范围的clip-space坐标。我们必须转换这个值,使它成为世界空间中的一个线性深度值。

    float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
    depth = Linear01Depth(depth);

    Linear01Depth内部实现:

    // Z buffer to linear 0..1 depth
    inline float Linear01Depth( float z )
    {
        return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
    }
    
    // Values used to linearize the Z buffer
    // (http://www.humus.name/temp/Linearize%20depth.txt)
    // x = 1-far/near
    // y = far/near
    // z = x/far
    // w = y/far
    float4 _ZBufferParams;
    View Code

    !然后。需要使用far_clip平面距离缩放该depth值,得到真实的深度视距。clip_space裁剪空间可通过float4 _ProjectionParams变量获得, 定义在UnityShaderVariables.cginc中。其中Z分量就是远平面far_clip距离。。

    depth = Linear01Depth(depth);
    float distance = depth * _ProjectionParams.z;

    !最后,计算实际的fog。

    float viewDistance = depth * _ProjectionParams.z;
    UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
    unityFogFactor = saturate(unityFogFactor);
    float3 sourceColor  = tex2D(_MainTex, i.uv).rgb;
    float3 color = lerp(unity_FogColor.rgb, sourceColor, unityFogFactor);
    return float4(color, 1);

    image

    图15 不太明显的雾

    2.4 Fixing the Fog

    对比图15,就像把雾蒙在了物体上方。解决办法就是在绘制物体之前,绘制雾。使用ImageEffectOpaque属性绘制

    [ImageEffectOpaque]
    void OnRenderImage (RenderTexture source, RenderTexture destination) {
        Graphics.Blit(source, destination, fogMate);
    }

    image

    image

    处理近平面(非精确处理),near plane存储在Y值中

    float viewDistance = depth * _ProjectionParams.z -_ProjectionParams.y;

    2.5 Distance-Based Fog

    deferred灯光的着色,从depth-buffer中重建世界空间位置,以便计算灯光。我们也可以这样仿照这样计算雾。

    透视相机的clip-space空间定义了一个梯形区域,如果忽略near-plane就得到的是一个以相机world-pos为顶点的三角形区域。它的高是far-plaen距离,那么线性化后的depth范围:顶点为0,底边为1。

    image

    image

    图16 金字塔区域

    对于渲染的后处理图形Image的每个像素,都能通过从顶点到底边发射一条射线(从屏幕射向3D空间),检测是否击中任何物体,击中渲染,未击中不渲染。

    image

    图17 Image每个像素发射一条射线

    若击中某个物体,那么对应像素的深度就要小于1. 如果,该射线在半道击中了物体,射线对应的像素深度值就是1/2.这就意味着射线对应的Z值 = 射线击中物体时的长度 ➗ 射线总长度。范围[0, 1]。又由于射线的方向都是一致的,X和Y坐标也应该减半。

    image

    图18 射线的缩放

    一旦得到该射线,就能从相机的位置出发,寻找可能会被渲染的物体表面的世界坐标(若击中)。同时,也要得到该射线的长度。

    要使用上述方法,必须知道从相机到平面的每一个像素的射线。但实际上,只需要4条射线,金字塔的每个角都需要一条射线。用插值可给出中间所有像素的光线。

    2.6 Calculating Rays

    基于相机远平面和视角计算光线,同时相机的方向和位置与距离无关,所以可以忽略变换。Camera提供了一个函数:CalculateFrustumCorners,四个参数
        矩形面积(image rect)
        光线投射距离(相机far-plane)
        立体渲染(相机自带)
        4个元素的3D向量组

            deferredCamera.CalculateFrustumCorners
            (
                rectArea,
                deferredCamera.farClipPlane,
                deferredCamera.stereoActiveEye,
                corners
            );

    下一步传递该数据至Shader,同时也得改变索引顺序。相机提供的是:左下、左上、右上、右下。shader需要:左下、右下、左上、右上

    //corners vectex index: b-l, u-l, u-r, b-r
    //shader vectex index : b-l, b-r, u-l, u-r
    frustumCorners[0] = corners[0];
    frustumCorners[1] = corners[3];
    frustumCorners[2] = corners[1];
    frustumCorners[3] = corners[2];

    fogMate.SetVectorArray("__FustumCorners", frustumCorners);

    2.7 Deriving Distances

    Shader需要一个接收变量,同时定义一个FOG_DISTANCE宏,当需要使用距离时再计算光线。

    #define FOG_DISTANCE
    
    struct Interpolators
    {
        float4 position : SV_POSITION;
        float2 uv       : TEXCOORD0;
    #if FOG_DISTANCE
        float3 ray : TEXCOORD1;
    #endif
    };

    根据UV坐标计算获取数组中对应的光线,传进shader的数组排列:(0,0) (1,0) (0,1) (1,1),使用U+2V可得

    #if FOG_DISTANCE
        i.ray = _FustumCorners[i.uv.x + 2 * i.uv.y];
    #endif

    最后在Fragment函数替换基于深度计算的雾,使用基于距离计算

    float viewDistance = 0;
    #if defined(FOG_DISTANCE)
        viewDistance = length(i.ray * depth);
    #else
        viewDistance = depth * _ProjectionParams.z - _ProjectionParams.y;
    #endif

    image

    图19 基于深度的雾 standard vs deferrd

    image

    图20 基于距离的雾 standard vs deferrd

    2.8 Fogged Skybox

    解放天空盒。两个不同渲染路径渲染的雾会有显著差异。延迟雾也会影响天空盒。它的作用就像far-plane是一个固体屏障,受到雾的影响。当深度值接近1时,表明已经到达了远平面。如果不想给天空盒蒙上雾,可以通过将雾因子设置为1来防止。

    if (depth > 0.999)
    {
        unityFogFactor = 1;
    }

    image

    2.9 No Fog

    最后考虑如何停止渲染雾。解决方案是当没有设置任何雾关键字,通过设置雾因子为1即可。

    #if !defined(FOG_LINEAR) || !defined(FOG_EXP) || !defined(FOG_EXP2)
        unityFogFactor = 1;
    #endif

    3原文

  • 相关阅读:
    37. Sudoku Solver(js)
    36. Valid Sudoku(js)
    35. Search Insert Position(js)
    34. Find First and Last Position of Element in Sorted Array(js)
    33. Search in Rotated Sorted Array(js)
    32. Longest Valid Parentheses(js)
    函数的柯里化
    俞敏洪:我和马云就差了8个字
    vue路由传值params和query的区别
    简述vuex的数据传递流程
  • 原文地址:https://www.cnblogs.com/baolong-chen/p/12902985.html
Copyright © 2011-2022 走看看