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

基于SDL2的Raycasting项目消除鱼眼效果技术咨询

Raycasting项目鱼眼效果修复求助

我正在使用SDL2开发一个Raycasting项目,已成功实现射线投射功能,但3D预览环节仍存在鱼眼效果,无法达到预期的3D呈现效果。以下是当前实现代码,恳请提供解决思路:

#include "include/maze.h"

#include "include/colors.h"

void drawRays(SDL_Renderer *renderer, SDL_Renderer *renderer2, Player *player, SDL_Point walls[200][2], int wallCount)

{

    SDL_Point target;

    SDL_Point closest;

    SDL_Rect rect;

    SDL_Surface *surface = createPlusTexture(0,0);

    SDL_Texture *t = SDL_CreateTextureFromSurface(renderer2, surface);//creates brown texture

    // The thickness the bars can be based on the window width

    int rayPreviewrenstep = windowW * 1.15 / fov;

    int rayCount = 0;

    int closestDistance;

    rect.w = rayPreviewrenstep; // We can pre calculate the width as this is a constant

    // Loop for each ray in fov at 1 degree step

    for (int a = player->angle - fov / 2; a < player->angle + fov / 2; a++)

    {

        rayCount++;

        target = CreateTarget(player->pos.p1.x, player->pos.p1.y, a, viewRange); // Find the point at which the ray is out of view distance

        closest = target;

        closestDistance = viewRange;

        // Loop over each wall

        for (int i = 0; i < wallCount; i++)

        {

            // Find the intersection point

            float a1 = target.y - player->pos.p1.y;

            float b1 = player->pos.p1.x - target.x;

            float c1 = a1 * player->pos.p1.x + b1 * player->pos.p1.y;

            float a2 = walls[i][1].y - walls[i][0].y;

            float b2 = walls[i][0].x - walls[i][1].x;

            float c2 = a2 * walls[i][0].x + b2 * walls[i][0].y;

            float det = a1 * b2 - a2 * b1;

            // If its not parallel

            if (det != 0)

            {

                int x = (b2 * c1 - b1 * c2) / det;

                int y = (a1 * c2 - a2 * c1) / det;

                // If the point falls inside the wall and the ray

                if (PointOnLine(player->pos.p1.x, player->pos.p1.y, target.x, target.y, x, y) &&

                    PointOnLine(walls[i][0].x, walls[i][0].y, walls[i][1].x, walls[i][1].y, x, y))

                {

                    // Calculate the distance to the point

                    int dist = PointDistance(player->pos.p1.x, player->pos.p1.y, x, y);

                    // If its the closest wall, store it

                    if (dist < closestDistance)

                    {

                        closest.x = x;

                        closest.y = y;

                        closestDistance = dist;

                    }

                }

            }

        }

        // If the ray hit something

        if (closestDistance != viewRange)

        {

            // Draw the ray

            SDL_SetRenderDrawColor(renderer, COLOR_DARK_GRAY.r, COLOR_DARK_GRAY.g, COLOR_DARK_GRAY.b, COLOR_DARK_GRAY.a);

            SDL_RenderDrawLine(renderer, player->pos.p1.x, player->pos.p1.y, closest.x, closest.y);

            // Draw the 3d preview

            // correct vision

            rect.x = rayPreviewrenstep * rayCount + (windowW - 900) / 3;

            rect.y = closestDistance / 2;

            rect.h = windowH - rect.y *2;

            int brightness = 255 - closestDistance / 2;

            SDL_SetRenderDrawColor(renderer2, brightness, brightness, brightness, 255);

            SDL_RenderFillRect(renderer2, &rect);

            // SDL_RenderCopy(renderer2, t, NULL, &rect);

            SDL_SetRenderDrawColor(renderer2, COLOR_BLACK.r, COLOR_BLACK.g, COLOR_BLACK.b, COLOR_BLACK.a);

        }

    }

}

SDL_Point CreateTarget(int x, int y, int dir, int dist)

{

    // Create a point dist pixels away in dir direction from x and y

    SDL_Point target;

    double translated = DEG2RAD(dir); // Convert to radians

    target.x = round(x + dist * cos(translated));

    target.y = round(y + dist * sin(translated));

    return target;

}

int PointOnLine(int x1, int y1, int x2, int y2, int tx, int ty)

{

    // Check if a point is on a line segment

    if (fmin(x1, x2) <= tx && tx <= fmax(x1, x2) && fmin(y1, y2) <= ty && ty <= fmax(y1, y2))

    {

        return true;

    }

    return false;

}

int PointDistance(int x1, int y1, int x2, int y2)

{

    // Find the distance between two points

    int out = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));

    return out;

}

解决思路

鱼眼效果的核心原因是直接使用射线的欧几里得距离(PointDistance计算的直线距离)计算墙高,实际需要的是垂直于玩家视角的投影距离。

具体修复步骤:

  1. 计算角度差:对每条射线,计算其角度与玩家当前朝向(player->angle)的差值,转换为弧度。
  2. 修正距离:用欧几里得距离乘以cos(角度差),得到消除鱼眼的投影距离——正前方射线角度差为0,cos值为1无修正;边缘射线角度差大,cos值小,修正幅度大。

代码修改点:

  • 在获取closestDistance后添加距离修正逻辑:
// 转换为弧度计算角度差
double rayAngleRad = DEG2RAD(a);
double playerAngleRad = DEG2RAD(player->angle);
double angleDiff = rayAngleRad - playerAngleRad;
// 修正距离,消除鱼眼
float correctedDist = closestDistance * cos(angleDiff);
  • 绘制3D墙时用correctedDist替代closestDistance:
rect.y = correctedDist / 2;
rect.h = windowH - rect.y * 2;
int brightness = 255 - correctedDist / 2;

额外优化建议:

  • 将closestDistance类型从int改为float/double,避免整数截断损失精度。
  • 修改PointDistance函数返回值为浮点数,进一步提升计算精度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 17:13:17