HDR及其应用(tone mapping/bloom

作者: 万里_aa3f | 来源:发表于2019-01-18 15:36 被阅读9次

HDR及其应用(tone mapping、bloom)

1.什么是HDR,作用是什么
2.HDR的有关应用及其原理
3.tone mapping应用ACES
4.bloom算法
5.unity 脚本+shader

1.什么是HDR

首先要了解一张32位4通道 bmp格式的图片,它的RGBA每个通道分别占有8位,每个通道每种颜色能表示的深度信息位2的8次方也就是256种颜色;
细分后只能表现出256:1的差别,然而在自然中太阳光下的对比度是5000:1;用256的区分度来描述5000的区分度,显然是吃力的。这时候,为了记录更多的光照信息,我们需要能表现出更大范围的对比的图像HDR(High Dynamic Range)。普通的范围就叫LDR(Low Dynamic Range)。

2.HDR的相关应用

在引擎的后期处理中相关应用一般有:automatic exposure control + Tonemapping + Bloom, 先根据场景的一帧计算出平均亮度,如果偏暗就加亮一些,反之亦然,调整好亮度之后再调整灰度,让明部跟暗部保持更多的细节,最后对高光部分做个Bloom,看起来更真实。

所谓tone mapping就是根据场景的当前亮度,将HDR映射到LDR上,并保证图像细节不丢失,不失真。
bloom效果将HDR中>1的像素部分通过高斯模糊,叠加到原图片上。来表明这个地方非常亮,亮度都溢出了!
原来图片效果

亮部没有细节亮成了一坨


tone mapping

亮部细节出现


tone mapping+bloom

+bloom表现亮到溢出的效果


3.tone mapping ACES算法

            float3 ACESToneMapping(float3 color, float adapted_lum)
            {
                const float A = 2.51f;
                const float B = 0.03f;
                const float C = 2.43f;
                const float D = 0.59f;
                const float E = 0.14f;
                color *= adapted_lum;
                return (color * (A * color + B)) / (color * (C * color + D) + E);
            }

4.Bloom算法

网上资料很多,这里就简答介绍下:
算法:卷积配高斯核
原理:原理是把图像的亮的部分通过卷积模糊再叠加到原图像上,产生了bloom效果。高斯核目的:当前像素和周围的像素按一定权重混合,产生一定模糊效果权重分布如下,离当前像素越远,权重越低。
高斯公式:



事实上,我们不用在片元上计算,直接用求出来的核就好了


5.Unity脚本+shader

实现思路(高斯模糊参考入门精要):
脚本:重点在OnRenderImage()中;
1.先将超过阈值的亮度信息提取出来,进行高斯模糊;使用shader 中的Pass0
2.高斯模糊:申请两个buffer,用RenderTexture分别进行横向及纵向模糊; 使用shader中的Pass1,2
3.与tone mapping转换过的图片相叠加,Pass3

脚本源码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class ToneMappingAndBloom : MonoBehaviour
{
    public Shader curShader;
    private Material curMaterial;
    Material material{
        get{
            if(curMaterial==null){
                curMaterial=new Material(curShader);
                curMaterial.hideFlags=HideFlags.HideAndDontSave;
            }
            return curMaterial;
        }
    }

    //tone mapping
    [Range(0,3)]
    public float _lum=0.5f;
    public float _Contrast=1.1f;

    //bloom
    [Range(0,5)]
    public float _LumThreshold=1.0f;
    [Range(1,5)]
    public int _iter=2;
    [Range(0,5)]
    public float blurSpread=0.6f;

    void Start()
    {
        if(!SystemInfo.supportsImageEffects){
            enabled=false;
            return ;
        }
        if(!curShader||!curShader.isSupported){
            enabled=false;
        }
    }

    void OnRenderImage(RenderTexture src,RenderTexture dest){
        if(material!=null){
            material.SetFloat("_LumThreshold",_LumThreshold);                               //000
            RenderTexture buffer0=RenderTexture.GetTemporary(src.width,src.height,0);
            buffer0.filterMode=FilterMode.Bilinear;
            Graphics.Blit(src,buffer0,material,0);  //加上筛选      pass 0

            for(int i=0;i<_iter;i++){
                material.SetFloat("_uvAdd",i+1*blurSpread);            // 111  222 

                RenderTexture buffer1=RenderTexture.GetTemporary(src.width,src.height,0);
                Graphics.Blit(buffer0,buffer1,material,1);                  //pass  1
                RenderTexture.ReleaseTemporary(buffer0);        
                buffer0=buffer1;

                buffer1=RenderTexture.GetTemporary(src.width,src.height,0);
                Graphics.Blit(buffer0,buffer1,material,2);          //pass 2
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0=buffer1;
            }


            material.SetFloat("_Lum",_lum);                     //333
            material.SetFloat("_Contrast",_Contrast);           //333
            material.SetTexture("_BloomTex",buffer0);           //333
            Graphics.Blit(src,dest,material,3);                 //pass 3

            RenderTexture.ReleaseTemporary(buffer0);
        }
        else{
            Graphics.Blit(src,dest);
        }
    }


    void OnDisable(){
        if(curMaterial){
            DestroyImmediate(curMaterial);
        }
    }
}

shader源码

Shader "Luzheng/ToneMappingAndBloom"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        //tone mapping
        _BloomTex("BloomTex",2D)="white"{}
        _Lum("Lum",float)=1
        _Contrast("Contrast",float)=1
        //bloom
        _uvAdd("uvAdd",float)=1
        //
        _LumThreshold("LumThreshold",float)=1.0
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        pass{
            CGPROGRAM
            #pragma vertex vert 
            #pragma fragment frag 
            #include"UnityCG.cginc"

            float _LumThreshold;
            sampler2D _MainTex;

            struct a2v{
                float4 vertex:POSITION;
                float2 uv:TEXCOORD0;
            };
            struct v2f{
                float4 pos:SV_POSITION;
                float2 uv:TEXCOORD0;
            };

            v2f vert(a2v v){
                v2f o;
                o.uv=v.uv;
                o.pos=UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i):SV_TARGET{
                float3 col=tex2D(_MainTex,i.uv).xyz;
                fixed lum=Luminance(col);
                fixed val=clamp(lum-_LumThreshold,0,1);
                return fixed4(col*val,1.0);
            }

            ENDCG
        }

        pass{
            CGPROGRAM
            #pragma vertex vert 
            #pragma fragment frag 

            #include"UnityCG.cginc"

            struct a2v{
                float4 vertex:POSITION;
                float2 uv:TEXCOORD0;
            };
            struct v2f{
                float4 pos:SV_POSITION;
                float2 uv[5]:TEXCOORD0;
            };

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;
            float _uvAdd;

            v2f vert(a2v v){
                v2f o;
                o.uv[0]=v.uv;
                o.uv[1]=v.uv + float2(_MainTex_TexelSize.x , 0) * _uvAdd;
                o.uv[2]=v.uv - float2(_MainTex_TexelSize.x , 0) * _uvAdd;
                o.uv[3]=v.uv + float2( _MainTex_TexelSize.x * 2.0 , 0) * _uvAdd;
                o.uv[4]=v.uv - float2(_MainTex_TexelSize.x * 2.0 , 0) * _uvAdd;
                o.pos=UnityObjectToClipPos(v.vertex);
                return o;
            }
            fixed4 frag(v2f i):SV_TARGET{
                
                fixed3 col=tex2D(_MainTex,i.uv[0]).xyz * 0.4026;
                col+=tex2D(_MainTex  , i.uv[1]).xyz * 0.2442;
                col+=tex2D(_MainTex , i.uv[2]).xyz * 0.2442;
                col+=tex2D(_MainTex , i.uv[3]).xyz * 0.0545;
                col+=tex2D(_MainTex , i.uv[4]).xyz * 0.0545;

                return fixed4(col,1.0);
            }
            ENDCG
        }

        pass{
            CGPROGRAM
            #pragma vertex vert 
            #pragma fragment frag 

            #include"UnityCG.cginc"

            struct a2v{
                float4 vertex:POSITION;
                float2 uv:TEXCOORD0;
            };
            struct v2f{
                float4 pos:SV_POSITION;
                float2 uv[5]:TEXCOORD0;
            };

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;
            float _uvAdd;

            v2f vert(a2v v){
                v2f o;
                o.uv[0]=v.uv;
                o.uv[1]=v.uv + float2(0 , _MainTex_TexelSize.y) * _uvAdd;
                o.uv[2]=v.uv - float2(0 , _MainTex_TexelSize.y) * _uvAdd;
                o.uv[3]=v.uv + float2(0 , _MainTex_TexelSize.y * 2.0 ) * _uvAdd;
                o.uv[4]=v.uv - float2(0 , _MainTex_TexelSize.y * 2.0 ) * _uvAdd;
                o.pos=UnityObjectToClipPos(v.vertex);
                return o;
            }
            fixed4 frag(v2f i):SV_TARGET{
                
                fixed3 col=tex2D(_MainTex,i.uv[0]).xyz * 0.4026;
                col+=tex2D(_MainTex  , i.uv[1]).xyz * 0.2442;
                col+=tex2D(_MainTex , i.uv[2]).xyz * 0.2442;
                col+=tex2D(_MainTex , i.uv[3]).xyz * 0.0545;
                col+=tex2D(_MainTex , i.uv[4]).xyz * 0.0545;

                return fixed4(col,1.0);
            }
            ENDCG
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            float _Lum;
            float _Contrast;
            sampler2D _BloomTex;

            float3 ACESToneMapping(float3 color, float adapted_lum)
            {
                const float A = 2.51f;
                const float B = 0.03f;
                const float C = 2.43f;
                const float D = 0.59f;
                const float E = 0.14f;
                color *= adapted_lum;
                return (color * (A * color + B)) / (color * (C * color + D) + E);
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 col = tex2D(_MainTex, i.uv).xyz;
                //ton mapping
                col=ACESToneMapping(col,_Lum);

                //控制对比度
                fixed3 avgColor=fixed3(0.5,0.5,0.5);
                col=lerp(avgColor,col,_Contrast);

                //bloom
                fixed3 bloomColor=tex2D(_BloomTex,i.uv);
                
                return fixed4(col+bloomColor,1.0);
            }
            ENDCG
        }
    }
}

相关文章

网友评论

    本文标题:HDR及其应用(tone mapping/bloom

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