I have a ray tracer (from www.scratchapixel.com) that I use to write a image to memory that I then display at once using Opengl (glut). I use the width and height and divide the screen to get a Opengl point for every pixels. It kinda works.
My problem is that my width has to be between 500 and 799. It cannot be <= 499 of >= 800, witch doesn't make sense to me. The image becomes skew. I have tried it on 2 computers with the same result.
799x480
800x480
Here's the full code:
#define _USE_MATH_DEFINES
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <fstream>
#include <vector>
#include <iostream>
#include <cassert>
// OpenGl
#include "GL/glut.h"
GLuint width = 799, height = 480;
GLdouble width_step = 2.0f / width, height_step = 2.0f / height;
const int MAX_RAY_DEPTH = 3;
const double INFINITY = HUGE_VAL;
template<typename T>
class Vec3
{
public:
T x, y, z;
// Vector constructors.
Vec3() : x(T(0)), y(T(0)), z(T(0)) {}
Vec3(T xx) : x(xx), y(xx), z(xx) {}
Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
// Vector normalisation.
Vec3& normalize()
{
T nor = x * x + y * y + z * z;
if (nor > 1) {
T invNor = 1 / sqrt(nor);
x *= invNor, y *= invNor, z *= invNor;
}
return *this;
}
// Vector operators.
Vec3<T> operator * (const T &f) const { return Vec3<T>(x * f, y * f, z * f); }
Vec3<T> operator * (const Vec3<T> &v) const { return Vec3<T>(x * v.x, y * v.y, z * v.z); }
T dot(const Vec3<T> &v) const { return x * v.x + y * v.y + z * v.z; }
Vec3<T> operator - (const Vec3<T> &v) const { return Vec3<T>(x - v.x, y - v.y, z - v.z); }
Vec3<T> operator + (const Vec3<T> &v) const { return Vec3<T>(x + v.x, y + v.y, z + v.z); }
Vec3<T>& operator += (const Vec3<T> &v) { x += v.x, y += v.y, z += v.z; return *this; }
Vec3<T>& operator *= (const Vec3<T> &v) { x *= v.x, y *= v.y, z *= v.z; return *this; }
Vec3<T> operator - () const { return Vec3<T>(-x, -y, -z); }
};
template<typename T>
class Sphere
{
public:
// Sphere variables.
Vec3<T> center; /// position of the sphere
T radius, radius2; /// sphere radius and radius^2
Vec3<T> surfaceColor, emissionColor; /// surface color and emission (light)
T transparency, reflection; /// surface transparency and reflectivity
// Sphere constructor.
// position(c), radius(r), surface color(sc), reflectivity(refl), transparency(transp), emission color(ec)
Sphere(const Vec3<T> &c, const T &r, const Vec3<T> &sc,
const T &refl = 0, const T &transp = 0, const Vec3<T> &ec = 0) :
center(c), radius(r), surfaceColor(sc), reflection(refl),
transparency(transp), emissionColor(ec), radius2(r * r)
{}
// compute a ray-sphere intersection using the geometric solution
bool intersect(const Vec3<T> &rayorig, const Vec3<T> &raydir, T *t0 = NULL, T *t1 = NULL) const
{
// we start with a vector (l) from the ray origin (rayorig) to the center of the curent sphere.
Vec3<T> l = center - rayorig;
// tca is a vector length in the direction of the normalise raydir.
// its length is streched using dot until it forms a perfect right angle triangle with the l vector.
T tca = l.dot(raydir);
// if tca is < 0, the raydir is going in the opposite direction. No need to go further. Return false.
if (tca < 0) return false;
// if we keep on into the code, it's because the raydir may still hit the sphere.
// l.dot(l) gives us the l vector length to the power of 2. Then we use Pythagoras' theorem.
// remove the length tca to the power of two (tca * tca) and we get a distance from the center of the sphere to the power of 2 (d2).
T d2 = l.dot(l) - (tca * tca);
// if this distance to the center (d2) is greater than the radius to the power of 2 (radius2), the raydir direction is missing the sphere.
// No need to go further. Return false.
if (d2 > radius2) return false;
// Pythagoras' theorem again: radius2 is the hypotenuse and d2 is one of the side. Substraction gives the third side to the power of 2.
// Using sqrt, we obtain the length thc. thc is how deep tca goes into the sphere.
T thc = sqrt(radius2 - d2);
if (t0 != NULL && t1 != NULL) {
// remove thc to tca and you get the length from the ray origin to the surface hit point of the sphere.
*t0 = tca - thc;
// add thc to tca and you get the length from the ray origin to the surface hit point of the back side of the sphere.
*t1 = tca + thc;
}
// There is a intersection with a sphere, t0 and t1 have surface distances values. Return true.
return true;
}
};
std::vector<Sphere<double> *> spheres;
// function to mix 2 T varables.
template<typename T>
T mix(const T &a, const T &b, const T &mix)
{
return b * mix + a * (T(1) - mix);
}
// This is the main trace function. It takes a ray as argument (defined by its origin
// and direction). We test if this ray intersects any of the geometry in the scene.
// If the ray intersects an object, we compute the intersection point, the normal
// at the intersection point, and shade this point using this information.
// Shading depends on the surface property (is it transparent, reflective, diffuse).
// The function returns a color for the ray. If the ray intersects an object, it
// returns the color of the object at the intersection point, otherwise it returns
// the background color.
template<typename T>
Vec3<T> trace(const Vec3<T> &rayorig, const Vec3<T> &raydir,
const std::vector<Sphere<T> *> &spheres, const int &depth)
{
T tnear = INFINITY;
const Sphere<T> *sphere = NULL;
// Try to find intersection of this raydir with the spheres in the scene
for (unsigned i = 0; i < spheres.size(); ++i) {
T t0 = INFINITY, t1 = INFINITY;
if (spheres[i]->intersect(rayorig, raydir, &t0, &t1)) {
// is the rayorig inside the sphere (t0 < 0)? If so, use the second hit (t0 = t1)
if (t0 < 0) t0 = t1;
// tnear is the last sphere intersection (or infinity). Is t0 in front of tnear?
if (t0 < tnear) {
// if so, update tnear to this closer t0 and update the closest sphere
tnear = t0;
sphere = spheres[i];
}
}
}
// At this moment in the program, we have the closest sphere (sphere) and the closest hit position (tnear)
// For this pixel, if there's no intersection with a sphere, return a Vec3 with the background color.
if (!sphere) return Vec3<T>(.5); // Grey background color.
// if we keep on with the code, it is because we had an intersection with at least one sphere.
Vec3<T> surfaceColor = 0; // initialisation of the color of the ray/surface of the object intersected by the ray.
Vec3<T> phit = rayorig + (raydir * tnear); // point of intersection.
Vec3<T> nhit = phit - sphere->center; // normal at the intersection point.
// if the normal and the view direction are not opposite to each other,
// reverse the normal direction.
if (raydir.dot(nhit) > 0) nhit = -nhit;
nhit.normalize(); // normalize normal direction
// The angle between raydir and the normal at point hit (not used).
//T s_angle = acos(raydir.dot(nhit)) / ( sqrt(raydir.dot(raydir)) * sqrt(nhit.dot(nhit)));
//T s_incidence = sin(s_angle);
T bias = 1e-5; // add some bias to the point from which we will be tracing
// Do we have transparency or reflection?
if ((sphere->transparency > 0 || sphere->reflection > 0) && depth < MAX_RAY_DEPTH) {
T IdotN = raydir.dot(nhit); // raydir.normal
// I and N are not pointing in the same direction, so take the invert.
T facingratio = std::max(T(0), -IdotN);
// change the mix value between reflection and refraction to tweak the effect (fresnel effect)
T fresneleffect = mix<T>(pow(1 - facingratio, 3), 1, 0.1);
// compute reflection direction (not need to normalize because all vectors
// are already normalized)
Vec3<T> refldir = raydir - nhit * 2 * raydir.dot(nhit);
Vec3<T> reflection = trace(phit + (nhit * bias), refldir, spheres, depth + 1);
Vec3<T> refraction = 0;
// if the sphere is also transparent compute refraction ray (transmission)
if (sphere->transparency) {
T ior = 1.2, eta = 1 / ior;
T k = 1 - eta * eta * (1 - IdotN * IdotN);
Vec3<T> refrdir = raydir * eta - nhit * (eta * IdotN + sqrt(k));
refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1);
}
// the result is a mix of reflection and refraction (if the sphere is transparent)
surfaceColor = (reflection * fresneleffect + refraction * (1 - fresneleffect) * sphere->transparency) * sphere->surfaceColor;
}
else {
// it's a diffuse object, no need to raytrace any further
// Look at all sphere to find lights
double shadow = 1.0;
for (unsigned i = 0; i < spheres.size(); ++i) {
if (spheres[i]->emissionColor.x > 0) {
// this is a light
Vec3<T> transmission = 1.0;
Vec3<T> lightDirection = spheres[i]->center - phit;
lightDirection.normalize();
T light_angle = (acos(raydir.dot(lightDirection)) / ( sqrt(raydir.dot(raydir)) * sqrt(lightDirection.dot(lightDirection))));
T light_incidence = sin(light_angle);
for (unsigned j = 0; j < spheres.size(); ++j) {
if (i != j) {
T t0, t1;
// Does the ray from point hit to the light intersect an object?
// If so, calculate the shadow.
if (spheres[j]->intersect(phit + (nhit * bias), lightDirection, &t0, &t1)) {
shadow = std::max(0.0, shadow - (1.0 - spheres[j]->transparency));
transmission = transmission * spheres[j]->surfaceColor * shadow;
//break;
}
}
}
// For each light found, we add light transmission to the pixel.
surfaceColor += sphere->surfaceColor * transmission *
std::max(T(0), nhit.dot(lightDirection)) * spheres[i]->emissionColor;
}
}
}
return surfaceColor + sphere->emissionColor;
}
// Main rendering function. We compute a camera ray for each pixel of the image,
// trace it and return a color. If the ray hits a sphere, we return the color of the
// sphere at the intersection point, else we return the background color.
Vec3<double> *image = new Vec3<double>[width * height];
static Vec3<double> cam_pos = Vec3<double>(0);
template<typename T>
void render(const std::vector<Sphere<T> *> &spheres)
{
Vec3<T> *pixel = image;
T invWidth = 1 / T(width), invHeight = 1 / T(height);
T fov = 30, aspectratio = T(width) / T(height);
T angle = tan(M_PI * 0.5 * fov / T(180));
// Trace rays
for (GLuint y = 0; y < height; ++y) {
for (GLuint x = 0; x < width; ++x, ++pixel) {
T xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
T yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
Vec3<T> raydir(xx, yy, -1);
raydir.normalize();
*pixel = trace(cam_pos, raydir, spheres, 0);
}
}
}
//********************************** OPEN GL ***********************************************
void init(void)
{
/* Select clearing (background) color */
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
/* Initialize viewing values */
//glMatrixMode(GL_PROJECTION);
gluOrtho2D(0,width,0,height);
}
void advanceDisplay(void)
{
cam_pos.z = cam_pos.z - 2;
glutPostRedisplay();
}
void backDisplay(void)
{
cam_pos.z = cam_pos.z + 2;
glutPostRedisplay();
}
void resetDisplay(void)
{
Vec3<double> new_cam_pos;
new_cam_pos = cam_pos;
cam_pos = new_cam_pos;
glutPostRedisplay();
}
void reshape(int w, int h)
{
glLoadIdentity();
gluOrtho2D(0,width,0,height);
glLoadIdentity();
}
void mouse(int button, int state, int x, int y)
{
switch (button)
{
case GLUT_LEFT_BUTTON:
if(state == GLUT_DOWN)
{
glutIdleFunc(advanceDisplay);
}
break;
case GLUT_MIDDLE_BUTTON:
if(state == GLUT_DOWN)
{
glutIdleFunc(resetDisplay);
}
break;
case GLUT_RIGHT_BUTTON:
if(state == GLUT_DOWN)
{
glutIdleFunc(backDisplay);
}
break;
}
}
void display(void)
{
int i;
float x, y;
/* clear all pixels */
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
render<double>(spheres); // Creates the image and put it to memory in image[].
i=0;
glBegin(GL_POINTS);
for(y=1.0f;y>-1.0;y=y-height_step)
{
for(x=1.0f;x>-1.0;x=x-width_step)
{
glColor3f((std::min(double(1), image[i].x)),
(std::min(double(1), image[i].y)),
(std::min(double(1), image[i].z)));
glVertex2f(x, y);
if(i < width*height)
{
i = i + 1;
}
}
}
glEnd();
glPopMatrix();
glutSwapBuffers();
}
int main(int argc, char **argv)
{
// position, radius, surface color, reflectivity, transparency, emission color
spheres.push_back(new Sphere<double>(Vec3<double>(0, -10004, -20), 10000, Vec3<double>(0.2), 0.0, 0.0));
spheres.push_back(new Sphere<double>(Vec3<double>(3, 0, -15), 2, Vec3<double>(1.00, 0.1, 0.1), 0.65, 0.95));
spheres.push_back(new Sphere<double>(Vec3<double>(1, -1, -18), 1, Vec3<double>(1.0, 1.0, 1.0), 0.9, 0.9));
spheres.push_back(new Sphere<double>(Vec3<double>(-2, 2, -15), 2, Vec3<double>(0.1, 0.1, 1.0), 0.05, 0.5));
spheres.push_back(new Sphere<double>(Vec3<double>(-4, 3, -18), 1, Vec3<double>(0.1, 1.0, 0.1), 0.3, 0.7));
spheres.push_back(new Sphere<double>(Vec3<double>(-4, 0, -25), 1, Vec3<double>(1.00, 0.1, 0.1), 0.65, 0.95));
spheres.push_back(new Sphere<double>(Vec3<double>(-1, 1, -25), 2, Vec3<double>(1.0, 1.0, 1.0), 0.0, 0.0));
spheres.push_back(new Sphere<double>(Vec3<double>(2, 2, -25), 1, Vec3<double>(0.1, 0.1, 1.0), 0.05, 0.5));
spheres.push_back(new Sphere<double>(Vec3<double>(5, 3, -25), 2, Vec3<double>(0.1, 1.0, 0.1), 0.3, 0.7));
// light
spheres.push_back(new Sphere<double>(Vec3<double>(-10, 20, 0), 3, Vec3<double>(0), 0, 0, Vec3<double>(3)));
spheres.push_back(new Sphere<double>(Vec3<double>(0, 10, 0), 3, Vec3<double>(0), 0, 0, Vec3<double>(1)));
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(width, height);
glutInitWindowPosition(10,10);
glutCreateWindow(argv[0]);
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMainLoop();
delete [] image;
while (!spheres.empty()) {
Sphere<double> *sph = spheres.back();
spheres.pop_back();
delete sph;
}
return 0;
}
This is where the image is written to memory:
Vec3<double> *image = new Vec3<double>[width * height];
static Vec3<double> cam_pos = Vec3<double>(0);
template<typename T>
void render(const std::vector<Sphere<T> *> &spheres)
{
Vec3<T> *pixel = image;
T invWidth = 1 / T(width), invHeight = 1 / T(height);
T fov = 30, aspectratio = T(width) / T(height);
T angle = tan(M_PI * 0.5 * fov / T(180));
// Trace rays
for (GLuint y = 0; y < height; ++y) {
for (GLuint x = 0; x < width; ++x, ++pixel) {
T xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
T yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
Vec3<T> raydir(xx, yy, -1);
raydir.normalize();
*pixel = trace(cam_pos, raydir, spheres, 0);
}
}
}
This is where I read it back and write it to each point of Opengl:
void display(void)
{
int i;
float x, y;
/* clear all pixels */
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
render<double>(spheres); // Creates the image and put it to memory in image[].
i=0;
glBegin(GL_POINTS);
for(y=1.0f;y>-1.0;y=y-height_step)
{
for(x=1.0f;x>-1.0;x=x-width_step)
{
glColor3f((std::min(double(1), image[i].x)),
(std::min(double(1), image[i].y)),
(std::min(double(1), image[i].z)));
glVertex2f(x, y);
if(i < width*height)
{
i = i + 1;
}
}
}
glEnd();
glPopMatrix();
glutSwapBuffers();
}
I have no idea what is causing this. Is it a bad design? An Opengl display mode? I don't know.
Is it a bad design?
Yes! Upload your rendered scene to a texture and then render a quad with it:
// g++ -O3 main.cpp -lglut -lGL -lGLU
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <fstream>
#include <vector>
#include <iostream>
#include <cassert>
// OpenGl
#include "GL/glut.h"
GLuint width = 800, height = 480;
GLdouble width_step = 2.0f / width;
GLdouble height_step = 2.0f / height;
const int MAX_RAY_DEPTH = 3;
template<typename T>
class Vec3
{
public:
T x, y, z;
// Vector constructors.
Vec3() : x(T(0)), y(T(0)), z(T(0)) {}
Vec3(T xx) : x(xx), y(xx), z(xx) {}
Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
// Vector normalisation.
Vec3& normalize()
{
T nor = x * x + y * y + z * z;
if (nor > 1) {
T invNor = 1 / sqrt(nor);
x *= invNor, y *= invNor, z *= invNor;
}
return *this;
}
// Vector operators.
Vec3<T> operator * (const T &f) const { return Vec3<T>(x * f, y * f, z * f); }
Vec3<T> operator * (const Vec3<T> &v) const { return Vec3<T>(x * v.x, y * v.y, z * v.z); }
T dot(const Vec3<T> &v) const { return x * v.x + y * v.y + z * v.z; }
Vec3<T> operator - (const Vec3<T> &v) const { return Vec3<T>(x - v.x, y - v.y, z - v.z); }
Vec3<T> operator + (const Vec3<T> &v) const { return Vec3<T>(x + v.x, y + v.y, z + v.z); }
Vec3<T>& operator += (const Vec3<T> &v) { x += v.x, y += v.y, z += v.z; return *this; }
Vec3<T>& operator *= (const Vec3<T> &v) { x *= v.x, y *= v.y, z *= v.z; return *this; }
Vec3<T> operator - () const { return Vec3<T>(-x, -y, -z); }
};
template<typename T>
class Sphere
{
public:
// Sphere variables.
Vec3<T> center; /// position of the sphere
T radius, radius2; /// sphere radius and radius^2
Vec3<T> surfaceColor, emissionColor; /// surface color and emission (light)
T transparency, reflection; /// surface transparency and reflectivity
// Sphere constructor.
// position(c), radius(r), surface color(sc), reflectivity(refl), transparency(transp), emission color(ec)
Sphere(const Vec3<T> &c, const T &r, const Vec3<T> &sc,
const T &refl = 0, const T &transp = 0, const Vec3<T> &ec = 0) :
center(c), radius(r), surfaceColor(sc), reflection(refl),
transparency(transp), emissionColor(ec), radius2(r * r)
{}
// compute a ray-sphere intersection using the geometric solution
bool intersect(const Vec3<T> &rayorig, const Vec3<T> &raydir, T *t0 = NULL, T *t1 = NULL) const
{
// we start with a vector (l) from the ray origin (rayorig) to the center of the curent sphere.
Vec3<T> l = center - rayorig;
// tca is a vector length in the direction of the normalise raydir.
// its length is streched using dot until it forms a perfect right angle triangle with the l vector.
T tca = l.dot(raydir);
// if tca is < 0, the raydir is going in the opposite direction. No need to go further. Return false.
if (tca < 0) return false;
// if we keep on into the code, it's because the raydir may still hit the sphere.
// l.dot(l) gives us the l vector length to the power of 2. Then we use Pythagoras' theorem.
// remove the length tca to the power of two (tca * tca) and we get a distance from the center of the sphere to the power of 2 (d2).
T d2 = l.dot(l) - (tca * tca);
// if this distance to the center (d2) is greater than the radius to the power of 2 (radius2), the raydir direction is missing the sphere.
// No need to go further. Return false.
if (d2 > radius2) return false;
// Pythagoras' theorem again: radius2 is the hypotenuse and d2 is one of the side. Substraction gives the third side to the power of 2.
// Using sqrt, we obtain the length thc. thc is how deep tca goes into the sphere.
T thc = sqrt(radius2 - d2);
if (t0 != NULL && t1 != NULL) {
// remove thc to tca and you get the length from the ray origin to the surface hit point of the sphere.
*t0 = tca - thc;
// add thc to tca and you get the length from the ray origin to the surface hit point of the back side of the sphere.
*t1 = tca + thc;
}
// There is a intersection with a sphere, t0 and t1 have surface distances values. Return true.
return true;
}
};
std::vector<Sphere<double> *> spheres;
// function to mix 2 T varables.
template<typename T>
T mix(const T &a, const T &b, const T &mix)
{
return b * mix + a * (T(1) - mix);
}
// This is the main trace function. It takes a ray as argument (defined by its origin
// and direction). We test if this ray intersects any of the geometry in the scene.
// If the ray intersects an object, we compute the intersection point, the normal
// at the intersection point, and shade this point using this information.
// Shading depends on the surface property (is it transparent, reflective, diffuse).
// The function returns a color for the ray. If the ray intersects an object, it
// returns the color of the object at the intersection point, otherwise it returns
// the background color.
template<typename T>
Vec3<T> trace(const Vec3<T> &rayorig, const Vec3<T> &raydir,
const std::vector<Sphere<T> *> &spheres, const int &depth)
{
T tnear = INFINITY;
const Sphere<T> *sphere = NULL;
// Try to find intersection of this raydir with the spheres in the scene
for (unsigned i = 0; i < spheres.size(); ++i) {
T t0 = INFINITY, t1 = INFINITY;
if (spheres[i]->intersect(rayorig, raydir, &t0, &t1)) {
// is the rayorig inside the sphere (t0 < 0)? If so, use the second hit (t0 = t1)
if (t0 < 0) t0 = t1;
// tnear is the last sphere intersection (or infinity). Is t0 in front of tnear?
if (t0 < tnear) {
// if so, update tnear to this closer t0 and update the closest sphere
tnear = t0;
sphere = spheres[i];
}
}
}
// At this moment in the program, we have the closest sphere (sphere) and the closest hit position (tnear)
// For this pixel, if there's no intersection with a sphere, return a Vec3 with the background color.
if (!sphere) return Vec3<T>(.5); // Grey background color.
// if we keep on with the code, it is because we had an intersection with at least one sphere.
Vec3<T> surfaceColor = 0; // initialisation of the color of the ray/surface of the object intersected by the ray.
Vec3<T> phit = rayorig + (raydir * tnear); // point of intersection.
Vec3<T> nhit = phit - sphere->center; // normal at the intersection point.
// if the normal and the view direction are not opposite to each other,
// reverse the normal direction.
if (raydir.dot(nhit) > 0) nhit = -nhit;
nhit.normalize(); // normalize normal direction
// The angle between raydir and the normal at point hit (not used).
//T s_angle = acos(raydir.dot(nhit)) / ( sqrt(raydir.dot(raydir)) * sqrt(nhit.dot(nhit)));
//T s_incidence = sin(s_angle);
T bias = 1e-5; // add some bias to the point from which we will be tracing
// Do we have transparency or reflection?
if ((sphere->transparency > 0 || sphere->reflection > 0) && depth < MAX_RAY_DEPTH) {
T IdotN = raydir.dot(nhit); // raydir.normal
// I and N are not pointing in the same direction, so take the invert.
T facingratio = std::max(T(0), -IdotN);
// change the mix value between reflection and refraction to tweak the effect (fresnel effect)
T fresneleffect = mix<T>(pow(1 - facingratio, 3), 1, 0.1);
// compute reflection direction (not need to normalize because all vectors
// are already normalized)
Vec3<T> refldir = raydir - nhit * 2 * raydir.dot(nhit);
Vec3<T> reflection = trace(phit + (nhit * bias), refldir, spheres, depth + 1);
Vec3<T> refraction = 0;
// if the sphere is also transparent compute refraction ray (transmission)
if (sphere->transparency) {
T ior = 1.2, eta = 1 / ior;
T k = 1 - eta * eta * (1 - IdotN * IdotN);
Vec3<T> refrdir = raydir * eta - nhit * (eta * IdotN + sqrt(k));
refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1);
}
// the result is a mix of reflection and refraction (if the sphere is transparent)
surfaceColor = (reflection * fresneleffect + refraction * (1 - fresneleffect) * sphere->transparency) * sphere->surfaceColor;
}
else {
// it's a diffuse object, no need to raytrace any further
// Look at all sphere to find lights
double shadow = 1.0;
for (unsigned i = 0; i < spheres.size(); ++i) {
if (spheres[i]->emissionColor.x > 0) {
// this is a light
Vec3<T> transmission = 1.0;
Vec3<T> lightDirection = spheres[i]->center - phit;
lightDirection.normalize();
T light_angle = (acos(raydir.dot(lightDirection)) / ( sqrt(raydir.dot(raydir)) * sqrt(lightDirection.dot(lightDirection))));
T light_incidence = sin(light_angle);
for (unsigned j = 0; j < spheres.size(); ++j) {
if (i != j) {
T t0, t1;
// Does the ray from point hit to the light intersect an object?
// If so, calculate the shadow.
if (spheres[j]->intersect(phit + (nhit * bias), lightDirection, &t0, &t1)) {
shadow = std::max(0.0, shadow - (1.0 - spheres[j]->transparency));
transmission = transmission * spheres[j]->surfaceColor * shadow;
//break;
}
}
}
// For each light found, we add light transmission to the pixel.
surfaceColor += sphere->surfaceColor * transmission *
std::max(T(0), nhit.dot(lightDirection)) * spheres[i]->emissionColor;
}
}
}
return surfaceColor + sphere->emissionColor;
}
// Main rendering function. We compute a camera ray for each pixel of the image,
// trace it and return a color. If the ray hits a sphere, we return the color of the
// sphere at the intersection point, else we return the background color.
Vec3<double> *image = new Vec3<double>[width * height];
static Vec3<double> cam_pos = Vec3<double>(0);
template<typename T>
void render(const std::vector<Sphere<T> *> &spheres)
{
Vec3<T> *pixel = image;
T invWidth = 1 / T(width), invHeight = 1 / T(height);
T fov = 30, aspectratio = T(width) / T(height);
T angle = tan(M_PI * 0.5 * fov / T(180));
// Trace rays
for (GLuint y = 0; y < height; ++y) {
for (GLuint x = 0; x < width; ++x, ++pixel) {
T xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
T yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
Vec3<T> raydir(xx, yy, -1);
raydir.normalize();
*pixel = trace(cam_pos, raydir, spheres, 0);
}
}
}
//********************************** OPEN GL ***********************************************
void advanceDisplay(void)
{
cam_pos.z = cam_pos.z - 2;
glutPostRedisplay();
}
void backDisplay(void)
{
cam_pos.z = cam_pos.z + 2;
glutPostRedisplay();
}
void resetDisplay(void)
{
Vec3<double> new_cam_pos;
new_cam_pos = cam_pos;
cam_pos = new_cam_pos;
glutPostRedisplay();
}
void mouse(int button, int state, int x, int y)
{
switch (button)
{
case GLUT_LEFT_BUTTON:
if(state == GLUT_DOWN)
{
glutIdleFunc(advanceDisplay);
}
break;
case GLUT_MIDDLE_BUTTON:
if(state == GLUT_DOWN)
{
glutIdleFunc(resetDisplay);
}
break;
case GLUT_RIGHT_BUTTON:
if(state == GLUT_DOWN)
{
glutIdleFunc(backDisplay);
}
break;
}
}
GLuint tex = 0;
void display(void)
{
int i;
float x, y;
render<double>(spheres); // Creates the image and put it to memory in image[].
std::vector< unsigned char > buf;
buf.reserve( width * height * 3 );
for( size_t y = 0; y < height; ++y )
{
for( size_t x = 0; x < width; ++x )
{
// flip vertically (height-y) because the OpenGL texture origin is in the lower-left corner
// flip horizontally (width-x) because...the original code did so
size_t i = (height-y) * width + (width-x);
buf.push_back( (unsigned char)( std::min(double(1), image[i].x) * 255.0 ) );
buf.push_back( (unsigned char)( std::min(double(1), image[i].y) * 255.0 ) );
buf.push_back( (unsigned char)( std::min(double(1), image[i].z) * 255.0 ) );
}
}
/* clear all pixels */
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnable( GL_TEXTURE_2D );
glBindTexture( GL_TEXTURE_2D, tex );
glTexSubImage2D
(
GL_TEXTURE_2D, 0,
0, 0,
width, height,
GL_RGB,
GL_UNSIGNED_BYTE,
&buf[0]
);
glBegin( GL_QUADS );
glTexCoord2i( 0, 0 );
glVertex2i( -1, -1 );
glTexCoord2i( 1, 0 );
glVertex2i( 1, -1 );
glTexCoord2i( 1, 1 );
glVertex2i( 1, 1 );
glTexCoord2i( 0, 1 );
glVertex2i( -1, 1 );
glEnd();
glutSwapBuffers();
}
int main(int argc, char **argv)
{
// position, radius, surface color, reflectivity, transparency, emission color
spheres.push_back(new Sphere<double>(Vec3<double>(0, -10004, -20), 10000, Vec3<double>(0.2), 0.0, 0.0));
spheres.push_back(new Sphere<double>(Vec3<double>(3, 0, -15), 2, Vec3<double>(1.00, 0.1, 0.1), 0.65, 0.95));
spheres.push_back(new Sphere<double>(Vec3<double>(1, -1, -18), 1, Vec3<double>(1.0, 1.0, 1.0), 0.9, 0.9));
spheres.push_back(new Sphere<double>(Vec3<double>(-2, 2, -15), 2, Vec3<double>(0.1, 0.1, 1.0), 0.05, 0.5));
spheres.push_back(new Sphere<double>(Vec3<double>(-4, 3, -18), 1, Vec3<double>(0.1, 1.0, 0.1), 0.3, 0.7));
spheres.push_back(new Sphere<double>(Vec3<double>(-4, 0, -25), 1, Vec3<double>(1.00, 0.1, 0.1), 0.65, 0.95));
spheres.push_back(new Sphere<double>(Vec3<double>(-1, 1, -25), 2, Vec3<double>(1.0, 1.0, 1.0), 0.0, 0.0));
spheres.push_back(new Sphere<double>(Vec3<double>(2, 2, -25), 1, Vec3<double>(0.1, 0.1, 1.0), 0.05, 0.5));
spheres.push_back(new Sphere<double>(Vec3<double>(5, 3, -25), 2, Vec3<double>(0.1, 1.0, 0.1), 0.3, 0.7));
// light
spheres.push_back(new Sphere<double>(Vec3<double>(-10, 20, 0), 3, Vec3<double>(0), 0, 0, Vec3<double>(3)));
spheres.push_back(new Sphere<double>(Vec3<double>(0, 10, 0), 3, Vec3<double>(0), 0, 0, Vec3<double>(1)));
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(width, height);
glutInitWindowPosition(10,10);
glutCreateWindow(argv[0]);
glutDisplayFunc(display);
glutMouseFunc(mouse);
glGenTextures( 1, &tex );
glBindTexture( GL_TEXTURE_2D, tex );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glTexImage2D( GL_TEXTURE_2D, 0, 3, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
glutMainLoop();
delete [] image;
while (!spheres.empty()) {
Sphere<double> *sph = spheres.back();
spheres.pop_back();
delete sph;
}
return 0;
}
How to load and display images is also explained on www.scratchapixel.com. Strange you didn't see this lesson:
http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-5-colors-and-digital-images/source-code/
It's all in there and they explain you how to display images using GL textures indeed.
Related
I am working on an assignment where I need to ray trace a sphere with a plane (floor). I have the sphere but I am having trouble with the plane. I use the ray-plane intersection formula:
t = -(o-p).n / d.n. I have this in Plane.h, however when I run my code I get errors from Ray.h. Could someone explain what I'm doing wrong? Any help would be appreciated.
Plane.h
`#include "..\..\raytrace\Ray.h"
class Plane
{
using Colour = cv::Vec3b; // RGB Value
private:
Vec3 normal_;
Vec3 distance_;
Colour color_;
public:
Plane();
Plane(Vec3 norm, Vec3 dis, Colour color) : normal_(norm), distance_(dis), color_(color) {}
Vec3 norm() const {
return normal_;
}
Vec3 dis() const {
return distance_;
}
Colour color() const {
return color_;
}
float findIntersection(Ray ray) {
Vec3 rayDirection = ray.mPosition();
float denominator = rayDirection.dot(normal_);
if (denominator == 0) {
return false;
}
else {
//mPosition() is origin in Ray.h
float t = -(((ray.mPosition() - distance_)).dot(normal_)) / denominator;
}
}
};
`
Ray.h
#include <Eigen/Dense>
#include <Eigen/Geometry>
#include <cmath>
#include "Image.h"
// Data types
typedef float Scalar; //**custom datatype: Scalar is float
typedef Eigen::Matrix<Scalar, 3, 1> Vec3; //***Vec3 is a custom datatype (specific kind)
typedef Eigen::Matrix<Scalar, 2, 1> Vec2;
typedef unsigned char uchar;
class Ray
{
private:
Vec3 mPosition_; //point
public:
Ray() {}
//constuctor, when we construct ray we get mPosition_
Ray(Vec3 mPosition) : mPosition_(mPosition) {
//
}
float t;
Vec3 mPosition() const {
return mPosition_;
}
public:
inline Vec3 generateRay(Vec3 const& pt) {
Vec3 origin = mPosition_;
Vec3 direction = pt - mPosition_; // d = s -e, pt is pixel Position
direction.normalize();
return pt + t * direction;
}
};
main.cpp
#include <cmath>
#include "Image.h"
#include "Ray.h"
#include "../build/raytrace/Plane.h"
//Color functions
using Colour = cv::Vec3b; // RGB Value
//Color is a Vec3b datatype, use Color instead of Vec3b, it has 3 vectors, hold 3 values b/w 0-255
Colour red() { return Colour(255, 0, 0); }
Colour green() { return Colour(0, 255,0); }
Colour blue() { return Colour(0, 0, 255); }
Colour white() { return Colour(255, 255, 255); }
Colour black() { return Colour(0, 0, 0); }
//bounding the channel wise pixel color between 0 to 255
//bounding the color value, if a value is beyond 255 clamp it to 255, and any value below 0 clamp to 0.
uchar Clamp(int color)
{
if (color < 0) return 0;
if (color >= 255) return 255;
return color;
}
int main(int, char**){
//Create an image object with 500 x 500 resolution.
Image image = Image(500, 500);
//Coordinates of image rectangle
Vec3 llc = Vec3(-1, -1, -1); //**llc - lower left corner
Vec3 urc = Vec3(1, 1, -1); //**urc - upper right corner
int width = urc(0) - llc(0);
int height = urc(1) - llc(1);
Vec2 pixelUV = Vec2((float)width / image.cols, (float)height / image.rows);
/// TODO: define camera position (view point), sphere center, sphere radius (Weightage: 5%)
Vec3 CameraPoint = Vec3(0, 0, 0); //**it is the origin
Vec3 SphereCenter = Vec3(0, 0, -5); //**it is the Sphere Position
float SphereRadius = 2.0;
Vec3 LightSource = Vec3(2.0, 0.0, 3.0); //**
Vec3 ambient = Vec3(0, 0, 0.5); //**
Vec3 diffuse = Vec3(224, 180, 255); //** 0, 255, 100 - green
Vec3 Origin = CameraPoint;
//end
for (int row = 0; row < image.rows; ++row) {
for (int col = 0; col < image.cols; ++col) {
//TODO: Build primary rays
//Find the pixel position (PixelPos) for each row and col and then construct the vector PixelPos-Origin
Vec3 pixelPos = Vec3(llc(0) + pixelUV(0) * (col + 0.5), llc(1) + pixelUV(1) * (row + 0.5), -1);
//create a ray object
Ray r; //**
//Vec3 rayDir = pixelPos - Origin; //**direction of the ray
Vec3 rayDir = r.generateRay(pixelPos); //**pixelPos-Origin
rayDir.normalize(); //**normalize the ray direction vector
//Ray-sphere intersection...(refer to the lecture slides and Section 4.4.1 of the textbook)
float a = rayDir.dot(rayDir);
Vec3 s0_r0 = Origin - SphereCenter; //***s0_r0 - sphere center - ray origin
float b = 2.0 * rayDir.dot(s0_r0);
float c = s0_r0.dot(s0_r0) - pow(SphereRadius, 2);
//compute the discriminant
float discriminant = pow(b, 2) - 4 * a * c;
//if the discriminant is greater than zero
if(discriminant > 0){
//find roots t1 and t2
float t1 = (-b - sqrt((pow(b, 2)) - 4.0 * a * c)) / (2.0 * a); //**
float t2 = (-b + sqrt((pow(b, 2)) - 4.0 * a * c)) / (2.0 * a); //**
//determine which one is the real intersection point
float t;
//Sphere s;
if (t1 < t2 && (t1 > 0 && t2 > 0)) {
t = t1;
//} //should this be after the if-statement below, so that it uses t = t1 and not just float t.????
if (t > 0) {
//Shade the pixel, normal is Intersection - SphereCenter, LightVector is LightSource- Intersection, make sure to normalize the vectors
Vec3 Intersection = Origin + (t * rayDir);
Vec3 Normal = Intersection - SphereCenter; //** normalize
Normal.normalize(); //**
Vec3 LightVector = LightSource - Intersection; //**normalize
LightVector.normalize(); //**
float diffuseTerm = LightVector.dot(Normal);
if (diffuseTerm < 0) diffuseTerm = 0;
Colour colour(0, 0, 0); //The ambient base
colour[0] = Clamp(ambient[0] + diffuse[0] * diffuseTerm);
colour[1] = Clamp(ambient[1] + diffuse[1] * diffuseTerm);
colour[2] = Clamp(ambient[2] + diffuse[2] * diffuseTerm);
image(row, col) = colour;
}
}//
else {
image(row, col) = black();
}
} else {
//No intersection, discriminant < 0
image(row, col) = red(); //**makes blue background colour
}
////**Plane intersection
//create a plane object
Plane plane(Vec3(-5, 0, -4), Vec3(0, 0, -1), black());
//Plane plane;
////if ray hits plane -> color black
//if (plane.findIntersection(rayDir) == 1) {
// image(row, col) = black();
//}
//else {
// image(row, col) = white();
//}
}
}
/// Required outputs: (1) Ray traced image of a sphere (2) Ray traced image when the camera is placed inside the sphere (complete black)
image.save("./result.png");
image.display();
return EXIT_SUCCESS;
}
Errors
enter image description here
#include is a shockingly simple directive. It literally just copy-pastes the content of the file.
main.cpp includes both Ray.h and Plane.h, and Plane.h includes Ray.h, so Ray.h ends up being included twice. That's why the compiler is complaining about a "class redefinition".
You can add #pragma once at the top of all your header files to let the compiler know know to skip the file if it was included already.
N.B. #pragma once is not officially part of the language, but it is supported by all compilers and has a few small advantages over the alternative.
i need to implement arcball camera. I got something similar, but it works very crookedly (the angle changes sharply, when turning to the right / left, the camera raises up / down strongly).
Here is my source code, can you tell me where I went wrong:
bool get_arcball_vec(double x, double y, glm::vec3& a)
{
glm::vec3 vec = glm::vec3((2.0 * x) / window.getWidth() - 1.0, 1.0 - (2.0 * y) / window.getHeight(), 0.0);
if (glm::length(vec) >= 1.0)
{
vec = glm::normalize(vec);
}
else
{
vec.z = sqrt(1.0 - pow(vec.x, 2.0) - pow(vec.y, 2.0));
}
a = vec;
return true;
}
...
void onMouseMove(double x, double y) {
if (rightMouseButtonPressed) {
glm::vec3 a,b;
cur_mx = x;
cur_my = y;
if (cur_mx != last_mx || cur_my != last_my)
if (get_arcball_vec(last_mx, last_my, a) && get_arcball_vec(cur_mx, cur_my, b))
viewport.getCamera().orbit(a,b);
last_mx = cur_mx;
last_my = cur_my;
...
void Camera::orbit(glm::vec3 a, glm::vec3 b)
{
forward = calcForward();
right = calcRight();
double alpha = acos(glm::min(1.0f, glm::dot(b, a)));
glm::vec3 axis = glm::cross(a, b);
glm::mat4 rotationComponent = glm::mat4(1.0f);
rotationComponent[0] = glm::vec4(right, 0.0f);
rotationComponent[1] = glm::vec4(up, 0.0f);
rotationComponent[2] = glm::vec4(forward, 0.0f);
glm::mat4 toWorldCameraSpace = glm::transpose(rotationComponent);
axis = toWorldCameraSpace * glm::vec4(axis, 1.0);
glm::mat4 orbitMatrix = glm::rotate(glm::mat4(1.0f), (float)alpha, axis);
eye = glm::vec4(target, 1.0) + orbitMatrix * glm::vec4(eye - target, 1.0f);
up = orbitMatrix * glm::vec4(up, 1.0f);
}
I use this code to map 2D mouse position to the sphere:
Vector3 GetArcBallVector(const Vector2f & mousePos) {
float radiusSquared = 1.0; //squared radius of the sphere
//compute mouse position from the centre of screen to interval [-half, +half]
Vector3 pt = Vector3(
mousePos.x - halfScreenW,
halfScreenH - mousePos.y,
0.0f
);
//if length squared is smaller than sphere diameter
//point is inside
float lengthSqr = pt.x * pt.x + pt.y * pt.y;
if (lengthSqr < radiusSquared){
//inside
pt.z = std::sqrtf(radiusSquared - lengthSqr);
}
else {
pt.z = 0.0f;
}
pt.z *= -1;
return pt;
}
To calculate rotation, I use the last (startPt) and current (endPt) mapped position and do:
Quaternion actRot = Quaternion::Identity();
Vector3 axis = Vector3::Cross(endPt, startPt);
if (axis.LengthSquared() > MathUtils::EPSILON) {
float angleCos = Vector3::Dot(endPt, startPt);
actRot = Quaternion(axis.x, axis.y, axis.z, angleCos);
}
I prefer to use Quaternions over matrices since they are easy to multiply (for acumulated rotation) and interpolate (for some smooting).
I am facing problems trying to make 3d objects clickable by mouse. For intersection checking I use ray casting. Code I found, ported for my solution:
Exactly picking
bool RaySphereIntersect(Vector3, Vector3, float);
bool TestIntersection(Matrix projectionMatrix, Matrix viewMatrix, Matrix worldMatrix, Vector3 origin, float radius, int m_screenWidth, int m_screenHeight, int mouseX, int mouseY)
{
float pointX, pointY;
Matrix inverseViewMatrix, translateMatrix, inverseWorldMatrix;
Vector3 direction, rayOrigin, rayDirection;
bool intersect, result;
// Move the mouse cursor coordinates into the -1 to +1 range.
pointX = ((2.0f * (float)mouseX) / (float)m_screenWidth) - 1.0f;
pointY = (((2.0f * (float)mouseY) / (float)m_screenHeight) - 1.0f) * -1.0f;
// Adjust the points using the projection matrix to account for the aspect ratio of the viewport.
pointX = pointX / projectionMatrix._11;
pointY = pointY / projectionMatrix._22;
// Get the inverse of the view matrix.
inverseViewMatrix=XMMatrixInverse(NULL, viewMatrix);
// Calculate the direction of the picking ray in view space.
direction.x = (pointX * inverseViewMatrix._11) + (pointY * inverseViewMatrix._21) + inverseViewMatrix._31;
direction.y = (pointX * inverseViewMatrix._12) + (pointY * inverseViewMatrix._22) + inverseViewMatrix._32;
direction.z = (pointX * inverseViewMatrix._13) + (pointY * inverseViewMatrix._23) + inverseViewMatrix._33;
// Get the origin of the picking ray which is the position of the camera.
// Get the world matrix and translate to the location of the sphere.
// Now get the inverse of the translated world matrix.
inverseWorldMatrix= XMMatrixInverse(NULL, worldMatrix);
// Now transform the ray origin and the ray direction from view space to world space.
rayOrigin=XMVector3TransformCoord(origin, inverseWorldMatrix);
rayDirection=XMVector3TransformNormal(direction, inverseWorldMatrix);
// Normalize the ray direction.
rayDirection=XMVector3Normalize(rayDirection);
// Now perform the ray-sphere intersection test.
intersect = RaySphereIntersect(rayOrigin, rayDirection, radius);
if (intersect == true)
return true;
else
return false;
}
bool RaySphereIntersect(Vector3 rayOrigin, Vector3 rayDirection, float radius)
{
float a, b, c, discriminant;
// Calculate the a, b, and c coefficients.
a = (rayDirection.x * rayDirection.x) + (rayDirection.y * rayDirection.y) + (rayDirection.z * rayDirection.z);
b = ((rayDirection.x * rayOrigin.x) + (rayDirection.y * rayOrigin.y) + (rayDirection.z * rayOrigin.z)) * 2.0f;
c = ((rayOrigin.x * rayOrigin.x) + (rayOrigin.y * rayOrigin.y) + (rayOrigin.z * rayOrigin.z)) - (radius * radius);
// Find the discriminant.
discriminant = (b * b) - (4 * a * c);
// if discriminant is negative the picking ray missed the sphere, otherwise it intersected the sphere.
if (discriminant < 0.0f)
return false;
else
return true;
}
How do I create sphere
D3DSphere(float x, float y, float z, float radius, float r, float g, float b, float a)
{
this->x = x;
this->y = y;
this->z = z;
this->radius = radius;
this->shape = GeometricPrimitive::CreateSphere(radius*2.0f);
this->world = Matrix::Identity;
this->world = XMMatrixMultiply(this->world, Matrix::CreateTranslation(x, y, z));
this->index = vsphere;
d3dsphere[vsphere] = this;
vsphere++;
}
How do I call raycaster
void Game::LButtonUp(int x, int y)
{
Vector3 eye(camx, camy, camz);
Vector3 at(Vector3::Zero);
m_view = Matrix::CreateLookAt(eye, at, Vector3::UnitY);
for (int i = 0; i < vsphere; i++)
{
if (TestIntersection(m_projection, m_view, d3dsphere[i]->world, eye, d3dsphere[i]->radius, 800, 600, x, y))
{
MessageBoxW(NULL, L"LOL", L"It works", MB_OK);
break;
}
}
}
Nothing happens by clicking, but if I rotate camera, perpendicularly to XOY, sometimes, clicking near the sphere, message box appears.
Update
MessageBox appears independently on camera angle, and it seems, that it detects intersection correctly, but mirrored, relatively to the window center. For example, if sphere is at (0, window.bottom-20) point then I will get MessageBox if I click at (0, 20) point.
What if calculation of the direction of the picking ray is wrong, if it was wrote for left-handed system, and I use right-handed?
Probably, because of the right-handed system, that is used by default in DirectX Tool Kit the next section from caster
pointX = ((2.0f * (float)mouseX) / (float)m_screenWidth) - 1.0f;
pointY = (((2.0f * (float)mouseY) / (float)m_screenHeight) - 1.0f) * -1.0f;
Should be changed to
pointX = (((2.0f * (float)mouseX) / (float)m_screenWidth) - 1.0f) * -1.0f;
pointY = (((2.0f * (float)mouseY) / (float)m_screenHeight) - 1.0f);
Important
That code also will work wrong because of depth independence, i.e. you may select sphere that is situated behind the sphere you clicking. For solve that I changed the code:
float distance3(float x1, float y1, float z1, float x2, float y2, float z2)
{
float dx=x1-x2;
float dy=y1-y2;
float dz=z1-z2;
return sqrt(dx*dx+dy*dy+dz*dz);
}
void Game::LButtonUp(int x, int y)
{
Vector3 eye(camx, camy, camz);
Vector3 at(Vector3::Zero);
m_view = Matrix::CreateLookAt(eye, at, Vector3::UnitY);
int last_index=-1;
float last_distance=99999.0f;//set the obviously highest value, may happen in your scene
for (int i = 0; i < vsphere; i++)
{
if (TestIntersection(m_projection, m_view, d3dsphere[i]->world, eye, d3dsphere[i]->radius, 800, 600, x, y))
{
float distance=distance3(camx,camy,camz, d3dsphere[i]->x, d3dsphere[i]->y, d3dsphere[i]->z);
if(distance<last_distance)
{
last_distance=distance;
last_index=i;
}
}
}
d3dsphere[last_index];//picked sphere
}
I'm writing a program to graph 3D surfaces. It uses CUDA to do rendering and then OpenGL to display the results. It works fine and renders pretty and accurate results, but it does not correctly update the window. Here's my program in pseudocode:
void display() {
//render stuff
glutSwapBuffers();
glutPostRedisplay();
}
int main() {
//set up OpenGL and CUDA
glutMainLoop();
return 0;
}
This should update continuously, but it doesn't. It correctly calls the display method and CUDA renders the results, but it does not display the results in the window until the window is resized. So it calls the display function something like 30 times a second, but the window doesn't actually show the results until resized or minimized.
Here's my entire program. It's EXTREMELY messy and has no comments since I was learning OpenGL as I went along.
#define GL_GLEXT_PROTOTYPES
#include <GL/freeglut_std.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdlib.h>
#include <stdio.h>
#include <cuda_gl_interop.h>
#define XSIZE 1280
#define YSIZE 640
float theta = .15;
float phi = 1;
float r = 10;
float3 lightDirection = make_float3(9, 5, -5);
float delta = .001;
GLuint pbo; // OpenGL pixel buffer object
struct cudaGraphicsResource *cuda_pbo_resource; // handles OpenGL-CUDA exchange
GLuint texid; // Texture
GLuint shader;
__host__ __device__ float3 operator+(float3 a, float3 b) {
return make_float3(a.x + b.x, a.y + b.y, a.z + b.z);
}
__host__ __device__ float3 operator-(float3 a, float3 b) {
return make_float3(a.x - b.x, a.y - b.y, a.z - b.z);
}
__host__ __device__ float3 operator*(float3 a, float b) {
return make_float3(a.x * b, a.y * b, a.z * b);
}
__host__ __device__ float3 operator/(float3 a, float b) {
return make_float3(a.x / b, a.y / b, a.z / b);
}
__host__ __device__ float operator*(float3 a, float3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
__host__ __device__ float magnitude(float3 a) {
return std::sqrt(a * a);
}
__host__ __device__ float3 normalize(float3 a) {
return a / magnitude(a);
}
__device__ float valueAt(float3 a) {
return std::sin(a.x) + std::sin(a.y) + std::sin(a.z);
}
__device__ float3 normalAt(float3 a) {
float x = valueAt(make_float3(a.x + .001, a.y, a.z)) - valueAt(a);
float y = valueAt(make_float3(a.x, a.y + .001, a.z)) - valueAt(a);
float z = valueAt(make_float3(a.x, a.y, a.z + .001)) - valueAt(a);
return make_float3(x, y, z) / .001;
}
__device__ float estimateDistance(float3 a) {
return std::abs(valueAt(a) / magnitude(normalAt(a)));
}
__device__ float3 trace(float3 from, float3 direction) {
float totalDistance = 0;
float3 v = from;
for(int steps = 0; steps < 256; steps++) {
if(magnitude(v) < 5) break;
totalDistance += 1;
v = from + direction * totalDistance;
}
for(int steps = 0; steps < 256; steps++) {
v = from + direction * totalDistance;
float distance = estimateDistance(v);
totalDistance += distance * .1;
if(distance < .1 && magnitude(v) < 5) return v;
}
return make_float3(0, 0, 0);
}
__device__ uchar4 colorAt(float3 v, float3 lightDirection, float3 viewDirection) {
float3 normal = normalize(normalAt(v));
float3 h = normalize(viewDirection + lightDirection);
float specular = std::abs(std::pow(h * normal, 15)) * .5;
float diffuse = std::abs(lightDirection * normal) * .5;
float lighting = (specular + diffuse) * .9;
lighting += .1;
float3 color;
if((fmod(v.x + 1000, 1) < .5 && fmod(v.y + 1000, 1) < .5) || (fmod(v.x + 1000, 1) > .5 && fmod(v.y + 1000, 1) > .5)) {
color.x = 1;
color.y = .5;
color.z = .25;
}
else {
color.x = .75;
color.y = .5;
color.z = .25;
}
color = color * lighting;
return make_uchar4((unsigned char)(color.x * 255), (unsigned char)(color.y * 255), (unsigned char)(color.z * 255), 255);
}
__global__ void eval(float3 position, float3 direction, float3 right, float3 up, float3 lightDirection, float delta, uchar4* dev_pixels) {
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
int offset = x + y * XSIZE;
float rAngle = (x - XSIZE / 2) * delta;
float uAngle = (-y + YSIZE / 2) * delta;
float3 rayDirection = direction + right * rAngle + up * uAngle;
rayDirection = normalize(rayDirection);
float3 v = trace(position, rayDirection);
uchar4 color;
if(magnitude(v) != 0) color = colorAt(v, lightDirection, direction);
dev_pixels[offset] = color;
}
void display() {
uchar4* dev_pixels;
cudaGLRegisterBufferObject(pbo);
cudaGLMapBufferObject((void**)&dev_pixels, pbo);
dim3 dim_block(16, 16);
dim3 dim_grid(XSIZE / 16, YSIZE / 16);
float3 position = make_float3(std::cos(theta) * std::sin(phi) * r, std::sin(theta) * std::sin(phi) * r, std::cos(phi) * r);
float3 direction = normalize(position) * -1;
float3 right = make_float3(-std::sin(theta), std::cos(theta), 0);
right = normalize(right);
float3 up = make_float3(-std::cos(phi) * std::cos(theta), -std::cos(phi) * std::sin(theta), std::sin(phi));
up = normalize(up);
eval<<<dim_grid, dim_block>>>(position, direction, right, up, lightDirection, delta, dev_pixels);
cudaGLUnmapBufferObject(pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBindTexture(GL_TEXTURE_2D, texid);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, XSIZE, YSIZE, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBegin(GL_QUADS);
glTexCoord2f(0.0f,1.0f); glVertex3f(0.0f,0.0f,0.0f);
glTexCoord2f(0.0f,0.0f); glVertex3f(0.0f,1.0f,0.0f);
glTexCoord2f(1.0f,0.0f); glVertex3f(1.0f,1.0f,0.0f);
glTexCoord2f(1.0f,1.0f); glVertex3f(1.0f,0.0f,0.0f);
glEnd();
glutSwapBuffers();
theta += .01;
glutPostRedisplay();
}
int main(int argc, char **argv) {
lightDirection = normalize(lightDirection);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowSize(XSIZE, YSIZE);
glutCreateWindow("Grapher");
glutDisplayFunc(display);
glViewport(0, 0, XSIZE, YSIZE);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 1.0f, 0, 1.0f, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable(GL_DEPTH_TEST);
glClearColor(1.0f, 1.0f, 1.0f, 1.5f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBufferData(GL_PIXEL_UNPACK_BUFFER, XSIZE * YSIZE * 4, NULL, GL_DYNAMIC_COPY);
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &texid);
glBindTexture(GL_TEXTURE_2D, texid);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, XSIZE, YSIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glutMainLoop();
return 0;
}
I'm using NSight Eclipse on Ubuntu 12.04.
At the end of your display routine, add these 2 lines:
...
glTexCoord2f(1.0f,1.0f); glVertex3f(1.0f,0.0f,0.0f);
glEnd();
glutSwapBuffers();
glClear(GL_DEPTH_BUFFER_BIT); // add this line
cudaGLUnregisterBufferObject(pbo); // add this line
theta += .01;
glutPostRedisplay();
...
The first addition allows the animation to proceed frame by frame. (Frames after the first will render properly.)
The second addition makes it so that if you close the animation window, you won't get a seg fault. cudaGLUnregisterBufferObject must be called on any previously registered object before the underlying OpenGL resource gets freed (or else you get a seg fault). When you click the X to close the window, the OpenGL buffer object (pbo) gets freed as the OpenGL context disappears.
I have a square area on which I have to determine where the mouse pointing.
With D3DXIntersectTri I can tell IF the mouse pointing on it, but I have trouble calculating the x,y,z coordinates.
The drawing from vertex buffer, which initialized with the vertices array:
vertices[0].position = D3DXVECTOR3(-10, 0, -10);
vertices[1].position = D3DXVECTOR3(-10, 0, 10);
vertices[2].position = D3DXVECTOR3( 10, 0, -10);
vertices[3].position = D3DXVECTOR3( 10, 0, -10);
vertices[4].position = D3DXVECTOR3(-10, 0, 10);
vertices[5].position = D3DXVECTOR3( 10, 0, 10);
I have this method so far, this is not giving me the right coordinates (works only on a small part of the area, near two of the edges and more less accurate inside):
BOOL Area::getcoord( Ray& ray, D3DXVECTOR3& coord)
{
D3DXVECTOR3 rayOrigin, rayDirection;
rayDirection = ray.direction;
rayOrigin = ray.origin;
float d;
D3DXMATRIX matInverse;
D3DXMatrixInverse(&matInverse, NULL, &matWorld);
// Transform ray origin and direction by inv matrix
D3DXVECTOR3 rayObjOrigin,rayObjDirection;
D3DXVec3TransformCoord(&rayOrigin, &rayOrigin, &matInverse);
D3DXVec3TransformNormal(&rayDirection, &rayDirection, &matInverse);
D3DXVec3Normalize(&rayDirection,&rayDirection);
float u, v;
BOOL isHit1, isHit2;
D3DXVECTOR3 p1, p2, p3;
p1 = vertices[3].position;
p2 = vertices[4].position;
p3 = vertices[5].position;
isHit1 = D3DXIntersectTri(&p1, &p2, &p3, &rayOrigin, &rayDirection, &u, &v, &d);
isHit2 = FALSE;
if(!isHit1)
{
p1 = vertices[0].position;
p2 = vertices[1].position;
p3 = vertices[2].position;
isHit2 = D3DXIntersectTri(&p1, &p2, &p3, &rayOrigin, &rayDirection, &u, &v, &d);
}
if(isHit1)
{
coord.x = 1 * ((1-u-v)*p3.x + u*p3.y + v*p3.z);
coord.y = 0.2f;
coord.z = -1 * ((1-u-v)*p1.x + u*p1.y + v*p1.z);
D3DXVec3TransformCoord(&coord, &coord, &matInverse);
}
if(isHit2)
{
coord.x = -1 * ((1-u-v)*p3.x + u*p3.y + v*p3.z);
coord.y = 0.2f;
coord.z = 1 * ((1-u-v)*p1.x + u*p1.y + v*p1.z);
D3DXVec3TransformCoord(&coord, &coord, &matWorld);
}
return isHit1 || isHit2;
}
Barycentric coordinates don't work the way you used them. u and v define the weight of the source vectors. So if you want to calculate the hit point, you will have to compute
coord = u * p1 + v * p2 + (1 - u - v) * p3
Alternatively you can use the d ray parameter:
coord = rayOrigin + d * rDirection
Both ways should result in the same coordinate.