[Unity]第一人称睁眼苏醒效果

第一人称睁眼苏醒效果

做一个简单的睁眼苏醒效果,也可用于眨眼、闭眼、昏睡等等:

效果

本篇文章中介绍的思路适用于Built-in渲染管线,如果您需要在URP中实现,可参考我的这篇文章,本篇文章末尾也有URP对应的仓库地址。

首先编写一个AwakeScreenEffect.cs脚本:

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class AwakeScreenEffect : MonoBehaviour
{
    public Shader shader;

    [SerializeField]
    Material material;
    Material Material 
    {
        get 
        {
            if (material == null)
            {
                material = new Material(shader);
                material.hideFlags = HideFlags.DontSave;
            }
            return material;
        }
    }

    void OnDisable() 
    {
        if (material)
        {
            DestroyImmediate(material);
        }
    }

    void OnRenderImage(RenderTexture src, RenderTexture dest) 
    {
        // TODO 处理...
    }
}

将脚本挂在相机上,接下来编写相应的shader。目标效果中,从闭眼到睁眼的过程用一个进度0~1表示,当进度从0到1时,眼睛逐渐睁开,视野从模糊逐渐变为清晰。

创建AwakeScreenEffect.shader,_Progress表示当前苏醒进度:

Shader "Custom/Awake Screen Effect"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Progress ("Progress", Range(0, 1)) = 1
    }
    SubShader
    {
        ZTest Always ZWrite Off Cull Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                half2 uv : TEXCOORD0;
            };

            struct v2f
            {
                half2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float _Progress;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                half2 uv = i.uv;
                fixed4 col = tex2D(_MainTex, uv);
                // TODO ...
                return col;
            }
            ENDCG
        }

    }

    Fallback Off
}

先写上下眼皮,它们的边界值分别是屏幕中线(0.5)加或减去当前进度乘以0.5,得出边界值后通过step函数对uv.v进行裁剪,大于上眼皮边界、小于下眼皮边界时裁剪值为0,否则为1,最后将它与颜色相乘得出效果:

fixed4 frag (v2f i) : SV_Target
{
    half2 uv = i.uv;
    fixed4 col = tex2D(_MainTex, uv);
    // 上眼皮与下眼皮边界
    float upBorder = .5 + _Progress * .5;
    float downBorder = .5 - _Progress * .5;
    // 可视区域
    float visibleV = (1 - step(upBorder, uv.y)) * (step(downBorder, uv.y));
    col *= visibleV;
    return col;
}

AwakeScreenEffect.cs中加入可调节的进度变量,OnRenderImage方法中应用:

...
public class AwakeScreenEffect : MonoBehaviour
{
    [Range(0f, 1f)][Tooltip("苏醒进度")]
    public float progress;
    ...
    void OnRenderImage(RenderTexture src, RenderTexture dest) 
    {
        Material.SetFloat("_Progress", progress);
        Graphics.Blit(src, dest, material);
    }
}

可以看到初步效果:

除非是能人异士,不然大部分人的眼皮都不是这样一条直线,shader中定义一个眼皮弧形的高度值_ArchHeight:

Properties
{
    ...
    _ArchHeight ("Arch Height", Range (0, .5)) = .2
}

用二次函数做出弧度:

float _ArchHeight;
fixed4 frag (v2f i) : SV_Target
{
    ...
    // 上眼皮与下眼皮边界
    float upBorder = .5 + _Progress * (.5 + _ArchHeight);
    float downBorder = .5 - _Progress * (.5 + _ArchHeight);
    upBorder -=  _ArchHeight * pow(uv.x - .5, 2);
    downBorder += _ArchHeight * pow(uv.x - .5, 2);
    ...
}

上下边界由原来的* .5改为* (.5 + _ArchHeight),用来调整上下边界随_Progress的变化范围,避免_Progress为1时仍有黑边的情况。

眼皮弧度

再加入模糊,模糊效果直接使用了《Unity Shader 入门精要》中的高斯模糊,新建一个GaussianBlur.shader,拷贝代码,确认一下shader和Pass的命名无误:

Shader "Custom/Gaussian Blur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _BlurSize ("Blur Size", Float) = 1
    }
    SubShader
    {
        ...
        Pass
        {
            NAME "GAUSSIAN_BLUR_VERTICAL"
            ...
        }
        Pass
        {
            NAME "GAUSSIAN_BLUR_HORIZONTAL"
            ...
        }
    }
    ...
}

AwakeScreenEffect.shader中原有Pass之后使用高斯模糊的两个Pass:

SubShader
{
    ...
    Pass
    {
        ...
    }
    ...
    UsePass "Custom/Gaussian Blur/GAUSSIAN_BLUR_VERTICAL"
    UsePass "Custom/Gaussian Blur/GAUSSIAN_BLUR_HORIZONTAL"
}

AwakeScreenEffect.cs加入模糊需要用到的参数:

[Range(0, 4)][Tooltip("模糊迭代次数")]
public int blurIterations = 3;
[Range(.2f, 3f)][Tooltip("每次模糊迭代时的模糊大小扩散")]
public float blurSpread = .6f;

修改OnRenderImage方法,基本和书中的差不多,不同的是没有使用降采样。随着progress从0变为1,blurSize也逐渐变为0。

void OnRenderImage(RenderTexture src, RenderTexture dest) 
{
    Material.SetFloat("_Progress", progress);
    if (progress < 1)
    {
        // 由于降采样会影响模糊到清晰的连贯性,这里没有使用
        int rtW = src.width;
        int rtH = src.height;
        var buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
        buffer0.filterMode = FilterMode.Bilinear;
        Graphics.Blit(src, buffer0, Material, 0);   // 眼皮Pass
        // 模糊
        float blurSize;
        for (int i = 0; i < blurIterations; i++)
        {
            // 将progress(0~1)映射到blurSize(blurSize~0)
            blurSize = 1f + i * blurSpread;
            blurSize = blurSize - blurSize * progress;
            Material.SetFloat("_BlurSize", blurSize);
            // 竖直方向的Pass
            var buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
            Graphics.Blit(buffer0, buffer1, Material, 1);
            RenderTexture.ReleaseTemporary(buffer0);
            // 竖直方向的Pass
            buffer0 = buffer1;
            buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
            Graphics.Blit(buffer0, buffer1, Material, 2);

            RenderTexture.ReleaseTemporary(buffer0);
            buffer0 = buffer1;
        }
        Graphics.Blit(buffer0, dest);
        RenderTexture.ReleaseTemporary(buffer0);
    }
    else
    {
        // 完全苏醒则无需处理,直接blit
        Graphics.Blit(src, dest);
    }
}
模糊配置
模糊

画面由暗转亮比较简单,AwakeScreenEffect.shader让颜色乘以_Progress即可:

fixed4 frag (v2f i) : SV_Target
{
    ...
    col *= _Progress;
    return col;
}
变暗

最后用Animator录制一个动画就完成了。

Demo项目地址:
Procedural-Map-Demo(Built-in渲染管线)
pamisu-kit-unity(URP自定义后处理)

你可能感兴趣的:([Unity]第一人称睁眼苏醒效果)