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,看起来更真实。
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
}
}
}
网友评论