Java 游戏引擎:光线投射的墙壁是空心的、破碎的,看起来像粪便

Posted

技术标签:

【中文标题】Java 游戏引擎:光线投射的墙壁是空心的、破碎的,看起来像粪便【英文标题】:Java Game Engine: Raycasted walls are hollow, broken, and look like fecal matter 【发布时间】:2016-06-14 05:18:21 【问题描述】:

我正在尝试编写一个光线投射引擎。

我研究了http://www.instructables.com/id/Making-a-Basic-3D-Engine-in-Java/ 上的教程和http://lodev.org/cgtutor/raycasting.html 上的 C++ 光线投射教程,经过几次尝试后,我将光线投射到正确的方向,因此得到了(半)工作输出。

我让墙壁出现在世界上,我在游戏中添加了运动,我能够四处移动。但是,无论我面对世界的哪个方向,墙壁(应该是立方体)都只显示立方体的两侧。因此,它不会显示一个实心立方体,而是从立方体实际上最靠近相机的一侧跳转,而是显示立方体的另一侧,这仅在我面向原点时发生(0,0 ) 的二维数组,我的地图存储在其中。此错误如上图所示。

我认为这个错误是由于整数四舍五入和光线检测到的墙壁位置被四舍五入造成的,但我似乎无法提出解决方案。我实际上为每个像素列投射了两条光线,一条用于检测垂直墙壁,一条用于检测水平墙壁。计算并比较每个的距离,然后绘制最短距离的墙。

