大家好,又见面了,我是你们的朋友全栈君。
简介
实现原理
屏幕水波纹效果,但是,往往我们并不希望全屏幕都发生扭曲,而是只希望某些地方发生了扭曲,比如上面的火炉的做法,拼关的同学肯定是希望在火炉的上方放一个特效片,就能够出扭曲的效果。那么,我们的这个片就需要是一个可以显示后面所有物体的片,换句话说,我们需要在这个面片上渲染面片后面所有的东西,这样,面片看起来就是透明的了。然后我们在采样uv的时候将uv进行偏移,就能够得到扭曲的效果了。恩,听起来很简单的样子,但是我们要怎么得到面片后面的所有东西呢?其实Unity已经为我们提供了这样的一个功能,GrabPass。下面看一下Grabpass的使用。
GrabPass
//Grabpass shader
//by: puppet_master
//2017.4.23
Shader "ApcShader/GrabPass"
{
SubShader
{
ZWrite Off
//GrabPass
GrabPass
{
//此处给出一个抓屏贴图的名称,抓屏的贴图就可以通过这张贴图来获取,而且每一帧不管有多个物体使用了该shader,只会有一个进行抓屏操作
//如果此处为空,则默认抓屏到_GrabTexture中,但是据说每个用了这个shader的都会进行一次抓屏!
"_GrabTempTex"
}
Pass
{
Tags
{
"RenderType" = "Transparent"
"Queue" = "Transparent+1"
}
CGPROGRAM
sampler2D _GrabTempTex;
float4 _GrabTempTex_ST;
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 grabPos : TEXCOORD0;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//计算抓屏的位置,其中主要是将坐标从(-1,1)转化到(0,1)空间并处理DX和GL纹理反向的问题
o.grabPos = ComputeGrabScreenPos(o.pos);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//根据抓屏位置采样Grab贴图,tex2Dproj等同于tex2D(grabPos.xy / grabPos.w)
fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos);
return 1 - color;
}
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
我们找个面片,附上这个shader的材质。为了更方便的看一下效果,我们就参照官网的写法,直接将最终输出的颜色反向,也就是1-原颜色作为输出(这个颜色不禁让我想起了宇智波鼬的月读……..)
inline float4 ComputeGrabScreenPos (float4 pos) {
#if UNITY_UV_STARTS_AT_TOP
float scale = -1.0;
#else
float scale = 1.0;
#endif
float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y*scale) + o.w;
#ifdef UNITY_SINGLE_PASS_STEREO
o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
o.zw = pos.zw;
return o;
}
我们传递进来的参数是经过mvp变换后的顶点坐标,传入之后这个函数主要做了两件事情,第一个是处理DX和OpenGL纹理坐标差异导致的问题,这个
之前的文章有记录过。第二件事主要就是将转化到标准裁剪空间(-1,1)区间的顶点转化到(0,1)区间。按照Unity的写法,本人推测,这个GrabPass获取的屏幕贴图应该是基于视空间的,而在这个信息传递到fragment shader后,用了tex2Dproj函数进行采样,tex2Dproj(i.xy)应该等同于tex2D(i.xy/i.w),也就是说这个采样点坐标进行了一次投影变换。
扭曲效果的实现
fixed4 frag(v2f i) : SV_Target { i.grabPos.x += _DistortStrength * sin(_Time.y * 10); i.grabPos.y += _DistortStrength * sin(_Time.y); fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos); return 1 - color; }
那么所有的顶点就都会按照一致的方向进行偏移:
//Distort shader
//by: puppet_master
//2017.4.24
Shader "ApcShader/Distort"
{
Properties
{
_DistortStrength("DistortStrength", Range(0,1)) = 0.2
_DistortTimeFactor("DistortTimeFactor", Range(0,1)) = 1
_NoiseTex("NoiseTexture", 2D) = "white" {}
}
SubShader
{
ZWrite Off
Cull Off
//GrabPass
GrabPass
{
//此处给出一个抓屏贴图的名称,抓屏的贴图就可以通过这张贴图来获取,而且每一帧不管有多个物体使用了该shader,只会有一个进行抓屏操作
//如果此处为空,则默认抓屏到_GrabTexture中,但是据说每个用了这个shader的都会进行一次抓屏!
"_GrabTempTex"
}
Pass
{
Tags
{
"RenderType" = "Transparent"
"Queue" = "Transparent + 100"
}
CGPROGRAM
sampler2D _GrabTempTex;
float4 _GrabTempTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _DistortStrength;
float _DistortTimeFactor;
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 grabPos : TEXCOORD1;
};
v2f vert(appdata_base v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.grabPos = ComputeGrabScreenPos(o.pos);
o.uv = TRANSFORM_TEX(v.texcoord, _NoiseTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//首先采样噪声图,采样的uv值随着时间连续变换,而输出一个噪声图中的随机值,乘以一个扭曲快慢系数
float4 offset = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);
//用采样的噪声图输出作为下次采样Grab图的偏移值,此处乘以一个扭曲力度的系数
i.grabPos.xy -= offset.xy * _DistortStrength;
//uv偏移后去采样贴图即可得到扭曲的效果
fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos);
return color;
}
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
基于后处理的优化效果
这篇文章解释得很好。文章中给了几种解决方案,一种是关抗锯齿,一个是用GL3.0,最后一个是直接改为用渲染到纹理。记得以前还看过一个帖子,不过忘记链接了,这个做法比较极端,就是最终渲染的结果都不走frame buffer,而是都渲染到一个纹理上。然后所有的后处理都在这个纹理上进行,完全绕开了OnRenderImage。额,不小心扯远了,只是希望能给和我遇到一样问题的倒霉蛋一点参考,下面进行正题。
一篇文章,作者给了这样的一个思路,感觉非常巧妙。简而言之,这个方法作扭曲的部分是用全屏后处理进行的,但是全屏都扭曲了,我们其实只需要扭曲一部分地方,所以我们需要一个Mask图来控制,而这张Mask图我们就可以直接用另一个相机渲染出来,其实就是我们上面用到的特效片,渲染到一个RT上就可以了。相比于用另一个摄像机把场景中的东西都渲染一遍,这种方式只是需要额外渲染一个片外加一次全屏后处理操作,两者各有千秋,视具体情况而定。
PostEffectBase类,实现后处理的C#部分代码:
/********************************************************************
FileName: DistortEffect.cs
Description: 屏幕扭曲效果
Created: 2017/04/27
by: puppet_master
*********************************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DistortEffect : PostEffectBase {
//扭曲的时间系数
[Range(0.0f, 1.0f)]
public float DistortTimeFactor = 0.15f;
//扭曲的强度
[Range(0.0f, 0.2f)]
public float DistortStrength = 0.01f;
//噪声图
public Texture NoiseTexture = null;
public void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (_Material)
{
_Material.SetTexture("_NoiseTex", NoiseTexture);
_Material.SetFloat("_DistortTimeFactor", DistortTimeFactor);
_Material.SetFloat("_DistortStrength", DistortStrength);
Graphics.Blit(source, destination, _Material);
}
else
{
Graphics.Blit(source, destination);
}
}
}
然后shader部分,扭曲的原理与上面一样,只是处理的对象变了一下,直接处理OnRenderImage传来的MainTex即可:
//全屏幕扭曲Shader
//by:puppet_master
//2017.4.28
Shader "Custom/DistortPostEffect"
{
Properties
{
_MainTex("Base (RGB)", 2D) = "white" {}
_NoiseTex("Base (RGB)", 2D) = "black" {}//默认给黑色,也就是不会偏移
}
CGINCLUDE
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform sampler2D _NoiseTex;
uniform float _DistortTimeFactor;
uniform float _DistortStrength;
fixed4 frag(v2f_img i) : SV_Target
{
//根据时间改变采样噪声图获得随机的输出
float4 noise = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);
//以随机的输出*控制系数得到偏移值
float2 offset = noise.xy * _DistortStrength;
//像素采样时偏移offset
float2 uv = offset + i.uv;
return tex2D(_MainTex, uv);
}
ENDCG
SubShader
{
Pass
{
ZTest Always
Cull Off
ZWrite Off
Fog{ Mode off }
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
ENDCG
}
}
Fallback off
}
描边效果这篇文章中,我们用过类似的方法。这里,我们故技重施,将需要扭曲的部分,也就是上面我们用的面片渲染到一张RenderTarget上,首先,我们还是创建一个新的摄像机,然后通过在OnPreRender函数中用RenderWithShader,将面片渲染到一张RT上(这个RT可以多降低一些分辨率),渲染的shader就用一个纯白色的shader就可以了。比如下面的这个Shader:
//Mask图生成shader
//by:puppet_master
//2017.5.3
Shader "ApcShader/MaskObjPrepass"
{
//子着色器
SubShader
{
Pass
{
Cull Off
CGPROGRAM
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//这个Pass直接输出颜色
return fixed4(1,1,1,1);
}
//使用vert函数和frag函数
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
/********************************************************************
FileName: DistortEffect.cs
Description: 屏幕扭曲效果
Created: 2017/04/27
by: puppet_master
*********************************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DistortEffect : PostEffectBase {
//扭曲的时间系数
[Range(0.0f, 1.0f)]
public float DistortTimeFactor = 0.15f;
//扭曲的强度
[Range(0.0f, 0.2f)]
public float DistortStrength = 0.01f;
//噪声图
public Texture NoiseTexture = null;
//渲染Mask图所用的shader
public Shader maskObjShader = null;
//降采样系数
public int downSample = 4;
private Camera mainCam = null;
private Camera additionalCam = null;
private RenderTexture renderTexture = null;
public void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (_Material)
{
_Material.SetTexture("_NoiseTex", NoiseTexture);
_Material.SetFloat("_DistortTimeFactor", DistortTimeFactor);
_Material.SetFloat("_DistortStrength", DistortStrength);
_Material.SetTexture("_MaskTex", renderTexture);
Graphics.Blit(source, destination, _Material);
}
else
{
Graphics.Blit(source, destination);
}
}
void Awake()
{
//创建一个和当前相机一致的相机
InitAdditionalCam();
}
private void InitAdditionalCam()
{
mainCam = GetComponent<Camera>();
if (mainCam == null)
return;
Transform addCamTransform = transform.FindChild("additionalDistortCam");
if (addCamTransform != null)
DestroyImmediate(addCamTransform.gameObject);
GameObject additionalCamObj = new GameObject("additionalDistortCam");
additionalCam = additionalCamObj.AddComponent<Camera>();
SetAdditionalCam();
}
private void SetAdditionalCam()
{
if (additionalCam)
{
additionalCam.transform.parent = mainCam.transform;
additionalCam.transform.localPosition = Vector3.zero;
additionalCam.transform.localRotation = Quaternion.identity;
additionalCam.transform.localScale = Vector3.one;
additionalCam.farClipPlane = mainCam.farClipPlane;
additionalCam.nearClipPlane = mainCam.nearClipPlane;
additionalCam.fieldOfView = mainCam.fieldOfView;
additionalCam.backgroundColor = Color.clear;
additionalCam.clearFlags = CameraClearFlags.Color;
additionalCam.cullingMask = 1 << LayerMask.NameToLayer("Distort");
additionalCam.depth = -999;
//分辨率可以低一些
if (renderTexture == null)
renderTexture = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample, 0);
}
}
void OnEnable()
{
SetAdditionalCam();
additionalCam.enabled = true;
}
void OnDisable()
{
additionalCam.enabled = false;
}
void OnDestroy()
{
if (renderTexture)
{
RenderTexture.ReleaseTemporary(renderTexture);
}
DestroyImmediate(additionalCam.gameObject);
}
//在真正渲染前的回调,此处渲染Mask遮罩图
void OnPreRender()
{
//maskObjShader进行渲染
if (additionalCam.enabled)
{
additionalCam.targetTexture = renderTexture;
additionalCam.RenderWithShader(maskObjShader, "");
}
}
}
还是上面的测试场景,我们将面片改为Distort层级,然后可以直接给这个面片设置一个透明的材质,比如最简单的粒子的shader,让它正常渲染不可见即可:
//全屏幕扭曲Shader
//by:puppet_master
//2017.5.3
Shader "Custom/DistortPostEffect"
{
Properties
{
_MainTex("Base (RGB)", 2D) = "white" {}
_NoiseTex("Noise", 2D) = "black" {}//默认给黑色,也就是不会偏移
_MaskTex("Mask", 2D) = "black" {}//默认给黑色,权重为0
}
CGINCLUDE
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform sampler2D _NoiseTex;
uniform sampler2D _MaskTex;
uniform float _DistortTimeFactor;
uniform float _DistortStrength;
fixed4 frag(v2f_img i) : SV_Target
{
//根据时间改变采样噪声图获得随机的输出
float4 noise = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);
//以随机的输出*控制系数得到偏移值
float2 offset = noise.xy * _DistortStrength;
//采样Mask图获得权重信息
fixed4 factor = tex2D(_MaskTex, i.uv);
//像素采样时偏移offset,用Mask权重进行修改
float2 uv = offset * factor.r + i.uv;
return tex2D(_MainTex, uv);
}
ENDCG
SubShader
{
Pass
{
ZTest Always
Cull Off
ZWrite Off
Fog{ Mode off }
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
ENDCG
}
}
Fallback off
}
扭曲效果动态图如下:
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/128178.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...