Shader熱空氣扭曲效果(轉(zhuǎn))

轉(zhuǎn)自:http://m.blog.csdn.net/puppet_master/article/details/70199330

簡(jiǎn)介

千等萬(wàn)等終于等到了《恥辱2》打折,本以為可以爽一發(fā)了扬虚,然而各種出問(wèn)題风宁,先是steam下載速度奇慢無(wú)比腐碱,下了三天晚上好不容易下完的游戲誊垢,第一次打開(kāi)給彈了個(gè)3D11CreateDeviceAndSwapChain Failed,折騰半天裝了個(gè)補(bǔ)丁算是能打開(kāi)游戲了症见,然而過(guò)完新手教學(xué)顯卡驅(qū)動(dòng)就崩了喂走,崩了!崩了谋作,連崩三回芋肠,差點(diǎn)想把坑爹的A卡從機(jī)箱掏出來(lái)順著窗戶扔出去,后來(lái)想想遵蚜,為了樓下同學(xué)的生命安全帖池,我還是忍了。好在AMD有專門為《恥辱2》R9380崩潰打了個(gè)補(bǔ)丁吭净,算是拯救我于水火之中了睡汹。《恥辱2》用了ID Tech5衍生的Void引擎寂殉,看起來(lái)畫面比《恥辱1》用的虛幻3好了不少囚巴。先來(lái)張帥帥噠截圖,最近每天沉迷于殺殺殺,感覺(jué)自己好頹廢:

一時(shí)間差點(diǎn)忘了自己是個(gè)程序員彤叉,差點(diǎn)變成游戲鑒賞博客庶柿,尷尬...下面步入正題,今天打游戲的時(shí)候路過(guò)了一個(gè)火爐秽浇,看到了火爐旁邊的熱空氣扭曲的效果浮庐,感覺(jué)做的還是蠻逼真的,今天打算自己實(shí)現(xiàn)一發(fā)玩一玩:

實(shí)現(xiàn)原理

扭曲效果是游戲里面經(jīng)常有的一個(gè)效果兼呵,說(shuō)道扭曲效果兔辅,一般就是當(dāng)前的畫面發(fā)生了扭曲,在現(xiàn)實(shí)世界中一般是折射導(dǎo)致的击喂,但是在圖形學(xué)中维苔,我們要模擬這種效果,原理就大不一樣了懂昂。首先介时,我們并不會(huì)真正影響光線的傳播,只是用uv的偏移來(lái)模擬扭曲的效果凌彬。有一種全屏的扭曲效果沸柔,這種是基于屏幕后處理的,可以參考前面的一篇文章屏幕水波紋效果铲敛,但是褐澎,往往我們并不希望全屏幕都發(fā)生扭曲,而是只希望某些地方發(fā)生了扭曲伐蒋,比如上面的火爐的做法工三,拼關(guān)的同學(xué)肯定是希望在火爐的上方放一個(gè)特效片,就能夠出扭曲的效果先鱼。那么俭正,我們的這個(gè)片就需要是一個(gè)可以顯示后面所有物體的片,換句話說(shuō)焙畔,我們需要在這個(gè)面片上渲染面片后面所有的東西掸读,這樣,面片看起來(lái)就是透明的了宏多。然后我們?cè)诓蓸觰v的時(shí)候?qū)v進(jìn)行偏移儿惫,就能夠得到扭曲的效果了。恩伸但,聽(tīng)起來(lái)很簡(jiǎn)單的樣子姥闪,但是我們要怎么得到面片后面的所有東西呢?其實(shí)Unity已經(jīng)為我們提供了這樣的一個(gè)功能砌烁,GrabPass筐喳。下面看一下Grabpass的使用催式。

GrabPass

