Skip to content
Permalink
7fe8ce4b7e
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time

Session 2 - Ray Casting

Table of Contents

  1. Sphere Ray Casting Solution
  2. Redesign data class
  3. Add triangle class
  4. Rendering complex shape
  5. Example of mesh class
  6. Add screen display using SDL2 Library

Sphere Ray Casting Solution

If you have trouble to implement sphere ray intersection. Here is the function codes.

//center is the sphere center position
//orig is the origin of the ray. You set it up as (0,0,0)
//dir is the direction of the ray
//t is the return parameter value of intersection. Range from [0,1]
bool intersectSphere(vec3 center, vec3 orig, vec3 dir, float radius, float &t)
{
	float t0, t1; // solutions for t if the ray intersects 

	// geometric solution  // vector dir has to be normalize, length is 1.0
	vec3 L = center - orig;
	float tca = dot(L, dir);
	if (tca < 0) return false;
	float d = dot(L,L) - tca * tca;
	if (d > (radius*radius)) return false;

	float thc = sqrt(radius*radius - d);
	t0 = tca - thc;
	t1 = tca + thc;

	if (t0 > t1) std::swap(t0, t1);

	if (t0 < 0) {
		t0 = t1; // if t0 is negative, let's use t1 instead 
		if (t0 < 0) return false; // both t0 and t1 are negative 
	}

	t = t0;
	return true;
}

A simple sphere class (header file):

#pragma once
#include <glm/glm.hpp>

using namespace glm;

class Sphere
{
private:
	float radius;
	vec3  center;
	vec3 mycolor;
public:
	Sphere(float,vec3,vec3);
	~Sphere();
	float getRadius(void);
	vec3 getCenter(void);
	vec3 getMyColor(void);
};

CPP file for the sphere class

#include "stdafx.h"
#include "sphere.h"

Sphere::Sphere(float r, vec3 cen, vec3 col) {
	radius = r;
	center = cen;
	mycolor = col;
}

Sphere::~Sphere()
{
}

float Sphere::getRadius(void)
{
	return radius;
}

vec3 Sphere::getCenter(void)
{
	return center;
}

vec3 Sphere::getMyColor(void)
{
	return mycolor;
}

Intersection points order sorting codes. So, the closest sphere will display the full sphere.

	vector<float> t_arr;
	vector<vec3> color_arr;
	
    for (int y = 0; y < HEIGHT; ++y)
	{
		for (int x = 0; x < WIDTH; ++x)
		{
			t_arr.clear();
			color_arr.clear();

			PixelNdx = (x + 0.5) / (float)WIDTH;
			PixelNdy = (y + 0.5) / (float)HEIGHT;
			PixelRdx = 2 * PixelNdx - 1.0;
			PixelRdy = 1.0 - 2 * PixelNdy;
			PixelRdx = PixelRdx * Iar;

			PCameraX = PixelRdx*tanvalue;
			PCameraY = PixelRdy*tanvalue;

			ttvec.x = PCameraX;
			ttvec.y = PCameraY;
			ttvec.z = -1.0;
			//dir need to be normalized
			dir = normalize(ttvec);

			org.x = 0.0; org.y = 0.0; org.z = 0.0;
			Intersection = intersectSphere(redsphere.getCenter(), org, dir, redsphere.getRadius(), t);
			if (Intersection)
			{
				t_arr.push_back(t);
				color_arr.push_back(redsphere.getMyColor());
			}

			Intersection = intersectSphere(yellowsphere.getCenter(), org, dir, yellowsphere.getRadius(), t);
			if (Intersection)
			{
				t_arr.push_back(t);
				color_arr.push_back(yellowsphere.getMyColor());
			}

			Intersection = intersectSphere(bluesphere.getCenter(), org, dir, bluesphere.getRadius(), t);
			if (Intersection)
			{
				t_arr.push_back(t);
				color_arr.push_back(bluesphere.getMyColor());
			}

			Intersection = intersectSphere(greyphere.getCenter(), org, dir, greyphere.getRadius(), t);
			if (Intersection)
			{
				t_arr.push_back(t);
				color_arr.push_back(greyphere.getMyColor());
			}

			if (t_arr.size() == 0)
			{
				image[x][y].x = 1.0;
				image[x][y].y = 1.0;
				image[x][y].z = 1.0;
			}
			else
			{
				min_t = 1000.0;
				whichone = 0;
				for (i = 0; i < t_arr.size(); i++)
				{
					if (t_arr[i] < min_t) { whichone = i; min_t = t_arr[i]; }
				}
				image[x][y].x = color_arr[whichone].x;
				image[x][y].y = color_arr[whichone].y;
				image[x][y].z = color_arr[whichone].z;
			}
		}
	}

Redesign data class

Before your code becomes too complex this is a good opportunity to refactor your ray casting code so that it is capable of rendering any primitive e.g. planes and triangles.

