此文章爲轉載:
作者:胡說ba道
原文:https://blog.csdn.net/qq_37925032/article/details/80146256
溶解效果在遊戲中十分常見,然而普通的溶解效果往往並不好看,本文將實現一種基於邊緣bloom的溶解效果的實現
先上最終效果圖
整體思路:將溶解的邊緣提取出來,bloom之後再與原圖像混合。
首先我們實現一下最基礎的溶解,下面給出關鍵代碼:
fixed4 frag(v2f i) : SV_Target
{
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r;
clip(cutout - _Threshold);
//溶解邊緣顏色
if (cutout - _Threshold < _EdgeLength)
return _EdgeColor;
fixed4 col = tex2D(_MainTex, i.uvMainTex);
return col;
}
_Threshold是控制溶解程度的值,思路是噪音紋理和透明度測試這裏不再贅述,這裏用純色表示邊緣,當然我們可以對溶解的邊緣進行更多的處理比如顏色混合和漸變紋理等,不過這不是我們的重點。得到效果如下。
好的接下來我們想要單獨獲取得到溶解的邊緣(DissolveEdge.shader),思路也很簡單,既然我們通過clip處理了cutout - _Threshold小於0的部分,我們同樣也可以將cout - _Threshold > _EdgeLength的部分clip掉,if能少用就少用所以將參數移到<的左邊可得clip(_EdgeLength - cutout + _Threshold)效果是一樣的。處理後得到效果如下。
等等…這個效果EMM…我們給噪音紋理加上一個流動控制,用_SpeedX,_SpeedY控制流動速度,然後在控制腳本中不斷改變_Threshold的值,得到的效果如下。(和塞爾達裏面神廟的激光柱的效果有點像有木有)。
Fixed cutout = tex2D(_NoiseTex,fixed2(i.uvNoiseTex.x + _Time.x * _SpeedX,i.uvNoiseTex.y + _Time.x * _SpeedY));
咳咳…扯遠了,回到主線上來,當前我們得到了溶解的邊緣,接下來我們需要考慮的就是如何單獨對這個邊緣進行bloom的處理了,提起bloom,大多數情況下bloom是基於全屏後處理的一種效果實現,用閾值採樣當前場景中亮度較高的部分進行處理,然而這裏我們想實現的是隻對溶解的邊緣進行bloom的處理,這樣就不能用上算方法處理,這裏我們選擇使用將溶解邊緣渲染到一張單獨的RenderTexture上進行處理,我們創建一個新的攝像機BloomRTCam和一張RT(BloomRT),使新相機的位置和角度和主相機完全一致,將其的TargetTexture設置爲BloomRT,那麼如何只將溶解邊緣渲染到BloomRT上呢,這裏我們會用到Camera.SetReplacementShader(Shader XX,string XX);和Camera.RenderWithShader(Shader XX,string XX);這兩個函數,官網對其的解釋(https://docs.unity3d.com/Manual/SL-ShaderReplacement.html),簡言之,它們可以使用特定的shader來渲染攝像機中的物體,而決定渲染哪些物體則由string來指明,string中的值爲一個標籤,系統會遍歷視口中的所有物體,如果物體的shader的subpass中有對應這個標籤的值,則攝像機會將其渲染,若沒有則不渲染,通常我們會將string設置爲”RenderType”或自定義的RenderType,因爲所有的shader都帶有”RenderType”標籤。其次,RenderWithShader只會在一幀中替換shader,而SetReplacementShader會在調用後始終用替換後的shader渲染相應物體。
對應到我們的場景,我們將DissolveEdge 中Subpass的"RenderType”= "Bloom”,我們在BloomRTCam上掛一個控制RT渲染的腳本,然後傳入DissolveEdge,在Start中調用SetReplacementShader(DissolveShader,”RenderType”)
void Start () {
this.GetComponent<Camera>().SetReplacementShader(DissolveShader, "RenderType");
}
這樣開始時遍歷視口中的物體,只要該物體的shader含”RenderType” = “Bloom”就會被渲染到BloomRT上,所以我們將需要溶解的物體的shader如此設置,這樣得到的BloomRT上就只有需要溶解物體的溶解邊緣,效果如下:
接下來的處理就和常規bloom一樣了,我們需要在主相機上掛載Bloom.cs腳本和一個材質BlurAndBlend用於做屏幕後處理。流程:降採樣→高斯模糊→混合。
關於高斯模糊:
高斯模糊總體上就是圖像與正態分佈做卷積的過程,每一個像素點的值由本身和周圍的像素值通過加權平均後得到。
關於正態分佈,上學期的《概率論》課程中有所提及
很明顯高斯卷積核的原點處μ=0,因爲中心點分配的權重最大,公式可化成:
其對應二維方程爲:
其中,μ是x的均值(方差),σ是x的標準差(均方差),當μ=0,σ=1時,稱之爲標準正態分佈,當x=μ時取得最大值。如果固定μ,改變σ,當σ越小時圖形變得越尖,固x落在附近的概率越大,相應模糊程度越弱,σ越大相應模糊程度更強。
計算平均值時我們將高斯卷積核的中心點做原點,周圍的點按照其在正態分佈曲線上的位置分配權重即可,這裏我們使用標準正態分佈(σ=1)獲取一個5X5的高斯卷積核。
(-2,2) | (-1,2) | (0,2) | (1,2) | (2,2) |
---|---|---|---|---|
(-2,1) | (-1,2) | (0,2) | (1,2) | (2,2) |
(-2,0) | (-1,2) | (0,2) | (1,2) | (2,2) |
(-2,-1) | (-1,2) | (0,2) | (1,2) | (2,2) |
(-2,-2) | (-1,2) | (0,2) | (1,2) | (2,2) |
帶入上述二維方程可得
0.002915) | 0.013064 | 0.021539 | 0.013064 | 0.002915 |
---|---|---|---|---|
0.013064 | 0.058550 | 0.096532 | 0.058550 | 0.013064 |
0.021539 | 0.096532 | 0.159155 | 0.096532 | 0.021539 |
0.013064 | 0.058550 | 0.096532 | 0.058550 | 0.013064 |
0.002915 | 0.013064 | 0.021539 | 0.013064 | 0.002915 |
表中所有值的和爲0.981811,爲計算加權平均,故將表中每個值都要除以0.981811以讓權重和爲1
最終得到的高斯卷積核爲
0.002969) | 0.013306 | 0.021938 | 0.013306 | 0.002969 |
---|---|---|---|---|
0.013306 | 0.059635 | 0.098320 | 0.058550 | 0.013306 |
0.021938 | 0.098320 | 0.162104 | 0.098320 | 0.021938 |
0.013306 | 0.059635 | 0.098320 | 0.059635 | 0.013306 |
0.002969 | 0.013306 | 0.021938 | 0.013306 | 0.002969 |
正如我們之前說過的高斯模糊總體上就是圖像與正態分佈做卷積的過程,得到高斯卷積核之後我們就要進行高斯核與BloomRT圖像的卷積操作了,關於卷積這篇文章講得很好,我們要將BloomRT中的每一個像素點與高斯卷積核做卷積操作得出新的像素值,由於能將二維高斯函數拆分成兩個一維函數進行處理,拆分出的一維高斯核爲(0.054488,0.244202,0.40262,0.244202,0.054488),所以主攝像機上掛載的BlurAndBlend的shader中含三個pass,第一個用於處理豎直方向的模糊,第二個用於處理水平方向的模糊,第三個用於合併最後的圖像。
下面是Bloom.cs的實現
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//掛載在主攝像機上
public class Bloom : MonoBehaviour {
//傳入BloomRT
public RenderTexture BloomRT;
public Material BlurAndBlendMat;
//控制Bloom強度
[Range(1,5)]
public float BloomStrength = 1;
//迭代次數
[Range(0, 4)]
public int iterations = 3;
//模糊範圍大小
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;
//縮放係數
[Range(1, 5)]
public int downSample = 2;
void Update()
{
BlurAndBlendMat.SetFloat("_BloomStrength", BloomStrength);
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
int rtW = BloomRT.width / downSample;
int rtH = BloomRT.height / downSample;
//申請RT用於降低分辨率並存入BloomRT
RenderTexture RT0 = RenderTexture.GetTemporary(rtW,rtH,0);
RT0.filterMode = FilterMode.Bilinear;
Graphics.Blit(BloomRT,RT0);
for (int i =0; i < iterations;i++) {
BlurAndBlendMat.SetFloat("_BlurSize", 1.0f + i * blurSpread);
RenderTexture RT1 = RenderTexture.GetTemporary(rtW, rtH, 0);
//豎直方向模糊處理
Graphics.Blit(RT0,RT1,BlurAndBlendMat,0);
RenderTexture.ReleaseTemporary(RT0);
RT0 = RT1;
RT1 = RenderTexture.GetTemporary(rtW,rtH,0);
//水平方向模糊處理
Graphics.Blit(RT0,RT1,BlurAndBlendMat,1);
RenderTexture.ReleaseTemporary(RT0);
RT0 = RT1;
}
BlurAndBlendMat.SetTexture("_BloomTex",RT0);
//將原圖像與處理後的BloomRT混合
Graphics.Blit(source,destination,BlurAndBlendMat,2);
RenderTexture.ReleaseTemporary(RT0);
}
}
下面是BlurAndBlend.shader的實現
Shader "Dissolve/BlurAndBlend"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_BloomTex("BloomTex",2D) = "white"{}
_BlurSize("BlurSize",Float) = 1
_BloomStrength("BloomStrength",Range(1,5)) = 1
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _BloomTex;
float _BlurSize, _BloomStrength;
//用於高斯模糊
struct v2f
{
float4 pos : SV_POSITION;
half2 uv[5] : TEXCOORD0;
};
//由於最後圖像混合
struct v2fBloom {
float4 pos: SV_POSITION;
half2 uv: TEXCOORD0;
half2 uv2: TEXCOORD1;
};
//豎直方向模糊處理
v2f vertBlurVertical(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
//定義的紋理數組用於存儲採樣時的像素點,由於頂點着色器到片元着色器差值線性,所以在頂點着色器中計算節省性能
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
//水平方向模糊處理
v2f vertBlurHorizontal(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
return o;
}
v2fBloom vertBloom(appdata_img v) {
v2fBloom o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy;
o.uv2 = v.texcoord.xy;
return o;
}
fixed4 fragBlur(v2f i) : SV_Target
{
//一維高斯核
float weight[3] = { 0.40262,0.244202,0.054488 };
//最終顏色=各像素點*權重值之和
fixed3 sum = tex2D(_MainTex,i.uv[0]).rgb * weight[0];
for (int it = 1; it<3; it++) {
sum += tex2D(_MainTex,i.uv[it * 2 - 1]).rgb * weight[it];
sum += tex2D(_MainTex,i.uv[it * 2]).rgb *weight[it];
}
return fixed4(sum,1.0);
}
fixed4 fragBloom(v2fBloom i) :SV_Target{
//調用第三個pass前傳入模糊過的BloomRT與原圖像混合
return tex2D(_MainTex,i.uv) + tex2D(_BloomTex,i.uv2) * _BloomStrength;
}
ENDCG
ZTest Always Cull Off ZWrite Off
Pass {
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDCG
}
Pass {
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDCG
}
Pass{
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
ENDCG
}
}
FallBack Off
}
學生黨一枚,文章中的錯誤,不足誠請各位指點!!
學習資料:
https://en.wikipedia.org/wiki/Gaussian_blur
https://www.jianshu.com/p/d8b535efa9db
http://www.cnblogs.com/wantnon/p/4542172.html
https://blog.csdn.net/u011047171/article/details/47977441