二十:Unity 后处理 Bloom mask,基于深度和单个物件的Bloom
后处理效果,在游戏画面表现中,是很常用的,Bloom是其中之一。在我当前项目的实践中,美术在调节战斗场景表现的过程中,提出了两个需求:
希望让场景中远景有较强的bloom效果,但是近景尽量少受到Bloom影响,且能自由调节
无bloom效果
无bloom效果
全局bloom效果,可以看到,远景和近景,包括近景的Cube,都呈现较强的Bloom效果
全局bloom效果
基于深度的Bloom强度调节,对比可见,远景Bloom强度不变,中景略低,近景几乎没有
基于深度的Bloom强度调节
结果上,这是美术希望得到的场景效果,美术可以根据深度自由调节Bloom强度
希望对某些攻击的刀光,特效等,强化Bloom效果(特别是将近景Bloom调弱的情况下),达到突出打击感的效果
这个需求的本质,就是希望针对单个物体(我们的需求主要是特效)调节其Bloom强度,例如:左cube不受Bloom影响,中cube受一点影响,右cube受强影响
以上是结果展示,我来简单说下解决思路和方法。
需求一:希望让场景中远景有较强的bloom效果,但是近景尽量少受到Bloom影响,且能自由调节
这个需求的解决思路比较简单,核心是两点:
获取渲染的深度图将调节的曲线和深度映射,计算出最终的效果
以下是我基于Unity 官方PostProcessing V2做的扩展
示例
想要在shader中获取深度图,需要开启camera的depth开关
camera.depthTextureMode = DepthTextureMode.Depth;
第二步,将根据深度映射的curve,转换未一张lut贴图,在Shader中取值
将curve转化为lut图
将lut提交材质
在shader中将深度和lut数值做映射,获取最终效果
这个问题的解决相对简单,带来的开销主要是需要渲染深度图,以及在shader中两次tex2D消耗,经过测试,对性能影响较小并可控。
需求二:希望对某些攻击的刀光,特效等,强化Bloom效果(特别是将近景Bloom调弱的情况下),达到突出打击感的效果
这个需求相对第一个更复杂一些,核心思路是对单个需要的物体做标记,生成一张mask图,在shader中做相应的映射
生成这个mask有两种思路,
是用另一个相机将标记的特殊物件单独渲染一遍,生成一张rendertexure mask在当前渲染中,将标记物体输出到现有渲染中一个不需要使用的通道上,常见的是alpha 通道
顺着这两种思路,我都找到已经有的文章,故原理就不详述了:
方法1:
方法2:
我总结一下两种方法的利弊:
方法1:好处是:实现比较简单,并且很容易控制当前效果开关,对项目的倾入性改动较小,后续维护成本小。坏处是:因为物体需要额外渲染一遍,性能开销会大一些。
方法2:好处是:性能开销小一些。坏处是:对项目的倾入性修改大,例如如果要实现此功能,需要shader层级的支持,需要修改所有用到的shader,并且对透明物体需要两个pass实现,对于不需要此功能的半透明物体,带来额外开销。
我个人选择了方法1,主要考虑的因素是我们需要处理的物体主要是半透明的特效(相对方法2的两次pass,性能开销并不会差特别多。),对项目倾入性改动小,并且我们没有专职TA,这种方法后续维护的负担小。
如果项目有专职TA,会专职维护项目中所有shader,或者需要处理的物体主要是不透明物体(不需要两次pass),可以尝试方法2。
我再简单介绍下我使用方法1的处理:
给需要处理的物体挂脚本,用于设置Bloom强度
using AdvancedInspector;
using UnityEngine;
public class CustomBloomObj : MonoBehaviour
{
public float bloomFactor = 0;
private Renderer[] _renders;
private const int CustomBloomLayerDefine = 14;
static int PID_bloomFactor = 0;
public void setBloomFactor()
{
if (PID_bloomFactor == 0)
{
PID_bloomFactor = Shader.PropertyToID("_bloomFactor");
}
if (_renders == null)
{
_renders = GetComponentsInChildren<Renderer>();
}
if (_renders.Length > 0)
{
for (int i = 0; i < _renders.Length; i++)
{
if (_renders.enabled)
{
_renders.gameObject.layer = CustomBloomLayerDefine;
Material[] mats = _renders.materials;
for (int j = 0; j < mats.Length; j++)
{
mats.SetFloat(PID_bloomFactor, bloomFactor);
}
}
}
}
}
public static uint ActiveObjCount;
private void OnEnable()
{
setBloomFactor();
ActiveObjCount++;
}
private void OnDisable()
{
setBloomFactor();
if (ActiveObjCount > 0)
{
ActiveObjCount--;
}
}
}通过Camera replace shader的方式,使用较小的开销来渲染mask到rendertexture上
using System;
using UnityEngine;
using System.Collections;
using AdvancedInspector;
using Engine;
using UnityEngine.SceneManagement;
public class CustomBloomCtrler
{
private Camera _customBloomCamera;
private RenderTexture _customBloomTexture;
public Texture customBloomTexture
{
get
{
if (CustomBloomObj.ActiveObjCount == 0)
{
return null;
}
return _customBloomTexture;
}
}
public void doRender(Camera mainCamera)
{
if (!Application.isPlaying)
return;
if (CustomBloomObj.ActiveObjCount == 0)
{
return;
}
if (mainCamera == null)
{
return;
}
if (mainCamera.enabld == false)
{
return;
}
if (mainCamera.cullingMask == 0)
{
return;
}
if (_customBloomCamera == null)
{
GameObject obj = new GameObject(&#34;customBloomCamera&#34;);
obj.transform.SetParent(mainCamera.transform, false);
_customBloomCamera = obj.AddComponent<Camera>();
_customBloomCamera.cullingMask = LayerDefine.CustomBloom_Mask;
_customBloomCamera.clearFlags = CameraClearFlags.SolidColor;
_customBloomCamera.backgroundColor = Color.black;
_customBloomCamera.enabled = false;
#if UNITY_EDITOR
_customBloomCamera.SetReplacementShader(Shader.Find(&#34;CustomBloom/Replace&#34;), &#34;RenderType&#34;);//不透明部分,替换为简单shader,透明部分不渲染,提高性能
#else
_customBloomCamera.SetReplacementShader(ShaderBinder.me.getShader(&#34;CustomBloom/Replace&#34;), &#34;RenderType&#34;);//不透明部分,替换为简单shader,透明部分不渲染,提高性能
#endif
_customBloomTexture = new RenderTexture((int)(Screen.width * 0.5f), (int)(Screen.height * 0.5f), 16);
_customBloomTexture.name = &#34;_customBloomTexture&#34;;
_customBloomCamera.targetTexture = _customBloomTexture;
}
_customBloomCamera.fieldOfView = mainCamera.fieldOfView;
_customBloomCamera.nearClipPlane = mainCamera.nearClipPlane;
_customBloomCamera.farClipPlane = mainCamera.farClipPlane;
_customBloomCamera.Render();
}
}
replace shader:此处需要处理透明和不透明两种情况
Shader &#34;CustomBloom/Replace&#34;
{
Properties
{
_bloomFactor (&#34;Bloom Factor&#34;, Float) = 0
_MainTex (&#34;Main Tex&#34;, 2D) = &#34;white&#34; {}
}
SubShader
{
Tags { &#34;RenderType&#34;=&#34;Opaque&#34; }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;UnityCG.cginc&#34;
struct vertexInput
{
float4 vertex : POSITION;
};
struct vertexOutput
{
float4 pos : SV_POSITION;
};
uniform fixed _bloomFactor;
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.pos = UnityObjectToClipPos(input.vertex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float4 c = float4(1, _bloomFactor, 0, 1);
return c;
}
ENDCG
}
}
SubShader
{
Tags { &#34;RenderType&#34;=&#34;Transparent&#34; }
LOD 100
Pass
{
Cull Off
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;UnityCG.cginc&#34;
sampler2D _MainTex;
fixed4 _MainTex_ST;
struct vertexInput
{
float4 vertex : POSITION;
float3 texCoord : TEXCOORD0;
};
struct vertexOutput
{
float4 pos : SV_POSITION;
fixed2 uv : TEXCOORD0;
};
uniform fixed _bloomFactor;
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.pos = UnityObjectToClipPos(input.vertex);
output.uv = TRANSFORM_TEX(input.texCoord, _MainTex);
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float4 c = tex2D(_MainTex, input.uv);
c = float4(1, _bloomFactor * c.a, 0, c.a);
return c;
}
ENDCG
}
}
}在渲染bloom前,渲染mask并传入bloom材质
shader中计算
最后,考虑到同一个物件在不同的场景下,bloom强度可能不同,需要在场景中设置一个系数,和物件的强度系数做一些运算,如上图代码中的_BloomMaskFactor 就是场景强度系数
三个特殊处理的物体mask,实际使用的是g通道
最终效果
总结:
这次分享两个需求的核心解决都是mask映射,在解决的过程中要考虑几点:
性能评估对项目的倾入性改动评估维护成本评估是否可以方便开关效果,比如只有开效果高的玩家,才给予这个效果
当解决问题的时候,特别是项目后期的改动,一定要根据项目情况选择合适方案,取得一种平衡。
谢谢观看。
评论有朋友提到:mask方案是如何处理bloom物体被遮挡的问题的呢?
这里是这样的,我以前用了一个取巧,但是不完美的方案实现过,原理就是在生成bloom mask的同时,再生成一张bloom depth texture,传入bloom shader,和主摄像机的depth做一个比较,来判断是否要bloom。相对之前的步骤改变如下:
1,replace camera,将colorBuffer和depthBuffer分别设置为两张不同的贴图,注意depth参数,要和主摄像机相同
2,将depth texture传给bloom材质
传入bloom depth texture
3,shader处理,根据depth来处理是否需要bloom,被遮挡的部分depth会小一些,0.00001这个是做为一个简单的精度处理,让自己这个物体depth一样的,可以显示,并兼容一些当物体旋转等导致的微小数值变化导致的depth差异。(此方法并不严谨)
最后得到效果如下
说明一下,这个方案我并没有完整测试,因为我们的需求是bloom 加强单个特效,特效都是短暂停留的,并且特效即使被角色挡住,bloom出来,效果上也能接受。所以,我们的方案并没有将物体遮挡这块的处理加进最终代码,因为少了这个处理,能减少一些性能消耗。
大家可以参考,如果有更好的方案,欢迎评论区提出。
页:
[1]