2020q3 Homework5 (render)
contributed by < Holychung
>
2020q3 Homework5 (render) 題目
Outline
背景知識
在開始 trace code 之前可以先閱讀 Casting Wolf3D-style Rays with an FPGA and Arduino,當中有提到要撰寫一個 ray casting engine 有兩個方法,其中一個比較直接明瞭的是 Lode's Computer Graphics Tutorial,強烈建議先讀完這篇。
我在讀這篇的時候順便做了點筆記,因為篇幅太長就記錄到 研讀筆記: Raycasting。
程式原理
這邊有用到 library SDL,全名是 Simple DirectMedia Layer,是一個跨平台的函式庫,透過 OpenGL、Direct3D 提供低階的存取對聲音、鍵盤、滑鼠等硬體,通常被用在影音軟體、模擬器、遊戲等。
SDL API
- SDL_Init 初始化並且設定為
SDL_INIT_VIDEO
的 video file I/O 和 threading 的 subsystem。
- SDL_Texture 一個結構包含特定驅動裝置的像素資料表示。
- SDL_CreateTexture 創造一個 texture 給指定的 renderer。
fixedTexture
、floatTexture
給定的 format 都是 SDL_PIXELFORMAT_ARGB8888
,表示 32 位元的各 8 bits 表示 ARGB,access 是 SDL_TEXTUREACCESS_STREAMING
。
main.cpp
DrawBuffer
用來把 frame buffer 中的東西畫到 renderer 上面。在 108 109 行各呼叫了一次,一個是畫畫面左邊定點數,一個是右邊浮點數。
ProcessEvent
- SDL_Event 一個 union 包含不同事件型態的結構,像是 SDL_WindowEvent、SDL_KeyboardEvent 等。
- SDL_PollEvent 用這個函式輪詢在等待的事件,並且透過
ProcessEvent
來處理事件,如果不是 exit 遊戲事件,事件分兩種 moveDirection
、rotateDirection
,移動事件就是前後 UP
、DOWN
,旋轉視角則是左右 LEFT
、RIGHT
。會更具對應的這四個事件去更新變數 moveDirection
、rotateDirection
,用正負來代表方向。
- 最後在呼叫
game.Move(moveDirection, rotateDirection, seconds);
來對角色移動。
game.cpp
Game
這個物件是用來記錄角色的位置跟視角,playerX, playerY, playerA
。
第 13 的迴圈是判斷視角是否在 之間,沒有就 mod 。
第 20 行則是判斷位置是否到達地圖邊界,如果超過地圖邊界就往回推 0.01,用這樣的手法可以做到角色在碰到牆邊時,繼續按方向鍵,角色會緩慢的沿著牆壁滑動的效果。
renderer
物件
會有一個指向 RayCaster
的指標 _rc
,還有一個 TraceFrame
的方法。
TraceFrame
這邊一開始呼叫的 _rc->Start
,會先把 playerX、playerY、playerA 變成定點數的格式,playerX、playerY 乘上 256.0f
,在轉成 uint16_t
,這樣就是用 uint16_t 前面 8 位元存整數部分,後面 8 位元存小數部分。playerA 是用代表角色的方向,範圍是 ,這邊會除上 在乘以 1024,以方便後面定點數的表示,會在 fixed point 的時候解釋。
再來用一個迴圈對屏幕每一個 x 軸的點開始畫圖。
接下來看到迴圈中的變數,會透過 Trace
去計算要要如何繪圖,得到下列變數的值,這邊部分參考sammer1107 的說明。
整個視窗的高度為 SCREEN_HEIGHT,SCREEN_HEIGHT/2 則為 HORIZON_HEIGHT,也就是中間的水平線。
- sso 是牆壁頂點到畫面中線的距離,不過牆的高度是可能會比畫面還高的,所以如果超過的話
sso = HORIZON_HEIGHT
。
- tn textureNo 代表不同的材質,這邊只有 0 和 1,如果是 Vertical hit 為 1,其餘為 0。
- tc 對應 x 軸的 texture x。
- tso 對應 y 軸的 texture y。
- tst 代表在畫牆面時下一個 y 軸位置要走多遠,假如牆很遠,則我們高 64 的牆面材質,可能只佔了畫面中 30 個 pixel,那 。
有了這些資訊,我們就可以畫出一條垂直線了。
第一個迴圈是先畫背景最上面背景的部分。
第二個迴圈是畫牆面的部份。
最後一個跟第一個同理,畫最底下背景的部分。
rayCaster_fixed.cpp
Start
用來初始化物件的變數,這邊在 TraceFrame
呼叫的時候會先做處理轉成 uint16_t
的定點數格式,_viewQuarter
則是用來判斷現在的角度在第幾象限,一開始先把角度 A 除以 範圍就會坐落在 0~1 之間,這邊其實只要乘以 256 也就可以轉乘定點數的格式,但是要用來判斷第幾象限的話,把他多乘上一個 4,也就變成乘以 1024,最只要透過 playerA >> 8
,就可以得到是在 0 1 2 3 得以判斷象限,再用 _viewAngle
把小數點的部分記下來。
raycaster_float.cpp
Start
用來初始化物件的變數,這邊因為是用浮點數,所以需要把前面 TraceFrame
呼叫時傳進來的定點數格式先轉回去浮點數。
修正問題
1. 邊界牆面距離大小不一
問題描述
可以看到下圖 fixed floating 兩者到邊界的最外圍牆面,離牆面的距離有明顯的不同。
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
fixed 走到角落,到角落兩邊的邊界距離也不同。
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正問題
在 raycaster.h
這邊的定義可以看到,地圖大小是 32x32 總共 1024 個方格。
在 raycaster_data.h
也可以看到地圖在這邊也是 1024 個,1 代表牆壁。
const uint8_t LOOKUP_TBL g_map[] = {
0b00000000, 0b10000000, 0b00000000, 0b00000000, 0b01111010, 0b10111111,
0b11111111, 0b00000000, 0b00111000, 0b10100000, 0b00001000, 0b01001100,
0b01000001, 0b00000100, 0b00100100, 0b00001100, 0b00000000, 0b10001010,
0b00000010, 0b01011100, 0b10000001, 0b11000100, 0b00000110, 0b00001100,
0b00000000, 0b11010010, 0b00010000, 0b00001100, 0b01001100, 0b10010000,
0b00000000, 0b00111100,
0b00100011, 0b10100100, 0b00000100, 0b00001100, 0b00000000, 0b11000001,
0b10001000, 0b00001100, 0b00110000, 0b10011100, 0b00111000, 0b01111100,
0b00000011, 0b00000000, 0b00000000, 0b00001100, 0b00011111, 0b11111110,
0b11111111, 0b11111100, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000};
推測應該是在角色移動的判斷寫錯了,所以到 game.cpp
當中查看 Move
的函式,這邊會對 playerX
、playerY
進行判斷,小於一了話就會回到 1.01,大於 MAP_X-2
就會 MAP_X - 2 - 0.01f
。
這邊的判斷應該是大於 MAP_X - 1
然後回到 MAP_X - 1 - 0.01f
,這邊也就可以很明顯看出,X Y 的範圍是 0-31 ,在 X Y 接近邊界 31 的時候就會離比較遠,而 0 的那一個邊界則是正常。
修正完後測試如下,fixed 的版本修正成功,但是 float 則是到牆邊發生了一點問題。

