《三弦》開發日誌3 波紋效果及其實現

上一篇日誌發表之後,反響有些熱烈。讓我這種迴避型人格有些對寫日誌有些畏懼(其實是懶)。過了一個月,才又鼓起勇氣在這邊靜靜的發一篇簡單的開發日誌。

這次主要聊一聊帶波浪的地面的實現,以及場景特效的實現方法。如下圖所示,地板隨時間以波浪狀變化,角色在地板上走的時候會隨著它的波浪高低起伏。

視覺實現

關於波浪效果的實現,不得不承認,本人開始的時候走了些彎路。一開始,我想的是試試用直接直接修改地板mesh的方式,達到視覺和物理上的同步實現。事實上這被證明是非常不科學的,因為修改mesh會嚴重影響性能。在涉及物理的情況下,我測試的結果是遊戲幀率調到了個位數。所以最終是選擇了利用shader實現視覺效果+利用腳本偽造行走物理效果的辦法。

首先是地板的模型,這裡使用了一個plane:

波浪函數的實現,這裡是函數實現,用的是兩個方向的波進行疊加,波紋隨時間變化。即構造一個全局函數,因為c#代碼和shader代碼里都要使用,所以這是一個以world-space的坐標x,z,以及遊戲時間Time.time為變數的,以波浪高度y為結果的函數。使用的波紋函數為三角函數:

兩路波形線性疊加,具體實現如下:

[SerializeField] float height1 = 1f; [SerializeField] float frequency1 = 1f; [SerializeField] float wavelength1 = 1f; [SerializeField] Vector3 wave1Direction; [SerializeField] float height2 = 1f; [SerializeField] float frequency2 = 1f; [SerializeField] float wavelength2 = 1f; [SerializeField] Vector3 wave2Direction; [SerializeField][Range(0,10f)] float overallHeight = 1f; [SerializeField][Range(0,10f)] float overallFrequency = 1f; [SerializeField][Range(0,10f)] float overallWaveLength = 1f; [SerializeField] float offset; public float GetHeight( float x , float z ) { float x1 = x * wave1Direction.normalized.x + z * wave1Direction.normalized.z; float y1 = Mathf.Sin (x1 / (wavelength1 * overallWaveLength) + Time.time * frequency1 * overallFrequency) * height1 * overallHeight; float x2 = x * wave2Direction.normalized.x + z * wave2Direction.normalized.z; float y2 = Mathf.Sin (x2 / (wavelength2 * overallWaveLength) + Time.time * frequency2 * overallFrequency) * height2 * overallHeight; return y1 + y2 + offset; }

當然這是在C#里的實現,其實在shader里的實現類似。不過注意,需要把函數的參數傳給material。同時,需要每幀對時間參數進行同步(C#代碼):

public void SetMaterial( Material material ) // called at start { material.SetFloat ("_height1", height1 * overallHeight); material.SetFloat ("_frequency1", frequency1 * overallFrequency); material.SetFloat ("_waveLength1", wavelength1* overallWaveLength); material.SetFloat ("_height2", height2 * overallHeight); material.SetFloat ("_frequency2", frequency2 * overallFrequency); material.SetFloat ("_waveLength2", wavelength2* overallWaveLength); material.SetVector ("_wave1Direction", wave1Direction.normalized); material.SetVector ("_wave2Direction", wave2Direction.normalized); material.SetFloat ("_waveOffset", offset); } public void Update Material(Material material ) // called at update { material.SetFloat ("_Timer", Time.time); }

在Shader里,需要在vert函數里,對頂點的位置進行重新計算:

v2f vert(appdata v ){ v2f o; o.uv = v.uv; o.worldPos = mul(unity_ObjectToWorld , v.vertex); o.worldPos.y = GetWaveHeight( o.worldPos.x , o.worldPos.z , _Timer); v.vertex.y = mul(unity_WorldToObject , o.worldPos).y; o.vertex = UnityObjectToClipPos( v.vertex );}

實現的效果如下:

至此,視覺部分已經完成。

這裡把錯誤示範列出來,利用mesh實現波浪效果的腳本:

