【问题标题】:Incomplete Light Circle不完整的光环
【发布时间】:2019-04-12 04:28:45
【问题描述】:

我制作了一个允许阴影的照明引擎。它适用于网格系统,其中每个像素都有一个以整数形式存储在数组中的光值。这是它的外观演示:

阴影和实际的像素着色效果很好。唯一的问题是圆圈中较远的未点亮像素,由于某种原因,这形成了一个非常有趣的图案(您可能需要放大图像才能看到它)。这是绘制灯光的代码。

public void implementLighting(){
    lightLevels = new int[Game.WIDTH*Game.HEIGHT];
    //Resets the light level map to replace it with the new lighting
    for(LightSource lightSource : lights) {
        //Iterates through all light sources in the world
        double circumference =  (Math.PI * lightSource.getRadius() * 2),
                segmentToDegrees = 360 / circumference, distanceToLighting = lightSource.getLightLevel() / lightSource.getRadius();
                //Degrades in brightness further out
        for (double i = 0; i < circumference; i++) {
            //Draws a ray to every outer pixel of the light source's reach
            double radians =  Math.toRadians(i*segmentToDegrees),
                    sine =  Math.sin(radians),
                    cosine =  Math.cos(radians),
                    x = lightSource.getVector().getScrX() + cosine,
                    y = lightSource.getVector().getScrY() + sine,
                    nextLit = 0;
            for (double j = 0; j < lightSource.getRadius(); j++) {
                int lighting = (int)(distanceToLighting * (lightSource.getRadius() - j));
                        double pixelHeight = super.getPixelHeight((int) x, (int)y);
                if((int)j==(int)nextLit) addLighting((int)x, (int)y, lighting);
                //If light is projected to have hit the pixel
                if(pixelHeight > 0) {
                    double slope = (lightSource.getEmittingHeight() - pixelHeight) / (0 - j);
                    nextLit = (-lightSource.getRadius()) / slope;
                    /*If something is blocking it
                    * Using heightmap and emitting height, project where next lit pixel will be
                     */
                }
                else nextLit++;
                //Advances the light by one pixel if nothing is blocking it
                x += cosine;
                y += sine;
            }
        }
    }
    lights = new ArrayList<>();
}

我使用的算法应该考虑到光源半径内未被物体阻挡的每个像素,所以我不确定为什么缺少一些外部像素。 谢谢。

编辑:我发现,光源半径内的未点亮像素实际上比其他像素更暗。这是 addLighting 方法的结果,它不只是改变像素的光照,而是将其添加到已经存在的值上。这意味着“未点亮”是仅添加一次的。 为了验证这个假设,我编写了一个程序,该程序以与生成照明相同的方式绘制一个圆圈。下面是画圆的代码:

    BufferedImage image = new BufferedImage(WIDTH, HEIGHT, 
    BufferedImage.TYPE_INT_RGB);
    Graphics g = image.getGraphics();
    g.setColor(Color.white);
    g.fillRect(0, 0, WIDTH, HEIGHT);
    double radius = 100,
            x = (WIDTH-radius)/2,
            y = (HEIGHT-radius)/2,
            circumference = Math.PI*2*radius,
            segmentToRadians = (360*Math.PI)/(circumference*180);
    for(double i = 0; i < circumference; i++){
        double radians = segmentToRadians*i,
                cosine = Math.cos(radians),
                sine = Math.sin(radians),
                xPos = x + cosine,
                yPos = y + sine;
        for (int j = 0; j < radius; j++) {
            if(xPos >= 0 && xPos < WIDTH && yPos >= 0 && yPos < HEIGHT) {
                int rgb = image.getRGB((int) Math.round(xPos), (int) Math.round(yPos));
                if (rgb == Color.white.getRGB()) image.setRGB((int) Math.round(xPos), (int) Math.round(yPos), 0);
                else image.setRGB((int) Math.round(xPos), (int) Math.round(yPos), Color.red.getRGB());
            }
            xPos += cosine;
            yPos += sine;
        }
    }

结果如下:

白色像素是未着色的像素 黑色像素是一次着色的像素 红色像素是着色 2 次或更多次的像素

所以它实际上比我最初建议的还要糟糕。它是未点亮的像素和多次点亮的像素的组合。

【问题讨论】:

  • “失踪”是什么意思?外部像素是黑色的,但我想这是由于光线没有到达它们。您是在谈论出现的圆形图案吗?没有照明/全光的图像是什么样的?
  • 我还不知道您的算法,但我的直觉是您遇到了舍入/整数转换问题。通过基于半径增加计算然后使用 sin / cos 你可能永远不会达到某些像素,因为通过舍入效果两次迭代会导致相同的 x 和 y。您可能想尝试基于板的计算(对于所有 x 和 y 计算与光源的距离?)
  • 无关:阅读干净的代码。您的代码可以从中受益。太好了。
  • 我尝试使用 addLighting((int)Math.round(x), (int)Math,round(y), lighting),而不是仅仅将它们转换为 int,但它没有任何东西。
  • 正确 - 相同的差异。舍入与 int 强制转换不会产生显着差异。双精度与沿圆周和半径的 int 进程的混合 - 我仍然觉得你这样缺少像素。从 0,0 移动到 GAME_WIDTH,GAME_HEIGHT 可能在计算数量等方面更差(任何光半径之外的像素),但会纠正每个像素的渲染光照。你会以这种方式获得光强度的四舍五入,而不是像素位置

标签: java game-engine lighting


【解决方案1】:

您应该迭代真实的图像像素,而不是极坐标网格点。

所以正确的像素遍历代码可能看起来像

for(int x = 0; x < WIDTH; ++x) {
  for(int y = 0; y < HEIGHT; ++y) {
    double distance = Math.hypot(x - xCenter, y - yCenter);
    if(distance <= radius) {
      image.setRGB(x, y, YOUR_CODE_HERE);
    }
  }
}

当然这个sn-p可以优化,选择好的填充多边形而不是矩形。

【讨论】:

  • 我按照自己的方式绘制灯光是有原因的。它允许阻挡向某个方向投射的光。以您的方式,无法判断某个像素是否被光线遮挡。
【解决方案2】:

这可以通过anti-aliasing解决。

由于您推送浮点坐标信息并对其进行压缩,因此会发生一些有损采样。

double x,y  ------(snap)---> lightLevels[int ?][int ?]

要彻底解决这个问题,您必须以正确的光强度在该线周围绘制透明像素(即光线较弱的像素)。不过还是蛮难计算的。 (见https://en.wikipedia.org/wiki/Spatial_anti-aliasing

解决方法

一种更简单(但很脏)的方法是在您绘制的线条上绘制另一条透明粗线,并根据需要调整强度。

或者只是让你的线条更粗,即使用更大的模糊点但更少的光线来补偿。
它应该使故障不那么明显。
(算法见how do I create a line of arbitrary thickness using Bresenham?

更好的方法是改变您的绘图方法。
手动绘制每条线非常昂贵。
您可以使用 2D 精灵画一个圆圈。
但是,如果您真的想要像此图像中的光线投射,则它不适用:http://www.iforce2d.net/image/explosions-raycast1.png

分割图形 - 游戏玩法

为了获得最佳性能和外观,您可能更喜欢 GPU 进行渲染,但使用更粗略的算法为游戏进行光线投射。

尽管如此,这是一个非常复杂的话题。 (例如http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/

参考

这里有更多信息:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-12
    • 2016-09-17
    • 1970-01-01
    相关资源
    最近更新 更多