【发布时间】:2021-08-10 23:57:48
【问题描述】:
我在我的 Raycaster 引擎上工作了一段时间,我在较慢的机器上运行。 我遇到的最具挑战性的问题是/是高效的地板和天花板铸造。
我的问题是: 我还可以使用什么其他更快的方法? (我不确定 Doom 地板和天花板是如何渲染的)
到目前为止,我尝试了两种典型的解决方案:
- 垂直和水平 - 铸造如众所周知的 lodev 教程中所述:https://lodev.org/cgtutor/raycasting2.html
水平方法当然要快得多,但我还用定点变量对其进行了优化。
不幸的是,即使是这种方法也是一种性能杀手 - 即使在更快的 CPU 上也有相当大的 fps 下降,而更慢的 CPU 则是一个瓶颈。
我的其他想法:
- 我想出了一种算法,它可以将可见的地板/天花板地图图块转换为四边形,然后将其分成两个三角形 - 并像在常规扫描线光栅化器中一样对它们进行光栅化。它要快得多——我也可以按纹理 ID 对图块进行排序,以使缓存更加友好。不幸的是,在这种情况下,我进入了“透视校正纹理映射”——为了解决这个问题,我必须添加一些分区,这会降低性能。但也有一些可以做的优化..
-
每 2 条射线(在列、行或两者中)使用水平投射 - 我将用平均纹理坐标填充空白空间
-
我也可以尝试将我的算法从 1 点与水平投射相结合 - 我可以按 ID 对纹理进行排序,例如,我认为不会有纹理扭曲
-
模式 7 ?
到目前为止我的进步: https://www.youtube.com/watch?v=u3zA2Wh0NB4
编辑(1):
Floor and Ceiling rednering 代码(基于 od lodev 教程的水平方法) 但用定点优化。 Ceil 计算反映到地板。
https://lodev.org/cgtutor/raycasting2.html
这种方法比垂直方法快,但大量计算是内循环,随机访问纹理像素会影响性能..
inline void RC_Raycast_Floor_Ceiling()
{
// get local copy of RC_level_textures
sBM_Bitmap* level_textures = RC_level_textures;
// ray direction for leftmost ray (x = 0) and rightmost ray (x = width)
float32 r_dx0 = RC_pp_dx - RC_pp_nsize_x_d2;
float32 r_dy0 = RC_pp_dy - RC_pp_nsize_y_d2;
//float32 r_dx1 = RC_pp_dx + RC_pp_nsize_x_d2;
//float32 r_dy1 = RC_pp_dy + RC_pp_nsize_y_d2;
// precalculated helpers for performance
float32 r_dx1_m_dx0_div_width = (RC_pp_nsize_x_d2 + RC_pp_nsize_x_d2) * RC_render_width__1div__f;
float32 r_dy1_m_dy0_div_width = (RC_pp_nsize_y_d2 + RC_pp_nsize_y_d2) * RC_render_width__1div__f;
int16 ray_y = -1;
u_int16 ray_y_counter = RC_render_height__i;
u_int8* walls_buffer__ptr = RC_walls_buffer;
// casting floor and ceiling - horizontal line by line - from left to right
while(ray_y_counter)
{
ray_y++;
ray_y_counter--;
// dont go further if the current floor/ceil scanline line won't be visible
if (ray_y >= RC_walls_start)
{
break;
if (ray_y < RC_walls_end)
{
ray_y = RC_walls_end;
ray_y_counter = RC_walls_start - 1;
walls_buffer__ptr += ((RC_walls_end - RC_walls_start) * RC_render_width__i);
continue;
}
}
// whether this section is floor or ceiling
u_int8 is_floor = ray_y > RC_render_height__div2__i;
// current ray y position compared to the center of the screen (the horizon)
float32 ry_pos = (float32)(is_floor ? (ray_y - RC_render_height__div2__i) : (RC_render_height__div2__i - ray_y));
// vertical position of projection plane, 0.5 is between floor and ceiling
float32 pp_z = (float32)(is_floor ? (RC_render_height__div2__i) : (RC_render_height__div2__i));
float32 straight_distance_to_point = pp_z / ry_pos;
// calculate the real world step vector we have to add for each x (parallel to camera plane)
// adding step by step avoids multiplications with a weight in the inner loop
float32 floor_step_x = straight_distance_to_point * r_dx1_m_dx0_div_width;
float32 floor_step_y = straight_distance_to_point * r_dy1_m_dy0_div_width;
float32 floor_x = RC_player_x + straight_distance_to_point * r_dx0;
float32 floor_y = RC_player_y + straight_distance_to_point * r_dy0;
// convert that values to fixed point
int32 floor_x__fp = (int32)(floor_x * 65536.0f);
int32 floor_y__fp = (int32)(floor_y * 65536.0f);
int32 floor_step_x__fp = (int32)(floor_step_x * 65536.0f);
int32 floor_step_y__fp = (int32)(floor_step_y * 65536.0f);
u_int32 ry_m_render_width = ray_y * RC_render_width__i;
u_int32 ry_m_render_width_i_mirror = (RC_render_height__i- ray_y-1) * RC_render_width__i;
int16 ray_x = -1;
u_int16 ray_x_counter = RC_render_width__i;
sLV_Cell* level_map = RC_level->map;
// drawing floor and ceiling from left to right
while(ray_x_counter)
{
ray_x++;
ray_x_counter--;
floor_x__fp += floor_step_x__fp;
floor_y__fp += floor_step_y__fp;
if (*walls_buffer__ptr != 0)
{
walls_buffer__ptr++;
continue;
}
walls_buffer__ptr++;
u_int32 output_pixel_index = ray_x + ry_m_render_width;
u_int32 output_pixel_index_mirror = ray_x + ry_m_render_width_i_mirror;
// the cell coord is simply got from the integer parts of floorX and floorY
u_int32 curr_cell_x = (floor_x__fp & FP_INTEGER_MASK_16) >> 16;
u_int32 curr_cell_y = (floor_y__fp & FP_INTEGER_MASK_16) >> 16;
// prevent overflow
// curr_cell_x &= LV_MAP_SIZE_m1;
// curr_cell_y &= LV_MAP_SIZE_m1;
u_int32 texture_pixel_x = (floor_x__fp & FP_FRACTION_MASK_16) >> 9;
u_int32 texture_pixel_y = (floor_y__fp & FP_FRACTION_MASK_16) >> 9;
u_int32 cell_index = curr_cell_x + (curr_cell_y << LV_MAP_SIZE_BITSHIFT);
// get the texture index depending on the cell
u_int32 texture_index;
/* if (is_floor)
{
texture_index = level_map[cell_index].floor_id;
}
else
{
texture_index = level_map[cell_index].ceil_id;
}*/
texture_index = level_map[cell_index].ceil_id;
// get pixel coords in texture
u_int32 tex_index = texture_pixel_x + (texture_pixel_y << 7);
u_int32 texture_current_pixel = level_textures[0].pixels[tex_index];
RC_output_buffer_32[output_pixel_index] = texture_current_pixel;
texture_index = level_map[cell_index].floor_id;
texture_current_pixel = level_textures[texture_index].pixels[tex_index];
RC_output_buffer_32[output_pixel_index_mirror] = texture_current_pixel;
}
}
}
【问题讨论】:
-
顺便说一句,因为我在这里的 cmets 中被纠正了 What techniques were used to reduce the required re-rendering in 3D programs? Doom 没有使用光线投射......还可以在这里查看答案 Perspective Vision on Canvas 以获得一些额外的想法
-
嗨@Spektre,“问题”是我正在使用复古硬件,我的意思是 Amiga,在 32 位模式下使用 320x240 或 640x480 的现代 68k CPU。我得到了很好的性能,但是地板/天花板算法太重了,这会造成瓶颈——可能是因为内部循环中有很多计算,没有排序的纹理访问等等。所以我只是在寻找不同的方法。我也可以使用 ammx 限制函数,但我不知道汇编程序,也许会有一些可用的宏允许我在 C 中使用它。
-
我用 C 编码,没有额外的库。我的代码是可移植的,我同时在我的 PC 和 Amiga 上检查它。在 PC 上,我在 640x480x32 中获得了约 100fps 的平滑效果,带有纹理和阴影。 Amiga 当然更慢 - 我的目标是在 320x200x32 下获得 ~50fps ......但正如我所说,lodev 的地板/天花板水平算法效率不高。我使用定点和将地板计算镜像到天花板对其进行了一些优化。主要问题是内部循环中的纹理访问。
-
@Spektre 感谢您的评论。我编辑了 1 篇文章 - 添加渲染代码。我没有添加它,因为正如我所说,该方法基于 lodev 教程 - 水平方法。 lodev.org/cgtutor/raycasting2.html 这种方法在内部循环中进行了一些计算,这会影响性能,而且对纹理的访问是随机的(未排序),这也会影响性能 - 我正在寻找更优化的方法..
-
@Spektre 嗯,你说的这似乎很有趣——但我不明白。您能否更准确地向我解释一下该算法。提前谢谢你:)
标签: graphics 3d rendering raycasting