這邊實驗後,發現 float 在計算「邊界」牆壁的距離時比 fixed 的版本還要小,非邊界的牆壁則是一樣的,所以推測是判斷牆壁的地方出錯了,所以看到 raycaster_float.cpp
的 IsWall
。
這邊的 tileX
不是大於等於,要改成 tileX > MAP_X - 1
,不然計算到牆壁的算法,在最後邊界牆壁的地方,就會計算錯誤,提早了一個方格找到牆壁,也就是造成這個問題的原因,在對照 fixed 版本的也是沒有等於,所以修正這個錯誤。
修正前後比對
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正前
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正後

Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正前
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正後

2. 接近牆面時 float 版本缺失
問題描述
在角色貼近牆壁的時候,float 版本的牆面會壞掉如下圖,推測有可能是因為 overflow 導致牆壁高度變為 0 導致。
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正問題
去找到運算牆壁高度 sso
的函式 Trace
,再看到 screenY
的型態是 uint8_t
,且等於 INV_FACTOR / distance
,當 distance 在很小的時候在這邊有機會發生 overflow,並且在 txs 大於 SCREEN_HEIGHT 的時候,要把 screenY 的值設成 HORIZON_HEIGHT 才對。
修正版本如下。
修正前後比對
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正前
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正後

3. 在外圍牆壁往地圖內看 fixed 會出現牆壁
問題描述
在外圍牆壁往地圖內看 fixed 會出現牆壁,這邊推測應該是牆壁距離太遠所造成的問題。
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正問題
查找到 LookupHeight
這個函式當中,發現 ds 大於等於 256 的這邊少了一個 else 的判斷,這樣進去 if 判斷完後出來又會在 LOOKUP 一次。
修正過後。
修正前後比對
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正前
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
修正後

輸出算繪過程的 frame rate
在 main.cpp
中可以看到一開始介紹的 SDL API 當中有提到 SDL_GetPerformanceFrequency
、SDL_GetPerformanceCounter
,透過這兩個 API 就可以計算出每次 while 迴圈相隔的時間,這個時間的倒數就是 frame rate(fps)。
印出來結果如下。