我的问题是如何正确绘制墙壁

 public class Screen 

     //VARIABLE DECLARATIONS
     //-----------------------
     int FOV = 60; //field of view in degrees
     int screenwidth = 800; //variable holds the vertical resolution of the screen
     int screenheight = 600; //variable holds the horizontal resolution of the screen
     double camx; //cameras x coordinate
     double camy; //cameras y coordinate
     double camAngle; //direction of camera in degrees
     double rayAngle; //angle of ray being cast in radians
     int x = 0; //holds the current pixel column being looped through
     double IncrementAngle = (double)FOV / (double)screenwidth; //calculates the change in the rays angle for each horizontal pixel

     int[][] map;  //stores the 2d map that represents the 3d world of the game

     public Screen() 

     public int[] update(int[] pixels, int[][] m, double ca, double cx, double cy, int fov) 

         FOV = fov;
         IncrementAngle = (double)FOV / (double)screenwidth; //calculates the change in the rays angle for each horizontal pixel

         camAngle = ca;
         camx = cx;
         camy = cy;

         map = m;

         int x = 0;

         Color c; //declares new color

         //fills background
         for (int n = 0; n < pixels.length; n++) pixels[n] = Color.BLACK.getRGB();

         for (double ray = (double)(FOV / 2); ray > (double)(-FOV / 2); ray -= IncrementAngle) 
             double vdist = Integer.MAX_VALUE, hdist  = Integer.MAX_VALUE;
             double perpendicularDist = 0;
             double theta;
             double lineheight;
             int drawstart, drawend;
             int side = 0;

             int r = 0, g = 0, b = 0, a = 0; //variables that determinbe what colours will be drawn (red, green, blue, and alpha for
 transparency)

             rayAngle = Math.toRadians(camAngle + ray);

             try 
                 vdist = VertDist(rayAngle);
             
             catch (ArrayIndexOutOfBoundsException e) 
             try 
                 hdist = HorDist(rayAngle);
             
             catch (ArrayIndexOutOfBoundsException e) 

             theta = Math.abs(rayAngle - Math.toRadians(camAngle)); //angle difference between the ray being cast and the cameras
 direction

             if (hdist < vdist) 
                 perpendicularDist = hdist * Math.cos(theta);
                 lastSide = 0;
                 r = Color.GRAY.getRed();
                 g = Color.GRAY.getGreen();
                 b = Color.GRAY.getBlue();
                 a = Color.GRAY.getAlpha();
             
             else 
                 perpendicularDist = vdist * Math.cos(theta);
                 lastSide = 1;
                 r = Color.DARK_GRAY.getRed();
                 g = Color.DARK_GRAY.getGreen();
                 b = Color.DARK_GRAY.getBlue();
                 a = Color.DARK_GRAY.getAlpha();
             
             //creates pulsating effect with wall colours
             r -= pulse;
             g += pulse * 2;
             b -= pulse;

             c = new Color(r, g, b, a);

             lineheight = screenheight / perpendicularDist;

             drawstart = (int)(-lineheight / 2) + (screenheight / 2);
             drawend = (int)(lineheight / 2) + (screenheight / 2);

             if (drawstart < 0) drawstart = 0;
             if (drawend >= screenheight) drawend = screenheight - 1;

             for (int y = drawstart; y < drawend; y++) 
                 pixels[x + (y * screenwidth)] = c.getRGB();
             

             if (x < screenwidth) x++;
             else x = 0;
         

         //returns pixels array to main class to be shown to screen
         return pixels;
     

     public double VertDist(double angle) 
         double rx = 0, ry = 0;
         double stepX = 0, stepY = 0;
         double FstepX = 0, FstepY = 0;
         double Fxcomp = 0, Fycomp = 0;
         double xcomp = 0, ycomp = 0;
         double mapx = camx, mapy = camy;
         boolean hit = false;
         double obliqueDist = 0;

             rx = Math.cos(angle);
             ry = Math.sin(angle);

             if (rx < 0) 
                 stepX = -1;
                 FstepX = (camx - ((int)camx)) * stepX;
             
             else if (rx > 0) 
                 stepX = 1;
                 FstepX = ((int)(camx + 1)) - camx;
             

             ycomp = (stepX * Math.tan(angle) * -1);
             Fycomp = Math.abs(FstepX) * ycomp;

             if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;

             mapx += FstepX;
             mapy += Fycomp;

             if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;
             else 
                 while (!hit && mapx > 0 && mapy > 0)  //loops while a wall has not been found and while positive indexes are still being
 checked
                     mapx += stepX;
                     mapy += ycomp;
                     if (map[(int)(mapx)][(int)(mapy)] > 0) 
                         hit = true;
                         //if (Math.toDegrees(rayAngle) < 270 && Math.toDegrees(rayAngle) > 90) 
                         //    mapy -= stepX;
                         //    mapx -= ycomp;
                         //
                     
                 
             

             mapx = Math.abs(mapx - camx);
             mapy = Math.abs(mapy - camy);

             obliqueDist = Math.sqrt((mapx*mapx) + (mapy*mapy));
             //change to be not fixed angle based
              //if (angle > Math.toRadians(135) && angle < Math.toRadians(225)) 
                // obliqueDist -= Math.sqrt(stepX*stepX + ycomp*ycomp);
             //
             return obliqueDist;
     

     public double HorDist(double angle) 
         double rx, ry;
         double stepX = 0, stepY = 0;
         double FstepX = 0, FstepY = 0;
         double Fxcomp, Fycomp;
         double xcomp, ycomp;
         double mapx = camx, mapy = camy;
         boolean hit = false;
         double obliqueDist = 0;

             rx = Math.cos(angle);
             ry = Math.sin(angle);

             if (ry < 0) 
                 stepY = 1;
                 FstepY = ((int)(camy + 1)) - camy;
             
             else if (ry > 0) 
                 stepY = -1;
                 FstepY = (camy - (int)camy) * stepY;
             

             xcomp = stepY / (Math.tan(angle) * -1);
             Fxcomp = Math.abs(FstepY) * xcomp;

             if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;

             mapx += Fxcomp;
             mapy += FstepY;

             if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;
             else 
                 while (!hit) 
                     mapx += xcomp;
                     mapy += stepY;
                     if (map[(int)(mapx)][(int)(mapy)] > 0) hit = true;
                 
             

             mapx = Math.abs(mapx - camx);
             mapy = Math.abs(mapy - camy);

             obliqueDist = Math.sqrt((mapx*mapx) + (mapy*mapy));
             //change to be not fixed angle based
             //if (angle > Math.toRadians(45) && angle < Math.toRadians(135)) 
               //  obliqueDist -= Math.sqrt(xcomp*xcomp + stepY*stepY);
             //
             return obliqueDist;
          

