利用GPU實現翻頁效果


0x00 前言

有一段時間沒有更新博客了,在考慮寫點什么的時候正好趕上了這個月我的書《Unity 3D腳本編程》又加印了。因此寫篇小文聊聊利用shader來實現翻書的效果吧。
雖然本文是這個周日下午雨天的臨時起意,而演示的Demo也有廣告之嫌,但是還是希望各位看官如果覺得有收獲的話能夠點贊支持。

0x01 Vertex Shader

之前看到過類似“Unity怎么實現類似書本的翻頁效果”之類的問題,答案大多是利用現成的插件來實現,這聽上去似乎並沒有實際上解決這個問題。后來又看到過一些更靠譜的解決方案例如利用UGUI的vertex modifier修改頂點、或者使用骨骼動畫。
等一下,修改頂點?
修改網格數據這事沒有必要一定要在cpu上進行,我們把這活放到GPU上讓它來實現頂點的修改是不是更有趣一點呢。
事實上我們只需要一個Plane,在vs中根據某個屬性來修改它頂點的x值和y值。

而一個最簡單的修改方案,就是根據玩家的翻頁角度theta來更改頂點的坐標。

	float4 flip_book(float4 vertex)
	{

                    ...
		temp.x = vertex.x * cos(theta);
		temp.y = vertex.x * sin(theta);

		vertex = temp;

		return vertex;
	}

那么theta的值是怎么來的呢?一頁書的翻動角度在[0,180]之間,變成弧度值就是[0,π],因此我們只需要在腳本中計算玩家拖動的距離和總長度的一個比例ratioValue,將這個ratioValue傳遞給vs后再和π相乘就求得了theta。

因此,在C#腳本中就需要使用這幾個接口了。

 IDragHandler, IPointerDownHandler, IPointerUpHandler

這樣,在只經過一個pass的情況下,翻頁的初步效果已經實現了。

但是如果翻過90°,可以發現此時不僅第二頁沒有內容,而且第一頁的背面也是空的。

因此,我們還需要另外2個Pass分別渲染第一頁的背面和第二頁的內容。

0x02 3個Pass

ok,接下來我們就來完成第二個pass。

	fixed4 frag_flip_back (v2f i) : SV_Target
	{
		i.uv.x = 1 - i.uv.x;
		fixed4 col = tex2D(_BackTex, i.uv);
		return col;
	}
	//翻起來的背面
	Pass
	{
		Cull Front

		CGPROGRAM
		#pragma vertex vert_flip
		#pragma fragment frag_flip_back
		ENDCG
	}

其實很簡單,只需要剔除正面,修改一下uv,然后正常的采用背面的紋理_BackTex就ok了。

可以看到當書頁被翻過90°之后,書頁的背面已經能夠正確的顯示了。

之后就是最后一個pass了,我們用這個pass來顯示第二頁的內容。其實這個pass很簡單,仍然是只需要正常的采用背面的紋理_BackTex就ok了。但是這里要注意一個問題,那就是深度的問題。還記得第一個pass嗎?第一個pass繪制了第一頁的內容。但是最后一個pass同樣也要繪制頁面的內容,而且默認情況下深度會覆蓋第一個pass繪制的內容。

因此,我們要在最后一個pass中正確的處理深度問題,所以我在這里使用了Offset。

	//第二頁
	Pass
	{
		Cull Back
		Offset 1, 1

		CGPROGRAM

		#pragma vertex vert_next_page
		#pragma fragment frag_flip_back

		ENDCG
	}

OK,shader部分完工了。之后我們只需要在C#腳本中簡單的確定當前的頁數,來設置相應的前頁的tex和后頁的tex給shader。
最后的結果大概是這個樣子的。

0x03 Update一下

當然,為了讓翻書的效果更自然,為翻動中的書頁增加一些弧度似乎是一個不錯的選擇。
其實原理也並不復雜,在vs修改頂點位置的時候處理就好了。首先來看看翻頁時候頁面彎曲的一個大概形狀,似乎有點像鍾型?
而一說到鍾型,各位應該能夠想到高斯函數了吧?
所以,接下來我們畫一個簡單的高斯函數圖形。

(推薦一下這個在線圖形計算器
它大概就長這樣。
所以在vs修改頂點坐標時,把這個高斯函數考慮進去,就能夠獲取一個更自然的效果了。

		float flipCurve = exp(-0.1 * pow(vertex.x - 0.5, 2)) * _CurPageAngle;

		theta += flipCurve;

		temp.x = vertex.x * cos(clamp(theta, 0, pi));
		temp.y = vertex.x * sin(clamp(theta, 0, pi));


不過這里又有一個新的問題需要考慮,就是變成了弧形的書頁可能會導致深度上的問題。
這個問題主要是在第二個pass,在翻書和前一頁快重合時,因為第二個pass中的某些頂點的深度要大於第一個pass的深度,從而造成穿幫。所以在第二個pass的時候就要加上Offset -1 -1了。

    //翻起來的背面
	Pass
	{
		Cull Front
		Offset -1, -1

		CGPROGRAM
		#pragma vertex vert_flip
		#pragma fragment frag_flip_back
		ENDCG
	}

當然,這個demo的代碼各位可以在這里獲取:
chenjd/Unity-Flip-Book-With-Shader

-EOF-
最后打個廣告,歡迎支持我的書《Unity 3D腳本編程》

歡迎大家關注我的公眾號慕容的游戲編程:chenjd01


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM