shader的简单应用—2

本文介绍如何在Unity中利用模板方法模式简化场景过渡动画的实现,通过抽象基类和子类继承,实现不同效果的场景过渡,减少重复代码并提高代码复用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    前面已经写了几个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个方法了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值