Pixel Perfect Rotation 效果投票!
書接上回(《像素鳥跳跳跳!》),在評論區里 @keyboard2109 同學提出了一個問題:
這個鳥的像素怎麼能整個斜過來...復古要認真一點啊哥哥們
我看完評論去仔細感覺了一下,然後覺得非常有道理,這個東西的旋轉怎麼能這樣呢!
於是在後幾天的下班後又花了點時間研究怎麼把旋轉也做成像素風格的。
一番 Google 之後發現這個東西其實有個術語,叫做 Pixel Perfect Rotation(下文中我就簡稱 PPR 了)。在一番 Coding 之後終於(勉強)搞定了這個東西。
再講 PPR 之前,我們先來看一下之前這個小鳥的旋轉是什麼樣子的(8M):
(注,本文里的視頻都有點大,不清楚知乎是否會進行壓縮,括弧里我均是標註的原始大小)
https://www.zhihu.com/video/945748512739172352可以看到,這個主角像素鳥的旋轉是非常【正常】的,也就是說,它實際上是利用了他所在的所有屏幕像素來進行的旋轉。
像素鳥素材圖片的大小其實只有 10x8,顯示在屏幕上時進行了 10x 的 scale,所以上個視頻中的旋轉,其實是將像素鳥擴展成了 100x80 的素材,然後再進行旋轉的。
當然實現上並沒有這麼複雜,我只是把這個 AnimationSprite 的 rotation 屬性根據當前速度方向賦了個值,旋轉其實是遊戲引擎在渲染到屏幕上時操作的。
但是這樣就會有很明顯的問題:Its not ugly!
在旋轉之後,可以看到像素鳥的一個正方形原始像素,有可能會變成個菱形(不要挑刺,我說的是這樣 ◇),而這在一個 Pixel Art 的世界裡是不應該出現的……
所以,經過上面的分析,直接使用 Sprite 的 rotation 屬性是不可行的。但是主角又確實需要旋轉啊,於是怎麼辦呢?
嘛,我問了下某你畫我猜微信群里的小夥伴們,有人建議多準備幾個不同角度的像素鳥的貼圖,然後根據當前旋轉角度來選一個貼到 Sprite 上。
這確實是 OK 的辦法,但這樣做的一個問題是:角度從連續變化(忽略屏幕像素是有限的這一因素)變成了只能離散變化。emmm,當然這也是可接受的嘛,大不了我們多準備點素材,盡量讓它看上去是連續的就好了。
但是這樣工作量就有些大了,Lets do some math.
一圈 360°,假設我們每隔 n 度做一個像素鳥旋轉之後的素材,再加上像素鳥是動態的,由三幀構成一輪 Animation,那麼所需要做的圖片數量就是: 。
就算我們犧牲一些連續性吧,每 10° 變換一下像素鳥樣子,也需要做 108 張圖片。
圖片大小倒不是什麼問題,畢竟像素鳥是 10x8 的,來個 100 張也不會有多大問題。
但我是個程序員不是設計師啊 T^T 讓我從一個 10x8 的圖做出來它旋轉各種角度之後的 Pixel Art 還是有些難的。而且還要做這麼多,你不如殺了我呢……
所以到了這一步,就只剩一個選擇了。根據角度動態生成素材,組成 Animation,貼到 Sprite 上。
想到這裡就基本差不多了,設定了下旋轉間隔 10 °(後來錄製演示視頻的時候改成了 11.25°)下面就是愉快的寫代碼了。
但是!當我成功獲取到 Sprite 的當前 Texture 轉成 Image 之後,猛然發現 Godot 的 Image Class 竟然沒有旋轉操作……
抱著期待去 Discord 頻道詢問了一下,貌似現在確實沒有。那咋辦呢,那隻能自己寫了啊。
於是首先寫了下 src to dst 的圖像旋轉,簡單測試了下動態設置 SpriteAnimation 的操作在 Godot 里是不是被支持,結果發現完全沒問題。嗯,很好~
但是眾所周知 src to dst 是不可能用來做圖像旋轉的,根據旋轉角度不同,會造成不同但有規律的空洞:
src to dst 的結果視頻就不放了,有點蠢。
之後又寫了下 dst to src,一個很 naive but 很 easy 的圖像旋轉演算法,看上去稍微能夠接受了,來看個視頻(11M):
https://www.zhihu.com/video/945755689809891328(一號參賽選手:1x)
emmm,但還是總覺得哪裡怪怪的啊……這旋轉之後有人能看出來是個鳥嗎?絕望在心中瀰漫。
鑒於上面那個旋轉的結果如此之可怕,想想只能搞點插值試試了,讓圖片旋轉之後看起來正常點。
所以花點時間用 GDScript 實現了雙線性插值圖像旋轉,這也是個比較常見的演算法了,具體實現就不貼了,我寫的很辣雞,因為其實完全不了解圖形學,實現也是沒什麼性能可言的(說不定還有 Bug,捂臉逃),如果有想了解的,給一個鏈接好了:Image rotation with bilinear interpolation。
然後測試了下,很幸運的,雖然我實現性能很差,但是測試下來還是闊以的,只是會在遊戲開始的時候稍微拉低 2 fps 左右的幀率,後面創建完動畫之後會復用,所以影響不大。
以下是雙線性插值後的效果(7M):
https://www.zhihu.com/video/945759265206530048(二號參賽選手:1x + interpolation)
額,這個嘛………
雖然看上去旋轉的效果是好了挺多的哈,而且差值之後還是保持了像素化的風格,不會產生 ◇ 的色塊……
但是這個模模糊糊的小鳥,總感覺和這個清晰的像素世界格格不入啊摔!這尼瑪自己試玩的時候都懷疑是開了動態模糊特效呢還是擼多了啊……(但是你如果離屏幕遠點看,效果真就挺好的(誤
不行不行,絕對不行,處女座的壞毛病發作了,繼續嘗試。
深入分析了一下產生上面現象的原因,那就是:原圖尺寸太小了。
在生成旋轉之後的素材時,由於使用了插值,幾乎每個像素都需要和相鄰像素髮生插值。但是原圖太小了,幾乎每個像素相鄰像素的顏色和此像素都是不同的,所以插值之後就成了新的顏色。原本同一種顏色的地方現在有些變深,有些變淺,所以看上去就會覺得是模糊了。
當然,這也是插值本省想要達到效果,在巨大的圖片上這種效果會讓圖片過渡顯得更加自然,但是我這裡這圖太小了呀,這種模模糊糊的過渡實在是有點難受。
找到了根本原因之後,解決方案就呼之欲出了:超採樣。
這個技術其實也很常見,如果你是大型遊戲玩家,那麼對【抗鋸齒級別】這個常見的 Game Graphics Option 肯定不會陌生。這個 Option 一般會給你幾個級別進行調整,比如:關閉、2x、4x、8x 之類的。
如果是這種選項的話,其實用到了超採樣技術。截取一段度娘百科的介紹:
大致過程為:首先,圖像創建到一個分離的緩衝區,緩衝區圖像解析度高於屏幕解析度,假設是2*1(或2x),那麼緩衝區場景的水平尺寸比屏幕解析度高兩倍,若是2*2(或4x)抗鋸齒,緩衝區圖像的水平和垂直均比顯示圖像大兩倍。像素計算加倍之後,選取2個或4個鄰近像素,此過程稱為採樣。把這些採樣混合起來後,生成的最終像素,擁有鄰近像素的特徵,那麼像素與像素之間的過渡色彩,就變得更為近似,整個圖像的色彩過渡趨於平滑。再把最終像素輸出到幀緩衝,作為一幅圖像存儲起來,然後發到顯示器,顯示出一幀畫面。每幀都進行抗鋸齒處理,遊戲過程中的所有畫面都變得帶有抗鋸齒效果了。
一句話說嘛,就是先把圖像放大再處理,然後再縮小回來。
但在我這個問題里並不是為了平滑,恰恰相反,是為了別那麼平滑 =。=。
所以就只需要抗鋸齒操作的前半部分:超採樣,也就是放大。如果再縮小回來的畫,又變回 10x8 了,又回到平滑但是模糊的狀態了。
一陣簡單的 Coding 之後,使用了插值 + 4x 超採樣的結果如下(7M):
https://www.zhihu.com/video/945764771207794688(三號參賽選手:4x + interpolation)
咦,看上去效果果然不錯,在保證沒有菱形色塊的前提下,旋轉之後的圖像也沒有之前那麼模糊了。當然其實還是有的,因為超採樣其實只是把像素增多了,所以每個模糊像素的面積就小了,看上去的效果就沒有那麼的…………恩…………模糊了。
其實可以類比老司機打碼嘛,如果打的碼色塊比較大,就很難看出原來到底是啥。反之如果比較小,就更容易看出原來是什麼,甚至你眯著眼睛(摘下眼鏡,誤)就能大概看出原來是啥。
本來應該就到這了,但是多試了幾次之後發現,這個效果還是和清晰的背景以及管子有那麼一點不和諧……
歸根結底還是因為模糊,然後一想,對啊,模糊是因為插值引起的,我加上超採樣是為了減少模糊,所以……為啥我還留著插值呢???
這麼一想,我把插值一關,但是超採樣保持打開,又做了一版,以下是效果(7M):
https://www.zhihu.com/video/945767239132135424
(四號參賽選手:4x)
嗯,這下果然清晰很多了。但是這周圍毛毛刺刺的。果然還是有點彆扭?
這下我也有點搞不清楚哪個比較好了……
其實這後面四種嘗試,每一種都是滿足 PPR 的,因為它們都在讓圖像進行旋轉的時候保持了最終在屏幕上不會有感覺像是菱形像素的東西。
好吧,處女座的另一個毛病,選擇困難症出現了,糾結 ing。
所以,Hey,看看這篇文章的標題,我自己決定不了的東西只好靠投票了(手動笑哭
所以,如果你比較偏好哪一個的話,請評論區告訴我吧。我也把最開始的非 PPR 的作為一個選項,所以參賽選手一共有:
- 零號:Sprite Rotation(非 PPR)
- 一號:1x
- 二號:1x + interpolation
- 三號:4x + interpolation
- 四號:4x
按照你的喜好投票吧,如果不太記得了可以回去再看下對應的視頻~
啊,糾結糾結糾結……
重新貼一下這個 Game Demo 的 Repo 鏈接:7sDream/pixel-bird-jump
對了,以上這四種 PPR 的代碼我都還沒上到 Github,無論是 Dev 還是 Master 分支。因為還沒決定嘛……所以如果是找實現的話可以不用點進去了,先幫我投個票決定用哪個實現吧!
(? ??_??)?
(然後順便點個 Watch,這樣如果更新就有提示了。Star 什麼的不怎麼在乎,看你心情吧~
墜後:終於放假了!!!!明天回家,然後就是一陣過年的準備活動 + 各種親戚奪命追魂問有沒有找對象……啊,絕望。
雖然這麼絕望但是還是很開心,不用上班當然開心啦~
回家後估計沒什麼時間擺弄代碼,得陪陪老爸老媽嘛,所以這個投票有效期應該會持續到年後幾天吧,但是也不一定,說不定我突然就很偏愛某一個然後決定用它了;或者和群里小夥伴討論了下就決定了……這都是有可能滴。
但還是很希望有人投票 (? ??_??)?
於是就到這裡,下期再見。
ヾ( ̄▽ ̄)Bye~Bye~
題圖來自 Pixabay,版權 CC0 。
推薦閱讀: