Unity游戏渲染效果之Bloom效果&高斯模糊
发表于2018-10-24
在使用Unity做项目开发时,为了让游戏渲染效果更逼真,通常会用到一些特殊效果,本篇中要说的就是Bloom效果与高斯模糊对游戏渲染效果带来的差异。
原图:
高斯模糊图:
Bloom效果图:
从上面三张图可以直观的看出模糊效果和bloom效果带来的视觉差异,模糊效果可以理解为打了马赛克,至于马赛克的精细程度根据参数可以调节,Bloom效果就是当光线比较亮的地方看起来有亮光模糊效果。
二、基本原理和实现方法介绍
高斯公式:高斯分布也称正态分布。高斯模糊的基本含义为通过该公式描述了周围各点对当前点的影响程度呈正态分布。比如当前点受周围第一圈的点影响度为30%,第二圈为23% 第三圈点影响改点8%,然后越来越少。这些受影响程度的值就是由高斯公式计算出来的,但是通常在计算过程中,我们就直接套用计算好的值了,而不是全计算高斯公式。
比如:
static const half curve[7] = { 0.0205, 0.0855, 0.232, 0.324, 0.232, 0.0855, 0.0205 }
static const half4 curve4[7] = { half4(0.0205,0.0205,0.0205,0), half4(0.0855,0.0855,0.0855,0), half4(0.232,0.232,0.232,0),
half4(0.324,0.324,0.324,1), half4(0.232,0.232,0.232,0), half4(0.0855,0.0855,0.0855,0), half4(0.0205,0.0205,0.0205,0) };
可以直接将上面的权重数据放在采样点的计算过程中,通常情况下严格的高斯公式指的肯定是计算周围的全部点,那样就会导致采样次数过高导致GPU无法负担,所以通常情况下我们只是象征性的采样水平和竖直方向的点进行采样,可以一次性进行上下左右点的采样进行高斯权重计算,但是二维核进行采样消耗过多,所以通常采用分别进行水平和竖直方向的一维计算,和二维计算效果一样,但是效率会高很多。模糊说到底其实是计算周围点像素读该点的影响程度如下:
Shader:shader比较简单:
v2f vert(appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); float2 uv = TRANSFORM_TEX(v.uv, _MainTex); o.uv = uv; return o; } fixed4 fragVertical(v2f i) : SV_Target { float gaussweight[3] = { 0.4026,0.2442 ,0.0545 }; float3 maskcol = tex2D(_MaskTex, i.uv); float3 col = tex2D(_MainTex, i.uv).rgb; //if ((maskcol.r + maskcol.g + maskcol.b) < 0.1f) // return float4(col, 1); //这个过滤会大幅降低模糊周围的效果 //if (Luminance(col)<0.01f) // return float4(col, 1); col = col.rgb*gaussweight[0]; col += tex2D(_MainTex, i.uv + _BlurSize*float2(0, _MainTex_TexelSize.y)).rgb*gaussweight[1]; col += tex2D(_MainTex, i.uv - _BlurSize*float2(0, _MainTex_TexelSize.y)).rgb*gaussweight[1]; col += tex2D(_MainTex, i.uv + _BlurSize*float2(0, _MainTex_TexelSize.y * 2)).rgb*gaussweight[2]; col += tex2D(_MainTex, i.uv - _BlurSize*float2(0, _MainTex_TexelSize.y * 2)).rgb*gaussweight[2]; return float4(col,1); } fixed4 fragHorizontal(v2f i) : SV_Target { float gaussweight[3] = { 0.4026,0.2442 ,0.0545 }; float3 maskcol = tex2D(_MaskTex, i.uv); float3 col = tex2D(_MainTex, i.uv).rgb; col += tex2D(_MainTex, i.uv + _BlurSize*float2(_MainTex_TexelSize.x, 0)).rgb*gaussweight[1]; col += tex2D(_MainTex, i.uv - _BlurSize*float2(_MainTex_TexelSize.x, 0)).rgb*gaussweight[1]; col += tex2D(_MainTex, i.uv + _BlurSize*float2(_MainTex_TexelSize.x * 2, 0)).rgb*gaussweight[2]; col += tex2D(_MainTex, i.uv - _BlurSize*float2(_MainTex_TexelSize.x * 2, 0)).rgb*gaussweight[2]; return float4(col,1); }
C#的后处理实现
public void OnRenderImage (RenderTexture source, RenderTexture destination) { if (CheckResources() == false) { Graphics.Blit (source, destination); return; } float widthMod = 1.0f / (1.0f * (1<<downsample)); blurMaterial.SetVector ("_Parameter", new Vector4 (blurSize * widthMod, -blurSize * widthMod, 0.0f, 0.0f)); source.filterMode = FilterMode.Bilinear; int rtW = source.width >> downsample; int rtH = source.height >> downsample; // downsample RenderTexture rt = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt.filterMode = FilterMode.Bilinear; Graphics.Blit (source, rt, blurMaterial, 0); var passOffs= blurType == BlurType.StandardGauss ? 0 : 2; for(int i = 0; i < blurIterations; i++) { float iterationOffs = (i*1.0f); blurMaterial.SetVector ("_Parameter", new Vector4 (blurSize * widthMod + iterationOffs, -blurSize * widthMod - iterationOffs, 0.0f, 0.0f)); // vertical blur RenderTexture rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit (rt, rt2, blurMaterial, 1 + passOffs); RenderTexture.ReleaseTemporary (rt); rt = rt2; // horizontal blur rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit (rt, rt2, blurMaterial, 2 + passOffs); RenderTexture.ReleaseTemporary (rt); rt = rt2; } Graphics.Blit (rt, destination); RenderTexture.ReleaseTemporary (rt); }
主要参数有几个参数:
downsample:降频模糊,通常把原图分别降低几倍后再进行模糊 以提高效率、
blurSize:可以理解为对周围采样点的距离倍数。
blurIterations:采样次数,次数越多效果越好。
Bloom效果的理解和实现:
Bloom效果分三步实现,
第一步:分离原图中亮度较大的像素,进行降分辨率处理。
第二步:把分离的亮度图进行高斯模糊
第三步:将模糊后的亮度图和原图进行叠加。
实现方法:
1.分离亮度阈值图:
v2f_tap vert4Tap ( appdata_img v ) { v2f_tap o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv20 = v.texcoord + _MainTex_TexelSize.xy; o.uv21 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h,-0.5h); o.uv22 = v.texcoord + _MainTex_TexelSize.xy * half2(0.5h,-0.5h); o.uv23 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h,0.5h); return o; } fixed4 fragDownsample ( v2f_tap i ) : SV_Target { fixed4 color = tex2D (_MainTex, i.uv20); color += tex2D (_MainTex, i.uv21); color += tex2D (_MainTex, i.uv22); color += tex2D (_MainTex, i.uv23); return max(color/4 - THRESHHOLD, 0) * ONE_MINUS_THRESHHOLD_TIMES_INTENSITY; }
2.模糊参考上面。
3.效果叠加:
v2f_simple vertBloom ( appdata_img v ) { v2f_simple o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord; #if UNITY_UV_STARTS_AT_TOP o.uv2 = v.texcoord; if (_MainTex_TexelSize.y < 0.0) o.uv.y = 1.0 - o.uv.y; #endif return o; } fixed4 fragBloom ( v2f_simple i ) : SV_Target { #if UNITY_UV_STARTS_AT_TOP fixed4 color = tex2D(_MainTex, i.uv2); return color + tex2D(_Bloom, i.uv); #else fixed4 color = tex2D(_MainTex, i.uv); return color + tex2D(_Bloom, i.uv); #endif }
void OnRenderImage (RenderTexture source, RenderTexture destination) if (CheckResources() == false) { Graphics.Blit (source, destination); return; } int divider = resolution == Resolution.Low ? 4 : 2; float widthMod = resolution == Resolution.Low ? 0.5f : 1.0f; fastBloomMaterial.SetVector ("_Parameter", new Vector4 (blurSize * widthMod, 0.0f, threshold, intensity)); source.filterMode = FilterMode.Bilinear; var rtW= source.width/divider; var rtH= source.height/divider; // downsample RenderTexture rt = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt.filterMode = FilterMode.Bilinear; Graphics.Blit (source, rt, fastBloomMaterial, 1); var passOffs= blurType == BlurType.Standard ? 0 : 2; for(int i = 0; i < blurIterations; i++) { fastBloomMaterial.SetVector ("_Parameter", new Vector4 (blurSize * widthMod + (i*1.0f), 0.0f, threshold, intensity)); // vertical blur RenderTexture rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit (rt, rt2, fastBloomMaterial, 2 + passOffs); RenderTexture.ReleaseTemporary (rt); rt = rt2; // horizontal blur rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit (rt, rt2, fastBloomMaterial, 3 + passOffs); RenderTexture.ReleaseTemporary (rt); rt = rt2; } fastBloomMaterial.SetTexture ("_Bloom", rt); Graphics.Blit (source, destination, fastBloomMaterial, 0); RenderTexture.ReleaseTemporary (rt); }
参数:
threshold:亮度分离阈值,比如大于改值的进行Bloom效果
intensity:强度
blurSize:模糊效果size,参与降分辨率倍数计算
blurIterations:模糊次数
实现方式很简单,但是可以看出使用模糊的地方要大量进行采样计算,开销很大,所以在手机上大部分使用性能都很差,不太推荐使用,另外Bloom效果需要在亮暗效果有明显对比的场景使用才能带来较好体验,如果单纯的比较亮的游戏画面用的效果也不明显,最主要还是采样过多消耗太大,当然可以减少采样但是效果也就减小了。本来处于优化模糊的思路想在黑的地方不进行模糊计算,但是发现效果不太对。所以还是得在效果和性能之间找一个平衡点吧。模糊其实很有意思,图像算法中很多其他地方都有模糊概念的思想在参与 比如软阴影和抗锯齿可以理解为对边缘点进行模糊处理,但只是算法不同,但都是要采样周全点进行一定的插值或者公式计算。
来自:https://blog.csdn.net/gy373499700/article/details/79681052