Design Hub

    • 注册
    • 登录
    • 搜索
    • 版块
    • 最新

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

    游戏开发
    1
    1
    203
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    • 在新帖中回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • Pamisu
      Pamisu 管理员 最后由 Pamisu 编辑

      第一人称睁眼苏醒效果

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

      效果

      本篇文章中介绍的思路适用于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 入门精要》12.4节中的高斯模糊,新建一个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自定义后处理)

      1 条回复 最后回复 回复 引用
      • 1 / 1
      • First post
        Last post
      版权所有 ©2023 Design Hub 保留所有权利
      鄂ICP备2021004164号-2