GrabPass是Unity為我們提供的一個(gè)很方便的功能,可以直接將當(dāng)前屏幕內(nèi)容渲染到一張貼圖上避归,我們可以直接在shader中使用這張貼圖而不用自己去實(shí)現(xiàn)渲染到貼圖這樣的一個(gè)過(guò)程荣月,大大的方便了我們的shader編寫。GrabPass的使用非常簡(jiǎn)單梳毙,我們?cè)趯憊ertex fragment shader的時(shí)候都需要寫一個(gè)pass哺窄,GrabPass也是一個(gè)pass,只不過(guò)是Unity為我們實(shí)現(xiàn)好的一個(gè)pass账锹。我們只需要在我們正常的Pass前面加一個(gè)GrabPass{}就可以了萌业。

官方文檔上有兩種GrabPass的寫法,第一種是直接GrabPass{}的寫法奸柬,這種寫法抓屏的圖片就直接存到_GrabTexture這個(gè)系統(tǒng)預(yù)定義的貼圖變量中了生年,我們可以直接訪問(wèn)該貼圖,但是這種寫法會(huì)導(dǎo)致每個(gè)使用GrabPass的物體進(jìn)行一次這種曠日持久的抓屏操作廓奕!如果用這種shader的物體多了的話抱婉,想想就很可怕。另一種是GrabPass{"TextureName"}的寫法桌粉,其中TextureName是我們自定義的一個(gè)貼圖名稱蒸绩,這種寫法,Unity每幀只會(huì)為第一個(gè)使用了該名稱的物體進(jìn)行抓屏操作铃肯,之后的就可以復(fù)用這張貼圖了患亿。所以,我們還是使用第二種方式更好一點(diǎn)押逼。下面附上一份最簡(jiǎn)單的抓屏代碼:

//Grabpass shader

//by: puppet_master

//2017.4.23

Shader "ApcShader/GrabPass"

{

SubShader

{

ZWrite Off

//GrabPass

GrabPass

{

//此處給出一個(gè)抓屏貼圖的名稱步藕,抓屏的貼圖就可以通過(guò)這張貼圖來(lái)獲取,而且每一幀不管有多個(gè)物體使用了該shader宴胧,只會(huì)有一個(gè)進(jìn)行抓屏操作

//如果此處為空,則默認(rèn)抓屏到_GrabTexture中表锻,但是據(jù)說(shuō)每個(gè)用了這個(gè)shader的都會(huì)進(jìn)行一次抓屏恕齐!

"_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);

//計(jì)算抓屏的位置,其中主要是將坐標(biāo)從(-1,1)轉(zhuǎn)化到(0,1)空間并處理DX和GL紋理反向的問(wèn)題

o.grabPos = ComputeGrabScreenPos(o.pos);

return o;

}

fixed4 frag(v2f i) : SV_Target

{

//根據(jù)抓屏位置采樣Grab貼圖,tex2Dproj等同于tex2D(grabPos.xy / grabPos.w)

fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos);

return 1 - color;

}

#pragma vertex vert

#pragma fragment frag

ENDCG

}

}

}

我們找個(gè)面片瞬逊,附上這個(gè)shader的材質(zhì)显歧。為了更方便的看一下效果,我們就參照官網(wǎng)的寫法确镊,直接將最終輸出的顏色反向士骤,也就是1-原顏色作為輸出(這個(gè)顏色不禁讓我想起了宇智波鼬的月讀........)

看一下這個(gè)shader用到的幾個(gè)函數(shù),第一個(gè)是ComputeGrabScreenPos這個(gè)函數(shù)蕾域,我們從UnityCG.cginc中可以找到這個(gè)函數(shù)的實(shí)現(xiàn):

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;

}

我們傳遞進(jìn)來(lái)的參數(shù)是經(jīng)過(guò)mvp變換后的頂點(diǎn)坐標(biāo)拷肌,傳入之后這個(gè)函數(shù)主要做了兩件事情到旦,第一個(gè)是處理DX和OpenGL紋理坐標(biāo)差異導(dǎo)致的問(wèn)題,這個(gè)之前的文章有記錄過(guò)巨缘。第二件事主要就是將轉(zhuǎn)化到標(biāo)準(zhǔn)裁剪空間(-1,1)區(qū)間的頂點(diǎn)轉(zhuǎn)化到(0,1)區(qū)間添忘。按照Unity的寫法,本人推測(cè)若锁,這個(gè)GrabPass獲取的屏幕貼圖應(yīng)該是基于視空間的搁骑,而在這個(gè)信息傳遞到fragment shader后,用了tex2Dproj函數(shù)進(jìn)行采樣又固,tex2Dproj(i.xy)應(yīng)該等同于tex2D(i.xy/i.w)仲器,也就是說(shuō)這個(gè)采樣點(diǎn)坐標(biāo)進(jìn)行了一次投影變換。

