Unity Shader之磨砂玻璃與水霧玻璃效果

遊資網發表於2019-05-21
Unity Shader之磨砂玻璃與水霧玻璃效果


導讀

玻璃效果是遊戲場景中常見的效果之一,除卻普通的透明玻璃外,磨砂玻璃也是較為常見的效果。玻璃與場景中的其他物體也會有互動,例如,浴室中的玻璃、雨天的窗戶會在水汽的作用下帶有一定差別的霧效。本文以Unity Frosted Glass專案與開源庫中相關專案為例,介紹磨砂玻璃的做法和在移動端執行的效能。

開源庫連結:https://lab.uwa4d.com/lab/5b5613a3d7f10a201fd80bbb

模糊效果

磨砂玻璃的效果特點是模糊與半透明,該專案通過自定義的卷積實現來達到模糊效果。具體程式碼實現在FrostedGlass.shader中。

  1. //vertex to fragment   
  2. struct v2f {
  3.   float4 pos : POSITION;
  4.   float2 uv : TEXCOORD0;
  5.   float4 uv01 : TEXCOORD1;
  6.   float4 uv23 : TEXCOORD2;
  7.   float4 uv45 : TEXCOORD3;
  8. };

  9. v2f vert (appdata_img v) {
  10.   v2f o;
  11.   o.pos = UnityObjectToClipPos(v.vertex);
  12.   o.uv.xy = v.texcoord.xy;
  13.   o.uv01 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1);
  14.   o.uv23 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 2.0;
  15.   o.uv45 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 3.0;
  16.   return o;
  17. }
複製程式碼
(頂點著色器及相關資料結構SeparableGlassBlur.shader)

offsets是一個float4型別的定值,表示偏移量。通過頂點著色器的運算,輸出的v2f結構體中,pos儲存了該頂點從Object Space轉換到相機裁剪空間中的齊次座標。uv、uv01、uv23、uv45分別儲存了該頂點、偏移量為offsets的兩個頂點、偏移量為2*offsets的兩個頂點、偏移量為3*offsets的兩個頂點的uv座標。

  1. half4 frag (v2f i) : COLOR {
  2.   half4 color = float4 (0,0,0,0);
  3.   color += 0.40 * tex2D (_MainTex, i.uv);
  4.   color += 0.15 * tex2D (_MainTex, i.uv01.xy);
  5.   color += 0.15 * tex2D (_MainTex, i.uv01.zw);
  6.   color += 0.10 * tex2D (_MainTex, i.uv23.xy);
  7.   color += 0.10 * tex2D (_MainTex, i.uv23.zw);
  8.   color += 0.05 * tex2D (_MainTex, i.uv45.xy);
  9.   color += 0.05 * tex2D (_MainTex, i.uv45.zw);
  10.   return color;
  11. }
複製程式碼
(片元著色器SeparableGlassBlur.shader)

該卷積核的一維權重分佈如下表所示:

Unity Shader之磨砂玻璃與水霧玻璃效果
  1. (進行濾波操作 CommandBufferBlur.cs)_CommandBuffer.SetGlobalVector("offsets", new Vector4(2.0f / sizes[i].x, 0, 0, 0));
  2. _CommandBuffer.Blit(blurredID, blurredID2, _Material);
  3. _CommandBuffer.SetGlobalVector("offsets", new Vector4(0, 2.0f / sizes[i].y, 0, 0));
  4. _CommandBuffer.Blit(blurredID2, blurredID, _Material);
複製程式碼

對影像使用水平方向一維卷積核與豎直方向一維卷積核進行兩次濾波得到最終的影像。等同於如下圖所示的二維卷積核進行濾波:

Unity Shader之磨砂玻璃與水霧玻璃效果

Unity Shader之磨砂玻璃與水霧玻璃效果
(使用水平方向的一維卷積核對影像進行濾波CommandBufferBlur.cs)

Unity Shader之磨砂玻璃與水霧玻璃效果
(兩次濾波後得到模糊效果)

卷積核的選擇有很多種,其中較為常用的有高斯模糊、kawase Blur,開源庫中有相關的專案實現了相關效果,例如:Blur for Unity、Gaussian Blur、Super Blur。

Unity Shader之磨砂玻璃與水霧玻璃效果
高斯模糊

