Unity Shader-后处理:时空扭曲效果

版权声明:欢迎转载,共同进步。请注明出处:http://blog.csdn.net/puppet_master https://blog.csdn.net/puppet_master/article/details/71437031

简介


最近刚刚通关《耻辱2》,在有一关的时候,竟然送给我了一个能够穿越时空的“神器”,有了这货,就可以一下子传送到过去或者回到现在。在使用这个道具的时候,会有一个屏幕扭曲的穿越的效果,感觉效果不错。

在穿越了无数回之后,我终于下决心准备在Unity里面实现一个简化的版本(肯定还原不到这种大作的水平,就当是练习啦,2333)。

屏幕收缩的效果


观察上面的效果图,最明显的一块就是屏幕有一个收缩的效果,这也是这种屏幕扭曲里面最明显也最容易实现的一部分。首先,扭曲效果就是uv偏移,超哪偏移,这个我们可以自己输入一个点给shader,默认就是屏幕中心点。我们让每个采样的点都朝着我们定义的中心点的方向偏移一段距离,就可以实现类似的屏幕收缩的效果。关于扭曲效果的基本知识可以参考一下 屏幕水波纹效果以及 热空气扭曲效果,这里就不多说了。下面附上收缩效果第一版。
c#脚本如下:
/********************************************************************
 FileName: PassthoughEffect.cs
 Description: "传说中的穿越"效果
 Created: 2017/05/07
 by :puppet_master
*********************************************************************/
using UnityEngine;

public class PassthoughEffect : PostEffectBase
{

    //扭曲强度
    [Range(0, 0.15f)]
    public float distortFactor = 1.0f;
    //扭曲中心(0-1)屏幕空间,默认为中心点
    public Vector2 distortCenter = new Vector2(0.5f, 0.5f);

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            _Material.SetFloat("_DistortFactor", distortFactor);
            _Material.SetVector("_DistortCenter", distortCenter);
            Graphics.Blit(source, destination, _Material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }

    }
}
shader代码如下:
//屏幕收缩效果
//by:puppet_master
Shader "ApcShader/PaththoughEffect"
 {
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	
	CGINCLUDE
	uniform sampler2D _MainTex;
	uniform float _DistortFactor;	//扭曲强度
	uniform float4 _DistortCenter;	//扭曲中心点xy值(0-1)屏幕空间
	#include "UnityCG.cginc"

	fixed4 frag(v2f_img i) : SV_Target
	{
		//计算偏移的方向
		float2 dir = i.uv - _DistortCenter.xy;
		//最终偏移的值:方向 * (1-长度),越靠外偏移越小
		float2 offset = _DistortFactor * normalize(dir) * (1 - length(dir));
		//计算采样uv值:正常uv值+从中间向边缘逐渐增加的采样距离
		float2 uv = i.uv + offset;
		return tex2D(_MainTex, uv);
	}
	ENDCG

	SubShader 
	{
		Pass
		{
			ZTest Always
			Cull Off 
			ZWrite Off
			Fog { Mode off }
			
			//调用CG函数	
			CGPROGRAM
			//使效率更高的编译宏
			#pragma fragmentoption ARB_precision_hint_fastest 
			//vert_img是在UnityCG.cginc中定义好的,当后处理vert阶段计算常规,可以直接使用自带的vert_img
			#pragma vertex vert_img
			#pragma fragment frag 
			ENDCG
		}
	}
}
找个测试场景,未收缩前的效果如下:
调整扭曲系数后,屏幕朝中心点收缩的效果如下:


屏幕扭曲效果与收缩效果结合


有了屏幕收缩效果,最基本的功能完成了。但是我们观察上面的动态图,还能发现,在马上要“穿越”到另一个场景的时候,屏幕会出现比较强烈的扰动效果,也正好是在这个状态下,进行的场景切换。其实这个状态很重要,切场景就相当于准备演员神马的,还是需要放块幕布之类的遮一下,不然就都现场直播了。我们需要做的就是让屏幕尽可能地扭曲,看不粗来到底发生了什么就好了。

