如何在遊戲中表現玻璃的質感?這個方法效率超高

Blood發表於2020-06-12
編者按:玻璃的質感表現,在遊戲中一直都是比較難製作的,因為玻璃包含了很多特殊的特性。本文作者將分享一種非常低開銷的實現unity玻璃材質的方法,希望對大家有所幫助。

一直以來玻璃的質感表現在遊戲中都是比較難製作的一種,因為玻璃包含了很多特殊的特性:反射、折射、厚度等,而這些特性在渲染的時候需要消耗大量的計算。這也導致真實的玻璃渲染基本都是離線渲染的。

包括目前主機端遊戲的一些玻璃效果也不是很理想,離離線品質還有很大的差距。基本都缺少了折射項,只有高光部分。

如何在遊戲中表現玻璃的質感?這個方法效率超高
(圖中玻璃杯來自ff7重置版、大表哥2)

本文分享一種非常低開銷的實現一種玻璃材質的思路。它基本可以適應目前的任何平臺。同時這個方案在迭代上非常靈活,只需要替換燈光反射與環境反射部分就可以運用到PBR上。

它沒有複雜的演算法,沒有光線追蹤。

可以沒有RT,沒有後處理。

主要從美術表現的視覺層面來實現最終效果。

我們先看下實現出來的效果!(以下所有圖片均為unity實時截圖,No tonemapping )

下面是模擬的高腳杯,以及香檳。

如何在遊戲中表現玻璃的質感?這個方法效率超高

杯子、啤酒、泡沫,都是同一個shader實現的。

如何在遊戲中表現玻璃的質感?這個方法效率超高

啤酒裡面的氣泡,以及香檳裡面的氣泡。

如何在遊戲中表現玻璃的質感?這個方法效率超高


如何在遊戲中表現玻璃的質感?這個方法效率超高

如何在遊戲中表現玻璃的質感?這個方法效率超高

先來分析下玻璃的特點。

如何在遊戲中表現玻璃的質感?這個方法效率超高

玻璃本身是透明的,影響玻璃質感的主要有2個點:

1)反射
玻璃會反射環境裡高亮的地方。

2)折射
玻璃越厚的地方折射越強。

而最難的地方就在於折射的計算。

如何在遊戲中表現玻璃的質感?這個方法效率超高

如何在遊戲中表現玻璃的質感?這個方法效率超高

但在人的視覺上,強烈的折射會打斷背景的連續性,在折射率強的地方已經無法看清背後的事物,會讓人感覺“這塊區域不是完全透明的”。我們主要從這一點入手,來模擬視覺層面的折射表現。

為了節省效能我這裡以Matcap來製作環境紋理的取樣。(不過這裡是很靈活的可以根據需求替換PBR的ibl環境以及實時高光)

關於Matcap

全稱MaterailCapture,在一張紋理裡面儲存光照資訊,通過模型法線的xy分量去取樣,得到在該方向法線的光照資訊。詳細的請看wiki連結:
http://wiki.unity3d.com/index.php/MatCap

如何在遊戲中表現玻璃的質感?這個方法效率超高

但是以上面的方法計算出來的效果會在物體不居中的時候產生拉伸變形,這裡需要對normal進行矯正。這裡放上2種方法的程式碼:

  1. //MatCap 普通版
  2. half2 MatCapUV ;
  3. matCapUV.x = dot(UNITY_MATRIX_IT_MV[0].xyz,v.normal);
  4. matCapUV.y = dot(UNITY_MATRIX_IT_MV[1].xyz,v.normal);
  5. matCapUV = matCapUV * 0.5 + 0.5;
  6. //MatCap 矯正版
  7. // float3 N = normalize(UnityObjectToWorldNormal(v.normal));
  8. // float3 viewPos = UnityObjectToViewPos(v.vertex);
  9.   float2 MatCapUV (in float3 N,in float3 viewPos)
  10.   {
  11.     float3 viewNorm = mul((float3x3)UNITY_MATRIX_V, N);
  12.         float3 viewDir = normalize(viewPos);
  13.         float3 viewCross = cross(viewDir, viewNorm);
  14.         viewNorm = float3(-viewCross.y, viewCross.x, 0.0);
  15.         float2 matCapUV = viewNorm.xy * 0.5 + 0.5;
  16.         return matCapUV;
  17.   }
複製程式碼

這裡如果不需要法線貼圖可以直接在VS裡計算。

如何在遊戲中表現玻璃的質感?這個方法效率超高

反射部分比較簡單,可以處理一張玻璃高光的Matcap紋理來處理酒杯的高光反射。

左邊是Matcap直接輸出,右邊是將Matcap當做alpha輸出。

