Unity Shader-热空气扭曲效果

2023-11-09

http://blog.csdn.net/zhou8jie/article/details/48052983

简介

千等万等终于等到了《耻辱2》打折,本以为可以爽一发了,然而各种出问题,先是steam下载速度奇慢无比,下了三天晚上好不容易下完的游戏,第一次打开给弹了个3D11CreateDeviceAndSwapChain Failed,折腾半天装了个补丁算是能打开游戏了,然而过完新手教学显卡驱动就崩了,崩了!崩了,连崩三回,差点想把坑爹的A卡从机箱掏出来顺着窗户扔出去,后来想想,为了楼下同学的生命安全,我还是忍了。好在AMD有专门为《耻辱2》R9380崩溃打了个补丁,算是拯救我于水火之中了。《耻辱2》用了ID Tech5衍生的Void引擎,看起来画面比《耻辱1》用的虚幻3好了不少。先来张帅帅哒截图,最近每天沉迷于杀杀杀,感觉自己好颓废:
一时间差点忘了自己是个程序员,差点变成游戏鉴赏博客,尴尬...下面步入正题,今天打游戏的时候路过了一个火炉,看到了火炉旁边的热空气扭曲的效果,感觉做的还是蛮逼真的,今天打算自己实现一发玩一玩:

实现原理

扭曲效果是游戏里面经常有的一个效果,说道扭曲效果,一般就是当前的画面发生了扭曲,在现实世界中一般是折射导致的,但是在图形学中,我们要模拟这种效果,原理就大不一样了。首先,我们并不会真正影响光线的传播,只是用uv的偏移来模拟扭曲的效果。有一种全屏的扭曲效果,这种是基于屏幕后处理的,可以参考前面的一篇文章 屏幕水波纹效果,但是,往往我们并不希望全屏幕都发生扭曲,而是只希望某些地方发生了扭曲,比如上面的火炉的做法,拼关的同学肯定是希望在火炉的上方放一个特效片,就能够出扭曲的效果。那么,我们的这个片就需要是一个可以显示后面所有物体的片,换句话说,我们需要在这个面片上渲染面片后面所有的东西,这样,面片看起来就是透明的了。然后我们在采样uv的时候将uv进行偏移,就能够得到扭曲的效果了。恩,听起来很简单的样子,但是我们要怎么得到面片后面的所有东西呢?其实Unity已经为我们提供了这样的一个功能,GrabPass。下面看一下Grabpass的使用。

GrabPass

GrabPass是Unity为我们提供的一个很方便的功能,可以直接将当前屏幕内容渲染到一张贴图上,我们可以直接在shader中使用这张贴图而不用自己去实现渲染到贴图这样的一个过程,大大的方便了我们的shader编写。GrabPass的使用非常简单,我们在写vertex fragment shader的时候都需要写一个pass,GrabPass也是一个pass,只不过是Unity为我们实现好的一个pass。我们只需要在我们正常的Pass前面加一个GrabPass{}就可以了。
官方文档上有两种GrabPass的写法,第一种是直接GrabPass{}的写法,这种写法抓屏的图片就直接存到_GrabTexture这个系统预定义的贴图变量中了,我们可以直接访问该贴图,但是这种写法会导致每个使用GrabPass的物体进行一次这种旷日持久的抓屏操作!如果用这种shader的物体多了的话,想想就很可怕。另一种是GrabPass{"TextureName"}的写法,其中TextureName是我们自定义的一个贴图名称,这种写法,Unity每帧只会为第一个使用了该名称的物体进行抓屏操作,之后的就可以复用这张贴图了。所以,我们还是使用第二种方式更好一点。下面附上一份最简单的抓屏代码:
[csharp]  view plain  copy
  1. //Grabpass shader  
  2. //by: puppet_master  
  3. //2017.4.23  
  4. Shader "ApcShader/GrabPass"   
  5. {  
  6.     SubShader  
  7.     {  
  8.         ZWrite Off  
  9.         //GrabPass  
  10.         GrabPass  
  11.         {  
  12.             //此处给出一个抓屏贴图的名称,抓屏的贴图就可以通过这张贴图来获取,而且每一帧不管有多个物体使用了该shader,只会有一个进行抓屏操作  
  13.             //如果此处为空,则默认抓屏到_GrabTexture中,但是据说每个用了这个shader的都会进行一次抓屏!  
  14.             "_GrabTempTex"  
  15.         }  
  16.   
  17.         Pass  
  18.         {  
  19.             Tags  
  20.             {   
  21.                 "RenderType" = "Transparent"  
  22.                 "Queue" = "Transparent+1"  
  23.             }  
  24.   
  25.             CGPROGRAM  
  26.             sampler2D _GrabTempTex;  
  27.             float4 _GrabTempTex_ST;  
  28.             #include "UnityCG.cginc"  
  29.             struct v2f  
  30.             {  
  31.                 float4 pos : SV_POSITION;  
  32.                 float4 grabPos : TEXCOORD0;  
  33.             };  
  34.   
  35.             v2f vert(appdata_base v)  
  36.             {  
  37.                 v2f o;  
  38.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
  39.                 //计算抓屏的位置,其中主要是将坐标从(-1,1)转化到(0,1)空间并处理DX和GL纹理反向的问题  
  40.                 o.grabPos = ComputeGrabScreenPos(o.pos);  
  41.                 return o;  
  42.             }  
  43.   
  44.             fixed4 frag(v2f i) : SV_Target  
  45.             {  
  46.                 //根据抓屏位置采样Grab贴图,tex2Dproj等同于tex2D(grabPos.xy / grabPos.w)  
  47.                 fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos);  
  48.                 return 1 - color;  
  49.             }  
  50.  
  51.             #pragma vertex vert  
  52.             #pragma fragment frag  
  53.             ENDCG  
  54.         }  
  55.     }  
  56. }  
