contributed by < quantabase13
>
在 void RayCasterFloat::Trace
中的程式碼:
*screenY = INV_FACTOR / distance;
screenY 是指向 uint8_t type 的 pointer,而 INV_FACTOR 和 distance 卻是 float type,這造成當 INV_FACTOR / distance 大於 256 時會出現 overflow 的情況,連帶影響到後面的程式碼
auto txs = (*screenY * 2.0f);
if (txs != 0) {
*textureStep = (256 / txs) * 256;
if (txs > SCREEN_HEIGHT) {
auto wallHeight = (txs - SCREEN_HEIGHT) / 2;
*textureY = wallHeight * (256 / txs) * 256;
}
}
我修正的方式是先檢查 INV_FACTOR / distance 是否大於 256,如果是的話就直接使用 INV_FACTOR / distance 計算 txs 。
float f = INV_FACTOR / distance;
if (f > 256) {
*screenY = SCREEN_HEIGHT >> 1;
auto txs = ( INV_FACTOR / distance * 2.0f);
if (txs != 0) {
*textureStep = (256 * distance / INV_FACTOR /2.0f) * 256 ;
if (txs > SCREEN_HEIGHT) {
auto wallHeight = (txs - SCREEN_HEIGHT) / 2;
Height = wallHeight;
*textureY = wallHeight * (256 / txs) * 256 ;
}
}
return;
}
void Renderer::TraceFrame(Game *g, uint32_t *fb)
{
_rc->Start(static_cast<uint16_t>(g->playerX * 256.0f),
static_cast<uint16_t>(g->playerY * 256.0f),
static_cast<int16_t>(g->playerA / (2.0f * M_PI) * 1024.0f);
從觀察 float point 的程式碼經驗,推測可能是與牆面的距離計算出現問題。
可以發現程式將
另外,觀察 ray_caster_table.h 裡的 g_tan[256]
,再另外觀察 C 內建的 tan() 輸出的結果,可以發現 g_tan[256]
記的就是 tan(0)
到 tan(90)
(單位:degree )的值乘上 256 的結果。
根據以上,再觀察 void RayCasterFixed::CalculateDistance
的程式碼,可以了解 fixed point 的實作是如何計算 distance 的:
const uint8_t offsetX = rayX % 256;
const uint8_t offsetY = rayY % 256;
最後我發現距離計算出現問題的部份,都是在 OffsetY/X 很小,而 rayAngle % 256接近255的情況。
下面這張圖是理想中的計算結果:
下面這張圖是實際計算出的結果:
注意到使用 Offset = 4 算出的值 217 低於正常的 282。
之所以會造成這種結果, 原因是tan(angle) 在 angle 接近 90 度的範圍內變化程度遠大於小角度範圍內的變化程度,導致把 90度 分成 256 等分的作法會在高角度時出現大的誤差。
目前我對此並沒有好的解決方式。參考 guaneec 的方法,把該方法的思路整理如下:
這是原來的實作:
for (;;) {
while ((tileStepY == 1 && (interceptY >> 8 < tileY)) ||
(tileStepY == -1 && (interceptY >> 8 >= tileY))) {
tileX += tileStepX;
if (IsWall(tileX, tileY)) {
goto VerticalHit;
}
interceptY += stepY;
}
while ((tileStepX == 1 && (interceptX >> 8 < tileX)) ||
(tileStepX == -1 && (interceptX >> 8 >= tileX))) {
tileY += tileStepY;
if (IsWall(tileX, tileY)) {
goto HorizontalHit;
}
interceptX += stepX;
}
}
這是改善後的版本:
for (;;) {
auto ty = (tileY - tileStepY) * 256;
if ((tileStepY == -1 && ty < interceptY) ||
(tileStepY == 1 && ty > interceptY))
interceptY = ty;
while ((tileStepY == 1 && (interceptY >> 8 < tileY)) ||
(tileStepY == -1 && (interceptY >> 8 >= tileY))) {
tileX += tileStepX;
if (IsWall(tileX, tileY)) {
goto VerticalHit;
}
interceptY += stepY;
}
auto tx = (tileX - tileStepX) * 256;
if ((tileStepX == -1 && tx < interceptX) ||
(tileStepX == 1 && tx > interceptX)){
interceptX = tx;
}
while ((tileStepX == 1 && (interceptX >> 8 < tileX)) ||
(tileStepX == -1 && (interceptX >> 8 >= tileX))) {
tileY += tileStepY;
if (IsWall(tileX, tileY)) {
goto HorizontalHit;
}
interceptX += stepX;
}
}
這是程式碼想法的圖解: