射线投射器变更地图尺寸后墙体渲染异常问题求助
射线投射器地图尺寸变更后的墙体渲染异常问题
我正在用C++编写一个射线投射器,使用GDI/GDI+进行渲染。我清楚GDI并非渲染图形的最优选择,原本应该使用OpenGL、SFML等库,但这个射线投射器不需要高级实时图形,GDI足以满足需求;另外我要在学校展示这个项目,安装OpenGL会非常麻烦。
实际遇到的问题是:当地图网格从8x8改为8x16这类尺寸时,部分墙体的渲染方式变得异常:

若有人能解释该问题的成因,我将非常感激,以便排查代码问题。
main.cpp
/* * Pseudo-code of the void renderer(): * Horizontal gridline check: * Set horizontal distance to a pretty high value, horizontal coordinates to camera coordinates * Calculate negative inverse of tangent * Set DOF variable to 0 * If ray angle is bigger than PI calculate ray Y-coordinate to be as close as possible to the gridline position and subtract 0.0001 for precision, calculate ray X-coordinate and offset coordinates for the ray moovement over the gridline * If ray angle is smaller than PI do the same as if ray angle < PI but add whatever the size of the map is to ray Y-coordinate * If ray angle is straight up or down set ray coordinates to camera coordinates and DOF to map size * Loop only if DOF is smaller than map size: * Calculate actual gridline coordinates * If the grid cell at [X, Y] is a wall break out from the loop, save the current ray coordinates, calculate the distance between the camera and the wall * Else update ray coordinates with the earlier calculated offsets * * Vertical gridline check: * Set vertical distance to a pretty high value, vertical coordinates to camera coordinates * Calculate inverse of tangent * Set DOF variable to 0 * If ray angle is bigger than PI / 2 and smaller than 3 * PI / 2 calculate ray X-coordinate to be as close as possible to the gridline position and subtract 0.0001 for precision, calculate ray Y-coordinate and offset coordinates for the ray moovement over the gridline * If ray angle is smaller than PI / 2 or bigger than 3 * PI / 2 do the same as if ray angle > PI / 2 && < 3 * PI / 2 but add whatever the size of the map is to ray X-coordinate * If ray angle is straight left or right set ray coordinates to camera coordinates and DOF to map size * Loop only if DOF is smaller than map size: * Calculate actual gridline coordinates * If the grid cell at [X, Y] is a wall break out from the loop, save the current ray coordinates, calculate the distance between the camera and the wall * Else update ray coordinates with the earlier calculated offsets * * If the vertical distance is smaller than the horizontal one update ray coordinates to the horizontal ones and set final distance to the horizontal one * Else update ray coordinates to the vertical ones and set final distance to the vertical one * Fix fisheye effect * Add one radian to the ray angle * Calculate line height by multiplying constant integer 400 by the map size and dividing that by the final distance * Calculate line offset (to make it more centered) by subtracting half of the line height from constant integer 400 * Draw 8-pixels wide column at [ray index * 8, camera Z-offset + line offset] and [ray index * 8, camera Z-offset + line offset + line height] (the color doesn't matter i think) */ #include "../../LIB/wsgl.hpp" #include "res/maths.hpp" #include <memory> using namespace std; const int window_x = 640, window_y = 640; float camera_x = 256, camera_y = 256, camera_z = 75; float camera_a = 0.001; int camera_fov = 80; int map_x; int map_y; int map_s; shared_ptr<int[]> map_w; void controls() { if(wsgl::is_key_down(wsgl::key::w)) { int mx = (camera_x + 30 * cos(camera_a)) / map_s; int my = (camera_y + 30 * sin(camera_a)) / map_s; int mp = my * map_x + mx; if(mp >= 0 && mp < map_s && !map_w[mp]) {camera_x += 15 * cos(camera_a); camera_y += 15 * sin(camera_a);} } if(wsgl::is_key_down(wsgl::key::s)) { int mx = (camera_x - 30 * cos(camera_a)) / map_s; int my = (camera_y - 30 * sin(camera_a)) / map_s; int mp = my * map_x + mx; if(mp >= 0 && mp < map_s && !map_w[mp]) {camera_x -= 5 * cos(camera_a); camera_y -= 5 * sin(camera_a);} } if(wsgl::is_key_down(wsgl::key::a_left)) {camera_a = reset_ang(camera_a - 5 * RAD);} if(wsgl::is_key_down(wsgl::key::a_right)) {camera_a = reset_ang(camera_a + 5 * RAD);} if(wsgl::is_key_down(wsgl::key::a_up)) {camera_z += 15;} if(wsgl::is_key_down(wsgl::key::a_down)) {camera_z -= 15;} } void renderer() { int map_x_pos, map_y_pos, map_cell, dof; float ray_x, ray_y, ray_a = reset_ang(camera_a - deg_to_rad(camera_fov / 2)); float x_offset, y_offset, tangent, distance_h, distance_v, h_x, h_y, v_x, v_y; float final_distance, line_height, line_offset; wsgl::clear_window(); for(int i = 0; i < camera_fov; i++) { distance_h = 1000000, h_x = camera_x, h_y = camera_y; tangent = -1 / tan(ray_a); dof = 0; if(ray_a > PI) {ray_y = (((int)camera_y / map_s) * map_s) - 0.0001; ray_x = (camera_y - ray_y) * tangent + camera_x; y_offset = -map_s; x_offset = -y_offset * tangent;} if(ray_a < PI) {ray_y = (((int)camera_y / map_s) * map_s) + map_s; ray_x = (camera_y - ray_y) * tangent + camera_x; y_offset = map_s; x_offset = -y_offset * tangent;} if(ray_a == 0 || ray_a == PI) {ray_x = camera_x; ray_y = camera_y; dof = map_s;} for(dof; dof < map_s; dof++) { map_x_pos = (int)(ray_x) / map_s; map_y_pos = (int)(ray_y) / map_s; map_cell = map_y_pos * map_x + map_x_pos; if(map_cell >= 0 && map_cell < map_s && map_w[map_cell]) {dof = map_s; h_x = ray_x; h_y = ray_y; distance_h = distance(camera_x, camera_y, h_x, h_y);} else {ray_x += x_offset; ray_y += y_offset;} } distance_v = 1000000, v_x = camera_x, v_y = camera_y; tangent = -tan(ray_a); dof = 0; if(ray_a > PI2 && ray_a < PI3) {ray_x = (((int)camera_x / map_s) * map_s) - 0.0001; ray_y = (camera_x - ray_x) * tangent + camera_y; x_offset = -map_s; y_offset = -x_offset * tangent;} if(ray_a < PI2 || ray_a > PI3) {ray_x = (((int)camera_x / map_s) * map_s) + map_s; ray_y = (camera_x - ray_x) * tangent + camera_y; x_offset = map_s; y_offset = -x_offset * tangent;} if(ray_a == PI2 || ray_a == PI3) {ray_x = camera_x; ray_y = camera_y; dof = map_s;} for(dof; dof < map_s; dof++) { map_x_pos = (int)(ray_x) / map_s; map_y_pos = (int)(ray_y) / map_s; map_cell = map_y_pos * map_x + map_x_pos; if(map_cell >= 0 && map_cell < map_s && map_w[map_cell]) {dof = map_s; v_x = ray_x; v_y = ray_y; distance_v = distance(camera_x, camera_y, v_x, v_y);} else {ray_x += x_offset; ray_y += y_offset;} } if(distance_v < distance_h) {ray_x = v_x; ray_y = v_y; final_distance = distance_v;} else {ray_x = h_x; ray_y = h_y; final_distance = distance_h;} final_distance *= cos(reset_ang(camera_a - ray_a)); ray_a = reset_ang(ray_a + RAD); line_height = (map_s * 400) / final_distance; line_offset = 200 - line_height / 2; wsgl::draw_line({i * 8, camera_z + line_offset}, {i * 8, camera_z + line_offset + line_height}, {0, 255 / (final_distance / 250 + 1), 0}, 8); if(i == camera_fov / 2) {wsgl::draw_text({0, 0}, {255, 255, 255}, L"Final distance: " + to_wstring(final_distance) + L" Line height: " + to_wstring(line_height) + L" X: " + to_wstring(camera_x) + L" Y: " + to_wstring(camera_y));} } wsgl::render_frame(); } void load_map(wsgl::wide_str wstr, int cell_size = 1) { shared_ptr<wsgl::bmp> map = shared_ptr<wsgl::bmp>(wsgl::bmp::FromFile(wstr.c_str(), true)); map_x = map->GetWidth(); map_y = map->GetHeight(); map_s = map_x * map_y; map_w = shared_ptr<int[]>(new int[map_s]); wsgl::color color; for(int y = 0; y < map_y; y += cell_size) { for(int x = 0; x < map_x; x += cell_size) { map->GetPixel(x, y, &color); if(color.GetR() == 255 && color.GetG() == 255 && color.GetB() == 255) {*(map_w.get() + ((y / cell_size) * map_x + (x / cell_size))) = 0;} else {*(map_w.get() + ((y / cell_size) * map_x + (x / cell_size))) = 1;} } } } int main() { wsgl::session sess = wsgl::startup(L"raycaster", {window_x, window_y}); load_map(L"res/map.png"); while(true) {controls(); renderer();} }
maths.hpp
#include <cmath> const float PI = 3.14159265359; const float PI2 = PI / 2; const float PI3 = 3 * PI2; const float RAD = PI / 180; float deg_to_rad(float deg) {return deg * RAD;} float distance(float ax, float ay, float bx, float by) { float dx = bx - ax; float dy = by - ay; return sqrt(dx * dx + dy * dy); } float reset_ang(float ang) { if(ang < 0) {ang += 2 * PI;} if(ang > 2 * PI) {ang -= 2 * PI;} return ang; }
注:wsgl.hpp是我基于GDI routines封装的自定义库。
内容的提问来源于stack exchange,提问作者Kazafka
相关产品推荐
相关产品推荐