我们找个面片,附上这个shader的材质。为了更方便的看一下效果,我们就参照官网的写法,直接将最终输出的颜色反向,也就是1-原颜色作为输出(这个颜色不禁让我想起了宇智波鼬的月读........)
看一下这个shader用到的几个函数,第一个是ComputeGrabScreenPos这个函数,我们从UnityCG.cginc中可以找到这个函数的实现:
[csharp]  view plain  copy
  1. inline float4 ComputeGrabScreenPos (float4 pos) {  
  2.     #if UNITY_UV_STARTS_AT_TOP  
  3.     float scale = -1.0;  
  4.     #else  
  5.     float scale = 1.0;  
  6.     #endif  
  7.     float4 o = pos * 0.5f;  
  8.     o.xy = float2(o.x, o.y*scale) + o.w;  
  9. #ifdef UNITY_SINGLE_PASS_STEREO  
  10.     o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);  
  11. #endif  
  12.     o.zw = pos.zw;  
  13.     return o;  
  14. }  
我们传递进来的参数是经过mvp变换后的顶点坐标,传入之后这个函数主要做了两件事情,第一个是处理DX和OpenGL纹理坐标差异导致的问题,这个 之前的文章有记录过。第二件事主要就是将转化到标准裁剪空间(-1,1)区间的顶点转化到(0,1)区间。按照Unity的写法,本人推测,这个GrabPass获取的屏幕贴图应该是基于视空间的,而在这个信息传递到fragment shader后,用了tex2Dproj函数进行采样,tex2Dproj(i.xy)应该等同于tex2D(i.xy/i.w),也就是说这个采样点坐标进行了一次投影变换。


扭曲效果的实现

准备工作完成,下面步入正题,来看看扭曲效果的实现。首先,要扭曲,就肯定要动,这个shader还是得需要Time系列的变量进行驱动。不过这只是其中一个条件,由于shader是高度并行化的计算,我们没有办法区分每个像素到底需要偏移多少。在屏幕水波纹效果中,我们是通过计算当前像素点到屏幕中心位置的距离作为偏移值的,对于后处理这样做可能比较方便,但是对于普通物体上使用的shader就没有那么简单了。比如,我们同样是让采样坐标按照sin值进行偏移:
[csharp]  view plain  copy
  1. fixed4 frag(v2f i) : SV_Target  
  2.             {  
  3.                 i.grabPos.x += _DistortStrength * sin(_Time.y * 10);  
  4.                 i.grabPos.y += _DistortStrength * sin(_Time.y);  
  5.                 fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos);  
  6.                 return 1 - color;  
  7.             }  
那么所有的顶点就都会按照一致的方向进行偏移:
为了让偏移变得随机,我们就要引入一个能够随机化输出的东东,也就是噪声图。比如我们找到了一张这个样子的噪声图:

