泛光是由于人眼晶状体散射产生的视觉效果,用于产生如梦似幻,朦朦胧胧的感觉。Unity中自带的图像效果中,有两种实现方式,Bloom 和 BloomOptimized,单就泛光来说,两者差不多。不过前者兼顾了HDR和Flare比较复杂,而后者只是单纯的Bloom,下面就BloomOptimized简单分析一下。
同颜色校正一样,Bloom也通过重写OnRenderImage方法,来进行图像处理。首先,对原图像进行DownSample
1 2 3 4 5 6 7 8 9 10 |
fastBloomMaterial.SetVector ("_Parameter", new Vector4 (blurSize , 0.0f, threshold, intensity)); source.filterMode = FilterMode.Bilinear; var rtW= source.width/2; var rtH= source.height/2; // downsample RenderTexture rt = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt.filterMode = FilterMode.Bilinear; Graphics.Blit (source, rt, fastBloomMaterial, 1); |
这里传入Shader的几个参数分别是:
blurSize:模糊尺寸,用于后面模糊算法的直径。
threshold:泛光阈值,颜色亮度超过这个阈值的像素才进行泛光处理。
intensity:泛光强度。
这里的rt是一张原图1/4大小的RenderTexture,通过RenderTexutre.GetTemporary获得,Unity内部维护了一个RenderTexture池,这样获取其实拿到的是一个早已经申请好的RenderTexture,省去了临时申请的开销,而且内部针对特定平台也做了一些优化。如果在一帧中需要中间的RenderTexture,最好使用这个方式来获取临时RenderTexture,但是不用的时候,需要手动释放。RenderTexture.ReleaseTemporary。
还有就是要确保源图像和目标图像,都采用双线性过滤,这是降采样和模糊需要的优先条件。
降采样使用的Shader代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
v2f_tap vert4Tap ( appdata_img v ) { v2f_tap o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv20 = v.texcoord + _MainTex_TexelSize.xy; // _MainTex_TexelSize.xy = (1/贴图宽度 , 1/贴图高度) 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 - threshold, 0) *intensity; } |
其实这段代码的采样并不是很规整,就是取了目标点周围四个点的平均颜色,然后选出大于阈值的像素,乘上强度系数。小于阈值的直接设为0,这样的像素在后面计算中不会影响其他的像素。
下面对这张1/4屏幕像素的降采样结果进行模糊处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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, 4); RenderTexture.ReleaseTemporary (rt); rt = rt2; // horizontal blur rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit (rt, rt2, fastBloomMaterial, 5); RenderTexture.ReleaseTemporary (rt); rt = rt2; } |
Shader中使用的一维的高斯模糊,用先纵向再横向的方式进行了两次模糊处理。
下面是高斯模糊系数数组:
1 2 |
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) }; |
Vertex Shader如下:在顶点上方和下方各取了3个采样点,一共7个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
v2f_withBlurCoordsSGX vertBlurHorizontalSGX (appdata_img v) { v2f_withBlurCoordsSGX o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord.xy; half2 netFilterWidth = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x; half4 coords = -netFilterWidth.xyxy * 3.0; o.offs[0] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); coords += netFilterWidth.xyxy; o.offs[1] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); coords += netFilterWidth.xyxy; o.offs[2] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); return o; } |
Fragment Shader如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
half4 fragBlurSGX ( v2f_withBlurCoordsSGX i ) : SV_Target { half2 uv = i.uv.xy; half4 color = tex2D(_MainTex, i.uv) * curve4[3]; for( int l = 0; l < 3; l++ ) { half4 tapA = tex2D(_MainTex, i.offs[l].xy); half4 tapB = tex2D(_MainTex, i.offs[l].zw); color += (tapA + tapB) * curve4[l]; } return color; } |
各个采样点的颜色使用高斯模糊系数加权相加,就得到了模糊后的像素颜色,横向的算法相同,只不过采样点是在横向上选择的。
1 2 |
fastBloomMaterial.SetTexture ("_Bloom", rt); Graphics.Blit (source, destination, fastBloomMaterial, 0); |
最后,把降采样,模糊后的RenderTexture与原图像叠加,就完成了Bloom效果。叠加Shader如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
v2f_simple vertBloom ( appdata_img v ) { v2f_simple o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord; o.uv2 = v.texcoord; if (_MainTex_TexelSize.y < 0.0) o.uv.y = 1.0 - o.uv.y; return o; } fixed4 fragBloom ( v2f_simple i ) : SV_Target { fixed4 color = tex2D(_MainTex, i.uv2); return color + tex2D(_Bloom, i.uv); } |