HDR及其应用(tone mapping/bloom

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
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • 想到家里的第一辆车,红色,外出郊游时车门掉了一半。但当时嘻嘻哈哈,不知道为什么始终对于交通工具充满恐惧的我反而特别...
    达尔文地雀阅读 223评论 0 0
  • 我和他的故事,永远是悲剧,。就像是永远画不成圆的圆规。注定了结局。 初次见到陆如初时,他还是个青涩的少年,一个总喜...
    4544187d615c阅读 544评论 0 0
  • 上午想到说那条票圈没人评论是因为只对我可见,晚上又觉得自己想太多了。患得患失了。想着未来,就把话都埋在心里了。等你...
    平衡非平衡阅读 116评论 0 0
  • 红苹果ACBX全球交易平台进度公告 发布时间:2018-03-01 尊敬的ACBX全球用户: 您好! 区块...
    绍斌2018阅读 4,685评论 2 0
  • 在新帅萨里麾下,恩格洛-坎特在蓝军开始担任新的角色,法国劳模在中场位置的改变可谓有利有弊,天空体育亚当-贝特为您评...
    pahui7933阅读 209评论 0 0