关于扭曲效果,在上一篇文章中已经介绍过原理并且实现过一次,不过这里的扭曲要和上面的屏幕收缩相结合。两者都是uv偏移的原理,这里我选择把两者进行相减操作,首先计算正常屏幕收缩的uv偏移值,这个偏移值一般比较大。然后再计算根据一张噪声图,得到一个小的uv偏移值,作为扰动的uv偏移,两者结合就能够得到最终扭曲+扰动的效果。
c#脚本如下,增加了一个噪声图槽位以及扰动控制系数:
/********************************************************************
 FileName: PassthoughEffect.cs
 Description: "传说中的穿越"效果
 Created: 2017/05/07
 by :puppet_master
*********************************************************************/
using UnityEngine;

public class PassthoughEffect : PostEffectBase
{

    //收缩强度
    [Range(0, 0.15f)]
    public float distortFactor = 1.0f;
    //扭曲中心(0-1)屏幕空间,默认为中心点
    public Vector2 distortCenter = new Vector2(0.5f, 0.5f);
    //噪声图
    public Texture NoiseTexture = null;
    //屏幕扰动强度
    [Range(0, 2.0f)]
    public float distortStrength = 1.0f;

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            _Material.SetTexture("_NoiseTex", NoiseTexture);
            _Material.SetFloat("_DistortFactor", distortFactor);
            _Material.SetVector("_DistortCenter", distortCenter);
            _Material.SetFloat("_DistortStrength", distortStrength);
            Graphics.Blit(source, destination, _Material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }

    }
}
shader代码如下:
//屏幕收缩效果
//by:puppet_master
Shader "ApcShader/PaththoughEffect"
 {
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_NoiseTex("Noise", 2D) = "black"{}
	}
	
	CGINCLUDE
	uniform sampler2D _MainTex;
	uniform sampler2D _NoiseTex;
	uniform float _DistortFactor;	//扭曲强度
	uniform float4 _DistortCenter;	//扭曲中心点xy值(0-1)屏幕空间
	uniform float _DistortStrength; 
	#include "UnityCG.cginc"

	fixed4 frag(v2f_img i) : SV_Target
	{
		//计算偏移的方向
		float2 dir = i.uv - _DistortCenter.xy;
		//最终偏移的值:方向 * (1-长度),越靠外偏移越小
		float2 scaleOffset = _DistortFactor * normalize(dir) * (1 - length(dir));
		//采样Noise贴图
		fixed4 noise = tex2D(_NoiseTex, i.uv);
		//noise的权重 = 参数 * 距离,越靠近外边的部分,扰动越严重
		float2 noiseOffset = noise.xy * _DistortStrength * dir;
		//计算最终offset = 两种扭曲offset的差(取和也行,总之效果好是第一位的)
		float2 offset = scaleOffset - noiseOffset;
		//计算采样uv值:正常uv值+从中间向边缘逐渐增加的采样距离
		float2 uv = i.uv + offset;
		return tex2D(_MainTex, uv);
	}
	ENDCG

	SubShader 
	{
		Pass
		{
			ZTest Always
			Cull Off 
			ZWrite Off
			Fog { Mode off }
			
			//调用CG函数	
			CGPROGRAM
			//使效率更高的编译宏
			#pragma fragmentoption ARB_precision_hint_fastest 
			//vert_img是在UnityCG.cginc中定义好的,当后处理vert阶段计算常规,可以直接使用自带的vert_img
			#pragma vertex vert_img
			#pragma fragment frag 
			ENDCG
		}
	}
}
还是上面的测试场景,这次,我们用一个噪声图,比如下图所示的这种:
然后设置一下扰动的权重,就可以看到,场景除了朝中心收缩了,还带有了扰动的效果:

动态的收缩和扭曲效果