如何在遊戲中表現玻璃的質感?這個方法效率超高


  1. [HDR]_SpColor("Sp Color", Color) = (1.0,1.0,1.0,1.0)
  2. //給高光一個單獨的色彩來控制反射的色彩與強度
  3. float3 spmatCap= tex2D(_CapTex,matCapuv);
  4. spmatCap *=_SpColor.rgb

  5. o.color.rgb = spmatCap  ;
  6. o.color.a = spmatCap .r;
複製程式碼

如何在遊戲中表現玻璃的質感?這個方法效率超高

3.1 玻璃折射MASK

製作折射效果前,首先我們需要計算出折射的範圍,確定哪些地方需要使用折射。

A.玻璃本身厚度範圍

我們需要預處理玻璃杯本身玻璃的厚度。比如杯子底部、以及玻璃杯口部分。
這裡的厚度可以通過2種方法來儲存輸出:

1)預處理一張厚度紋理。
2)通過繪製到頂點色來輸出。

  1. float3 thicknessTex= tex2D(_MaskTex, i.uv);
  2. float sThickness = thicknessTex.r * i.color.r; //杯體本身實心玻璃部分
複製程式碼

我將紋理儲存在R通道里面,其他通道可以為後面留坑。

這樣我們可以得到手動可控的厚度範圍。

如何在遊戲中表現玻璃的質感?這個方法效率超高

B.玻璃側面厚度

邊緣部分這裡使用了菲尼爾來計算。


  1. _FenierEdge("Fenier Range", Range(-2, 2)) = 0.0
  2. _FenierIntensity("Fenier intensity", Range(0, 10)) = 2.0
  3. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4. float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
  5. float NoV = dot(N,V);

  6. float EdgeThickness (in float NoV)
  7. {
  8.    float ET = saturate((NoV-_FenierEdge)*_FenierIntensity);
  9.    return ET;
  10. }
複製程式碼

通過調節引數我們可以得到杯子的邊緣範圍。

如何在遊戲中表現玻璃的質感?這個方法效率超高

最後將兩種範圍合併到一起,我們就得到了完整的玻璃折射區域。

如何在遊戲中表現玻璃的質感?這個方法效率超高

  1. _FenierEdge("FenierRange", Range(-2, 2)) = 0.0
  2. _FenierIntensity("Fenierintensity", Range(0, 10)) = 2.0
  3. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4. float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
  5. float NoV = dot(N,V);
  6. float3 thicknessTex= tex2D(_MaskTex, i.uv) ;
  7. float sThickness = thicknessTex.r * i.color.r; //杯體本身實心玻璃部分
  8. float fThickness = thicknessTex.g;// 杯體菲尼爾厚度

  9. float EdgeThickness (in float NoV ,in float eThickness )
  10. {
  11.    fThickness = (eThickness -0.5)*0.5;
  12.    float ET = saturate((NoV-_FenierEdge+fThickness)*_FenierIntensity);
  13.    return 1-ET*eThickness ;
  14. }
複製程式碼

3.2 模擬玻璃折射

折射同樣使用matcap來進行處理,但需要新的UV取樣。因為我們需要使用上面得到的折射mask來扭曲這張matcap紋理。(可以和高光使用同一張matcap紋理,也可以單獨新建一張。我這裡偷懶和高光使用的是同一張)

如何在遊戲中表現玻璃的質感?這個方法效率超高

  1. float Refintensity = Thickness*_Refintensity;
  2. float3 rfmatCap = tex2D(_RfCapTex,matCapuv+Refintensity);
  3. float3 rfmatColor= RFLerpColor(rfmatCap,Thickness)
  4. //_BaseColor新增一個自定義的顏色引數,就可以自由控制玻璃本體色彩
  5. float3 RFLerpColor (in float3 rfmatCap,in float Thickness)
  6. {
  7.   float3 c1 = _BaseColor.rgb*0.5;
  8.   float3 c2 = rfmatCap*_BaseColor.rgb;
  9.   float cMask = Thickness;
  10.     return lerp(c1,c2,cMask ); //這裡也可以 *v.color.rgb 用頂點色來控制玻璃區域性色彩,製作出彩色玻璃的效果
  11. }
複製程式碼

折射的表現做好後,只需要把我們之前製作的折射Mask 當做alpha來輸出,整個折射部分就製作完畢。

如何在遊戲中表現玻璃的質感?這個方法效率超高

最後將反射與折射合併在一起輸出整個效果基本就完成了。

如何在遊戲中表現玻璃的質感?這個方法效率超高

  1. float alpha = saturate(max(spmatCap.r*_SpColor.a ,Thickness)*_BaseColor.a);
  2. //_SpColor 是給高光顏色單獨一個色彩控制項
  3. //alpha這裡的計算是為了可以分別控制高光的透明度,以及整體杯子的透明度
  4. col.rgb = rfColor+spColor;//反射與折射合併
  5. col.a = alpha;