【问题讨论】:

请学会使用段落。大量没有逻辑中断的文字墙只会让人们望而却步,并导致他们跳过您的问题和/或投反对票。然后,在写你的问题时,要简明扼要。省略所有无关信息。我们真的不需要知道你是如何走到这一步的整个历史。只问具体问题。 为了清晰和简洁,我编辑了您的帖子。如果您认为我删除了太多,请随时恢复和编辑自己。但是,尝试将代码简化为演示该问题的 minimal 示例可能对您非常有用。与您的散文一样,删除与问题无关的任何内容并构建minimal reproducible example。这样做的过程可能会教给您很多东西,您甚至可能会解决自己的问题。对我来说总是这样。 感谢您在我的帖子上留下了一些建设性的批评,而不是直接投反对票并离开。下次我发帖时,我会记住你所说的。实际上,我正要自己删除所有不必要的部分,所以谢谢你。 你显然有一些天赋,可以学习成为一名优秀的开发人员,但正如你所承认的,你还年轻,还是个初学者。所以我不想让你气馁。清晰简洁的沟通是一项极其重要的技能,检查问题并从噪音中提取重要部分的能力也是如此。祝你好运! 你需要开始调试。在这个 QA ***.com/a/32733650/2521214 中查看我的 Wolfenstein 示例图像,尤其是包含光线的左上角 2D 地图预览。尝试将其添加到您的应用程序中,以便您可以直接查看光线有什么问题。如果它们正确,则检查 V 线渲染是否正确计算了位置和大小。最后检查防鱼眼。 ...您还应该发布一些图片,以便有人可能发现任何东西 ,,, 【参考方案1】:

好的,所以我能够修复它。事实证明,正如我所想的那样,问题是由于整数舍入(墙坐标会向下舍入)。当光线投射到二维数组中 x 或 y(或两者)接近零的方向时,墙壁坐标会向下舍入,到墙壁的距离会被错误计算,结果看起来像图片以上。

我发现这是因为我将墙壁坐标存储在双精度数中,尽管双精度数肯定比整数更准确,但它们仍然不是精确的。所以发生的事情是墙坐标将非常接近它们应该稍微偏离的值,当我在检查射线墙碰撞时将这些值转换为整数时,它们会向下舍入到实际值下协调并向我提供不正确的距离。

所以为了解决这个问题,我添加了一个非常小的值(大约 0.0001)乘以光线的步进方向(步进方向可以是正或负 1,并确定后续垂直/水平阵列网格线之间的垂直距离)到射线坐标,同时检查射线壁碰撞,以平衡我的算法的轻微不准确性。简而言之,这样做是使检测到的墙更靠近玩家 0.0001 个单位,因此绕过了不准确并导致射线坐标成功地向下舍入到墙的实际坐标。

【讨论】:

永远不要比较浮点数是否相等。始终将差异与增量进行比较。 是的,我现在明白了。浮点值是不可信的 你有很多东西要学。这不仅仅是浮动数字。没有数字是可信的。或者是人。

以上是关于Java 游戏引擎:光线投射的墙壁是空心的、破碎的,看起来像粪便的主要内容,如果未能解决你的问题,请参考以下文章

用于游戏开发和其他目的的光线投射教程

用于游戏开发和其他目的的光线投射教程

使用光线投射防止破折号穿过墙壁

光线投射算法中的精度问题

如何为光线投射生成相机光线

NVIDIA软阴影,效果怎么样?