然后,只需要用一个连续变化的值去采这个噪声图,就可以得到不连续的随机输出偏移值。下面附上扭曲效果的实现:
[csharp]  view plain  copy
  1. //Distort shader  
  2. //by: puppet_master  
  3. //2017.4.24  
  4. Shader "ApcShader/Distort"   
  5. {  
  6.     Properties  
  7.     {  
  8.         _DistortStrength("DistortStrength", Range(0,1)) = 0.2  
  9.         _DistortTimeFactor("DistortTimeFactor", Range(0,1)) = 1  
  10.         _NoiseTex("NoiseTexture", 2D) = "white" {}  
  11.     }  
  12.     SubShader  
  13.     {  
  14.         ZWrite Off  
  15.         Cull Off  
  16.         //GrabPass  
  17.         GrabPass  
  18.         {  
  19.             //此处给出一个抓屏贴图的名称,抓屏的贴图就可以通过这张贴图来获取,而且每一帧不管有多个物体使用了该shader,只会有一个进行抓屏操作  
  20.             //如果此处为空,则默认抓屏到_GrabTexture中,但是据说每个用了这个shader的都会进行一次抓屏!  
  21.             "_GrabTempTex"  
  22.         }  
  23.   
  24.         Pass  
  25.         {  
  26.             Tags  
  27.             {   
  28.                 "RenderType" = "Transparent"  
  29.                 "Queue" = "Transparent + 100"  
  30.             }  
  31.   
  32.             CGPROGRAM  
  33.             sampler2D _GrabTempTex;  
  34.             float4 _GrabTempTex_ST;  
  35.             sampler2D _NoiseTex;  
  36.             float4 _NoiseTex_ST;  
  37.             float _DistortStrength;  
  38.             float _DistortTimeFactor;  
  39.             #include "UnityCG.cginc"  
  40.             struct v2f  
  41.             {  
  42.                 float4 pos : SV_POSITION;  
  43.                 float2 uv : TEXCOORD0;  
  44.                 float4 grabPos : TEXCOORD1;  
  45.             };  
  46.   
  47.             v2f vert(appdata_base v)  
  48.             {  
  49.                 v2f o;  
  50.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
  51.                 o.grabPos = ComputeGrabScreenPos(o.pos);  
  52.                 o.uv = TRANSFORM_TEX(v.texcoord, _NoiseTex);  
  53.                 return o;  
  54.             }  
  55.   
  56.             fixed4 frag(v2f i) : SV_Target  
  57.             {  
  58.                 //首先采样噪声图,采样的uv值随着时间连续变换,而输出一个噪声图中的随机值,乘以一个扭曲快慢系数  
  59.                 float4 offset = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);  
  60.                 //用采样的噪声图输出作为下次采样Grab图的偏移值,此处乘以一个扭曲力度的系数  
  61.                 i.grabPos.xy -= offset.xy * _DistortStrength;  
  62.                 //uv偏移后去采样贴图即可得到扭曲的效果  
  63.                 fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos);  
  64.                 return color;  
  65.             }  
  66.  
  67.             #pragma vertex vert  
  68.             #pragma fragment frag  
  69.             ENDCG  
  70.         }  
  71.     }  
  72. }  
为了更加应景,我搜刮了一下我的资源库,找到了一个火把,2333:
然后在火把附近放一个面片,用上我们的扭曲shader:
最终效果如下图所示:

基于后处理的优化效果

GrabPass非常耗时,在安卓平台也会有问题,虽然对于安卓机的性能,用shader lod直接干掉扭曲效果也是一个不错的选择,不过这个毕竟是下策,首先还是要解决这个问题。正常渲染是往frame buffer中渲染,但是grabpass应该是从当前的frame buffer中将内容再读出来,从显存往内存中拷贝,应该是一个阻塞的过程,我记得之前一帧渲染过3000ms,简直可怕。PS:这种情况在两个(或多个)相机渲染,后面的相机没有Clear并且在后面的相机上挂了后处理的时候也会出现这种情况,猜测原因也是因为在后面的相机进行后处理时需要上一个相机的内容,然而这个东东已经在frame buffer中了,所以后处理如果要在上层相机运用,最好还是慎重考虑一下。关于用后处理卡的问题, 这篇文章解释得很好。文章中给了几种解决方案,一种是关抗锯齿,一个是用GL3.0,最后一个是直接改为用渲染到纹理。记得以前还看过一个帖子,不过忘记链接了,这个做法比较极端,就是最终渲染的结果都不走frame buffer,而是都渲染到一个纹理上。然后所有的后处理都在这个纹理上进行,完全绕开了OnRenderImage。额,不小心扯远了,只是希望能给和我遇到一样问题的倒霉蛋一点参考,下面进行正题。
既然GrabPass比较费,那么最简单的,我们可能会想直接用另外一个相机去渲染这个场景到一个RenderTarget上,然后用这个RenderTarget代替我们上面用的GrabTexture。不过这种做法会导致DrawCall翻倍,如果我们的场景中内容较少,比较适合用这种方法。或者我们可以设置另一个相机的层级,使之只渲染某些内容,这样也可以降低一些开销。不过这里就不用这种方式了。之前看到了 一篇文章,作者给了这样的一个思路,感觉非常巧妙。简而言之,这个方法作扭曲的部分是用全屏后处理进行的,但是全屏都扭曲了,我们其实只需要扭曲一部分地方,所以我们需要一个Mask图来控制,而这张Mask图我们就可以直接用另一个相机渲染出来,其实就是我们上面用到的特效片,渲染到一个RT上就可以了。相比于用另一个摄像机把场景中的东西都渲染一遍,这种方式只是需要额外渲染一个片外加一次全屏后处理操作,两者各有千秋,视具体情况而定。
我们先写一个全屏扭曲的shader,首先,需要后处理,我们继承这个已经用了无数次的 PostEffectBase类,实现后处理的C#部分代码:
[csharp]  view plain  copy
  1. /******************************************************************** 
  2.  FileName: DistortEffect.cs 
  3.  Description: 屏幕扭曲效果 
  4.  Created: 2017/04/27 
  5.  by: puppet_master 
  6. *********************************************************************/  
  7. using System.Collections;  
  8. using System.Collections.Generic;  
  9. using UnityEngine;  
  10.   
  11. public class DistortEffect : PostEffectBase {  
  12.   
  13.     //扭曲的时间系数  
  14.     [Range(0.0f, 1.0f)]  
  15.     public float DistortTimeFactor = 0.15f;  
  16.     //扭曲的强度  
  17.     [Range(0.0f, 0.2f)]  
  18.     public float DistortStrength = 0.01f;  
  19.     //噪声图  
  20.     public Texture NoiseTexture = null;  
  21.   
  22.     public void OnRenderImage(RenderTexture source, RenderTexture destination)  
  23.     {  
  24.         if (_Material)  
  25.         {  
  26.             _Material.SetTexture("_NoiseTex", NoiseTexture);  
  27.             _Material.SetFloat("_DistortTimeFactor", DistortTimeFactor);  
  28.             _Material.SetFloat("_DistortStrength", DistortStrength);  
  29.             Graphics.Blit(source, destination, _Material);  
  30.         }  
  31.         else  
  32.         {  
  33.             Graphics.Blit(source, destination);  
  34.         }  
  35.     }  
  36. }  
