C#使用Shader实现夜幕降临倒计时的效果

2019-12-30 14:24:06刘景俊

最近火爆全球的PC游戏Battlerite(战争仪式)在倒计时的会生成一种类似夜幕降临的效果,会以战场中心为圆心,某个长度为半径的范围外是暗的,而这个半径会逐渐缩小,而圆之外的阴暗部分是附着地形的,本文就尝试使用屏幕后处理的手段来实现这种效果。

(暂时缺少Battlerite的截图,稍后会补上)

首先看效果图:

c#夜幕降临,c#倒计时

注:本文参考了Tasharen Fog of War插件

创建一个C#脚本,命名为NightFall.cs,为NightFall类创建一些公共变量(nightColor,center和radius),另外还需要一个NightFall.shader。

首先,我们要确定这个效果是在场景渲染之后还未送到屏幕显示之前的实现的,所以,NightFall脚本是要挂载到主Camera上的(添加特性[RequireComponent(typeof(Camera))]),并要实现OnRenderImage方法。

其次,在OnRenderImage方法里,我们最终需要调用Graphics.Blit方法,而这个方法的第三个参数是Material类型,所以我们需要在代码里创建一个临时材质,这个材质使用了NightFall.shader。

再次,我们需要在Shader里面将屏幕坐标转换为世界坐标,来计算与世界中心的坐标,所以我们需要MVP的逆矩阵(参考Shader山下(十六)坐标空间与转换矩阵)。

最后,为了附着地形,我们需要在Shader计算深度,也就是坐标点与摄像机的相对距离,所以需要摄像机的位置。

C#的代码:

using UnityEngine;


[RequireComponent(typeof(Camera))] 
public class NightFall : MonoBehaviour 
{ 
public Shader shader; 
public Color nightColor = new Color(0.05f, 0.05f, 0.05f, 0.5f); 
public Vector3 center = Vector3.zero; 
public float radius = 10; 
Camera mCam; 
Matrix4x4 mInverseMVP; 
Material mMat; 
/// The camera we're working with needs depth. 
void OnEnable () 
{ 
mCam = GetComponent<Camera>(); 
mCam.depthTextureMode = DepthTextureMode.Depth; 
if (shader == null) shader = Shader.Find("Image Effects/NightFall"); 
} 
/// Destroy the material when disabled. 
void OnDisable () { if (mMat) DestroyImmediate(mMat); } 
/// Automatically disable the effect if the shaders don't support it. 
void Start () 
{ 
if (!SystemInfo.supportsImageEffects || !shader || !shader.isSupported) 
{ 
enabled = false; 
} 
} 
// Called by camera to apply image effect 
void OnRenderImage (RenderTexture source, RenderTexture destination) 
{ 
print (nightColor); 
print (destination); 
// Calculate the inverse modelview-projection matrix to convert screen coordinates to world coordinates 
mInverseMVP = (mCam.projectionMatrix * mCam.worldToCameraMatrix).inverse; 
if (mMat == null) 
{ 
mMat = new Material(shader); 
mMat.hideFlags = HideFlags.HideAndDontSave; 
} 
Vector4 camPos = mCam.transform.position; 
// This accounts for Anti-aliasing on Windows flipping the depth UV coordinates. 
// Despite the official documentation, the following approach simply doesn't work: 
// http://www.easck.com/Documentation/Components/SL-PlatformDifferences.html 
if (QualitySettings.antiAliasing > 0) 
{ 
RuntimePlatform pl = Application.platform; 
if (pl == RuntimePlatform.WindowsEditor || 
pl == RuntimePlatform.WindowsPlayer || 
pl == RuntimePlatform.WindowsWebPlayer) 
{ 
camPos.w = 1f; 
} 
} 
mMat.SetVector("_CamPos", camPos); 
mMat.SetMatrix("_InverseMVP", mInverseMVP); 
mMat.SetColor("_NightColor", nightColor); 
mMat.SetVector ("_Center", center); 
mMat.SetFloat ("_Radius", radius); 
Graphics.Blit(source, destination, mMat); 
} 
}