Reputation: 109
Working thourh Peter Shirley's Raytracing In One Weekend and I've come across a problem when trying to render a sphere in red. My math seems to be accurate, however it is outputting what looks to be like 2 spheres side by side and also skewed/stretched, instead of the 1 sphere that is in the scene. I have a suspision it has something to do with the width of the view the rays are being cast through but not sure how to confirm. Here is the code:
main.cpp
#include "vectors.h"
#include "Geometry3D.h"
#include <fstream>
vec3 Color(const Ray& r, const Sphere& s) {
if (Raycast(s, r) != -1)
return vec3(1, 0, 0);
else
return vec3(0, 0, 0);
}
int main() {
const int WIDTH = 200;
const int HEIGHT = 100;
std::ofstream out;
out.open("image.ppm");
if (out.is_open()) {
out << "P3\n" << WIDTH << ' ' << HEIGHT << "\n255\n";
vec3 lowerLeftCorner(-2.0f, -1.0f, -1.0f);
vec3 horizontal(4.0f, 0.0f, 0.0f);
vec3 vertical(0.0, 2.0f, 0.0);
vec3 origin(0.0, 0.0, 0.0);
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
float u = float(i) / float(WIDTH);
float v = float(j) / float(HEIGHT);
Sphere s(vec3(0, 0, -5.0f), 1.0f);
Ray r(origin, lowerLeftCorner + (horizontal * u) + (vertical * v));
vec3 color = Color(r, s);
int ir = int(255.99f * color.x);
int ig = int(255.99f * color.y);
int ib = int(255.99f * color.z);
out << ir << ' ' << ig << ' ' << ib << '\n';
}
}
}
}
Geometry3D.h
#ifndef _GEOMETRY_3D_H
#define _GEOMETRY_3D_H
#include "vectors.h"
#include <cmath>
#include <cfloat>
typedef vec3 Point;
typedef struct Ray {
Point origin;
vec3 direction;
Ray() : direction(0.0f, 0.0f, 1.0f) { }
Ray(const Point& o, const vec3& d) : origin(o), direction(d) {
NormalizeDirection();
}
inline void NormalizeDirection() {
Normalize(direction);
}
} Ray;
typedef struct Sphere {
Point position;
float radius;
Sphere() : radius(1.0f) { }
Sphere(const Point& p, float r) : position(p), radius(r) { }
} Sphere;
float Raycast(const Sphere& sphere, const Ray& ray);
#endif
Geometry3D.cpp
#include "Geometry3D.h"
#include <iostream>
float Raycast(const Sphere& sphere, const Ray& ray) {
vec3 e = sphere.position - ray.origin;
float rSq = sphere.radius * sphere.radius;
float eSq = MagnitudeSq(e);
float a = Dot(e, ray.direction);
float bSq = eSq - (a * a);
float f = sqrt(rSq - bSq);
if (rSq - (eSq - (a * a)) < 0.0f)
return -1;
else if (eSq < rSq) {
return a + f;
}
return a - f;
}
Here is the output:
Any help is appreciated.
Upvotes: 0
Views: 190
Reputation: 511
The error lies in writing the .ppm image.
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
float u = float(i) / float(WIDTH);
float v = float(j) / float(HEIGHT);
...
out << ir << ' ' << ig << ' ' << ib << '\n';
}
}
You are using a Plain PPM image (P3). According to the PPM Specification the raster (color data) should look like this:
A raster of Height rows, in order from top to bottom. Each row consists of Width pixels, in order from left to right. Each pixel is a triplet of red, green, and blue samples, in that order.
However you're building a raster of WIDTH rows with HEIGHT pixels with your loop. The outer loop creates a row and the inner loop the pixels in the row. Changing the indices around will fix the issue.
//switch the loop indices
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
float u = float(i) / float(HEIGHT);
float v = float(j) / float(WIDTH);
...
//added spaces between each color value
//this way each pixel is separated with 2 spaces
out << ' '<< ir << ' ' << ig << ' ' << ib << ' ';
}
//and each row ends on a newline
//this makes it easier to read them with a text editor
out << '\n';
}
What you are experiencing is the following: Width is twice the size of height,
const int WIDTH = 200;
const int HEIGHT = 100;
and you write 200 rows of 100 pixels to the ppm image. However the image viewer reads 100 rows of 200 pixels, which causes the "mirror" effect. Consider the following image:
B is a black pixel, R is a red pixel
I tried to illustrate it with a paint image, but in reality the image data is nothing more than a matrix ;)
Width: 8
Height: 4
B B B B B B B B
B B B B R R R R
R R R R B B B B
B B B B B B B B
This image has a black line of pixels at the top, followed by a line of half black, half red pixels. The next line has the same pattern just the opposite way round: half red, half black. The last line is plain black.
Your code basically transposes the image. It switches width with height and writes this:
B B R B
B B R B
B B R B
B B R B
B R B B
B R B B
B R B B
B R B B
So the image reader reads this:
B B R B B B R B
B B R B B B R B
B R B B B R B B
B R B B B R B B
If you go through each row you can see that the image is mirrored after the 4th row. This is what you are seeing in your image, but since you use a circle it looks like the image is written twice because of the symmetry. You don't see that the image is turned upside down.
Upvotes: 2