一個簡單有趣的「黑洞」效果
註:如果gif不能動,就點開等一會兒,如果還是不能動,我也沒轍。
這個效果純粹是做著玩兒。這裡只講原理,大部分代碼都有優化的空間。需要用的同學請自行優化。
這個效果最簡單的原理,就是判斷多邊形的頂點和「黑洞」(圖中的圓球)之間的距離。當兩者距離越近,頂點向「黑洞」偏移的量就越大。所以我們能看出來,這個效果主要集中在shader的Vertex函數部分。
首先寫一個簡單的腳本,在update的時候向多邊形的material傳一個變數_BlackHolePos,即「黑洞」的世界位置。如果「黑洞」位置是不變的,也可以考慮在初始化的時候直接傳給material。這樣就不必每幀都做這個工作了。
這裡有一個需要注意的點,就是this.GetComponent<Renderer>()這句。如果確定是meshrenderer或者skinnedMeshRenderer就去GetComponent這兩個類。如果不能確定,簡單的做法就是直接GetComponent Renderer這個基類。
接下來就是shader代碼的 部分。首先獲取當前頂點的世界位置:
fixed4 oriWorldPos = mul(unity_ObjectToWorld , i.pos); n
接下來獲取該頂點的世界位置與「黑洞」的世界位置之間的距離:
fixed dis = distance (oriWorldPos , _BlackHolePos);n
同時聲明一個修改之後的世界位置worldPos,暫時將oriWorldPos的值賦給它。
fixed4 worldPos = oriWorldPos;n
準備工作都做好了,接下來就是考慮拉伸這個頂點的工作了。首先我們先劃定一個範圍_Range,並將該效果限制在距離「黑洞」這個範圍之內才會顯示。如果不做這一步的話可能會出現一些意想不到的結果。
之後將主要的計算都放在if (dis < _Range) {}內。
現在要考慮的是偏移該頂點的量。這裡涉及到兩個變數:一個是該頂點在世界坐標系中偏移的方向;另一個是該頂點在世界坐標系中偏移的距離。
其中方向向量是normalize(_BlackHolePos - worldPos.xyz)。已知兩點求向量這個是中學數學,不多贅述。
而偏移量的問題,我們要這麼考慮,當該頂點距離「黑洞」越近的時候,其在已知向量上移動的距離數值就越大。其中該頂點距離「黑洞」的距離是已知的dis。而dis的範圍是0到_Range。換句話說,當該頂點受到影響最小的時候,是dis=_Range的時候;而受到影響最大的時候,dis=0的時候。所以說該頂點與「黑洞」的距離關係,可以用_Range - dis來表示。
有了方向和距離之後,那麼將該頂點位置加上計算得出的偏移,就是新頂點的位置:
worldPos.xyz += normalize(_BlackHolePos - worldPos.xyz) * (_Range - dis);n
如果你以為這樣就大功告成,那就大錯特錯了。因為一個大大的「驚喜」正等著你。
如圖所示,當角色與「黑洞」距離非常近的時候,會出現一個十分詭異的形變。
主要的原因是該頂點經過偏移之後,跑到了「黑洞」的另一側。而事實上我們需要的是所有的頂點,都保持在該多邊形自己這一側。
如上圖,第二種情況是我們希望得到的結果,而第一種卻是錯誤的效果。如何避免出現第一種情況?
看上圖,如果是我們希望的效果,那麼向量頂點->黑洞的方向,與向量黑洞->新頂點的方向相反;反之則是第一種情況。既然如此,那麼我們就先算出這兩個向量之後再dot一下,通過兩個向量的夾角就能知道這兩個向量的方向是相同還是相反。如果相同(也就是我們不想要的結果),那麼就把該頂點的位置直接放在「黑洞」的位置上——也就相當於該頂點完全被「黑洞」吞噬的效果。
if (dot((oriWorldPos - _BlackHolePos) , (_BlackHolePos - worldPos)) > 0)n{n worldPos.xyz = _BlackHolePos;n}n
我要強調的是,這裡我只寫一種最簡單的方法,看起來確實有點蠢。我相信肯定有更高明的演算法。還是那句話,優化的事兒留給大家自己做作業吧。
到此這個Shader就已經完成了。整個VS代碼如下:
推薦閱讀:
※開發一款RPG遊戲需要哪些編程方面的知識?例如暗黑破壞神、博德之門這樣的2D遊戲。
※dota中英雄技能設計的出發點是什麼?
※可配置的有限狀態機
※【基於dx或gl】怎麼用兩張地圖(heightmap和colormap)實現高解析度的地形?