光线投射渲染 - 墙的边缘交叉问题
Raycasting rendering - wall's edge crossing issue
我正在使用光线投射方法(例如 Wolfenstein3D)实现伪 3D 渲染。我遵循了 permandi 的教程 (https://permadi.com/1996/05/ray-casting-tutorial-table-of-contents/)
乍一看,它似乎运行良好,但我正在努力解决一个问题,我无法弄清楚发生了什么。事实上,墙壁的边缘相互交叉,而且有时光线似乎没有检测到任何墙壁。
但情况并非总是如此。有时效果很好,这取决于玩家的方向。
在 2D 场景渲染后,我可以看到光线确实没有检测到墙壁。但根据方向,情况并非总是如此。
在上面的屏幕截图中,左侧不起作用,但右侧有效。
这就是我检测墙壁并获取距离和 Y 轴的方式
float getDistanceOnHorizontalGrid(float rayAngle, float playerPosX, float playerPosY)
{
float pointInRayY = playerPosY + (100 * sin(DEGREE_TO_RADIAN(rayAngle))); // Arbitrary Point to evaluate whether ray is facing up or down
float firstIntersectPointY;
float ya;
float xa;
if (pointInRayY <= playerPosY) // ray is facing up
{
firstIntersectPointY = floorf(playerPosY / WALL_SIZE) * (WALL_SIZE)-1;
ya = -WALL_SIZE;
xa = WALL_SIZE / tan(DEGREE_TO_RADIAN(rayAngle)) * -1;
}
else // ray is facing down
{
firstIntersectPointY = floorf(playerPosY / WALL_SIZE) * (WALL_SIZE)+WALL_SIZE;
ya = WALL_SIZE;
xa = WALL_SIZE / tan(DEGREE_TO_RADIAN(rayAngle));
}
float firstIntersectPointX = playerPosX - (playerPosY - firstIntersectPointY) / tan(DEGREE_TO_RADIAN(rayAngle));
float nextPointX = firstIntersectPointX;
float nextPointY = firstIntersectPointY;
for (int i = 0; i < MAP_HEIGHT; i++)
{
int toMapX = convertWorldToGridCoordinate(floor(nextPointX));
int toMapY = convertWorldToGridCoordinate(floor(nextPointY));
if (!(toMapX >= 0 && toMapX <= MAP_WIDTH - 1 && toMapY >= 0 && toMapY <= MAP_HEIGHT - 1))
{
return 0.0f;
}
if (map[toMapY][toMapX] == 1)
{
return abs(sqrt(((playerPosX - nextPointX) * (playerPosX - nextPointX)) + ((playerPosY - nextPointY) * (playerPosY - nextPointY))));
}
nextPointX += xa;
nextPointY += ya;
}
return 0.0f;
}
X轴也一样
float getDistanceOnverticalGrid(float rayAngle, float playerPosX, float playerPosY)
{
float pointInRayX = playerPosX + (100 * cos(DEGREE_TO_RADIAN(rayAngle))); // Arbitrary Point to evaluate whether ray is facing left or right
float firstIntersectX;
float firstIntersectY;
float xa;
float ya;
if (pointInRayX <= playerPosX) // ray facing left
{
firstIntersectX = floorf(playerPosX / WALL_SIZE) * (WALL_SIZE)-1;
xa = -WALL_SIZE;
ya = (WALL_SIZE * tan(DEGREE_TO_RADIAN(rayAngle))) * -1;
}
else
{
firstIntersectX = floorf(playerPosX / WALL_SIZE) * (WALL_SIZE)+WALL_SIZE;
ya = WALL_SIZE * tan(DEGREE_TO_RADIAN(rayAngle));
xa = WALL_SIZE;
}
firstIntersectY = playerPosY - (playerPosX - firstIntersectX) * tan(DEGREE_TO_RADIAN(rayAngle));
float nextPointX = firstIntersectX;
float nextPointY = firstIntersectY;
for (int i = 0; i < MAP_WIDTH; i++)
{
int toMapX = convertWorldToGridCoordinate(floorf(nextPointX));
int toMapY = convertWorldToGridCoordinate(floorf(nextPointY));
if (!(toMapX >= 0 && toMapX <= (MAP_WIDTH - 1) && toMapY >= 0 && toMapY <= (MAP_HEIGHT - 1)))
{
return 0.0f;
}
if (map[toMapY][toMapX] == 1)
{
return abs(sqrt(((playerPosX - nextPointX) * (playerPosX - nextPointX)) + ((playerPosY - nextPointY) * (playerPosY - nextPointY))));
}
nextPointX += xa;
nextPointY += ya;
}
return 0.0f;
}
我肯定做错了什么,但我不知道错在哪里。
感谢您的帮助
更新
提到的 BCT 实际上是正确的,尽管从 1 开始减少值仍然是必要的,但不是在那个计算中。
在仔细研究 permadi 的实现之后,在执行此操作后
horizontalGrid = Math.floor(this.fPlayerY/this.TILE_SIZE)*this.TILE_SIZE + this.TILE_SIZE;
他做到了:
horizontalGrid--;
一开始以为和自己做的一样,其实不是。之所以后来把这个值调小是因为这只是为了获取网格坐标。
为了解决我的问题,我已经从计算中删除了 -1,并且仅在我寻找网格坐标时减小该值。
我看了你发的link。似乎其中一个 if 块的值不正确:
这是教程代码:
// Ray is facing down
if (castArc > this.ANGLE0 && castArc < this.ANGLE180)
{
// truncuate then add to get the coordinate of the FIRST grid (horizontal
// wall) that is in front of the player (this is in pixel unit)
// ROUNDED DOWN
horizontalGrid = Math.floor(this.fPlayerY/this.TILE_SIZE)*this.TILE_SIZE + this.TILE_SIZE;
这是您的代码:
if (pointInRayY <= playerPosY) // ray is facing up
{
firstIntersectPointY = floorf(playerPosY / WALL_SIZE) * (WALL_SIZE)-1;
请注意,您在末尾有 -1
而不是 this.TILE_SIZE
,在您的情况下应该是 WALL_SIZE
我正在使用光线投射方法(例如 Wolfenstein3D)实现伪 3D 渲染。我遵循了 permandi 的教程 (https://permadi.com/1996/05/ray-casting-tutorial-table-of-contents/)
乍一看,它似乎运行良好,但我正在努力解决一个问题,我无法弄清楚发生了什么。事实上,墙壁的边缘相互交叉,而且有时光线似乎没有检测到任何墙壁。
但情况并非总是如此。有时效果很好,这取决于玩家的方向。
在 2D 场景渲染后,我可以看到光线确实没有检测到墙壁。但根据方向,情况并非总是如此。
在上面的屏幕截图中,左侧不起作用,但右侧有效。
这就是我检测墙壁并获取距离和 Y 轴的方式
float getDistanceOnHorizontalGrid(float rayAngle, float playerPosX, float playerPosY)
{
float pointInRayY = playerPosY + (100 * sin(DEGREE_TO_RADIAN(rayAngle))); // Arbitrary Point to evaluate whether ray is facing up or down
float firstIntersectPointY;
float ya;
float xa;
if (pointInRayY <= playerPosY) // ray is facing up
{
firstIntersectPointY = floorf(playerPosY / WALL_SIZE) * (WALL_SIZE)-1;
ya = -WALL_SIZE;
xa = WALL_SIZE / tan(DEGREE_TO_RADIAN(rayAngle)) * -1;
}
else // ray is facing down
{
firstIntersectPointY = floorf(playerPosY / WALL_SIZE) * (WALL_SIZE)+WALL_SIZE;
ya = WALL_SIZE;
xa = WALL_SIZE / tan(DEGREE_TO_RADIAN(rayAngle));
}
float firstIntersectPointX = playerPosX - (playerPosY - firstIntersectPointY) / tan(DEGREE_TO_RADIAN(rayAngle));
float nextPointX = firstIntersectPointX;
float nextPointY = firstIntersectPointY;
for (int i = 0; i < MAP_HEIGHT; i++)
{
int toMapX = convertWorldToGridCoordinate(floor(nextPointX));
int toMapY = convertWorldToGridCoordinate(floor(nextPointY));
if (!(toMapX >= 0 && toMapX <= MAP_WIDTH - 1 && toMapY >= 0 && toMapY <= MAP_HEIGHT - 1))
{
return 0.0f;
}
if (map[toMapY][toMapX] == 1)
{
return abs(sqrt(((playerPosX - nextPointX) * (playerPosX - nextPointX)) + ((playerPosY - nextPointY) * (playerPosY - nextPointY))));
}
nextPointX += xa;
nextPointY += ya;
}
return 0.0f;
}
X轴也一样
float getDistanceOnverticalGrid(float rayAngle, float playerPosX, float playerPosY)
{
float pointInRayX = playerPosX + (100 * cos(DEGREE_TO_RADIAN(rayAngle))); // Arbitrary Point to evaluate whether ray is facing left or right
float firstIntersectX;
float firstIntersectY;
float xa;
float ya;
if (pointInRayX <= playerPosX) // ray facing left
{
firstIntersectX = floorf(playerPosX / WALL_SIZE) * (WALL_SIZE)-1;
xa = -WALL_SIZE;
ya = (WALL_SIZE * tan(DEGREE_TO_RADIAN(rayAngle))) * -1;
}
else
{
firstIntersectX = floorf(playerPosX / WALL_SIZE) * (WALL_SIZE)+WALL_SIZE;
ya = WALL_SIZE * tan(DEGREE_TO_RADIAN(rayAngle));
xa = WALL_SIZE;
}
firstIntersectY = playerPosY - (playerPosX - firstIntersectX) * tan(DEGREE_TO_RADIAN(rayAngle));
float nextPointX = firstIntersectX;
float nextPointY = firstIntersectY;
for (int i = 0; i < MAP_WIDTH; i++)
{
int toMapX = convertWorldToGridCoordinate(floorf(nextPointX));
int toMapY = convertWorldToGridCoordinate(floorf(nextPointY));
if (!(toMapX >= 0 && toMapX <= (MAP_WIDTH - 1) && toMapY >= 0 && toMapY <= (MAP_HEIGHT - 1)))
{
return 0.0f;
}
if (map[toMapY][toMapX] == 1)
{
return abs(sqrt(((playerPosX - nextPointX) * (playerPosX - nextPointX)) + ((playerPosY - nextPointY) * (playerPosY - nextPointY))));
}
nextPointX += xa;
nextPointY += ya;
}
return 0.0f;
}
我肯定做错了什么,但我不知道错在哪里。 感谢您的帮助
更新
提到的 BCT 实际上是正确的,尽管从 1 开始减少值仍然是必要的,但不是在那个计算中。 在仔细研究 permadi 的实现之后,在执行此操作后
horizontalGrid = Math.floor(this.fPlayerY/this.TILE_SIZE)*this.TILE_SIZE + this.TILE_SIZE;
他做到了:
horizontalGrid--;
一开始以为和自己做的一样,其实不是。之所以后来把这个值调小是因为这只是为了获取网格坐标。
为了解决我的问题,我已经从计算中删除了 -1,并且仅在我寻找网格坐标时减小该值。
我看了你发的link。似乎其中一个 if 块的值不正确:
这是教程代码:
// Ray is facing down
if (castArc > this.ANGLE0 && castArc < this.ANGLE180)
{
// truncuate then add to get the coordinate of the FIRST grid (horizontal
// wall) that is in front of the player (this is in pixel unit)
// ROUNDED DOWN
horizontalGrid = Math.floor(this.fPlayerY/this.TILE_SIZE)*this.TILE_SIZE + this.TILE_SIZE;
这是您的代码:
if (pointInRayY <= playerPosY) // ray is facing up
{
firstIntersectPointY = floorf(playerPosY / WALL_SIZE) * (WALL_SIZE)-1;
请注意,您在末尾有 -1
而不是 this.TILE_SIZE
,在您的情况下应该是 WALL_SIZE