美文网首页
Unity自定义SRP(十二):HDR

Unity自定义SRP(十二):HDR

作者: Dragon_boy | 来源:发表于2021-01-20 15:09 被阅读0次

    https://catlikecoding.com/unity/tutorials/custom-srp/hdr/

    1 HDR

    1.1 HDR反射探针

    ​ 我们可以在反射探针中开启HDR选项,默认是开启的。

    1.2 HDR摄像机

    ​ 摄像机也有HDR选项,有OffUse Graphics Settings两个选项。Use Graphics Settings只对应那些允许HDR的摄像机。我们在CustomRenderPipelineAsset中添加对应的选项:

        [SerializeField]
        bool allowHDR = true;
    

    ​ 并传入到CreatePipeline中。

    CustomRenderPipeline也添加对应的属性:

        bool allowHDR;
    
        …
    
        public CustomRenderPipeline (
            bool allowHDR,
            …
        ) 
        {
            this.allowHDR = allowHDR;
            …
        }
    
        protected override void Render (
            ScriptableRenderContext context, Camera[] cameras
        ) 
        {
            foreach (Camera camera in cameras) {
                renderer.Render(
                    context, camera, allowHDR,
                    useDynamicBatching, useGPUInstancing, useLightsPerObject,
                    shadowSettings, postFXSettings
                );
            }
        }
    

    ​ 在CameraRenderer中添加,必须RP和摄像机都允许:

        bool useHDR;
    
        public void Render (
            ScriptableRenderContext context, Camera camera, bool allowHDR,
            bool useDynamicBatching, bool useGPUInstancing, bool useLightsPerObject,
            ShadowSettings shadowSettings, PostFXSettings postFXSettings
        ) 
        {
            …
            if (!Cull(shadowSettings.maxDistance)) 
            {
                return;
            }
            useHDR = allowHDR && camera.allowHDR;
    
            …
        }
    

    1.3 HDR渲染纹理

    ​ 在CameraRenderer.Setup中,我们创建HDR格式的中间帧缓冲,替换原有的LDR:

                buffer.GetTemporaryRT(
                    frameBufferId, camera.pixelWidth, camera.pixelHeight,
                    32, FilterMode.Bilinear, useHDR ?
                        RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default
                );
    

    ​ 现在帧调试器中可以看到默认的HDR格式,R16G16B16A16\_SFloat,即每个通道16位。

    1.4 HDR后处理

    ​ 我们需要在HDR范围内进行后处理。在PostFXStack.Setup中传入参数:

            postFXStack.Setup(context, camera, postFXSettings, useHDR);
    

    Setup:

        bool useHDR;
    
        …
    
        public void Setup (
            ScriptableRenderContext context, Camera camera, PostFXSettings settings,
            bool useHDR
        ) 
        {
            this.useHDR = useHDR;
            …
        }
    

    ​ 在DoBloom中使用恰当的纹理格式:

            RenderTextureFormat format = useHDR ?
                RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default;
    

    ​ HDR和LDR发光的区别取决于场景的亮度。

    1.5 闪烁问题

    ​ HDR的一个缺点是会产生亮度远高于周围区域的小型亮部,区域足够小的话,在移动时可能会产生闪烁问题。我们可以在预滤波阶段加大模糊程度来减少闪烁问题。在BloomSettings中加上选项:

            public bool fadeFireflies;
    

    ​ 添加一个新的BloomPrefilterFirefliespass,在DoBloom中应用:

            Draw(
                sourceId, bloomPrefilterId, bloom.fadeFireflies ?
                    Pass.BloomPrefilterFireflies : Pass.BloomPrefilter
            );
    

    ​ 最直接的方式是将2\times 2降采样滤波器改为更大的6\times 6盒状滤波器。

    image
    float4 BloomPrefilterFirefliesPassFragment (Varyings input) : SV_TARGET {
        float3 color = 0.0;
        float2 offsets[] = {
            float2(0.0, 0.0),
            float2(-1.0, -1.0), float2(-1.0, 1.0), float2(1.0, -1.0), float2(1.0, 1.0),
            float2(-1.0, 0.0), float2(1.0, 0.0), float2(0.0, -1.0), float2(0.0, 1.0)
        };
        for (int i = 0; i < 9; i++) 
        {
            float3 c =
                GetSource(input.screenUV + offsets[i] * GetSourceTexelSize().xy * 2.0).rgb;
            c = ApplyBloomThreshold(c);
            color += c;
        }
        color *= 1.0 / 9.0;
        return float4(color, 1.0);
    }
    

    ​ 这还不足以改善问题,我们需要使用基于颜色亮度的加权平均,而不是简单的算术平均。颜色的亮度是其可察觉亮度,我们使用定义在Color.hlsl中的Luminance方法。一个采样的权重为\frac {1} {l+1}l是亮度。

    float4 BloomPrefilterFirefliesPassFragment (Varyings input) : SV_TARGET {
        float3 color = 0.0;
        float weightSum = 0.0;
        …
        for (int i = 0; i < 9; i++) 
        {
            …
            float w = 1.0 / (Luminance(c) + 1.0);
            color += c * w;
            weightSum += w;
        }
        color /= weightSum;
        return float4(color, 1.0);
    }
    

    ​ 因为我们在最开始的预滤波步骤后执行高斯模糊,我们可以预中心点邻近的4个样本,减少采样数:

        float2 offsets[] = {
            float2(0.0, 0.0),
            float2(-1.0, -1.0), float2(-1.0, 1.0), float2(1.0, -1.0), float2(1.0, 1.0)//,
            //float2(-1.0, 0.0), float2(1.0, 0.0), float2(0.0, -1.0), float2(0.0, 1.0)
        };
        for (int i = 0; i < 5; i++) { … }
    

    2 散射发光

    ​ 散射发光是一种摄像机效果,它并不加亮度,只是将光散射开。

    2.1 发光模式

    ​ 我们在BloomSettings中添加发光模式枚举来支持不同种的发光:

            public enum Mode { Additive, Scattering }
    
            public Mode mode;
    
            [Range(0f, 1f)]
            public float scatter;
    

    ​ 将BloomCombine更名为BloomAdd,引入新的BloomScatterPass。在DoBloom的结合阶段应用正确的pass:

            Pass combinePass;
            if (bloom.mode == PostFXSettings.BloomSettings.Mode.Additive) 
            {
                combinePass = Pass.BloomAdd;
                buffer.SetGlobalFloat(bloomIntensityId, 1f);
            }
            else 
            {
                combinePass = Pass.BloomScatter;
                buffer.SetGlobalFloat(bloomIntensityId, bloom.scatter);
            }
            
            if (i > 1) 
            {
                buffer.ReleaseTemporaryRT(fromId - 1);
                toId -= 5;
                for (i -= 1; i > 0; i--) {
                    buffer.SetGlobalTexture(fxSource2Id, toId + 1);
                    Draw(fromId, toId, combinePass);
                    …
                }
            }
            else 
            {
                buffer.ReleaseTemporaryRT(bloomPyramidId);
            }
            buffer.SetGlobalFloat(bloomIntensityId, bloom.intensity);
            buffer.SetGlobalTexture(fxSource2Id, sourceId);
            Draw(fromId, BuiltinRenderTextureType.CameraTarget, combinePass);
    

    BloomScatterBloomAdd类似,只不过会将两张源纹理根据强度插值,而不是简单的相加:

    float4 BloomScatterPassFragment (Varyings input) : SV_TARGET 
    {
        float3 lowRes;
        if (_BloomBicubicUpsampling) 
        {
            lowRes = GetSourceBicubic(input.screenUV).rgb;
        }
        else 
        {
            lowRes = GetSource(input.screenUV).rgb;
        }
        float3 highRes = GetSource2(input.screenUV).rgb;
        return float4(lerp(highRes, lowRes, _BloomIntensity), 1.0);
    }
    

    2.2 散射限制

    ​ 我们将散射的范围限制在0.05-0.95:

        public struct BloomSettings 
        {
    
            …
    
            [Range(0.05f, 0.95f)]
            public float scatter;
        }
    
        [SerializeField]
        BloomSettings bloom = new BloomSettings {
            scatter = 0.7f
        };
    

    ​ 我们在DoBloom中不使用高于1的强度来散射发光:

            float finalIntensity;
            if (bloom.mode == PostFXSettings.BloomSettings.Mode.Additive) 
            {
                combinePass = Pass.BloomAdd;
                buffer.SetGlobalFloat(bloomIntensityId, 1f);
                finalIntensity = bloom.intensity;
            }
            else 
            {
                combinePass = Pass.BloomScatter;
                buffer.SetGlobalFloat(bloomIntensityId, bloom.scatter);
                finalIntensity = Mathf.Min(bloom.intensity, 0.95f);
            }
    
            if (i > 1) 
            {
                …
            }
            else 
            {
                buffer.ReleaseTemporaryRT(bloomPyramidId);
            }
            buffer.SetGlobalFloat(bloomIntensityId, finalIntensity);
    

    2.3 阈值

    ​ 散射发光应该只针对很亮的区域使用,我们使用阈值来限制区域。创建一个额外的BloomScatterFinalpass来进行散射发光最后的绘制:

            Pass combinePass, finalPass;
            float finalIntensity;
            if (bloom.mode == PostFXSettings.BloomSettings.Mode.Additive) 
            {
                combinePass = finalPass = Pass.BloomAdd;
                buffer.SetGlobalFloat(bloomIntensityId, 1f);
                finalIntensity = bloom.intensity;
            }
            else 
            {
                combinePass = Pass.BloomScatter;
                finalPass = Pass.BloomScatterFinal;
                buffer.SetGlobalFloat(bloomIntensityId, bloom.scatter);
                finalIntensity = Mathf.Min(bloom.intensity, 1f);
            }
    
            …
            Draw(fromId, BuiltinRenderTextureType.CameraTarget, finalPass);
        }
    

    ​ 唯一的区别在于,为第分辨率pass奖赏了一些值:

    float4 BloomScatterFinalPassFragment (Varyings input) : SV_TARGET {
        float3 lowRes;
        if (_BloomBicubicUpsampling) 
        {
            lowRes = GetSourceBicubic(input.screenUV).rgb;
        }
        else 
        {
            lowRes = GetSource(input.screenUV).rgb;
        }
        float3 highRes = GetSource2(input.screenUV).rgb;
        lowRes += highRes - ApplyBloomThreshold(highRes);
        return float4(lerp(highRes, lowRes, _BloomIntensity), 1.0);
    }
    

    3 色调映射

    3.1 额外的后处理步骤

    ​ 我们在发光后进行色调映射,添加DoToneMapping方法:

        void DoToneMapping(int sourceId) 
        {
            Draw(sourceId, BuiltinRenderTextureType.CameraTarget, Pass.Copy);
        }
    

    ​ 我们需要调整发光的结果,因此添加一个全分辨率的临时渲染纹理,在DoBloom中作为最后的目标:

        int
            bloomBucibicUpsamplingId = Shader.PropertyToID("_BloomBicubicUpsampling"),
            bloomIntensityId = Shader.PropertyToID("_BloomIntensity"),
            bloomPrefilterId = Shader.PropertyToID("_BloomPrefilter"),
            bloomResultId = Shader.PropertyToID("_BloomResult"),
            …;
    
        …
        
        bool DoBloom (int sourceId) 
        {
            //buffer.BeginSample("Bloom");
            PostFXSettings.BloomSettings bloom = settings.Bloom;
            int width = camera.pixelWidth / 2, height = camera.pixelHeight / 2;
            
            if (
                bloom.maxIterations == 0 || bloom.intensity <= 0f ||
                height < bloom.downscaleLimit * 2 || width < bloom.downscaleLimit * 2
            ) 
            {
                //Draw(sourceId, BuiltinRenderTextureType.CameraTarget, Pass.Copy);
                //buffer.EndSample("Bloom");
                return false;
            }
            
            buffer.BeginSample("Bloom");
            …
            buffer.SetGlobalFloat(bloomIntensityId, finalIntensity);
            buffer.SetGlobalTexture(fxSource2Id, sourceId);
            buffer.GetTemporaryRT(
                bloomResultId, camera.pixelWidth, camera.pixelHeight, 0,
                FilterMode.Bilinear, format
            );
            Draw(fromId, bloomResultId, finalPass);
            buffer.ReleaseTemporaryRT(fromId);
            buffer.EndSample("Bloom");
            return true;
        }
    

    ​ 调整Render,让其基于发光结果执行色调映射:

        public void Render (int sourceId) 
        {
            if (DoBloom(sourceId)) 
            {
                DoToneMapping(bloomResultId);
                buffer.ReleaseTemporaryRT(bloomResultId);
            }
            else 
            {
                DoToneMapping(sourceId);
            }
            context.ExecuteCommandBuffer(buffer);
            buffer.Clear();
        }
    

    3.2 色调映射模式

    ​ 在PostFXSettings中添加ToneMappingSettings结构体:

        [System.Serializable]
        public struct ToneMappingSettings 
        {
    
            public enum Mode { None }
    
            public Mode mode;
        }
    
        [SerializeField]
        ToneMappingSettings toneMapping = default;
    
        public ToneMappingSettings ToneMapping => toneMapping;
    

    3.3 莱因哈特

    \frac {c} {1+c}

    ​ 加入枚举:

            public enum Mode { None = -1, Reinhard }
    

    ​ 在DoToneMapping中使用:

        void DoToneMapping(int sourceId) 
        {
            PostFXSettings.ToneMappingSettings.Mode mode = settings.ToneMapping.mode;
            Pass pass = mode < 0 ? Pass.Copy : Pass.ToneMappingReinhard;
            Draw(sourceId, BuiltinRenderTextureType.CameraTarget, pass);
        }
    

    ToneMappingReinhardPassFragment:

    float4 ToneMappingReinhardPassFragment (Varyings input) : SV_TARGET 
    {
        float4 color = GetSource(input.screenUV);
        color.rgb /= color.rgb + 1.0;
        return color;
    }
    

    ​ 我们会因为精度限制得到错误值,因此可以将颜色进行限制:

        color.rgb = min(color.rgb, 60.0);
        color.rgb /= color.rgb + 1.0;
    

    3.4 中性色调映射

    ​ 使用Color.hlsl中的NeutralTonemap方法,\frac {t(\frac{c}{t(w)})}{t(w)}c为颜色,w为经过调整的白点,t(x)=\frac{x(ax+cb)+de}{x(ax+b)+df}-\frac{e}{f},其中x为输入颜色,其余为可配置常量:

    real3 NeutralCurve(real3 x, real a, real b, real c, real d, real e, real f)
    {
        return ((x * (a * x + c * b) + d * e) / (x * (a * x + b) + d * f)) - e / f;
    }
    
    real3 NeutralTonemap(real3 x)
    {
        // Tonemap
        const real a = 0.2;
        const real b = 0.29;
        const real c = 0.24;
        const real d = 0.272;
        const real e = 0.02;
        const real f = 0.3;
        const real whiteLevel = 5.3;
        const real whiteClip = 1.0;
    
        real3 whiteScale = (1.0).xxx / NeutralCurve(whiteLevel, a, b, c, d, e, f);
        x = NeutralCurve(x * whiteScale, a, b, c, d, e, f);
        x *= whiteScale;
    
        // Post-curve white point adjustment
        x /= whiteClip.xxx;
    
        return x;
    }
    
    float4 ToneMappingNeutralPassFragment (Varyings input) : SV_TARGET 
    {
        float4 color = GetSource(input.screenUV);
        color.rgb = min(color.rgb, 60.0);
        color.rgb = NeutralTonemap(color.rgb);
        return color;
    }
    

    3.5 ACES

    ​ 使用AcesTonemap方法:

    float4 ToneMappingACESPassFragment (Varyings input) : SV_TARGET 
    {
        float4 color = GetSource(input.screenUV);
        color.rgb = min(color.rgb, 60.0);
        color.rgb = AcesTonemap(unity_to_ACES(color.rgb));
        return color;
    }
    

    相关文章

      网友评论

          本文标题:Unity自定义SRP(十二):HDR

          本文链接:https://www.haomeiwen.com/subject/odykzktx.html