I am trying to make my camera representation work for images whose aspect ratio isn't 1 (640x480, 1920x1080...), but I am having some trouble getting it to work.
The camera model is pretty simple, as it's a pinhole camera with (F)orward
, (L)eft
and (U)p
vectors, and a point in space which serves as its (O)rigin
. F
points towards the center of the image plane, and U
and L
are perpendicular with each other and with F
, forming the camera's coordinate space.
The way it works is that I define everything except the L
vector, which is the normalized cross product of F
and U
multiplied by the aspect ratio, so that its length is "as wide" as the image is going to be compared to its height.
Then, to get a ray pointing at (i, j)
, I just create a ray starting at O
and pointing at the top-left corner (U + F + L
)
The code is this:
class Camera {
public:
Direction L, U, F;
Point O;
size_t width, height;
size_t rays_per_pixel;
private:
// For randomizing ray's directions inside the pixel's square
static std::mt19937 gen;
static std::uniform_real_distribution<double> pixel_distr;
public:
Camera(Point _O, Direction _U, Direction _F, size_t _width, size_t _height, size_t _rays_per_pixel) :
U(_U),
F(_F),
O(_O),
width(_width),
height(_height),
rays_per_pixel(_rays_per_pixel) {
// L perpendicular to F and U, then multiplied with the aspect ratio
double aspect_ratio = ((double) _width / (double) _height);
// * between vectors is cross product,
// .v just addresses their internal vector class
L = (F.v * U.v).normalize() * aspect_ratio;
}
// Return a ray pointing from O to a pixel in the image, with a small
// random variation across the pixel's area
[[nodiscard]] Ray get_ray(size_t _i, size_t _j) const {
// Puts the ray in the pixel's center, then adds to it a random value between [0, 0.5)
double i = (double) _i + 0.5 + pixel_distr(gen);
double j = (double) _j + 0.5 + pixel_distr(gen);
return {
O, // Origin
Direction((U.v + F.v + L.v // Top left corner
- ((2*L.v.modulus()*L.v)/((double) width) * j) // Right advance (as a substraction)
- ((2*U.v.modulus()*U.v)/((double) height) * i))) // Downwards advance (as a substraction)
}; //
}
If I indicate width
and height
with an aspect ratio of 1
, I get this, with 512x512 as an example (ignore the texture, I know)
:
If I indicate width
and height
with an aspect ratio of 2
or whatever else, like 640x480 I get this, which at least is somewhat hilarious:
The vectors used are:
Point O(0, 0, -3.5);
Direction U(0, 1, 0);
Direction F(0, 0, 3);
And L
in the first case is Direction L(-1, 0, 0)
whereas in the second case is L(-2, 0, 0)
.
It seems to repeat the image vertically with a pattern, and I made sure the logic error shouldn't be outside of this (I ask for every single pixel across the plane correctly, etc.)
Is something else needed to get a "widescreen" image? Should I change the camera model?