美文网首页
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