這些模糊方式,所採用的卷積核各不相同,有興趣的讀者可以進行相關試驗。

捕捉螢幕紋理

實現模糊效果後,需要捕捉到玻璃後方的螢幕效果圖片交予模糊效果著色器進行處理。在Unity Shader中有一種特殊的Pass:GrabPass,可以很方便地獲取螢幕影像。但這種方式開銷太大,並不適合在移動端執行。

該專案採取CommandBuffer來達到這一目的。可以節省開銷、提高效能。

  1. //建立名為“Blur screen”的CommandBuffer
  2. _CommandBuffer = new CommandBuffer();
  3. _CommandBuffer.name = "Blur screen";


  4. int screenCopyID = Shader.PropertyToID("_ScreenCopyTexture");
  5. //新建一個臨時RenderTexture
  6. _CommandBuffer.GetTemporaryRT(screenCopyID, -1, -1, 0, FilterMode.Bilinear, _TextureFormat);
  7. //獲取當前螢幕影像
  8. _CommandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, screenCopyID);
  9. int blurredID = Shader.PropertyToID("_Grab" + i + "_Temp1");
  10. int blurredID2 = Shader.PropertyToID("_Grab" + i + "_Temp2");
  11. _CommandBuffer.GetTemporaryRT(blurredID, (int)sizes[i].x, (int)sizes[i].y, 0, FilterMode.Bilinear, _TextureFormat);
  12. _CommandBuffer.GetTemporaryRT(blurredID2, (int)sizes[i].x, (int)sizes[i].y, 0, FilterMode.Bilinear, _TextureFormat);
  13. _CommandBuffer.Blit(screenCopyID, blurredID);
  14. _CommandBuffer.ReleaseTemporaryRT(screenCopyID);
複製程式碼
(獲取螢幕影像CommandBufferBlur.cs)

但此時獲取到的影像是一整張螢幕圖象,而我們需要進行模糊處理的影像,只有玻璃模型背後的影像,這時我們需要在頂點著色器中對頂點進行處理。

  1. v2f vert (appdata v)
  2. {
  3.   v2f o;
  4.   o.vertex = UnityObjectToClipPos(v.vertex);
  5.   //確保材質球中的縮放和偏移位置正確
  6.   o.uvfrost = TRANSFORM_TEX(v.uv, _FrostTex);
  7.   //獲得該頂點在螢幕圖象中正確的紋理座標
  8.   o.uvgrab = ComputeGrabScreenPos(o.vertex);
  9.   return o;
  10. }
複製程式碼

Unity內建的ComputeGrabScreenPos函式,幫助我們完成了座標轉換,根據o.uvgrab在螢幕影像中採集到的影像資訊,便是玻璃模型背後的螢幕影像:

  1. half4 ref00=tex2Dproj(_GrabBlurTexture_0,i.uvgrab);
複製程式碼

將CommandBuffer掛載到相機上,實現實時更新渲染。

  1. _Camera.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, _CommandBuffer);
複製程式碼

Unity Shader之磨砂玻璃與水霧玻璃效果
(效果圖)

水霧玻璃

在磨砂玻璃的基礎上,進一步擴充其他的特殊效果。

浴室中的玻璃、雨天的窗戶會在水汽的作用下帶有一定差別的霧效。這種效果的特點除卻模糊與半透明外,還會有模糊程度的差別。

  1. Vector2[] sizes = {
  2.   new Vector2(Screen.width, Screen.height),
  3.   new Vector2(Screen.width / 2, Screen.height / 2),
  4.   new Vector2(Screen.width / 4, Screen.height / 4),
  5.   new Vector2(Screen.width / 8, Screen.height / 8),
  6. };
複製程式碼
(定義不同大小的偏移量CommandBufferBlur.cs)

定義不同大小的偏移量,通過SeparableGlassBlur.shader的運算,得到四張模糊程度不同的螢幕影像,由0~3模糊程度加深:

  1. sampler2D _GrabBlurTexture_0;
  2.         sampler2D _GrabBlurTexture_1;
  3.         sampler2D _GrabBlurTexture_2;
  4.         sampler2D _GrabBlurTexture_3;
複製程式碼

