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.
Without Matrix Multiplication
With "ray_dir = cam_mat.MultiplyVector(ray_dir);" used to modify the rays, this is the result.
With Matrix Multiplication
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;
- Am I pre-calculating the rays correctly?
- Am I doing the right thing multiplying the camera matrix in order to change the ray directions?
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.
Without Matrix Multiplication
With "ray_dir = cam_mat.MultiplyVector(ray_dir);" used to modify the rays, this is the result.
With Matrix Multiplication
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;
- Am I pre-calculating the rays correctly?
- Am I doing the right thing multiplying the camera matrix in order to change the ray directions?
1 Answer
Reset to default 2Typically 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?
new float3[pixels]();
this is C++ so you can/should use std::vector<float3>. Also you seem to be overusingauto
when declaring your local variables, make sure you do that with concrete types (now some of your variables are ints where floats would be better) – Pepijn Kramer Commented Mar 17 at 5:35std::vector
is just plain silly. Chances are you'll end up wanting to move portion of the execution to the GPU, at which point you're going to need code that can work on a mem mapped GPU buffer. usingstd::vector
in this case, just shoots yourself in the foot. Admittedly, the memory leak (by not freeing ray_dir_frac ever is an issue), however see my response below. Algorithmic optimisations, always trump pedantic C++ trivialities. There are no auto->integers in use here. They are all floats. Which ones do you mean exactly? – robthebloke Commented Mar 17 at 8:05