Unity3d HDR和Bloom效果(高动态范围图像和泛光)
文章开始先放两组效果,文章结尾再放两组效果
本文测试场景资源来自浅墨大神,shader效果为本文效果
HDR
人们有限的视觉系统,只支持16.7百万的颜色,超出这个范围的颜色就不能显示了
bmp或jprg每个像素就是16,24或32位
每个像素都由红绿蓝构成,如果储存为24位,每个值的范围就在0,255之间,
只能表现出256:1的差别,unity的shader中是0到1
然而在自然中太阳光下的对比度是50000:1
HDR(High Dynamic Range)使图像能表现出更大范围的对比,普通的范围就叫LDR(Low Dynamic Range)
你在照相的时候能控制曝光时间从而控制亮度。
HDR效果就是可控的曝光,
色调映射 tone-mapping
传统的显示设备不能完全的显示出HDR,所以我们用tone-mapping技术。
tone-mapping让图像从HDR映射为LDR显示
http://www.ownself.org/blog/2011/tone-mapping.html中的解释很好
Tone
Mapping原是摄影学中的一个术语,因为打印相片所能表现的亮度范围不足以表现现实世界中的亮度域,而如果简单的将真实世界的整个亮度域线性压缩到照
片所能表现的亮度域内,则会在明暗两端同时丢失很多细节,这显然不是所希望的效果,Tone
Mapping就是为了克服这一情况而存在的,既然相片所能呈现的亮度域有限则我们可以根据所拍摄场景内的整体亮度通过光圈与曝光时间的长短来控制一个合
适的亮度域,这样既保证细节不丢失,也可以不使照片失真。人的眼睛也是相同的原理,这就是为什么当我们从一个明亮的环境突然到一个黑暗的环境时,可以从什
么都看不见到慢慢可以适应周围的亮度,所不同的是人眼是通过瞳孔来调节亮度域的。
一个tone-mapping的公式
Middlegrey为全屏幕或部分屏幕的中间灰度,可以控值屏幕的亮度
AvgLogLuminance就是全屏幕或部分屏幕的亮度的对数的平均值
AvgLogLuminance的公式
Lw是亮度,n是所取亮度数
这个操作能让L值限制位[0,1)
一些tone-mapping操作用exposure或gamma作为参数控制最终的图像。
tone-mapping是非线性的,他对暗色保有一定范围并且对亮色逐步接近动态
这个技术产生吸引人的视觉效果,有着强烈的对比和细节。
HDR Rendering In OpenGL一文中给出简要且效果不错的公式
关键代码如下
float4 frag(v2f i) :COLOR { float4 c = tex2D(_MainTex, i.uv_MainTex); float y = dot(float4(0.3,0.59,0.11,1),c); float yd = _Exp * (_Exp / _BM + 1) / (_Exp + 1); return c*yd; }
_Exp,_BM为外部可控变量
HDR流程如下
如果分不清HDR与加亮light,可以看看skybox,加亮light是不会加亮skybox的,HDR使颜色更鲜明,像素更清晰。
Bloom泛光
辉光的原因是由于人眼晶状体的散射
我们制造bloom的原理是把图像的亮的部分通过卷积模糊再叠加到原图像上,就产生了bloom效果。
高斯模糊的滤波器是一种低通滤波器
就是去当前像素和周围的像素按一定权重混合,产生一定模糊效果
权重分布如下,离当前像素越远,权重越低
高斯正态分布曲线
二维公式
可以通过这个公式直接算出权重
double sigma = (double)radius / 3.0; double sigma2 = 2.0 * sigma * sigma; double sigmap = sigma2 * PI; for(long n = 0, i = - radius; i <=radius; ++i) { long i2 = i * i; for(long j = -radius; j <= radius; ++j, ++n) kernel[n] = exp(-(double)(i2 + j * j) / sigma2) / sigmap; }
Kernel即为权重
Radius为所求像素与当前像素距离(半径)
针对这个公式我们可以算出3*3,5*5,7*7等滤波器,出于性能考虑,我们还是使用5*5滤波器
3*3滤波器
5*5滤波器
有现成的就不算了,算这个也消耗一些性能
我们直接用这个权重
关键代码如下
float3 mc00 = tex2D (_MainTex, i.uv_MainTex-fixed2(2,2)/_inten).rgb; float3 mc10 = tex2D (_MainTex, i.uv_MainTex-fixed2(1,2)/_inten).rgb; float3 mc20 = tex2D (_MainTex, i.uv_MainTex-fixed2(0,2)/_inten).rgb; float3 mc30 = tex2D (_MainTex, i.uv_MainTex-fixed2(-1,2)/_inten).rgb; float3 mc40 = tex2D (_MainTex, i.uv_MainTex-fixed2(-2,2)/_inten).rgb; float3 mc01 = tex2D (_MainTex, i.uv_MainTex-fixed2(2,1)/_inten).rgb; float3 mc11 = tex2D (_MainTex, i.uv_MainTex-fixed2(1,1)/_inten).rgb; float3 mc21 = tex2D (_MainTex, i.uv_MainTex-fixed2(0,1)/_inten).rgb; float3 mc31 = tex2D (_MainTex, i.uv_MainTex-fixed2(-1,1)/_inten).rgb; float3 mc41 = tex2D (_MainTex, i.uv_MainTex-fixed2(-2,1)/_inten).rgb; float3 mc02 = tex2D (_MainTex, i.uv_MainTex-fixed2(2,0)/_inten).rgb; float3 mc12 = tex2D (_MainTex, i.uv_MainTex-fixed2(1,0)/_inten).rgb; float3 mc22mc = tex2D (_MainTex, i.uv_MainTex).rgb; float3 mc32 = tex2D (_MainTex, i.uv_MainTex-fixed2(-1,0)/_inten).rgb; float3 mc42 = tex2D (_MainTex, i.uv_MainTex-fixed2(-2,0)/_inten).rgb; float3 mc03 = tex2D (_MainTex, i.uv_MainTex-fixed2(2,-1)/_inten).rgb; float3 mc13 = tex2D (_MainTex, i.uv_MainTex-fixed2(1,-1)/_inten).rgb; float3 mc23 = tex2D (_MainTex, i.uv_MainTex-fixed2(0,-1)/_inten).rgb; float3 mc33 = tex2D (_MainTex, i.uv_MainTex-fixed2(-1,-1)/_inten).rgb; float3 mc43 = tex2D (_MainTex, i.uv_MainTex-fixed2(-2,-1)/_inten).rgb; float3 mc04 = tex2D (_MainTex, i.uv_MainTex-fixed2(2,-2)/_inten).rgb; float3 mc14 = tex2D (_MainTex, i.uv_MainTex-fixed2(1,-2)/_inten).rgb; float3 mc24 = tex2D (_MainTex, i.uv_MainTex-fixed2(0,-2)/_inten).rgb; float3 mc34 = tex2D (_MainTex, i.uv_MainTex-fixed2(-1,-2)/_inten).rgb; float3 mc44 = tex2D (_MainTex, i.uv_MainTex-fixed2(-2,-2)/_inten).rgb; float3 c=0; c+=(mc00+mc40+mc04+mc44);//4 c+=4*(mc10+mc30+mc14+mc34+mc01+mc41+mc03+mc43);//16 c+=7*(mc20+mc24+mc02+mc42);//16 c+=16*(mc11+mc13+mc03+mc33);//32 c+=26*(mc21+mc23+mc12+mc32);//64 c+=41*mc22mc;//32 c/=273;
_inten为模糊程度
觉得冗长麻烦也可用for循环代替。
然后我们要取其中的亮色部分与原有图像混合,
这一部分直接调用unity内部函数Luminance函数求出亮度,把它与模糊的图像相乘,暗色部分自然消除
但如果直接相乘就会在暗色的边缘产生不自然的黑影,就是把暗色也“泛光了”,为此我们不让Luminance后的值为0,再加上0.1,也不影响亮度。
float lum = Luminance(c); c = mc22mc + c * (lum+0.1) * _Lum; return float4(c,1);
最终与HDR结合再一起就是上图例子的最终效果
最后一道工序就是放入相机中,我们建立一个c#并负责传值
代码如下:
using UnityEngine; using System.Collections; [ExecuteInEditMode] public class HDRGlow : MonoBehaviour { #region Variables public Shader curShader; private Material curMaterial; public float exp = 0.4f; public float bm = 0.4f; public int inten = 512; public float lum = 1f; #endregion #region Properties Material material { get { if (curMaterial == null) { curMaterial = new Material(curShader); curMaterial.hideFlags = HideFlags.HideAndDontSave; } return curMaterial; } } #endregion void Start() { if (!SystemInfo.supportsImageEffects) { enabled = false; return; } if (!curShader && !curShader.isSupported) { enabled = false; } } void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture) { if (curShader != null) { material.SetFloat("_Exp", exp); material.SetFloat("_BM", bm); material.SetFloat("_Inten", inten); material.SetFloat("_Lum", lum); Graphics.Blit(sourceTexture, destTexture, material); } else { Graphics.Blit(sourceTexture, destTexture); } } void OnDisable() { if (curMaterial) { DestroyImmediate(curMaterial); } } }
Unity 的imageEffect有一个叫做BloomAndLensFlares
与本文的差别是多了色彩平衡和lens flare效果,可以试着再加上去
------- by wolf96