我们实现了收缩和扭曲的效果。和上面的动图比较的话,除了挫了点,就是不能动了。下面研究下怎么让这个动起来。首先,这种类型的控制,基本就不能再在shader里面做了,我们下面要做的,就是动态改变我们在静态时设置的几个参数。在update或者开一个协程,让收缩系数从0逐渐增加到最大,然后迅速降低为0;在快要结束时,突然增大扰动系数,并执行切换镜头等的操作。

先来看收缩效果,这里呢,我们为了方便调整,直接给一个曲线控制。曲线控制可以得到一些特别好玩的效果,而且可以免去我们写很多麻烦的控制代码,还可以直接把效果参数开放出来,让策划和美术同学根据需要来调整,最主要的是可以顺便偷下懒,2333。还是上面的shader,我们仅仅修改了C#脚本,用一个协程控制采样曲线:
/********************************************************************
 FileName: PassthoughEffect.cs
 Description: "传说中的穿越"效果
 Created: 2017/05/10
 by :puppet_master
*********************************************************************/
using UnityEngine;
using System.Collections;

public class PassthoughEffect : PostEffectBase
{

    //收缩强度
    [Range(0, 0.15f)]
    public float distortFactor = 1.0f;
    //扭曲中心(0-1)屏幕空间,默认为中心点
    public Vector2 distortCenter = new Vector2(0.5f, 0.5f);
    //噪声图
    public Texture NoiseTexture = null;
    //屏幕扰动强度
    [Range(0, 2.0f)]
    public float distortStrength = 1.0f;

    //屏幕收缩总时间
    public float passThoughTime = 4.0f;
    //当前时间
    private float currentTime = 0.0f;
    //曲线控制权重
    public float curveFactor = 0.2f;
    //屏幕收缩效果曲线控制
    public AnimationCurve curve;

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            _Material.SetTexture("_NoiseTex", NoiseTexture);
            _Material.SetFloat("_DistortFactor", distortFactor);
            _Material.SetVector("_DistortCenter", distortCenter);
            _Material.SetFloat("_DistortStrength", distortStrength);
            Graphics.Blit(source, destination, _Material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }

    //ContexMenu,可以直接在Component上右键调用该函数,比较好用的小技巧哈
    [ContextMenu("Play")]
    public void StartPassThoughEffect()
    {
        currentTime = 0.0f;
        StartCoroutine(UpdatePassthoughEffect());
    }

    private IEnumerator UpdatePassthoughEffect()
    {
        while(currentTime < passThoughTime)
        {
            currentTime += Time.deltaTime;
            //根据时间占比在曲线(0,1)区间采样,再乘以权重作为收缩系数
            distortFactor = curve.Evaluate(currentTime / passThoughTime) * curveFactor;
            yield return null;
            //结束时强行设置为0
            distortFactor = 0.0f;
        }
    }


}
然后呢,我们就可以来调整曲线来得到我们希望的效果啦。《耻辱》中的扭曲效果是一个缓慢加大,然后再突然恢复的一个过程,我们用曲线调整一个类似的效果,如下:
得到的效果如下面的动态图所示,基本还原了这种收缩的效果:


下面就是在收缩到最大的时候,给一个比较大的扰动系数,达到转场的目的。尴尬,好不容易调好的曲线,把变量改了个名字,曲线丢掉了...只好重新调一遍。最终版本如下:
/********************************************************************
 FileName: PassthoughEffect.cs
 Description: "传说中的穿越"效果
 Created: 2017/05/10
 by :puppet_master
*********************************************************************/
using UnityEngine;
using System.Collections;

public class PassthoughEffect : PostEffectBase
{

    //收缩强度
    [Range(0, 0.15f)]
    public float distortFactor = 1.0f;
    //扭曲中心(0-1)屏幕空间,默认为中心点
    public Vector2 distortCenter = new Vector2(0.5f, 0.5f);
    //噪声图
    public Texture NoiseTexture = null;
    //屏幕扰动强度
    [Range(0, 2.0f)]
    public float distortStrength = 1.0f;

