如何在Raycaster游戏引擎中检测射线击中墙体的哪一面?
如何在Raycaster引擎中检测射线击中的墙体面(用于纹理映射)
我用C语言实现Raycaster引擎已有两天,完成了基础的射线墙体检测算法。现在需要判断射线击中的是墙体的左/右还是上/下面,这是实现纹理映射的必要步骤。之前考虑通过比较射线X、Y分量的大小来判断,但不确定该方法是否正确。
正确的碰撞面判断方法
直接比较rayX和rayY的大小并不可靠——这两个值是射线的方向增量,无法直接反映碰撞时先触发了哪个方向的网格边界。正确的做法是计算射线到达网格X边界和Y边界的相对时间,判断哪个更早发生碰撞:
- 在射线步进时,保存碰撞前的位置(
prevWallX/prevWallY) - 计算碰撞点所在的网格坐标
- 分别计算射线到达X方向墙体和Y方向墙体所需的步数(时间)
- 比较这两个时间,确定先碰撞的墙体方向
修改后的代码实现
在你的射线碰撞检测循环中加入碰撞面判断逻辑:
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(); }
额外代码建议
- 改用DDA算法:当前固定步长步进方式存在精度和性能问题,DDA算法可直接计算射线与网格边界的下一个交点,避免循环步进,效率更高且碰撞检测更准确。
- 变量命名优化:将
rayX/rayY改为rayDirX/rayDirY,更清晰体现其方向增量的含义;区分步进次数和实际距离,避免混淆。 - OpenGL性能优化:将
glLineWidth等状态设置移到循环外,减少状态切换开销;或采用批量绘制方式提升渲染效率。
内容的提问来源于stack exchange,提问作者Moldytzu
相关产品推荐
相关产品推荐