public Mesh m_mesh;void Update (){ { List<Vector3> verticles = new List<Vector3> (m_mesh.vertices); for (int i = 0; i < verticles.Count; ++i) { var vect = verticles [i]; vect.y = GetWaveHeight( vecticles[i].x , verticles[i].z ); verticles [i] = vect; } m_mesh.SetVertices (verticles); m_mesh.RecalculateBounds (); }}

邏輯實現

邏輯部分的實現也是饒了不少彎路。

一開始想的是直接更改mesh來實現,但是消耗太大,故棄之。

接下來的思路還是利用物理系統,用一個cube模擬玩家腳下的地面,每幀更新它的位置和角度信息。理論上來說,這種實現方法是可以模擬玩家和地表的物理碰撞。但。。。只是理論,實際上這種實現方法會出現比較嚴重的抖動情況。(角色的控制使用的是自帶的character Controller。)

最後的實現方法是,在角色控制的腳本里,直接對角色的位置進行修改。由於波紋函數是平滑的,所以實現的效果也可以做到比較平滑。同時,根據場地形的斜度對速度進行一定的調整,達到上坡下坡的效果。

float climbRate = Mathf.Clamp( WaveController.Instance.GetGradient ( Position.x , Position.z , m_MoveDir) * 10f + 1f , 0.6f , 1.5f ) ;speed *= climbRate;

斜率的計算直接用數值法求導(用導數法太麻煩了):

public float GetGradient( float x , float z , Vector3 velocity ) { // if the velocity is too small if (velocity.magnitude < Mathf.Epsilon) return 0; float thisY = GetHeight (x, z); Vector3 delta = velocity * Time.deltaTime; float otherY = GetHeight (x + delta.x, z + delta.z); // the gradient is delta Y / delta V return - (otherY - thisY) / delta.magnitude; }

弄好之後,就可以愉快地用角色爬坡了~

後期特效實現

後期的特效實現的是一種反向bloom的效果,下圖是該效果誇張處理後的實現:

物體的黑色部分被加強,相當於一個反向的Bloom。

選擇standard asset里的optimized bloom作為基礎,進行修改。源代碼在Asset Store上可以免費下載:

https://assetstore.unity.com/packages/essentials/legacy-image-effects-83913?

assetstore.unity.com

首先分析bloom的代碼,查看BloomOptimized和在MobileBloom.shader里,可與看到Bloom的流程是:

Downsample(提取顏色高亮的部分)->Blur(進行橫豎方向的模糊)->Bloom(把高光顏色和原畫面進行疊加)

根據這個流程,如果我們需要把bloom效果反向,在Bloom部分把顏色進行剔除,同時需要在DownSample部分進行修改,進行曲線上的調整。

具體的做法如下:

fixed4 fragDownsample ( v2f_tap i ) : SV_Target{ fixed4 color = tex2D (_MainTex, i.uv20); color += tex2D (_MainTex, i.uv21); color += tex2D (_MainTex, i.uv22); color += tex2D (_MainTex, i.uv23); //return max(color/4 - THRESHHOLD, 0) * ONE_MINUS_THRESHHOLD_TIMES_INTENSITY; // this curve is used for no reason, I just feel it looks good return ( color / 4 ) * THRESHHOLD + 1 - THRESHHOLD;}fixed4 fragBloom ( v2f_simple i ) : SV_Target{ #if UNITY_UV_STARTS_AT_TOP fixed4 color = tex2D(_MainTex, i.uv2); // return color + tex2D(_Bloom, i.uv); // combine the color by mutiply return color * max( 1 - ( 1 - tex2D(_Bloom, i.uv)) * INTENSITY , 0 ); #else fixed4 color = tex2D(_MainTex, i.uv); // return color + tex2D(_Bloom, i.uv); return color * max( 1 - ( 1 - tex2D(_Bloom, i.uv)) * INTENSITY , 0 ); #endif}

最後的成果展示~


推薦閱讀:

《Real Time Rendering》之Vertex Blending/Skinning
深入淺出基於物理的渲染一

TAG:獨立遊戲開發 | 實時渲染 | Unity遊戲引擎 |