複製程式碼

3.3 新增法線細節

因為遊戲裡的模型精度相對較低,為了提升表面細節,我們還可以新增法線貼圖。這樣就可以製作更加豐富的表現了。

因為上面的 折射MASK 與 Matcap對映,都是基於 N(normal) 來計算 。所以這裡只需要將normalMap計算進來就行了。

如何在遊戲中表現玻璃的質感?這個方法效率超高

  1. o.worldTangent =normalize(UnityObjectToWorldNormal(v.tangent));
  2. o.worldBinormal = cross(o.worldNorm, o.worldTangent) * v.tangent.w;   
  3. o.uv.zw = TRANSFORM_TEX(v.texcoord.xy,_NormalTex) ;//(給法線單獨的UV這樣可以使用細節法線,
  4. 也可以使用多套法線紋理來混合)
  5. o.uv.xy = TRANSFORM_TEX(v.texcoord.xy,_MaskTex) ;
  6. //------↑VSout----------------------------------------------------------------------------------------------

  7. void GetNormal(v2f i, inout float3 N)
  8.   {
  9.     float4 normalTex = tex2D(_NormalTex, i.uv.zw);
  10.     float3 normalTS = normalize(UnpackNormal(normalTex));
  11.     float3x3 tbn = float3x3(i.worldTangent, i.worldBinormal, i.worldNorm);
  12.     N = normalize(mul(normalTS, tbn));
  13.   }
複製程式碼

PS:如果需要更加真實的表現,可以使用一個RT來製作背後物體被折射扭曲的效果,但其實完全可以不用加。

到此材質思路方面就完結了,下面會用一些例項來講一下運用方面的細節。

如何在遊戲中表現玻璃的質感?這個方法效率超高

4.1 磨砂玻璃

其實磨砂玻璃在Matcap下非常非常簡單,我們只需要將Matcap的紋理拿到Photoshop裡面模糊處理下就可以了。

如何在遊戲中表現玻璃的質感?這個方法效率超高

如果是IBL、Phone、GGX等高光反射, 直接按粗糙度方式處理就行。

4.2 多層玻璃和液體

現在我只做了杯子的外表面,玻璃杯內部還沒處理。

先看看這個杯子模型,這裡有一些需要注意的點。

我把杯子模型分成了3個部分:

  • 杯子外部
  • 杯子內壁
  • 杯子裡的液體


區分內外壁的原因主要是為了解決半透明排序引起的前後關係錯誤的問題。

如何在遊戲中表現玻璃的質感?這個方法效率超高

在這裡我們新建1個材質球,也可以將之前的外壁材質球拷貝一份 賦予杯子內壁模型。

如何在遊戲中表現玻璃的質感?這個方法效率超高

內壁材質 :建議調節引數把邊緣厚度去掉,除非你做的是雙層玻璃杯。杯底部分以厚度mask控制(紋理、頂點色都行)。

新增液體材質

同樣的方式建立一個新的液體材質球。修改顏色,可以做出水、啤酒、紅酒或其他飲料。

如何在遊戲中表現玻璃的質感?這個方法效率超高

在這裡將厚度mask貼圖紋理替換成了一張小氣泡的紋理。用來模擬啤酒裡面的小氣泡。

如何在遊戲中表現玻璃的質感?這個方法效率超高

同時用動畫給這張紋理取樣K關鍵幀,新增一個UV流動的動畫。就能做出啤酒氣泡流動的效果。

如何在遊戲中表現玻璃的質感?這個方法效率超高

關於渲染層級

如何在遊戲中表現玻璃的質感?這個方法效率超高

半透明材質---需要修改內部材質球的 RenderQueue 在外部材質的引數上-1。否則在某些角度或者複雜模型的時候深度的前後關係會出錯。

例如:上面的啤酒杯的渲染順序與RenderQueue。

杯子外部(3000)←杯內液體(2999)←杯子內部(2998)

半透明部分:其實就是以我們看到的順序來排就行。因為我們看到整個杯子的時候優先是外壁,其次看到的是液體部分,最後才是內壁部分。數值越小越優先渲染。

4.3 冰裂效果的玻璃

使用一張裂紋的紋理,搭配頂點色繪製的玻璃基礎厚度。能模擬出類似冰裂玻璃的效果。

如何在遊戲中表現玻璃的質感?這個方法效率超高

關於PBR動態環境的適配

可以將環境的球協光照、LightProbes等低配顏色與折射部分做混合,就能隨環境顏色變化而變化。

作者:Blood
來源:騰訊GWB遊戲無界
原地址:https://mp.weixin.qq.com/s/-ukjq_pJqCCAYHQEcmzO3w

相關文章