圖多注意歐
之前就一直好奇 <
超閾限空間 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 回到正確的位置
終於寫好ㄌ
晚安