基于GLFW的C语言Raycaster开发求助:射线距离计算与画线问题
我正在用C语言结合GLFW库开发简易Raycaster,卡在了update函数末尾的画线部分。我认为错误源于不合理的距离计算算法,请求帮忙排查该算法问题,同时指出代码中的其他错误。附上的代码包含地图、窗口创建、相机、墙体探测射线、距离计算算法等模块,除距离计算外其余模块看似正常运行。
代码
#include <stdio.h> #include <GLFW/glfw3.h> #include <math.h> #define SCREENWIDTH 1920 #define SCREENHEIGHT 1080 #define MAPWIDTH 24 #define MAPHEIGHT 24 float boxPositionX = MAPWIDTH; float boxPositionY = MAPHEIGHT; float boxSize = 5.0f; double posX = 22, posY = 12; //x and y start position double dirX = -1, dirY = 0; //initial direction vector double planeX = 0, planeY = 0.66; //the 2d raycaster version of camera plane double time = 0; //time of current frame double oldTime = 0; //time of previous frame int worldMap[MAPWIDTH][MAPHEIGHT]= { {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1}, {1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1}, {1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} }; void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (action == GLFW_PRESS || action == GLFW_REPEAT) { switch (key) { case GLFW_KEY_A: break; case GLFW_KEY_D: break; case GLFW_KEY_W: break; } } } void draw() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_QUADS); glVertex2f(boxPositionX, boxPositionY); glVertex2f(boxPositionX + boxSize, boxPositionY); glVertex2f(boxPositionX + boxSize, boxPositionY + boxSize); glVertex2f(boxPositionX, boxPositionY + boxSize); glEnd(); } void update() { for(int x = 0; x < MAPWIDTH * MAPHEIGHT; x++){ double cameraX = 2 * x / MAPWIDTH - 1; double rayDirX = dirX + planeX * cameraX; double rayDirY = dirY + planeY * cameraX; int mapX = posX; int mapY = posY; double sideDistX; double sideDistY; //length of ray from one x or y-side to next x or y-side double deltaDistX = (rayDirX == 0); double deltaDistY = (rayDirY == 0); if (deltaDistX == 0){deltaDistX=1e30;} if (deltaDistY == 0){deltaDistY=1e30;} double perpWallDist; //what direction to step in x or y-direction (either +1 or -1) int stepX; int stepY; int hit = 0; //was there a wall hit? int side; //was a NS or a EW wall hit? if (rayDirX < 0) { stepX = -1; sideDistX = (posX - mapX) * deltaDistX; } else { stepX = 1; sideDistX = (mapX + 1.0 - posX) * deltaDistX; } if (rayDirY < 0) { stepY = -1; sideDistY = (posY - mapY) * deltaDistY; } else { stepY = 1; sideDistY = (mapY + 1.0 - posY) * deltaDistY; } while (hit == 0) { //jump to next map square, either in x-direction, or in y-direction if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0; } else { sideDistY += deltaDistY; mapY += stepY; side = 1; } //Check if ray has hit a wall if (worldMap[mapX][mapY] > 0) hit = 1; if(side == 0) perpWallDist = (sideDistX - deltaDistX); } int lineHeight = SCREENHEIGHT / perpWallDist; int drawStart = -lineHeight / 2 + SCREENHEIGHT / 2; if(drawStart < 0)drawStart = 0; int drawEnd = lineHeight / 2 + SCREENHEIGHT / 2; if(drawEnd >= SCREENHEIGHT)drawEnd = SCREENHEIGHT - 1; glBegin(GL_LINES); glVertex2f(drawStart, drawEnd); // Start point glEnd(); } } int main() { if (!glfwInit()) { fprintf(stderr, "Failed to initialize GLFW\n"); return -1; } GLFWwindow* window = glfwCreateWindow(SCREENWIDTH, SCREENHEIGHT, "Box with Jumps Everywhere", NULL, NULL); if (!window) { fprintf(stderr, "Failed to create GLFW window\n"); glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetKeyCallback(window, keyCallback); glViewport(0, 0, SCREENWIDTH, SCREENHEIGHT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, SCREENWIDTH, SCREENHEIGHT, 0, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); while (!glfwWindowShouldClose(window)) { update(); glfwSwapBuffers(window); glfwPollEvents(); } }
错误排查与修复
核心距离计算问题
deltaDistX/Y 计算完全错误
原代码中double deltaDistX = (rayDirX == 0);用布尔值赋值,导致deltaDistX/Y只能是0或1,完全不符合射线步进逻辑。正确的计算应该是射线在x/y方向上,每移动一个地图格子的长度增量:double deltaDistX = fabs(1 / rayDirX); double deltaDistY = fabs(1 / rayDirY);当rayDirX/Y为0时,结果为无穷大,无需额外处理,此时射线不会在对应方向步进。
perpWallDist 计算不完整且逻辑错误
原代码仅在side=0时计算距离,且未考虑射线方向,无法避免鱼眼效应。正确的垂直距离计算应该是:if(side == 0) perpWallDist = (mapX - posX + (1 - stepX)/2) / rayDirX; else perpWallDist = (mapY - posY + (1 - stepY)/2) / rayDirY;或者基于sideDist和deltaDist计算:
if(side == 0) perpWallDist = (sideDistX - deltaDistX) / rayDirX; else perpWallDist = (sideDistY - deltaDistY) / rayDirY;
其他关键错误
射线循环范围错误
原代码for(int x = 0; x < MAPWIDTH * MAPHEIGHT; x++)循环次数为576次,但Raycaster需要为屏幕每一列发射一条射线,应改为:for(int x = 0; x < SCREENWIDTH; x++)画线函数参数缺失
glVertex2f(drawStart, drawEnd)仅提供一个顶点,GL_LINES需要两个顶点才能绘制线段。正确的做法是绘制当前列的垂直线:glBegin(GL_LINES); glVertex2f(x, drawStart); // 当前列顶部 glVertex2f(x, drawEnd); // 当前列底部 glEnd();同时需在
update函数开头添加glClear(GL_COLOR_BUFFER_BIT),避免画面叠加混乱。cameraX 计算错误
原代码用MAPWIDTH作为分母,导致cameraX范围错误,应改为基于屏幕宽度计算:double cameraX = 2.0 * x / SCREENWIDTH - 1.0;键盘控制未实现
keyCallback中W/A/D按键逻辑为空,需补充移动和旋转逻辑(以W键移动为例):case GLFW_KEY_W: if(worldMap[(int)(posX + dirX * 0.1)][(int)posY] == 0) posX += dirX * 0.1; if(worldMap[(int)posX][(int)(posY + dirY * 0.1)] == 0) posY += dirY * 0.1; break;时间变量未使用
代码中定义的time和oldTime未用于控制帧率一致性,需在main循环中计算帧时间:while (!glfwWindowShouldClose(window)) { time = glfwGetTime(); double frameTime = time - oldTime; oldTime = time; update(frameTime); // 传入帧时间控制移动步长 glfwSwapBuffers(window); glfwPollEvents(); }
内容的提问来源于stack exchange,提问作者Тимур Сенников