扭曲效果的實(shí)現(xiàn)

準(zhǔn)備工作完成仰冠,下面步入正題乏冀,來(lái)看看扭曲效果的實(shí)現(xiàn)。首先沪停,要扭曲煤辨,就肯定要?jiǎng)樱@個(gè)shader還是得需要Time系列的變量進(jìn)行驅(qū)動(dòng)木张。不過(guò)這只是其中一個(gè)條件众辨,由于shader是高度并行化的計(jì)算姿搜,我們沒(méi)有辦法區(qū)分每個(gè)像素到底需要偏移多少仁烹。在屏幕水波紋效果中,我們是通過(guò)計(jì)算當(dāng)前像素點(diǎn)到屏幕中心位置的距離作為偏移值的筒占,對(duì)于后處理這樣做可能比較方便妻献,但是對(duì)于普通物體上使用的shader就沒(méi)有那么簡(jiǎn)單了蛛株。比如,我們同樣是讓采樣坐標(biāo)按照sin值進(jìn)行偏移:

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;

}

那么所有的頂點(diǎn)就都會(huì)按照一致的方向進(jìn)行偏移:

為了讓偏移變得隨機(jī)育拨,我們就要引入一個(gè)能夠隨機(jī)化輸出的東東谨履,也就是噪聲圖。比如我們找到了一張這個(gè)樣子的噪聲圖:

然后熬丧,只需要用一個(gè)連續(xù)變化的值去采這個(gè)噪聲圖笋粟,就可以得到不連續(xù)的隨機(jī)輸出偏移值。下面附上扭曲效果的實(shí)現(xiàn):

//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

{

//此處給出一個(gè)抓屏貼圖的名稱析蝴,抓屏的貼圖就可以通過(guò)這張貼圖來(lái)獲取害捕,而且每一幀不管有多個(gè)物體使用了該shader,只會(huì)有一個(gè)進(jìn)行抓屏操作

//如果此處為空闷畸,則默認(rèn)抓屏到_GrabTexture中尝盼,但是據(jù)說(shuō)每個(gè)用了這個(gè)shader的都會(huì)進(jìn)行一次抓屏!

"_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值隨著時(shí)間連續(xù)變換盾沫,而輸出一個(gè)噪聲圖中的隨機(jī)值裁赠,乘以一個(gè)扭曲快慢系數(shù)

float4 offset = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);

//用采樣的噪聲圖輸出作為下次采樣Grab圖的偏移值,此處乘以一個(gè)扭曲力度的系數(shù)

i.grabPos.xy -= offset.xy * _DistortStrength;

//uv偏移后去采樣貼圖即可得到扭曲的效果

fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos);

return color;

}

#pragma vertex vert

#pragma fragment frag

ENDCG

}

}

}

為了更加應(yīng)景疮跑,我搜刮了一下我的資源庫(kù)组贺,找到了一個(gè)火把,2333:

然后在火把附近放一個(gè)面片祖娘,用上我們的扭曲shader:

最終效果如下圖所示:

基于后處理的優(yōu)化效果

