I'm working on ray-casting an octree and am unsure on the correct method of pre-calculating a bunch of rays, one for each pixel. This is what I have at the moment. Please ignore the ortho projection stuff, I'm currently just working on getting perspective working.
float3* Renderer::CalculateRays(int width, int height, float fov, float range_far, bool ortho) {
auto clip_plane_size = 2.0f;
const int pixels = width * height;
auto aspect_ratio = (float)width / (float)height;
auto ray_id = 0;
auto hx = width / 2;
auto hy = height / 2;
auto plane_width = clip_plane_size;
auto plane_height = clip_plane_size / aspect_ratio;
float3* ray_directions = new float3[pixels]();
float3* ray_dir_frac = new float3[pixels]();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (ortho == true) {
auto wx = x * (plane_width / width);
auto wy = y * (plane_height / height);
auto wz = 0.0f;
Vec3 ray_start = { wx, wy, wz };
Vec3 ray_dir = { 0.0f, 0.0f, 1.0f };
ray_directions[ray_id] = { ray_dir.x, ray_dir.y, ray_dir.z };
ray_dir_frac[ray_id] = { 1.0f / ray_dir.x, 1.0f / ray_dir.y, 1.0f / ray_dir.z };
}
else {
Vec3 ray_dir = CalculateRayDirection(x, y, width, height, fov);
ray_directions[ray_id] = { ray_dir.x, ray_dir.y, ray_dir.z };
ray_dir_frac[ray_id] = { 1.0f / ray_dir.x, 1.0f / ray_dir.y, 1.0f / ray_dir.z };
}
ray_id++;
}
}
return ray_directions;
}
Vec3 Renderer::CalculateRayDirection(float x, float y, float width, float height, float fov) {
const float ASPECT_RATIO = (float)width / (float)height;
const float NEAR_PLANE = 1.0f;
float px = (2 * ((x + 0.5f) / width) - 1) * std::tan(fov / 2 * M_PI / 180) * ASPECT_RATIO;
float py = (1 - 2 * ((y + 0.5f) / height)) * std::tan(fov / 2 * M_PI / 180);
return Vec3(px, py, NEAR_PLANE).Normalized();
}
This is how the octree is searched in the render loop.
//For loop for x & y pixels
int pixel_index = y * world->screen_width + x;
auto cam_pos = world->camera_position;
auto cam_mat = world->camera_matrix;
float3 ray_dir = world->ray_directions[pixel_index];
ray_dir = cam_mat.MultiplyVector(ray_dir);
float3 ray_frac = { 1.0f / ray_dir.x, 1.0f / ray_dir.y, 1.0f / ray_dir.z };
float3 from = { 0,0,0 };
from = cam_mat.MultiplyVector(from);
float3 normal;
if (IntersectsOctree(root, from, ray_frac, normal) == true) {
output[pixel_index].x = 255.0f;
output[pixel_index].y = 0;
output[pixel_index].z = 0;
}
else {
output[pixel_index].x = world->clear_color.x;
output[pixel_index].y = world->clear_color.y;
output[pixel_index].z = world->clear_color.z;
}
I haven't posted "IntersectsOctree()" as it's not really relevant to creating and manipulating the rays themselves.
Results appear to be correct until the rays are transformed with the camera matrix. Without any rotation the camera can move around along the world axis just fine, however with camera rotation things become skewed the further I get away from the objects. I initially thought barrel distortion but am I uncertain.
With "ray_dir = cam_mat.MultiplyVector(ray_dir);" commented out (so the ray un-modified by the camera matrix), this is the result.
With "ray_dir = cam_mat.MultiplyVector(ray_dir);" used to modify the rays, this is the result.
I could be doing my matrix wrong, but first I'd like to see if I've got this much right!
My main queries are this;
Typically when you do this stuff, you store 2 pre-calculated values on your camera:
htan = std::tan(fov / 2 * M_PI / 180) * ASPECT_RATIO
vtan = std::tan(fov / 2 * M_PI / 180)
(Since they only need to be recalculated when your fov or aspect ratio changes, which isn't often). Other than that, the ray calculation you have looks right.
Personally I'd recommend NOT pre-calculating the rays. Once you remove the calls to std::tan, the cost of calculating the rays is cheap (compared to the L1 cache hit you'll take from storing all of those rays!). For a 4K image, that's about 8 million rays!
Your ray-dir calculation is correct, however from
is a position (w=1), not a vector (w=0). Just assign the cam-matrix translation into from
, and that should fix the problem I'm guessing?