不完整的光圈
Incomplete Light Circle
我制作了一个允许阴影的照明引擎。它在网格系统上工作,其中每个像素都有一个以整数形式存储在数组中的光值。这是它的外观演示:
阴影和实际像素着色效果很好。唯一的问题是圆圈中更远的未点亮像素,出于某种原因形成了一个非常有趣的图案(您可能需要放大图像才能看到它)。这是绘制光线的代码。
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 方法的结果,它不仅仅是改变像素的光照,而是将其添加到已经存在的值中。这意味着 "unlit" 是只被添加到一次的。
为了验证这个假设,我制作了一个程序,以与生成照明相同的方式绘制一个圆圈。这是绘制圆圈的代码:
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 次或更多次的像素
所以它实际上比我最初提出的还要糟糕。它是未点亮像素和多次点亮像素的组合。
这可以通过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 sprite 绘制圆圈。
但是,如果您真的想要此图像中的光线投射,则它不适用:http://www.iforce2d.net/image/explosions-raycast1.png
拆分图形 - 游戏玩法
为了获得最佳性能和外观,您可能更喜欢 GPU 渲染,但使用更粗略的算法为游戏进行光线投射。
尽管如此,这是一个非常复杂的话题。 (例如 http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/ )
参考
这里有更多信息:
http://what-when-how.com/opengl-programming-guide/antialiasing-blending-antialiasing-fog-and-polygon-offset-opengl-programming/(带图像的 opengl 抗锯齿)
DirectX11 Non-Solid wireframe(关于directx11 with image的相关问题)
您应该遍历真实图像像素,而不是极坐标网格点。
所以正确的像素漫游代码可能看起来像
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);
}
}
}
当然可以优化此代码段,选择填充良好的多边形而不是矩形。
我制作了一个允许阴影的照明引擎。它在网格系统上工作,其中每个像素都有一个以整数形式存储在数组中的光值。这是它的外观演示:
阴影和实际像素着色效果很好。唯一的问题是圆圈中更远的未点亮像素,出于某种原因形成了一个非常有趣的图案(您可能需要放大图像才能看到它)。这是绘制光线的代码。
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 方法的结果,它不仅仅是改变像素的光照,而是将其添加到已经存在的值中。这意味着 "unlit" 是只被添加到一次的。 为了验证这个假设,我制作了一个程序,以与生成照明相同的方式绘制一个圆圈。这是绘制圆圈的代码:
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 次或更多次的像素
所以它实际上比我最初提出的还要糟糕。它是未点亮像素和多次点亮像素的组合。
这可以通过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 sprite 绘制圆圈。
但是,如果您真的想要此图像中的光线投射,则它不适用:http://www.iforce2d.net/image/explosions-raycast1.png
拆分图形 - 游戏玩法
为了获得最佳性能和外观,您可能更喜欢 GPU 渲染,但使用更粗略的算法为游戏进行光线投射。
尽管如此,这是一个非常复杂的话题。 (例如 http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/ )
参考
这里有更多信息:
http://what-when-how.com/opengl-programming-guide/antialiasing-blending-antialiasing-fog-and-polygon-offset-opengl-programming/(带图像的 opengl 抗锯齿)
DirectX11 Non-Solid wireframe(关于directx11 with image的相关问题)
您应该遍历真实图像像素,而不是极坐标网格点。
所以正确的像素漫游代码可能看起来像
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);
}
}
}
当然可以优化此代码段,选择填充良好的多边形而不是矩形。