GrabPass非常耗時(shí)失尖,在安卓平臺(tái)也會(huì)有問(wèn)題,雖然對(duì)于安卓機(jī)的性能渐苏,用shader lod直接干掉扭曲效果也是一個(gè)不錯(cuò)的選擇掀潮,不過(guò)這個(gè)畢竟是下策,首先還是要解決這個(gè)問(wèn)題琼富。正常渲染是往frame buffer中渲染仪吧,但是grabpass應(yīng)該是從當(dāng)前的frame buffer中將內(nèi)容再讀出來(lái),從顯存往內(nèi)存中拷貝鞠眉,應(yīng)該是一個(gè)阻塞的過(guò)程薯鼠,我記得之前一幀渲染過(guò)3000ms,簡(jiǎn)直可怕械蹋。PS:這種情況在兩個(gè)(或多個(gè))相機(jī)渲染出皇,后面的相機(jī)沒(méi)有Clear并且在后面的相機(jī)上掛了后處理的時(shí)候也會(huì)出現(xiàn)這種情況,猜測(cè)原因也是因?yàn)樵诤竺娴南鄼C(jī)進(jìn)行后處理時(shí)需要上一個(gè)相機(jī)的內(nèi)容哗戈,然而這個(gè)東東已經(jīng)在frame buffer中了郊艘,所以后處理如果要在上層相機(jī)運(yùn)用,最好還是慎重考慮一下唯咬。關(guān)于用后處理卡的問(wèn)題纱注,這篇文章解釋得很好。文章中給了幾種解決方案胆胰,一種是關(guān)抗鋸齒狞贱,一個(gè)是用GL3.0,最后一個(gè)是直接改為用渲染到紋理蜀涨。記得以前還看過(guò)一個(gè)帖子瞎嬉,不過(guò)忘記鏈接了,這個(gè)做法比較極端勉盅,就是最終渲染的結(jié)果都不走frame buffer佑颇,而是都渲染到一個(gè)紋理上顶掉。然后所有的后處理都在這個(gè)紋理上進(jìn)行草娜,完全繞開(kāi)了OnRenderImage。額痒筒,不小心扯遠(yuǎn)了宰闰,只是希望能給和我遇到一樣問(wèn)題的倒霉蛋一點(diǎn)參考茬贵,下面進(jìn)行正題。

既然GrabPass比較費(fèi)移袍,那么最簡(jiǎn)單的解藻,我們可能會(huì)想直接用另外一個(gè)相機(jī)去渲染這個(gè)場(chǎng)景到一個(gè)RenderTarget上,然后用這個(gè)RenderTarget代替我們上面用的GrabTexture葡盗。不過(guò)這種做法會(huì)導(dǎo)致DrawCall翻倍螟左,如果我們的場(chǎng)景中內(nèi)容較少,比較適合用這種方法觅够〗罕常或者我們可以設(shè)置另一個(gè)相機(jī)的層級(jí),使之只渲染某些內(nèi)容喘先,這樣也可以降低一些開(kāi)銷钳吟。不過(guò)這里就不用這種方式了。之前看到了一篇文章窘拯,作者給了這樣的一個(gè)思路红且,感覺(jué)非常巧妙。簡(jiǎn)而言之涤姊,這個(gè)方法作扭曲的部分是用全屏后處理進(jìn)行的暇番,但是全屏都扭曲了,我們其實(shí)只需要扭曲一部分地方砂轻,所以我們需要一個(gè)Mask圖來(lái)控制奔誓,而這張Mask圖我們就可以直接用另一個(gè)相機(jī)渲染出來(lái),其實(shí)就是我們上面用到的特效片搔涝,渲染到一個(gè)RT上就可以了厨喂。相比于用另一個(gè)攝像機(jī)把場(chǎng)景中的東西都渲染一遍,這種方式只是需要額外渲染一個(gè)片外加一次全屏后處理操作庄呈,兩者各有千秋蜕煌,視具體情況而定。

我們先寫一個(gè)全屏扭曲的shader诬留,首先斜纪,需要后處理,我們繼承這個(gè)已經(jīng)用了無(wú)數(shù)次的PostEffectBase類文兑,實(shí)現(xiàn)后處理的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 {

//扭曲的時(shí)間系數(shù)

[Range(0.0f, 1.0f)]

public float DistortTimeFactor = 0.15f;

//扭曲的強(qiáng)度

[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部分盒刚,扭曲的原理與上面一樣,只是處理的對(duì)象變了一下绿贞,直接處理OnRenderImage傳來(lái)的MainTex即可:

//全屏幕扭曲Shader

//by:puppet_master

//2017.4.28

Shader "Custom/DistortPostEffect"

{

Properties

{

_MainTex("Base (RGB)", 2D) = "white" {}

_NoiseTex("Base (RGB)", 2D) = "black" {}//默認(rèn)給黑色因块,也就是不會(huì)偏移

}

CGINCLUDE

#include "UnityCG.cginc"

uniform sampler2D _MainTex;

uniform sampler2D _NoiseTex;

uniform float _DistortTimeFactor;

uniform float _DistortStrength;

fixed4 frag(v2f_img i) : SV_Target

{

//根據(jù)時(shí)間改變采樣噪聲圖獲得隨機(jī)的輸出

float4 noise = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);

//以隨機(jī)的輸出*控制系數(shù)得到偏移值

float2 offset = noise.xy * _DistortStrength;

//像素采樣時(shí)偏移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

}

這樣,整個(gè)屏幕就都扭曲了籍铁,動(dòng)圖如下(趕腳好像來(lái)到了沙漠一樣.....):

這里我把扭曲的強(qiáng)度設(shè)置得高一些涡上,感覺(jué)也可以直接當(dāng)一些全屏后處理的樣子趾断,比如扭曲,水幕效果:

我們有了全屏的扭曲效果之后吩愧,下面我們考慮要怎么把需要扭曲的部分摳出來(lái)芋酌。那么,第一個(gè)想到的就是Mask圖雁佳,我們可以給一個(gè)Mask圖脐帝,作為權(quán)重,白色為需要偏移的權(quán)重糖权,黑色為無(wú)偏移的權(quán)重腮恩,這樣,我們就可以控制哪個(gè)地方需要扭曲温兼。但是秸滴,這里,我們的Mask圖需要是一個(gè)動(dòng)態(tài)的Mask圖募判,因?yàn)橄鄼C(jī)會(huì)移動(dòng)荡含,所以,我們需要實(shí)時(shí)地生成這張Mask圖届垫。在描邊效果這篇文章中释液,我們用過(guò)類似的方法。這里装处,我們故技重施误债,將需要扭曲的部分,也就是上面我們用的面片渲染到一張RenderTarget上妄迁,首先寝蹈,我們還是創(chuàng)建一個(gè)新的攝像機(jī),然后通過(guò)在OnPreRender函數(shù)中用RenderWithShader登淘,將面片渲染到一張RT上(這個(gè)RT可以多降低一些分辨率)箫老,渲染的shader就用一個(gè)純白色的shader就可以了。比如下面的這個(gè)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

{

//這個(gè)Pass直接輸出顏色

return fixed4(1,1,1,1);

}

//使用vert函數(shù)和frag函數(shù)

#pragma vertex vert

#pragma fragment frag

ENDCG

}

}

}

下面附上扭曲效果的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 {

//扭曲的時(shí)間系數(shù)

[Range(0.0f, 1.0f)]

public float DistortTimeFactor = 0.15f;

//扭曲的強(qiáng)度

[Range(0.0f, 0.2f)]

public float DistortStrength = 0.01f;

//噪聲圖

public Texture NoiseTexture = null;

//渲染Mask圖所用的shader

public Shader maskObjShader = null;

//降采樣系數(shù)

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()

{

//創(chuàng)建一個(gè)和當(dāng)前相機(jī)一致的相機(jī)

InitAdditionalCam();

}

private void InitAdditionalCam()

{

mainCam = GetComponent();

if (mainCam == null)

return;

Transform addCamTransform = transform.FindChild("additionalDistortCam");

if (addCamTransform != null)

DestroyImmediate(addCamTransform.gameObject);

GameObject additionalCamObj = new GameObject("additionalDistortCam");

additionalCam = additionalCamObj.AddComponent();

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);

}

//在真正渲染前的回調(diào)黔州,此處渲染Mask遮罩圖

void OnPreRender()

{

//maskObjShader進(jìn)行渲染

if (additionalCam.enabled)

{

additionalCam.targetTexture = renderTexture;

additionalCam.RenderWithShader(maskObjShader, "");

}

}

}

