光线投射渲染 - 墙的边缘交叉问题

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