然后shader部分,扭曲的原理与上面一样,只是处理的对象变了一下,直接处理OnRenderImage传来的MainTex即可:
[csharp]  view plain  copy
  1. //全屏幕扭曲Shader  
  2. //by:puppet_master  
  3. //2017.4.28  
  4.   
  5. Shader "Custom/DistortPostEffect"  
  6. {  
  7.     Properties  
  8.     {  
  9.         _MainTex("Base (RGB)", 2D) = "white" {}  
  10.         _NoiseTex("Base (RGB)", 2D) = "black" {}//默认给黑色,也就是不会偏移  
  11.     }  
  12.   
  13.     CGINCLUDE  
  14.     #include "UnityCG.cginc"  
  15.     uniform sampler2D _MainTex;  
  16.     uniform sampler2D _NoiseTex;  
  17.     uniform float _DistortTimeFactor;  
  18.     uniform float _DistortStrength;  
  19.   
  20.     fixed4 frag(v2f_img i) : SV_Target  
  21.     {  
  22.         //根据时间改变采样噪声图获得随机的输出  
  23.         float4 noise = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);  
  24.         //以随机的输出*控制系数得到偏移值  
  25.         float2 offset = noise.xy * _DistortStrength;  
  26.         //像素采样时偏移offset  
  27.         float2 uv = offset + i.uv;  
  28.         return tex2D(_MainTex, uv);  
  29.     }  
  30.   
  31.     ENDCG  
  32.   
  33.     SubShader  
  34.     {  
  35.         Pass  
  36.         {  
  37.             ZTest Always  
  38.             Cull Off  
  39.             ZWrite Off  
  40.             Fog{ Mode off }  
  41.   
  42.             CGPROGRAM  
  43.             #pragma vertex vert_img  
  44.             #pragma fragment frag  
  45.             #pragma fragmentoption ARB_precision_hint_fastest   
  46.             ENDCG  
  47.         }  
  48.     }  
  49.     Fallback off  
  50. }  
这样,整个屏幕就都扭曲了,动图如下(赶脚好像来到了沙漠一样.....):
这里我把扭曲的强度设置得高一些,感觉也可以直接当一些全屏后处理的样子,比如扭曲,水幕效果:
我们有了全屏的扭曲效果之后,下面我们考虑要怎么把需要扭曲的部分抠出来。那么,第一个想到的就是Mask图,我们可以给一个Mask图,作为权重,白色为需要偏移的权重,黑色为无偏移的权重,这样,我们就可以控制哪个地方需要扭曲。但是,这里,我们的Mask图需要是一个动态的Mask图,因为相机会移动,所以,我们需要实时地生成这张Mask图。在 描边效果这篇文章中,我们用过类似的方法。这里,我们故技重施,将需要扭曲的部分,也就是上面我们用的面片渲染到一张RenderTarget上,首先,我们还是创建一个新的摄像机,然后通过在OnPreRender函数中用RenderWithShader,将面片渲染到一张RT上(这个RT可以多降低一些分辨率),渲染的shader就用一个纯白色的shader就可以了。比如下面的这个Shader:
[csharp]  view plain  copy
  1. //Mask图生成shader  
  2. //by:puppet_master  
  3. //2017.5.3  
  4.   
  5. Shader "ApcShader/MaskObjPrepass"  
  6. {  
  7.     //子着色器    
  8.     SubShader  
  9.     {  
  10.         Pass  
  11.         {     
  12.             Cull Off  
  13.             CGPROGRAM  
  14.             #include "UnityCG.cginc"  
  15.               
  16.             struct v2f  
  17.             {  
  18.                 float4 pos : SV_POSITION;  
  19.             };  
  20.               
  21.             v2f vert(appdata_full v)  
  22.             {  
  23.                 v2f o;  
  24.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
  25.                 return o;  
  26.             }  
  27.               
  28.             fixed4 frag(v2f i) : SV_Target  
  29.             {  
  30.                 //这个Pass直接输出颜色  
  31.                 return fixed4(1,1,1,1);  
  32.             }  
  33.               
  34.             //使用vert函数和frag函数  
  35.             #pragma vertex vert  
  36.             #pragma fragment frag  
  37.             ENDCG  
  38.         }  
  39.     }  
  40. }  
下面附上扭曲效果的C#脚本:
[csharp]  view plain  copy
  1. /******************************************************************** 
  2.  FileName: DistortEffect.cs 
  3.  Description: 屏幕扭曲效果 
  4.  Created: 2017/04/27 
  5.  by: puppet_master 
  6. *********************************************************************/  
  7. using System.Collections;  
  8. using System.Collections.Generic;  
  9. using UnityEngine;  
  10.   
  11. public class DistortEffect : PostEffectBase {  
  12.   
  13.     //扭曲的时间系数  
  14.     [Range(0.0f, 1.0f)]  
  15.     public float DistortTimeFactor = 0.15f;  
  16.     //扭曲的强度  
  17.     [Range(0.0f, 0.2f)]  
  18.     public float DistortStrength = 0.01f;  
  19.     //噪声图  
  20.     public Texture NoiseTexture = null;  
  21.     //渲染Mask图所用的shader  
  22.     public Shader maskObjShader = null;  
  23.     //降采样系数  
  24.     public int downSample = 4;  
  25.   
  26.     private Camera mainCam = null;  
  27.     private Camera additionalCam = null;  
  28.     private RenderTexture renderTexture = null;  
  29.   
  30.     public void OnRenderImage(RenderTexture source, RenderTexture destination)  
  31.     {  
  32.         if (_Material)  
  33.         {  
  34.             _Material.SetTexture("_NoiseTex", NoiseTexture);  
  35.             _Material.SetFloat("_DistortTimeFactor", DistortTimeFactor);  
  36.             _Material.SetFloat("_DistortStrength", DistortStrength);  
  37.             _Material.SetTexture("_MaskTex", renderTexture);  
  38.             Graphics.Blit(source, destination, _Material);  
  39.         }  
  40.         else  
  41.         {  
  42.             Graphics.Blit(source, destination);  
  43.         }  
  44.     }  
  45.   
  46.     void Awake()  
  47.     {  
  48.         //创建一个和当前相机一致的相机  
  49.         InitAdditionalCam();  
  50.     }  
  51.   
  52.     private void InitAdditionalCam()  
  53.     {  
  54.         mainCam = GetComponent<Camera>();  
  55.         if (mainCam == null)  
  56.             return;  
  57.   
  58.         Transform addCamTransform = transform.FindChild("additionalDistortCam");  
  59.         if (addCamTransform != null)  
  60.             DestroyImmediate(addCamTransform.gameObject);  
  61.   
  62.         GameObject additionalCamObj = new GameObject("additionalDistortCam");  
  63.         additionalCam = additionalCamObj.AddComponent<Camera>();  
  64.   
  65.         SetAdditionalCam();  
  66.     }  
  67.   
  68.     private void SetAdditionalCam()  
  69.     {  
  70.         if (additionalCam)  
  71.         {  
  72.             additionalCam.transform.parent = mainCam.transform;  
  73.             additionalCam.transform.localPosition = Vector3.zero;  
  74.             additionalCam.transform.localRotation = Quaternion.identity;  
  75.             additionalCam.transform.localScale = Vector3.one;  
  76.             additionalCam.farClipPlane = mainCam.farClipPlane;  
  77.             additionalCam.nearClipPlane = mainCam.nearClipPlane;  
  78.             additionalCam.fieldOfView = mainCam.fieldOfView;  
  79.             additionalCam.backgroundColor = Color.clear;  
  80.             additionalCam.clearFlags = CameraClearFlags.Color;  
  81.             additionalCam.cullingMask = 1 << LayerMask.NameToLayer("Distort");  
  82.             additionalCam.depth = -999;  
  83.             //分辨率可以低一些  
  84.             if (renderTexture == null)  
  85.                 renderTexture = RenderTexture.GetTemporary(Screen.width >> downSample, Screen.height >> downSample, 0);  
  86.         }  
  87.     }  
  88.   
  89.     void OnEnable()  
  90.     {  
  91.         SetAdditionalCam();  
  92.         additionalCam.enabled = true;  
  93.     }  
  94.   
  95.     void OnDisable()  
  96.     {  
  97.         additionalCam.enabled = false;  
  98.     }  
  99.   
  100.     void OnDestroy()  
  101.     {  
  102.         if (renderTexture)  
  103.         {  
  104.             RenderTexture.ReleaseTemporary(renderTexture);  
  105.         }  
  106.         DestroyImmediate(additionalCam.gameObject);  
  107.     }  
  108.   
  109.     //在真正渲染前的回调,此处渲染Mask遮罩图  
  110.     void OnPreRender()  
  111.     {  
  112.         //maskObjShader进行渲染  
  113.         if (additionalCam.enabled)  
  114.         {  
  115.             additionalCam.targetTexture = renderTexture;  
  116.             additionalCam.RenderWithShader(maskObjShader, "");  
  117.         }  
  118.     }  
  119. }  
