contributed by < ccs100203
>
linux2020
raycasting 的原理是在 screen 範圍內打出一條條的光線,偵測物體距離,藉此把 2D 轉為 3D
在專案內 renderer.cpp
中的 TraceFrame
實現此原理
用 x
去模擬每一條光線
嘗試將原理補充的更詳細
floating-point 的計算主要由 raycaster_float.cpp
負責,void RayCasterFloat::Trace
即是負責計算每條光線 (每個 column)。由於這是在貼近牆面時出現的 bug,研判跟 distance
(與牆面的距離) 有關聯,所以在程式中找尋使用到 distance
的地方。
接著在程式中發現以下問題,151 行的除法,很可能在 distance
是一個小於 1 的值出問題。觀察三個變數的 type,會發現當 INV_FACTOR / distance
大於 255 時就會發生 overflow,導致 render 錯誤。
我不懂這裡的運作原理,只好純粹對 overflow 的問題做修正。
txs
得到正確的值,我設立了一個 tmp
作為 INV_FACTOR / distance
的答案。這樣已經有效修正 txs
產生錯誤的情況screenY
應該設為多少sso
即為 screenY
,發現當 sso
大於 HORIZON_HEIGHT
時會被修正為 HORIZON_HEIGHT
。而在 Trace
內會發生此情況,代表 txs > SCREEN_HEIGHT
成立,因為 txs
原先應為 *screenY * 2.0f
,而 HORIZON_HEIGHT
又等同 SCREEN_HEIGHT / 2
。所以在此條件成立時將 screenY
設為 HORIZON_HEIGHT;
以下是改良後的版本
else
Condition Missing)因為跑到邊邊才能看到這個 bug,所以推斷是在 distance
處在邊界時才會發生
由 raycaster_fixed.cpp 處理 fixed-point 的計算,一樣在程式內尋找使用 distance
的地方。
LookupHeight
內有處理 distance
在邊界時的情況if (ds >= 256)
成立也會在接下來的運算把正確的值覆蓋掉,所以把缺少的 else 補上改良後的程式
delta
is wrong)後來經過測試發現是在 playerX
或 playerY
有趨近 1.0 的情況時就會發生牆面消失,反之則不會
研判是在計算 distance 時出現問題,為了了解 distance 計算方式,從 Wolfenstein 3D's map renderer 去釐清原理。
在 raycaster_float.cpp 中測試,經過實驗發現在 offsetX
或 offsetY
等於 0 時會發生此情形。影片中 對應到程式內的 float interceptX = rayX + startDeltaX;
。而 offset
是影響到 startDelta
的關鍵,所以在決定 startDeltaX
和 startDeltaY
的值時出問題。
這邊看到程式在 offset 為 0 時會特別處理
我認為這樣很奇怪,按照式子來看 應該為 0,這邊卻是 。所以對式子進行修正,將 1 改為 0
發現原本的問題解決了,於是接著精簡程式碼。當 if 成立時 offset 必定是 0,所以整個判斷式變得多餘,直接保留 else 內部的運算就好。
這邊是全部修改完的程式
經過實驗後發現只有在 playerX
或 playerY
為最大值 (約 30) 時會發生這種情形,也就是圖上的紅色牆面,而在綠色牆面是正常的。
藉由這張圖發現 fixed-point 判斷邊界牆面的位置時出現問題,明顯可以看出左邊的牆面 (邊界),應往內 (右) 一格。
已經知道問題是判斷牆面的位置出錯,所以去找程式內的 IsWall
,來比較一下 fixed-point 與 floating-point 的 IsWall
因為已經知道會在 X 或 Y 在最大值時出現問題,於是推斷在與 MAP_X
或 MAP_Y
比較時有錯誤,因為這兩個變數代表著地圖的右邊界與上邊界。
繼續觀察可以發現兩份程式的判斷式不同,一個是 >
一個是 >=
,顯然這就是造成牆面位置相差 1 的原因。
因為現在出問題的是 fixed-point 的牆面,所以把他的判斷式修正成 >=
,下面是修正後的程式
觀察以下的圖片
第一張是綠色的牆面,也就是正常的牆面
第二張是紅色的牆面,也就是異常的牆面
可以看出牆面在畫面中的比例,floating-point 是維持一致的,但是 fixed-point 的牆面比例明顯改變,故我認為判斷條件寫錯的是 fixed-point。
TODO