還是上面的測(cè)試場(chǎng)景耍鬓,我們將面片改為Distort層級(jí),然后可以直接給這個(gè)面片設(shè)置一個(gè)透明的材質(zhì)流妻,比如最簡(jiǎn)單的粒子的shader牲蜀,讓它正常渲染不可見(jiàn)即可:

通過(guò)上面的腳本,我們臨時(shí)將這個(gè)Mask圖輸出到屏幕上(為了性能好一些绅这,降采樣比較多涣达,已經(jīng)有鋸齒了,不過(guò)在正式使用的時(shí)候是看不出來(lái)的):

有了Mask圖,我們就可以根據(jù)Mask圖的權(quán)重進(jìn)行修改了峭判,白色的地方是需要扭曲的,黑色的地方不需要扭曲棕叫,我們將上面的shader中的offest用這個(gè)mask采樣圖進(jìn)行修正就能夠得到最終的扭曲效果了林螃。后處理版本的shader如下:

//全屏幕扭曲Shader

//by:puppet_master

//2017.5.3

Shader "Custom/DistortPostEffect"

{

Properties

{

_MainTex("Base (RGB)", 2D) = "white" {}

_NoiseTex("Noise", 2D) = "black" {}//默認(rèn)給黑色,也就是不會(huì)偏移

_MaskTex("Mask", 2D) = "black" {}//默認(rèn)給黑色俺泣,權(quán)重為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

{

//根據(jù)時(shí)間改變采樣噪聲圖獲得隨機(jī)的輸出

float4 noise = tex2D(_NoiseTex, i.uv - _Time.xy * _DistortTimeFactor);

//以隨機(jī)的輸出*控制系數(shù)得到偏移值

float2 offset = noise.xy * _DistortStrength;

//采樣Mask圖獲得權(quán)重信息

fixed4 factor = tex2D(_MaskTex, i.uv);

//像素采樣時(shí)偏移offset疗认,用Mask權(quán)重進(jìn)行修改

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

}

扭曲效果動(dòng)態(tài)圖如下:

通過(guò)后處理制作的熱空氣扭曲效果與GrabPass的效果大致相同,雖然多了全屏后處理操作伏钠,但是能夠避免安卓機(jī)上GrabPass讀幀緩存卡死的問(wèn)題横漏,而且也不需要DrawCall翻倍,對(duì)于復(fù)雜的場(chǎng)景來(lái)說(shuō)相對(duì)效率更高一些熟掂。如果場(chǎng)景比較簡(jiǎn)單缎浇,也可以使用另一個(gè)相機(jī)渲染場(chǎng)景到RT上的方法進(jìn)行制作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赴肚,一起剝皮案震驚了整個(gè)濱河市素跺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誉券,老刑警劉巖指厌,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異踊跟,居然都是意外死亡踩验,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門商玫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)箕憾,“玉大人,你說(shuō)我怎么就攤上這事拳昌〔蘧牛” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵地回,是天一觀的道長(zhǎng)扁远。 經(jīng)常有香客問(wèn)我,道長(zhǎng)刻像,這世上最難降的妖魔是什么畅买? 我笑而不...
    開(kāi)封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮细睡,結(jié)果婚禮上谷羞,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好湃缎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布犀填。 她就那樣靜靜地躺著,像睡著了一般嗓违。 火紅的嫁衣襯著肌膚如雪九巡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天蹂季,我揣著相機(jī)與錄音冕广,去河邊找鬼。 笑死偿洁,一個(gè)胖子當(dāng)著我的面吹牛撒汉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涕滋,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼睬辐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了宾肺?” 一聲冷哼從身側(cè)響起溉委,我...
    開(kāi)封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爱榕,沒(méi)想到半個(gè)月后瓣喊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡黔酥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年藻三,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跪者。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棵帽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渣玲,到底是詐尸還是另有隱情逗概,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布忘衍,位于F島的核電站逾苫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏枚钓。R本人自食惡果不足惜铅搓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搀捷。 院中可真熱鬧星掰,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至播玖,卻和暖如春椎工,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背黎棠。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留镰绎,地道東北人脓斩。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像畴栖,于是被迫代替她去往敵國(guó)和親随静。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容