还是上面的测试场景,我们将面片改为Distort层级,然后可以直接给这个面片设置一个透明的材质,比如最简单的粒子的shader,让它正常渲染不可见即可:
通过上面的脚本,我们临时将这个Mask图输出到屏幕上(为了性能好一些,降采样比较多,已经有锯齿了,不过在正式使用的时候是看不出来的):
有了Mask图,我们就可以根据Mask图的权重进行修改了,白色的地方是需要扭曲的,黑色的地方不需要扭曲,我们将上面的shader中的offest用这个mask采样图进行修正就能够得到最终的扭曲效果了。后处理版本的shader如下:
[csharp]  view plain  copy
  1. //全屏幕扭曲Shader  
  2. //by:puppet_master  
  3. //2017.5.3  
  4.   
  5. Shader "Custom/DistortPostEffect"  
  6. {  
  7.     Properties  
  8.     {  
  9.         _MainTex("Base (RGB)", 2D) = "white" {}  
  10.         _NoiseTex("Noise", 2D) = "black" {}//默认给黑色,也就是不会偏移  
  11.         _MaskTex("Mask", 2D) = "black" {}//默认给黑色,权重为0  
  12.     }  
  13.   
  14.     CGINCLUDE  
  15.     #include "UnityCG.cginc"  
  16.     uniform sampler2D _MainTex;  
  17.     uniform sampler2D _NoiseTex;  
  18.     uniform sampler2D _MaskTex;  
  19.     uniform float _DistortTimeFactor;  
  20.     uniform float _DistortStrength;  
  21.   
  22.     fixed4 frag(v2f_img i) : SV_Target  
  23.     {  
  24.       
  25.         //根据时间改变采样噪声图获得随机的输出  
  26.         float4 noise = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);  
  27.         //以随机的输出*控制系数得到偏移值  
  28.         float2 offset = noise.xy * _DistortStrength;  
  29.         //采样Mask图获得权重信息  
  30.         fixed4 factor = tex2D(_MaskTex, i.uv);  
  31.         //像素采样时偏移offset,用Mask权重进行修改  
  32.         float2 uv = offset * factor.r + i.uv;  
  33.         return tex2D(_MainTex, uv);  
  34.     }  
  35.   
  36.     ENDCG  
  37.   
  38.     SubShader  
  39.     {  
  40.         Pass  
  41.         {  
  42.             ZTest Always  
  43.             Cull Off  
  44.             ZWrite Off  
  45.             Fog{ Mode off }  
  46.   
  47.             CGPROGRAM  
  48.             #pragma vertex vert_img  
  49.             #pragma fragment frag  
  50.             #pragma fragmentoption ARB_precision_hint_fastest   
  51.             ENDCG  
  52.         }  
  53.     }  
  54.     Fallback off  
  55. }  
