johnsoncodehk 发表于 2022-2-25 15:12

手写Unity Shader 热扭曲效果

大家好,我是老莫,今天分享一个热扭曲效果的实现。首先看一下效果。
场景资源地址Lighting Optimisation Tutorial | Tutorial Projects | Unity Asset Store


首先列一下可能需要的属性
      _DistortTex("Texture", 2D)            = "white" {}
      _DistortValue("DistortValue",Range(0,1))= 1
      _DistortSpeed("DistortSpeed",float)       = 1
GrabPass
在unityshader中,我们可以使用GrabPass实现屏幕抓取。有两种写法。

GrabPass{}
**********
************
sampler2D _GrabTex;1、GrabPass{} 抓取当前屏幕存储到_GrabTexture中,然后我们就可以直接调用_GrabTexture,抓取当前屏幕。但是这样写有个弊端,就是每一个使用了此shader 的物体都睡抓取一次屏幕,消耗很大。可以在FrameDebug中看到。
2。我们一般用第二种方法,GrabPass{"GrabTex"}   {}中的纹理名称可以自定义。使用了指定的纹理名称时,不管场景中有多少物体(同意shader),都只会抓取一次屏幕,大大减少运算量。
所以我们这里使用第二种方法
GrabPass{"_GrabTex"}定义GrabPass{"GrabTex"} 之后,需要写入sampler2D _GrabTex; 用于后面的调用
和模型采样贴图一样,有了纹理,我们还需要UV。unity中屏幕UV的获取方式为ComputeScreenPos(o.pos),注意这里的pos是进行了齐次剪裁后的值,原理是在齐次剪裁空间坐标下获取每个像素在屏幕中的UV坐标。ComputeScreenPos可以自动根据不同平台匹配屏幕坐标,因为DX和OpenGL的坐标轴起始点位置是不同的。这里更深入的知识点后面我会单独写一篇文章详述。
o. pos   = UnityObjectToClipPos(v.vertex);   
o.screenUV = ComputeScreenPos(o.pos);接下来采样我们抓取到的纹理看一下效果
fixed4 grabTex = tex2Dproj(_GrabTex,1-i.screenUV);


抓取成功,接下来我们让抓取到的纹理动起来,并且加上扭曲效果。
采样一张noise图,不用太讲究,网上随便下载一张


这里在片段着色器中做一个常规的uv移动,采样屏幕纹理时,在uv上加一个简单的lerp
fixed4 distortTex = tex2D(_DistortTex,i.uv.xy + _Time.xy * _DistortSpeed );
fixed4 grabTex = tex2Dproj(_GrabTex,lerp(i.screenUV,distortTex ,_DistortValue));


这时候我们所要的功能基本都已经实现了:屏幕抓取,UV扭曲+移动,但是边缘过于生硬,需要进一步优化一下。我们只需要中间火团的部分有热扭曲效果,那么我们需要得到一张这样的蒙版,可以自己在PS做一个,也可以在shader中生成,这里我随便拿公式写一个。


            float fade = pow(1-length(i.maskUV-0.5),_Radius);

               fixed4 grabTex = tex2Dproj(_GrabTex,lerp(i.screenUV,distortTex * fade,_DistortValue));
加上蒙版后再看一下效果,和没有加热扭曲效果的火把对比一下。


到这里今天的热扭曲效果就制作完成了,游戏中我们经常能在不经意中看到这种小细节,而往往是这种容易被忽略的细枝末节,最能表现出制作人的用心,也最能给玩家一种“此物从何来,对之惊且喜”之感。我是老莫,希望大家喜欢此次分享,不吝赐教!
以下是完整shader代码
Shader "Unlit/Distort04"
{
    Properties
    {
      _DistortTex("Texture", 2D)            = "white" {}
      _DistortValue("DistortValue",Range(0,1))= 1
      _DistortSpeed("DistortSpeed",float)       = 1
      _Radius ( "_Radius",float ) =1
    }
    SubShader
    {
      Tags { "Queue" = "Transparent"}
      LOD 100
      Cull Off

      GrabPass{"_GrabTex"}//如果使用仅GrabPass{} 则每个使用了抓取shader的物体都会调用一次抓取,消耗大

      Pass
      {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct appdate{
                float2 uv       : TEXCOORD0;
                float4 vertex   : POSITION;
               
            };

            struct v2f
            {
                float2 uv       : TEXCOORD0;
                float2 maskUV   : TEXCOORD1;
                fixed4 pos      : SV_POSITION;
                float4 screenUV : TEXCOORD2;
            };

            sampler2D _GrabTex;
            sampler2D _DistortTex; float4 _DistortTex_ST;
            fixed _DistortValue;
            float _DistortSpeed;
            float _Radius;
            

            v2f vert (appdate v )
            {
                v2f o;
               
                o.uv = TRANSFORM_TEX(v.uv,_DistortTex) ;
                o.maskUV = v.uv;
                o. pos = UnityObjectToClipPos(v.vertex);
                o.screenUV = ComputeScreenPos(o.pos);//根据不同平台匹配屏幕坐标(DX或opengl)
               
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            
            {

               fixed4 distortTex = tex2D(_DistortTex,i.uv.xy + _Time.xy * _DistortSpeed );

               float fade = pow(1-length(i.maskUV-0.5),_Radius);

               fixed4 grabTex = tex2Dproj(_GrabTex,lerp(i.screenUV,distortTex * fade,_DistortValue));//和下面的结果一样
      
               return grabTex;

            }
            ENDCG
      }
    }
}

RecursiveFrog 发表于 2022-2-25 15:15

谢谢大佬,好东西,收藏起来[赞同]
页: [1]
查看完整版本: 手写Unity Shader 热扭曲效果