    //屏幕收缩总时间
    public float passThoughTime = 4.0f;
    //当前时间
    private float currentTime = 0.0f;
    //曲线控制权重
    public float scaleCurveFactor = 0.2f;
    //屏幕收缩效果曲线控制
    public AnimationCurve scaleCurve;
    //扰动曲线系数
    public float distortCurveFactor = 1.0f;
    //屏幕扰动效果曲线控制
    public AnimationCurve distortCurve;

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            _Material.SetTexture("_NoiseTex", NoiseTexture);
            _Material.SetFloat("_DistortFactor", distortFactor);
            _Material.SetVector("_DistortCenter", distortCenter);
            _Material.SetFloat("_DistortStrength", distortStrength);
            Graphics.Blit(source, destination, _Material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }

    //ContexMenu,可以直接在Component上右键调用该函数,比较好用的小技巧哈
    [ContextMenu("Play")]
    public void StartPassThoughEffect()
    {
        currentTime = 0.0f;
        StartCoroutine(UpdatePassthoughEffect());
    }

    private IEnumerator UpdatePassthoughEffect()
    {
        while(currentTime < passThoughTime)
        {
            currentTime += Time.deltaTime;
            float t = currentTime / passThoughTime;
            //根据时间占比在曲线(0,1)区间采样,再乘以权重作为收缩系数
            distortFactor = scaleCurve.Evaluate(t) * scaleCurveFactor;
            distortStrength = distortCurve.Evaluate(t) * distortCurveFactor;
            yield return null;
            //结束时强行设置为0
            distortFactor = 0.0f;
            distortStrength = 0.0f;
        }
    }
}
各项参数以及扰动曲线设置如下图所示,噪声图换了一张其他的:
最终效果如下面动图所示:


漩涡扭曲效果


漩涡扭曲效果,本来打算尝试一下卡卡西的“神威”技能效果,不过效果不是很好,所以只实现了一版漩涡扭曲的效果,以后有时间再慢慢研究。说道漩涡类型的东东,第一个想到的应该就是sin,cos函数,这俩跟漩涡非常搭边。然后我们分析一下漩涡的效果,我们先需要在fragment shader中对应每个像素点先平移到中心点(也可以是我们自己定义的中心),然后让这个像素点绕着当前中心点的轴旋转一定角度,最后再将这个像素点平移回去,就能够达到了整个图像绕着固定点旋转的操作。而为了让漩涡效果看起来更加自然,我们需要按照离中心点的距离作为权重,缩放旋转的角度值,离中心点越远的像素点,旋转值越小,而离中心点越近的位置,旋转得就越剧烈,这是一个反比的关系,所以我们在给旋转值的时候,除以一个距离中心点的距离即可。下面附上漩涡扭曲效果的shader以及C#脚本。
Shader部分如下,将扭曲部分改为漩涡形,附加一个噪声扰动的效果:
//漩涡扭曲效果
//by:puppet_master
Shader "ApcShader/RotationDistortEffect"
 {
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_NoiseTex("Noise", 2D) = "black"{}
	}
	
	CGINCLUDE
	uniform sampler2D _MainTex;
	uniform sampler2D _NoiseTex;
	uniform float _DistortFactor;	//扭曲强度
	uniform float4 _DistortCenter;	//扭曲中心点xy值(0-1)屏幕空间
	uniform float _DistortStrength; 
	#include "UnityCG.cginc"

	fixed4 frag(v2f_img i) : SV_Target
	{
		//平移坐标点到中心点,同时也是当前像素点到中心的方向
		fixed2 dir = i.uv - _DistortCenter.xy;
		//计算旋转的角度:对于像素点来说,距离中心越远,旋转越少,所以除以距离。相当于用DistortFactor作为旋转的角度值Distort/180 * π,π/180 = 0.1745
		float rot = _DistortFactor * 0.1745 / (length(dir) + 0.001);//+0.001防止除零
		//计算sin值与cos值,构建旋转矩阵
		fixed sinval, cosval;
		sincos(rot, sinval, cosval);
		float2x2  rotmatrix = float2x2(cosval, -sinval, sinval, cosval);
		//旋转
		dir = mul(dir, rotmatrix);
		//再平移回原位置
		dir += _DistortCenter.xy;
		//采样noise图
		fixed4 noise = tex2D(_NoiseTex, i.uv);
		//noise的权重 = 参数 * 距离,越靠近外边的部分,扰动越严重
		float2 noiseOffset = noise.xy * _DistortStrength * dir;
		//用偏移过的uv+扰动采样MainTex
		return tex2D(_MainTex, dir + noiseOffset);
	}
	ENDCG

	SubShader 
	{
		Pass
		{
			ZTest Always
			Cull Off 
			ZWrite Off
			Fog { Mode off }
			
			//调用CG函数	
			CGPROGRAM
			//使效率更高的编译宏
			#pragma fragmentoption ARB_precision_hint_fastest 
			//vert_img是在UnityCG.cginc中定义好的,当后处理vert阶段计算常规,可以直接使用自带的vert_img
			#pragma vertex vert_img
			#pragma fragment frag 
			ENDCG
		}
	}
}
C#脚本部分,仍然使用了两条曲线进行控制,一条控制旋转值,一条控制扰动值:
/********************************************************************
 FileName: PassthoughEffect.cs
 Description: 漩涡扭曲效果
 Created: 2017/05/10
 by :puppet_master
*********************************************************************/
using UnityEngine;
using System.Collections;

