圖多注意歐
之前就一直好奇 <
超閾限空間 Superliminal> 中神奇的透視操作是怎搞的,但不是放大縮小那個,那個不難理解,我指的是可以用透視把物體投影進背景的神奇效果,像這樣
原本以為是用頂點操作的方式,Raycast 把所有頂點貼到背景上,但多想一下之後就會發現問題了,交界處怎麼裁切? 所以這個做法就被否定了。
直到前天晚上洗澡的時候,我在腦中模擬各種方法嘗試計算,等洗完的那一刻思路剛好通ㄌ,答案浮現在大腦理,阿斯。
先上成果
簡單來說,就是用像素的世界座標,反推原本透視視角的 UV,在把原本視角中的圖像用後處理疊上當前畫面。
至於怎麼只投影特定物件,可以透過攝影機的渲染 Layer 達成,我用兩個攝影機來做效果。
主攝影機,負責遊戲畫面渲染的 mainCamera,不渲染要被投影的物件(投影時)
投影攝影機,在放下物體時會把當下畫面輸出成 Texture2D,只畫出被投影的物件,用 Layer
最後把 Texture 傳給 mainCamera 的後處理 Shader 進行計算,透過這種方法就能畫出被投影進背景的物體了。
如何取得像素世界的座標就參考
大佬趴趴鼠的文章和
整個程式碼,我這裡就只解說後面的步驟,基本上就是手動作一次渲染流程的各種 transform 而已
首先是 worldSpace 轉換成投影攝影機的 cameraSpace,用攝影機的 transform 反矩陣乘
接著投影矩陣投影進 projectionCamera 的 viewSpace,但把深度壓掉變成 -1 ~ 1 的 float2
只要 * 0.5 + 0.5 就反推出原本視角的 UV 了
最後把反推出的 UV 當作 Texture 採樣,就能畫出被投影進背景的物體。
用一連串帥氣的矩陣操作完成投影!
我也希望我做得到...但實際開始才發現窩的圖學觀念不夠扎實,搞不出來
這也是為什麼上面的解說沒什麼數學解釋
殘念阿,明明邏輯都對,結果卡在 rendering pipline 的基本知識不充足
所以最後還是到處翻資料,找了一個現成的方法取得 viewProjection Matrix,直接完成所有工作。
_projectionMat = camera.nonJitteredProjectionMatrix * transform.worldToLocalMatrix
float4 projected = mul(_projectionMat, worldPos)
float2 projUV = (projected.xy / projected.w) * 0.5 + 0.5;
(fake code
參考資料,直接幫我完成上面解說的所有步驟ㄌ
老實說我覺得這不算難拉,理解之後應該會 Shader 的人都搞得出來,難就難在一開始憑空猜測原理的時候
然後實際做還有幾個問題在,第一就是
陰影的問題,因為投影之後要把原本的物件隱藏,所以會讓原本渲染的物體陰影也消失。至於修正方法,我是直接弄一個只會讓 mesh 投影出陰影的透明材質,讓陰影保持渲染。
Texture Repeat
UV 像素採樣的時候,如果超出 0 ~ 1 的範圍,會根據 wrapMode 讓 texture 重複之類的
直接用 saturate 把 uv 限制在 0 ~ 1 之間就好了,但這樣還是會變成邊緣拉伸啦,如果投影時物體被畫面截斷...懶得修了w
反向的投影
如圖,懶ㄉ解釋了
就加個方向的判斷而已,檢查像素和原點的相對方向是不是和原本視角相同,用 dot product < 0 檢查就行。
最後還有一個沒修的問題,找不出方法修
就是用投影反推的 UV 沒辦法做深度判斷,所以障礙物的前後都會被畫上投影物體,有大佬知道怎麼解決嗎
就醬,想通以後花了些時間實現,回復了不少能量
原始檔在這,有興趣自己載來玩玩
請不要介意那個超級糟糕的控制手感,我沒有花很多時間在 playerControler 上 www
WSAD 移動
左鍵長按拖動物件,按 R 旋轉物件,滾輪移動距離,放開就把物體投影到背景上了
投影之後只有視角正確才能把它拿回來,可以長按 E 回到正確的位置
終於寫好ㄌ
晚安