前面已经写了几个shader还没教大家怎么用,这节就交大家怎么去配合GL和shader使用做场切。场切是一个场景过渡到另外一个场景时的过渡动画,那么它的显示的级别应该是最高的吧,它应该覆盖掉所有的场景中的东西,所以应该在屏幕上绘制,这样层级是最高的,所以unity为我们提供了模板方法—OnPostRender();只要类继承MonoBehaviour就可以了,这里就会出现另外一个问题,现在我已经做好了一个场切了,我觉得不是很好,然后我又想换一个,所以我花了一早上又做了一个场切的类,结果他们的代码都很像,几乎没怎么变,就是OnPostRender()方法体里面发生了一点变化,还有就是初始化的属性和值可能不一样,如果这样去写下去的话,我做了10几种场切,然后重复的代码都写了好多了。我们写代码就是在达到目的同时也为了最大程度上实现代码的复用,所以这里将会讲到设计模式里面的模板方法,我可以定义一个抽象的基类,然后拓展的时候只需继承这个基类,然后重写一下基类里面的几个方法,然后我们就实现了不同效果的场切代码,我觉得这才是我们应该追求的东西。
好了前面讲到了好多,开始写我们的代码了,首先这个基类应该继承MonoBehaviour吧,然后写一个OnPostRender()方法吧。当然我们可以吧初始化Awake方法改成虚方法,可以让子类重写,应该每种效果需要初始化的值可能不一样。还有一个重要的东西就是我们要给负责场切的物体添加相机(这个应该很好理解,没有相机是什么都看不到的);所以具体抽象类的代码如下:
public abstract class BaseTranEffect : SMTransition
{
public Material holdMaterial;
private Camera tempCamera;
private bool reentrantLock = false;
protected virtual void Awake()
{
if (holdMaterial == null)
{
Debug.LogError("'Hold' material is missing");
}
tempCamera = gameObject.AddComponent<Camera>();
tempCamera.cullingMask = 0;
tempCamera.renderingPath = RenderingPath.Forward;
tempCamera.depth = Mathf.Floor(float.MaxValue);
tempCamera.clearFlags = CameraClearFlags.Depth;
}
void OnPostRender()
{
if (reentrantLock)
{
return;
}
reentrantLock = true;
StartCoroutine(ProcessFrame());
}
IEnumerator ProcessFrame()
{
yield return new WaitForEndOfFrame();
if (state == SMTransitionState.Prefetch)
{
reentrantLock = false;
yield break;
}
if (state == SMTransitionState.Hold)
{
OnRenderHold();
}
else
{
OnRender();
}
reentrantLock = false;
}
protected virtual void OnRenderHold()
{
GL.PushMatrix();
GL.LoadOrtho();
GL.LoadIdentity();
for (var i = 0; i < holdMaterial.passCount; ++i)
{
holdMaterial.SetPass(i);
GL.Begin(GL.QUADS);
GL.TexCoord3(0, 0, 0);
GL.Vertex3(0, 0, 0);
GL.TexCoord3(0, 1, 0);
GL.Vertex3(0, 1, 0);
GL.TexCoord3(1, 1, 0);
GL.Vertex3(1, 1, 0);
GL.TexCoord3(1, 0, 0);
GL.Vertex3(1, 0, 0);
GL.End();
}
GL.PopMatrix();
}
protected abstract void OnRender();
}
这里我写了一个简单的场切代码,代码如下:
public class CartoonEffect :BaseTranEffect
{
private float length;
private float _progress;
public Shader CartoonEffectShader;
public float Duration;
private Material _material;
protected override void OnRender()
{
GL.PushMatrix();//首先我们要把材质以矩阵的形式压入
GL.LoadOrtho();//给定我们是按0-1计算还是用像素来计算,如果是像素的话下面的GL.TexCoord3(0, 1, 0)应该改成GL.TexCoord3(0, 1280, 0),这里1280表示这里分辨率。一般这里都是GL.LoadOrtho()。
GL.LoadIdentity();//基本的初始化
_material.SetFloat("_Distance", (1 - _progress) * (length));//为shader里面的_Distance变量赋值
for (var i = 0; i < _material.passCount; ++i)
{
_material.SetPass(i);//这里对应shader里面有多少个pass块。
GL.Begin(GL.QUADS);//表示在屏幕上绘矩形。下面对应的是贴UV和指定顶点。
GL.TexCoord3(0, 0, 0);
GL.Vertex3(0, 0, 0);
GL.TexCoord3(0, 1, 0);
GL.Vertex3(0, 1, 0);
GL.TexCoord3(1, 1, 0);
GL.Vertex3(1, 1, 0);
GL.TexCoord3(1, 0, 0);
GL.Vertex3(1, 0, 0);
GL.End();
}
GL.PopMatrix();
}
protected override void Prepare()
{
if (_material == null)
{
_material = new Material(CartoonEffectShader);
_material.SetTexture("_Background", holdMaterial.mainTexture);
}
Vector2 pixelCenter = new Vector2(0.5f * Screen.width, 0.5f * Screen.height);
Vector2 bottomLeftPath = pixelCenter - new Vector2(0, 0);
Vector2 topLeftPath = pixelCenter - new Vector2(0, Screen.height);
Vector2 topRightPath = pixelCenter - new Vector2(Screen.width, Screen.height);
Vector2 bottomRightPath = pixelCenter - new Vector2(Screen.width, 0);
length = Mathf.Max(bottomLeftPath.magnitude, topLeftPath.magnitude, topRightPath.magnitude, bottomRightPath.magnitude);
_material.SetFloat("_CenterX", pixelCenter.x);
_material.SetFloat("_CenterY", pixelCenter.y);
_material.SetColor("_BorderColor", new Color(.5f, 0, 0, 1));
}
protected override bool Process(float elapsedTime)
{
float effectTime = elapsedTime;
if (state == SMTransitionState.In)
{
effectTime = Duration - effectTime;
}
_progress = SMTransitionUtils.SmoothProgress(0, Duration, effectTime);
return elapsedTime < Duration;
}
}
如果你还想再写一个场切的类,那就直接继承上面那个基类就行了,然后重写OnRender(),Prepare(),Process()方法,3个方法的功能分别是渲染部分,准备部分,运行的时候。当然也可以把process方法放到基类里面去,因为每个场切都会随着时间而结束嘛,并且他们结束都应该是和时间有关,和绘制之间的耦合性比较低。所以我建议把它放到基类中去,所以这里可以只用重写2个方法了。