public class RotationDistortEffect : PostEffectBase
{

    //收缩强度
    [Range(0, 20.0f)]
    public float distortFactor = 1.0f;
    //扭曲中心(0-1)屏幕空间,默认为中心点
    public Vector2 distortCenter = new Vector2(0.5f, 0.5f);
    //噪声图
    public Texture NoiseTexture = null;
    //屏幕扰动强度
    [Range(0, 2.0f)]
    public float distortStrength = 1.0f;

    //屏幕扭曲时间
    public float passThoughTime = 3.0f;
    //当前时间
    private float currentTime = 0.0f;
    //曲线控制权重
    public float rotationCurveFactor = 10.0f;
    //屏幕全传效果曲线控制
    public AnimationCurve rotationCurve;
    //扰动曲线系数
    public float distortCurveFactor = 0.1f;
    //屏幕扰动效果曲线控制
    public AnimationCurve distortCurve;

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (_Material)
        {
            _Material.SetTexture("_NoiseTex", NoiseTexture);
            _Material.SetFloat("_DistortFactor", distortFactor);
            _Material.SetVector("_DistortCenter", distortCenter);
            _Material.SetFloat("_DistortStrength", distortStrength);
            Graphics.Blit(source, destination, _Material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }

    //ContexMenu,可以直接在Component上右键调用该函数,比较好用的小技巧哈
    [ContextMenu("Play")]
    public void StartPassThoughEffect()
    {
        currentTime = 0.0f;
        StartCoroutine(UpdatePassthoughEffect());
    }

    private IEnumerator UpdatePassthoughEffect()
    {
        while (currentTime < passThoughTime)
        {
            currentTime += Time.deltaTime;
            float t = currentTime / passThoughTime;
            //根据时间占比在曲线(0,1)区间采样,再乘以权重作为收缩系数
            distortFactor = rotationCurve.Evaluate(t) * rotationCurveFactor;
            distortStrength = distortCurve.Evaluate(t) * distortCurveFactor;
            yield return null;
            //结束时强行设置为0
            distortFactor = 0.0f;
            distortStrength = 0.0f;
        }
    }
}
配置两条曲线以及参数:

漩涡扭曲效果动态图如下:




猜你喜欢

转载自blog.csdn.net/puppet_master/article/details/71437031
今日推荐