You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Raycaster游戏引擎中检测射线击中墙体的哪一面?

如何在Raycaster引擎中检测射线击中的墙体面(用于纹理映射)

我用C语言实现Raycaster引擎已有两天,完成了基础的射线墙体检测算法。现在需要判断射线击中的是墙体的左/右还是上/下面,这是实现纹理映射的必要步骤。之前考虑通过比较射线X、Y分量的大小来判断,但不确定该方法是否正确。

正确的碰撞面判断方法

直接比较rayX和rayY的大小并不可靠——这两个值是射线的方向增量,无法直接反映碰撞时先触发了哪个方向的网格边界。正确的做法是计算射线到达网格X边界和Y边界的相对时间,判断哪个更早发生碰撞:

  1. 在射线步进时,保存碰撞前的位置(prevWallX/prevWallY)
  2. 计算碰撞点所在的网格坐标
  3. 分别计算射线到达X方向墙体和Y方向墙体所需的步数(时间)
  4. 比较这两个时间,确定先碰撞的墙体方向

修改后的代码实现

在你的射线碰撞检测循环中加入碰撞面判断逻辑:

double rayAngle, wallX, wallY, rayX, rayY, h, lineHeight, lineOffset, cameraAngle, shadeDistance;
// 新增变量:保存碰撞前的位置、碰撞面标记、纹理偏移
double prevWallX, prevWallY;
bool hitVerticalWall; // true=击中左右墙,false=击中上下墙
double textureOffset;

for (int ray = 0; ray < FOV * RAY_PER_DEG; ray++) // 改用int循环变量,避免浮点精度问题
{
    rayAngle = playerAngle + TO_RADIANS((double)ray / RAY_PER_DEG - FOV / 2);
    wallX = playerX;
    wallY = playerY;
    rayX = cos(rayAngle) * DISTANCE_COEFFICIENT; // 按常规坐标习惯先X后Y
    rayY = sin(rayAngle) * DISTANCE_COEFFICIENT;

    hitVerticalWall = false;
    textureOffset = 0.0;

    for(int stepCount = 0; stepCount < MAX_DISTANCE; stepCount++) // 用stepCount替代原distance变量,区分步进次数与实际距离
    {
        prevWallX = wallX; // 保存步进前的位置
        prevWallY = wallY;
        wallX += rayX;
        wallY += rayY;

        if (wallX >= 0 && wallX < mapWidth && wallY >=0 && wallY < mapHeight &&
            mapWalls[(int)wallY * mapWidth + (int)wallX]) // 修复数组索引错误:用mapWidth作为行宽
        {
            // 计算碰撞面
            int gridX = (int)wallX;
            int gridY = (int)wallY;
            double deltaX, deltaY;
            double distToXWall, distToYWall;

            if (rayX > 0) {
                // 射线向右,碰撞网格右边界(x=gridX+1)
                deltaX = (gridX + 1.0) - prevWallX;
            } else {
                // 射线向左,碰撞网格左边界(x=gridX)
                deltaX = prevWallX - gridX;
            }

            if (rayY > 0) {
                // 射线向下,碰撞网格下边界(y=gridY+1)
                deltaY = (gridY + 1.0) - prevWallY;
            } else {
                // 射线向上,碰撞网格上边界(y=gridY)
                deltaY = prevWallY - gridY;
            }

            // 计算到达墙体所需的相对步数(时间)
            distToXWall = deltaX / fabs(rayX);
            distToYWall = deltaY / fabs(rayY);

            hitVerticalWall = (distToXWall < distToYWall);

            // 计算纹理偏移量
            if (hitVerticalWall) {
                // 垂直墙:用Y坐标的小数部分作为纹理X偏移
                textureOffset = fmod(wallY, 1.0);
                // 可选:左墙翻转纹理
                if (rayX < 0) textureOffset = 1.0 - textureOffset;
            } else {
                // 水平墙:用X坐标的小数部分作为纹理X偏移
                textureOffset = fmod(wallX, 1.0);
                // 可选:上墙翻转纹理
                if (rayY < 0) textureOffset = 1.0 - textureOffset;
            }

            break;
        }
    }

    // fisheye补偿与绘制逻辑
    cameraAngle = playerAngle - rayAngle;
    WRAP_AROUND_RADIANS(cameraAngle);
    double actualDistance = sqrt(pow(wallX - playerX, 2) + pow(wallY - playerY, 2)) * cos(cameraAngle);
    lineHeight = WALL_HEIGHT * MAX_WALL_HEIGHT / actualDistance;
    lineOffset = WALL_HEIGHT - lineHeight/2;

    shadeDistance = 1.0 / actualDistance;

    glLineWidth(1 / RESOLUTION);
    glBegin(GL_LINES);
    glColor3f(shadeDistance * SHADE_COEFFICIENT, 0, 0); // 墙体
    glVertex2f((double)ray / RESOLUTION, lineHeight + lineOffset);
    glVertex2f((double)ray / RESOLUTION, lineOffset);

    glColor3f(0, shadeDistance * SHADE_COEFFICIENT, 0); // 地面
    glVertex2f((double)ray / RESOLUTION, lineOffset);
    glVertex2f((double)ray / RESOLUTION, 0);
    glEnd();
}

额外代码建议

  1. 改用DDA算法:当前固定步长步进方式存在精度和性能问题,DDA算法可直接计算射线与网格边界的下一个交点,避免循环步进,效率更高且碰撞检测更准确。
  2. 变量命名优化:将rayX/rayY改为rayDirX/rayDirY,更清晰体现其方向增量的含义;区分步进次数和实际距离,避免混淆。
  3. OpenGL性能优化:将glLineWidth等状态设置移到循环外,减少状态切换开销;或采用批量绘制方式提升渲染效率。

内容的提问来源于stack exchange,提问作者Moldytzu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 23:35:57