如何在遊戲中表現玻璃的質感?這個方法效率超高
一直以來玻璃的質感表現在遊戲中都是比較難製作的一種,因為玻璃包含了很多特殊的特性:反射、折射、厚度等,而這些特性在渲染的時候需要消耗大量的計算。這也導致真實的玻璃渲染基本都是離線渲染的。
包括目前主機端遊戲的一些玻璃效果也不是很理想,離離線品質還有很大的差距。基本都缺少了折射項,只有高光部分。
(圖中玻璃杯來自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種方法的程式碼:
- //MatCap 普通版
- half2 MatCapUV ;
- matCapUV.x = dot(UNITY_MATRIX_IT_MV[0].xyz,v.normal);
- matCapUV.y = dot(UNITY_MATRIX_IT_MV[1].xyz,v.normal);
- matCapUV = matCapUV * 0.5 + 0.5;
- //MatCap 矯正版
- // float3 N = normalize(UnityObjectToWorldNormal(v.normal));
- // float3 viewPos = UnityObjectToViewPos(v.vertex);
- float2 MatCapUV (in float3 N,in float3 viewPos)
- {
- float3 viewNorm = mul((float3x3)UNITY_MATRIX_V, N);
- float3 viewDir = normalize(viewPos);
- float3 viewCross = cross(viewDir, viewNorm);
- viewNorm = float3(-viewCross.y, viewCross.x, 0.0);
- float2 matCapUV = viewNorm.xy * 0.5 + 0.5;
- return matCapUV;
- }
這裡如果不需要法線貼圖可以直接在VS裡計算。
反射部分比較簡單,可以處理一張玻璃高光的Matcap紋理來處理酒杯的高光反射。
左邊是Matcap直接輸出,右邊是將Matcap當做alpha輸出。
- [HDR]_SpColor("Sp Color", Color) = (1.0,1.0,1.0,1.0)
- //給高光一個單獨的色彩來控制反射的色彩與強度
- float3 spmatCap= tex2D(_CapTex,matCapuv);
- spmatCap *=_SpColor.rgb
- o.color.rgb = spmatCap ;
- o.color.a = spmatCap .r;
3.1 玻璃折射MASK
製作折射效果前,首先我們需要計算出折射的範圍,確定哪些地方需要使用折射。
A.玻璃本身厚度範圍
我們需要預處理玻璃杯本身玻璃的厚度。比如杯子底部、以及玻璃杯口部分。
這裡的厚度可以通過2種方法來儲存輸出:
1)預處理一張厚度紋理。
2)通過繪製到頂點色來輸出。
- float3 thicknessTex= tex2D(_MaskTex, i.uv);
- float sThickness = thicknessTex.r * i.color.r; //杯體本身實心玻璃部分
我將紋理儲存在R通道里面,其他通道可以為後面留坑。
這樣我們可以得到手動可控的厚度範圍。
B.玻璃側面厚度
邊緣部分這裡使用了菲尼爾來計算。
- _FenierEdge("Fenier Range", Range(-2, 2)) = 0.0
- _FenierIntensity("Fenier intensity", Range(0, 10)) = 2.0
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
- float NoV = dot(N,V);
- float EdgeThickness (in float NoV)
- {
- float ET = saturate((NoV-_FenierEdge)*_FenierIntensity);
- return ET;
- }
通過調節引數我們可以得到杯子的邊緣範圍。
最後將兩種範圍合併到一起,我們就得到了完整的玻璃折射區域。
- _FenierEdge("FenierRange", Range(-2, 2)) = 0.0
- _FenierIntensity("Fenierintensity", Range(0, 10)) = 2.0
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- float3 V = normalize(_WorldSpaceCameraPos - i.worldPos);
- float NoV = dot(N,V);
- float3 thicknessTex= tex2D(_MaskTex, i.uv) ;
- float sThickness = thicknessTex.r * i.color.r; //杯體本身實心玻璃部分
- float fThickness = thicknessTex.g;// 杯體菲尼爾厚度
- float EdgeThickness (in float NoV ,in float eThickness )
- {
- fThickness = (eThickness -0.5)*0.5;
- float ET = saturate((NoV-_FenierEdge+fThickness)*_FenierIntensity);
- return 1-ET*eThickness ;
- }
3.2 模擬玻璃折射
折射同樣使用matcap來進行處理,但需要新的UV取樣。因為我們需要使用上面得到的折射mask來扭曲這張matcap紋理。(可以和高光使用同一張matcap紋理,也可以單獨新建一張。我這裡偷懶和高光使用的是同一張)
- float Refintensity = Thickness*_Refintensity;
- float3 rfmatCap = tex2D(_RfCapTex,matCapuv+Refintensity);
- float3 rfmatColor= RFLerpColor(rfmatCap,Thickness)
- //_BaseColor新增一個自定義的顏色引數,就可以自由控制玻璃本體色彩
- float3 RFLerpColor (in float3 rfmatCap,in float Thickness)
- {
- float3 c1 = _BaseColor.rgb*0.5;
- float3 c2 = rfmatCap*_BaseColor.rgb;
- float cMask = Thickness;
- return lerp(c1,c2,cMask ); //這裡也可以 *v.color.rgb 用頂點色來控制玻璃區域性色彩,製作出彩色玻璃的效果
- }
折射的表現做好後,只需要把我們之前製作的折射Mask 當做alpha來輸出,整個折射部分就製作完畢。
最後將反射與折射合併在一起輸出整個效果基本就完成了。
- float alpha = saturate(max(spmatCap.r*_SpColor.a ,Thickness)*_BaseColor.a);
- //_SpColor 是給高光顏色單獨一個色彩控制項
- //alpha這裡的計算是為了可以分別控制高光的透明度,以及整體杯子的透明度
- col.rgb = rfColor+spColor;//反射與折射合併
- col.a = alpha;
3.3 新增法線細節
因為遊戲裡的模型精度相對較低,為了提升表面細節,我們還可以新增法線貼圖。這樣就可以製作更加豐富的表現了。
因為上面的 折射MASK 與 Matcap對映,都是基於 N(normal) 來計算 。所以這裡只需要將normalMap計算進來就行了。
- o.worldTangent =normalize(UnityObjectToWorldNormal(v.tangent));
- o.worldBinormal = cross(o.worldNorm, o.worldTangent) * v.tangent.w;
- o.uv.zw = TRANSFORM_TEX(v.texcoord.xy,_NormalTex) ;//(給法線單獨的UV這樣可以使用細節法線,
- 也可以使用多套法線紋理來混合)
- o.uv.xy = TRANSFORM_TEX(v.texcoord.xy,_MaskTex) ;
- //------↑VSout----------------------------------------------------------------------------------------------
- void GetNormal(v2f i, inout float3 N)
- {
- float4 normalTex = tex2D(_NormalTex, i.uv.zw);
- float3 normalTS = normalize(UnpackNormal(normalTex));
- float3x3 tbn = float3x3(i.worldTangent, i.worldBinormal, i.worldNorm);
- N = normalize(mul(normalTS, tbn));
- }
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
相關文章
- 提升休閒遊戲的廣告變現效率,可以用這些方法遊戲
- 科幻二遊幾連發!這些遊戲的科幻感如何在美術上實現遊戲
- 如何在海外發好一款遊戲?這5個方法很關鍵遊戲
- 嫌棄遊戲劇情太爛?不如來這個超高自由度的RPG裡自己寫遊戲
- 品質≠平臺,這些好評獨遊讓“搓玻璃”這件事變得“泰褲辣”
- 超高畫質4K精確診療 奧林巴斯超高畫質影像系統問世
- 如何定義遊戲的動作風格——品質感篇遊戲
- 中國遊戲稀缺的,或許是那一點“生活的質感”遊戲
- 畫面好的大型手遊 十大畫質超高的手機單機遊戲排行榜遊戲
- 不戴口罩的逆行者,這屆機器人在抗疫中表現如何?機器人
- 這可能是實現高斯模糊(毛玻璃)最簡單的庫了
- 遊戲難以變現?或許你應該試下這5個方法遊戲
- 133 行程式碼實現質感地形行程
- 提高django model效率的幾個小方法Django
- 質量與效率
- Android 毛玻璃效果的實現Android
- 探索如何在武漢鏈(基於ETH)的一個合約中實現同質化與非同質化功能
- MySql中表單輸入資料出現中文亂碼的解決方法MySql
- PS新手教程:如何在Photoshop中使用“漸變工具”製作質感按鈕?
- 毛玻璃效果在Android的實現Android
- 一個自己都感覺幼稚的猜數遊戲遊戲
- PS新手教程!教你繪製一枚通透質感的遊戲按鈕遊戲
- 感謝 learnku!~真心感覺到這是一個學習知識的平臺!
- React實現的超高仿豆瓣電影React
- 如何快速大量繪製遊戲物件?這個方法值得一試遊戲物件
- 中國超高清視訊產業這一年產業
- 在這個遊戲裡,我是如何在一天內把馬雲的2700億全敗光的?遊戲
- 安芯網盾獲公安部感謝信,在網路安全專項行動中表現卓越
- 提高程式執行效率的10個簡單方法
- 玻璃的節奏
- 好東西都在這!推薦一大波質量超高的網站、設計師和攝影師網站
- 幾個小技巧,幫你快速提高遊戲的打擊感遊戲
- 這 16 個 CSS 偽類,助你提升佈局效率!CSS
- 如何在 Linux 下當個遊戲主播Linux遊戲
- 這個男人讓你的爬蟲開發效率提升8倍爬蟲
- 如何在你的 Python 遊戲中新增一個玩家Python遊戲
- 中秋不加班!這樣做可以提高遊戲開發效率遊戲開發
- 【質數判斷】給定兩個數,判斷這兩個數是否互質?