Step 1: Create a base Shape class which should contain a virtual method for the intersection and then inherit Sphere from this class. Test that your code still runs correctly after the refactoring.

Step 2: Add a Plane class that also inherits from your Shape class. The plane can be defined by a point on the plane and the normal to the plane. Add the intersect method using the ray-plane intersection method (see lecture slides for more details). Add a floor plane instead of the floor Sphere and test your code. Your scene may look very similar to before but actually the previous floor Sphere had a very slight curve whereas your floor will now be flat.

Note: you will need to experiment with the field of view angle to effectively zoom out and see more of the floor.

Plane picture

Add triangle class

Add a Triangle class that also inherits from your Shape class. A triangle can be defined by 3 vertices. Add the intersect method using the ray-triangle intersection method (see lecture slide for more details). Add a triangle to a scene and test that it appears correctly.

Triangle picture

Triangle with vertices: (0, 1, -2), (-1.9, -1, -2), (1.6, -0.5, -2)

Rendering complex shape

This section is advanced level so it is optional. To render more complex shapes e.g. teapot you will need to be able to load a mesh from a file and render each of its triangles. A simple mesh loader (OBJloader.h) written in C++ can be downloaded from Week 2 folder. Note this code is limited to only loading meshes of file type OBJ. For using OBJloader.h, you also need to add Vertex.h and GLEW libary header (glew.h) into the project.

The mesh loader handle the import of OBJ file is inside the header file : OBJloader.h. This is a head file only library. It is a simplified version of Bly7 OBJ Loader library ( https://github.com/Bly7/OBJ-Loader ).

Examples of a cube and a teapot OBJ files can also be downloaded from week 2.

Example of mesh class

This section is advanced level so it is optional.

An example of abstract shape class

#pragma once
#include "glm/glm/glm.hpp"

using namespace glm;

class shape
{
private:
   vec3 center;
   vec3 diffuseColor; 
   vec3 diffuseItensity; 

public:
   shape();
   virtual bool intersection(vec3 rayDirection, vec3 rayOrigin, float& t, vec3& IntPt, vec3& normVec);
   
   //for future ray tracing
   virtual void ComputeColor(const float ambientIntensity, const vec3 IntPt, const vec3 lightPt, const vec3 rayDirection, float& ColValue);
   ~shape();
   vec3 position;
   vec3 mcolor;
};

You need to write your own CPP file for shape class

An example of mesh/triangle class

#pragma once
#include "shape.h"
#include "glm/glm/glm.hpp"

class triangle : public shape
{
private:
   vec3 vertex0, vertex1, vertex2;
   vec3 norm0, norm1, norm2;

public:
   triangle(vec3 pos, vec3 v0, vec3 v1, vec3 v2, vec3 color);
   triangle(vec3 pos, vec3 v0, vec3 v1, vec3 v2,
   	vec3 n0, vec3 n1, vec3 n2,
   	vec3 color) :
   	vertex0(v0), vertex1(v1), vertex2(v2), norm0(n0), norm1(n1), norm2(n2)
   {
   	mcolor = color;
   };

   bool intersection(vec3 rayDirection, vec3 rayOrigin, float& t, vec3 &IntPt, vec3& normVec)override;
   void ComputeColor(const float ambientIntensity, const vec3 IntPt, const vec3 lightPt, const vec3 rayDirection, const vec3 tNormvec, float& ColValue);
   ~triangle();
};

An example of triangle interection implementation (Moller-Trumbore method). The method is explained in https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection

bool triangle::intersection(vec3 rayDirection, vec3 rayOrigin, float &t, vec3 &IntPt, vec3 &normVec)
{
	//Moller-Trumbore

	vec3 v0v1 = vertex1 - vertex0;
	vec3 v0v2 = vertex2 - vertex0;

	float u = (dot((rayOrigin - vertex0), (cross(rayDirection, v0v2)))) / dot(v0v1, cross(rayDirection, v0v2));
	float v = (dot(rayDirection, cross(rayOrigin - vertex0, v0v1))) / dot(v0v1, cross(rayDirection, v0v2));

	float w = 1 - u - v;

	if (u < 0 || u > 1)
		return false;
	else if (v < 0 || (u + v) > 1)
		return false;
	else
	{
        //compute intersection point, t and normal vector
		//add your codes here
		return true;
	}
}

Add screen display using SDL2 Library

If you want to output rendering result directly to the screen, you should use SDL2. Simple DirectMedia Layer (SDL2) is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.

SDL officially supports Windows, Mac OS X, Linux, iOS, and Android.

An example of screen display is provide in "SDLPixel.cpp". The example code only display a white screen. You need to add ray casting/tracing codes.

The function PutPixel32_nolock ouput color into computer screen. You need to create a Uint32 color object using convertColour() function.

If you use empty project as startup option in Visual Studio, you must choose the Console option. It is Project Property->Linker->System->SubSystem. SubSystem should set to Console (/SUBSYSTEM:CONSOLE)