制定取樣規則從這四張模糊程度不同的螢幕影像中進行採集,為了讓過渡圓滑,使用lerp函式進行插值。載入一張效果灰度圖(_FrostTex),確定霧效差別的強度(_FrostIntensity):

  1. fixed4 frag (v2f i) : SV_Target
  2. {
  3.   float surfSmooth = 1 - tex2D(_FrostTex, i.uvfrost)* _FrostIntensity;
  4.   //如果x 值小於 a,則返回a;如果 x 值大於 b,返回b;否則,返回 x
  5.   surfSmooth = clamp(0, 1, surfSmooth);
  6.   half4 refraction;
  7.   //二維紋理投影對映
  8.   half4 ref00 = tex2Dproj(_GrabBlurTexture_0, i.uvgrab);
  9.   half4 ref01 = tex2Dproj(_GrabBlurTexture_1, i.uvgrab);
  10.   half4 ref02 = tex2Dproj(_GrabBlurTexture_2, i.uvgrab);
  11.   half4 ref03 = tex2Dproj(_GrabBlurTexture_3, i.uvgrab);
  12.   //進行平滑過渡
  13.   float step00 = smoothstep(0.75, 1.00, surfSmooth);
  14.   float step01 = smoothstep(0.5, 0.75, surfSmooth);
  15.   float step02 = smoothstep(0.05, 0.5, surfSmooth);
  16.   refraction = lerp(lerp(lerp(ref03, ref02, step02), ref01, step01), ref00, step00);               
  17.   return refraction;
  18. }
複製程式碼
(片元著色器FrostedGlass.shader)

專案中以_FrostTex影像中r值作為取樣依據。根據1-r值*_FrostIntensity得到的數值(surfSmooth)為權重,分別從上述四張模糊程度不同的螢幕影像中進行採集,最後得到該頂點最終的顏色。

以下圖作為效果圖,驗證不同的surfSmooth大小,渲染得到不同的模糊程度:

Unity Shader之磨砂玻璃與水霧玻璃效果
(從左至右R值依次為:0,0.25,0.5,0.75,1)

Unity Shader之磨砂玻璃與水霧玻璃效果
(效果圖)

由此可以模擬出不同的水霧玻璃效果:

Unity Shader之磨砂玻璃與水霧玻璃效果
(有點髒的霧玻璃)

Unity Shader之磨砂玻璃與水霧玻璃效果
(覆蓋著一層小水珠)

效能測試(使用UWA GOT Online工具測評)

選擇低端機型紅米4x進行測試(不開啟多執行緒渲染):

使用水霧玻璃效果:

Unity Shader之磨砂玻璃與水霧玻璃效果

FPS均值為26幀:

Unity Shader之磨砂玻璃與水霧玻璃效果

Camera.Render函式耗時11ms左右開銷不大:

Unity Shader之磨砂玻璃與水霧玻璃效果

可以看到CPU等待GPU渲染完成時間較長,當前渲染壓力在GPU端:

Unity Shader之磨砂玻璃與水霧玻璃效果

可見實時抓取螢幕圖片並進行渲染操作在移動端開銷還是巨大的。該效果生成四張模糊效果圖片,每張圖片的生成通過了兩次SeparableGlassBlur.shader的計算。最終每個頂點在FrostedGlass.shader中進行運算。GPU計算量過大。

只使用磨砂玻璃效果時,紅米4xFPS均值為42幀左右。

Unity Shader之磨砂玻璃與水霧玻璃效果

此時只生成一張模糊效果圖片,該圖片的生成通過了兩次SeparableGlassBlur.shader的計算。相比較而言GPU端計算大幅減少,CPU等待時間縮短,效能提升明顯。

故而開發者在實現此效果時,需要在效能與效果之間平衡,儘可能減少計算量,例如可以使用3x3的卷積核,而不是5x5的,可以在取樣之前做判斷,減少取樣次數。

快用UWA Lab合輯Mark好專案!

Unity Shader之磨砂玻璃與水霧玻璃效果

今天的推薦就到這兒啦,或者它可直接使用,或者它需要您的潤色,或者它啟發了您的思路......

請不要吝嗇您的點贊和轉發,讓我們知道我們在做對的事。當然如果您可以留言給出寶貴的意見,我們會越做越好。


來源:UWA
原地址:https://blog.uwa4d.com/archives/UWALab_UnityShader.html

相關文章