扭曲效果动态图如下:
通过后处理制作的热空气扭曲效果与GrabPass的效果大致相同,虽然多了全屏后处理操作,但是能够避免安卓机上GrabPass读帧缓存卡死的问题,而且也不需要DrawCall翻倍,对于复杂的场景来说相对效率更高一些。如果场景比较简单,也可以使用另一个相机渲染场景到RT上的方法进行制作。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity Shader-热空气扭曲效果 的相关文章

  • XSS、CSRF、SSRF漏洞的攻击原理以及防御

    目录 XSS 攻击原理 攻击方式 xss漏洞防范 CSRF CSRF攻击成功的两个必要条件 csrf漏洞防范
  • 安装Esxi系统&重装Esxi系统

    安装esxi系统 或者已经安装了esxi需要重装环境 Dell R730服务器 前期准备 将VMware ESXi 5 5 0的安装镜像要提前准备好 通过光碟 U盘 idrac管理界面挂载方式安装都行 1 加载VMware ESXi 5 5
  • 组装一台电脑需要选购哪些基本部件

    1 机箱 一般电脑的主要零件都放在这里 2 显示器 用来看电脑的工作过程 要不然 你都不知道电脑究竟在做什么 3 键盘和鼠标 向电脑输入有用的命令 让它去为我们工作 4 主板 这里是决定你这台电脑性能的重要零件之一 5 内存 当电脑工作时
  • Go Web编程实战(2)----流程控制语句

    目录 流程控制语句 if else语句 for循环语句 用for循环实现do while 用for循环实现while break指定跳出循环 continue语句 for range循环 遍历数组 遍历字符串 遍历map 遍历通道 chan
  • c语言程序位置式pid算法,增量式与位置式PID算法(C语言实现与电机控制项目)...

    4 2核心代码 函数功能 增量PI控制器 入口参数 编码器测量值 目标速度 返回 值 电机PWM 根据增量式离散PID公式 pwm Kp e k e k 1 Ki e k Kd e k 2e k 1 e k 2 e k 代表本次偏差 e k
  • linux 终端使用aplay播放wav

    aplay D plughw 0 0 xxx wav plughw后面的0 0指的是card0 device0 声卡id和设备id 根据个人情况会有不同 声卡id和设备id可以通过aplay l命令来查看 比如 upsquared ubun
  • Redis5.0集群搭建(Redis Cluster)

    Redis集群 redis集群是一个由多个主从节点群组成的分布式服务器群 它具有复制 高可用和分片特性 Redis集群不需要sentinel也能完成节点移除和故障转移的功能 需要将每个节点设置成集群模式 这种集群模式没有中心节点 可水平扩展
  • [1116]mobaxterm使用rz/sz

    安装 yum y install lrzsz 下载 步骤1 sz filename 步骤2 ctrl 鼠标右键 步骤3 Receive file using Z modem 上传 步骤1 rz 步骤2 ctrl 鼠标右键 步骤3 Send
  • ArchLinux安装fcitx5以及拼音输入法

    简介 输入法引擎 需要注意的是 fcitx5 只是提供了基本框架 基本框架只对英文提供了输入支持 如果需要输入其他语言 则需要安装相应的输入法引擎 中文 fcitx5 chinese addons 包含了大量中文输入方式 拼音 双拼 五笔拼
  • SMTP邮件格式、SMTP 协议,SMTP的MIME写法,SMTP发送HTML邮件

    转载 http blog sina com cn s blog 759444350100vx8u html MIME邮件格式 在RFC 2822文档中定义了简单的ASCII编码的Email的邮件格式 然而随着Internet的发展 Emai
  • JSP JSTL 判断List 大小

    JSTL判断List 大小必须先引入的二个核心包 jsp页面判断获得action设置attribute的List是否为空或者list size的长度 就可以用fn这个标签
  • python写的一个-批量下载股票年报的小工具

    python写的一个 批量下载股票年报的小工具 from urllib import request import requests import os import openpyxl print os getcwd def getKeyL
  • Java中怎么定义字符串?

    字符串是 Java 中特殊的类 使用方法像一般的基本数据类型 被广泛应用在 Java 编程中 Java 没有内置的字符串类型 而是在标准 Java 类库中提供了一个 String 类来创建和操作字符串 在 Java 中定义一个字符串最简单的
  • oracle 学习之:for循环中包涵select语句

    oracle中的for循环用法比较简单 但是在一次用到包涵select语句的for循环时 还是发现了一些自己以前没有注意的东西 我的代码如下 declare val1 date val2 date begin for i in select
  • pc端微信二维码支付流程及问题排查

    场景 在做pc端的支付时 我们常用的就是生成二维码让用户去扫码支付 like this 当然你想像我一样有个二维码支付的图片 还需要先申请微信支付的native支付功能 native支付会提供一个二维码供用户扫码 页面内 通常会有一个按钮
  • Rust学习记录 -> 关于Crates.io的问题

    文章目录 前言 问题描述与解析 1 版本更迭带来的依赖包适配问题 2 openssl 总结 前言 最近我在使用rust语言编写一个商场后端demo时 由于需要与mysql进行交互以及序列化等操作 所以通过crates io下载了许多外部依赖
  • SLAM评估工具evo的使用

    evo官方指南 参考博客 lt 官方手册 这篇参考博客 完全可以掌握evo的基本操作 gt Then 实践出真知 1 安装evo sudo apt install python pip pip install evo upgrade no
  • 阿里云图标使用 (symbol 引用方式)

    阿里云图标网址 https www iconfont cn 一 登录注册 这个简单 就不说了 二 给当前项目找图库 2 1 添加项目 2 2 寻找图标添加入库 添加入库 2 3 打开入库 的图标添加到指定项目 添加到当前项目 1 2 三 项
  • 在线考试平台搭建

    出于工作需要 在万能的Github上找到的考试平台 在此感谢平台的创作者 github https github com YXJ2018 SpringBoot Vue OnlineExam 在线考试系统 下载该项目后 因为各种各样的原因 导

随机推荐

  • 【Vue3】Fragment组件、Teleport组件和Suspense组件

    Fragment组件 在Vue2中 组件必须有一个根标签 在Vue3中 组件可以没有根标签 内部会将多个标签包含在一个Fragment虚拟元素中 好处 减少标签层次 减少内侧占用 Teleport组件 是一种能够将我们的组件html结构移动
  • 【GIT 坑&常见问题】

    文章目录 前言 git github 使用问题 坑 连接不上github 1 过后再连 2 使用某些途径 3 修改git 的http 和https的代理 4 使用VS 在创建远程仓库的时候 别在远程仓库进行修改 git LFS上传超过100
  • C++ try{} catch(…){} 与 Throw()的编译器优化

    try catch 用 try catch 来捕获C 中一些意想不到的异常 这种方法在VC中其实是靠不住的 例如下面的代码 try BYTE pch pch BYTE 00001234 给予一个非法地址 pch 6 对非法地址赋值 会造成A
  • Linux中主线程和子线程的终止次序

    Linux中pthread是我们进行多线程并发时经常使用的 pthread创建的子线程和主线程的终止顺序有什么样的关系 下面通过代码来总结下 在代码测试前 先说下结论 1 主线程和子线程之间没有必然的退出次序关系 主线程退出 子线程可以继续
  • 每日一练python:求输入两个数之间的所有质数

    前言 立个flag 每天更新一则今日学到的知识 为成为一个合格的AI训练师而努力 今天将用两种编程思路来求两个数之间的所有质数 方案一 coding utf 8 输入数据 st input input first number gt nd
  • MySQL读写锁总结

    读写锁 读锁 是一种共享锁 一个事务持有读锁时 不会阻塞其它的读锁 其他事务都可以对该数据进行读取 写锁 是一种排他锁 一个锁持有写锁会阻塞其他的写锁和读锁 从而保证了一个只有一个事务进行写操作 并且防止其他事务读取正在写入资源 避免了脏读
  • mybatis 运用 PageHelper 实现分页

    1 添加pom依赖
  • Charles破解安装

    抓包神器 charles 官网下载安装Charles https www charlesproxy com download 百度找到的资源 https pan baidu com s 1i59ekw1 文件的密码 xclient info
  • Mac 环境下Android studio无法打开解决

    最近在使用mac电脑虽然鼓捣了半天终于能翻墙了 但是gradle下载的龟速真是让我不敢恭维啊好几次都是下载1到2个小时候由于没耐心了不再等了 接下来我们就介绍个解决mac创建工程等待半天无法打开的方法 1 首先我们需要下载对应的greale
  • 查看欧拉系统服务器ip,euler os 查看center进程命令

    euler os 查看center进程命令 内容精选 换一换 如果日志目录下没有生成日志文件 您需要检查Host侧对应进程是否正常运行 如果Host侧进程不存在 请参考启动日志进程启动进程 执行如下命令 执行如下命令 如果日志目录下没有生成
  • Android native 层使用opengl渲染YUV420p和NV12

    Ndk中使用Mediacode解码 android mediacodec 编码demo java NDK中使用mediacodec编码h264 Android native 层使用opengl渲染YUV420p和NV12 android 使
  • 剑指offer_第17题_树的子结构_Python

    题目描述 输入两棵二叉树A B 判断B是不是A的子结构 其中空树不是任意一个树的子结构 class TreeNode def init self x self val x self left None self right None 解题思
  • 细说设计模式七大原则(6):开闭原则

    2 8 1 基本介绍 英文名 Open Closed Principle OCP 定义 一个软件实体如类 模块和函数应该对扩展开放 对修改关闭 开闭原则是编程中最基础 最重要的设计原则 一个软件实体如类 模块和函数应该对扩展开放 对提供方
  • 无我编程:你的工作不代表你

    原文作者 Jeff Atwood Johanna Rothman是这么描述 无我编程 这个概念的 25年前 Gerald M Weinberg写了 程序开发心理学 我在1977年发现了这本书 然后做了一个决定 放弃在电台做DJ的工作 打算做
  • Java各类在线API

    JavaTM Platform Enterprise Edition v 5 0 http java sun com javaee 5 docs api 1 Hibernate API Documentation 3 2 2 ga http
  • 阿里云OSS存储整合若依框架,SpringBoot

    阿里云OSS文档 阿里云服务文档 Cannot resolve com alibaba cloud aliyun oss spring boot starter unknown 参考博客 解决方案
  • 网络体系结构总结

    网络体系结构总结 七层结构 应用层 各种应用 网络虚拟终端 SMTP 文件传输 HTTP EMALL等 表示层 完成特定的常见的功能 关心所传输信息的语法和语义 数据压缩 数据转换 数据加密 会话层 为会话用户提供一个建立连接及在上按顺序传
  • 蓝桥杯:与DP的应用

    目录 一 状态压缩DP 1 糖果 2019年省赛 2 矩阵计数 二 树状DP 3 生命之树 4 大臣的旅费 5 蓝桥舞会 三 区间DP 1 石子合并 2 制作回文串 3 最少操作次数 一 状态压缩DP 1 糖果 2019年省赛 题目描述 糖
  • 期货公司速度哪家强? 期货业掀起装备竞赛

    期货公司速度哪家强 期货业掀起装备竞赛 2014年12月01日 01 00 中国证券报 中证网 转自 http finance sina com cn money future fmnews 20141201 010020959627 sh
  • Unity Shader-热空气扭曲效果

    http blog csdn net zhou8jie article details 48052983 简介 千等万等终于等到了 耻辱2 打折 本以为可以爽一发了 然而各种出问题 先是steam下载速度奇慢无比 下了三天晚上好不容易下完的