Introduction
Extending our ray tracing algorithms now we will implement a camera class.
The objective is to render the images setting the viewer at any position without restrictions since in our previous models, the viewer must be located at positive z axis, (0,0,z) z>0.
Background
Although not implementing raytracing in this article, it is required to have an advanced level in mathematics and also read the previous articles on Raytracing on CodeProject.
Link to previous articles
The objective is to render the images setting the viewer at any position without restrictions as our previous model where the viewer must be located at positive z axis, (0,0,z) z > 0.
What is a pinhole camera?
"...A pinhole camera is a very simple camera with no lens and a single very small aperture. Simply explained, it is a light-proof box with a small hole on one side. Light from a scene passes through this single point and projects an inverted image on the opposite side of the box. Cameras using small apertures and the human eye in bright light both act like a pinhole camera.
The smaller the hole, the sharper the image, but the dimmer the projected image. Optimally, the size of the aperture should be 1/100 or less of the distance between it and the screen..."
See http://en.wikipedia.org/wiki/Pinhole_camera
A pinhole camera
Step 1 - Defining Parameters
In order to perform all calculations, we do need some predefined parameters:
- Camera position Cp(x,y,z)
- Viewing direction vector V (x,y,z) normalized... means its module = 1 |V| = 1
- Focal length F (the distance between Camera position Cp and the camera projection plane S)
From the above parameters, we can retrieve other parameters which will be useful further:
- P, a point located at the plane
P = Cp-V*F(x,y,z)
From math books plane equation is in the form Ax+By+Cz+D=0
and also if we have a normal vector it is quite simple, and luckily we do have it and it is V.
From the same book...
A = Vx
B = Vy
C = VZ
we need to get D, but it simple now because we do have a point at the plane
so D = -(A*Px+B*PY+C*PZ)
A = normalVector.x;
B = normalVector.y;
C = normalVector.z;
D = -(normalVector.x * point.x + normalVector.y * point.y + normalVector.z * point.z);
The Camera Coordinates System
Now let's call pinhole model as camera coordinates system.
So to represent a model, now we do have a Cartesian reference system x,y,z and also a camera coordinates system.
The plan is to translate and rotate our camera coordinates system to fit the reference Cartesian coordinates system, for this we need some equations on spherical coordinates to retrieve the theta and phi angles.
"...In mathematics, a spherical coordinate system is a coordinate system for three-dimensional space where the position of a point is specified by three numbers: the radial distance of that point from a fixed origin, its elevation angle measured from a fixed plane, and the azimuth angle of its orthogonal projection on that plane, measured from a fixed reference direction on the same. The elevation angle is often replaced by the inclination angle measured from a zenith direction perpendicular to the reference plane. ..."
See http://en.wikipedia.org/wiki/Spherical_coordinate_system
Spherical coordinates
From my math book, we get how to represent a given R3 point into spherical coordinates and also conversions... (it will be used further).
Converting Cartesian coordinates to spherical
x = R sin (phi) cos (theta)
y = R sin (phi) sin (theta)
z = R cos (phi)
Converting spherical coordinates to Cartesian
R = sqrt(x2+y2+z2)
S = sqrt(x2+y2)
phi = acos(z/R)
theta = x>=0 asin(y/S)
x<0 PI - asin(y/S)
Follows how to calculate phi and theta angles, this will help us to rotate our coordinate system to fit the reference system
Point oNormalizedDirection = Algebra.Normalize(m_oDirection.x,
m_oDirection.y, m_oDirection.z);
m_oFrontPoint = new Point(m_oOrigin.x + oNormalizedDirection.x,
m_oOrigin.y + oNormalizedDirection.y,
m_oOrigin.z + oNormalizedDirection.z);
Point oTranslatedFrontPoint = new Point(m_oFrontPoint.x - m_oOrigin.x,
m_oFrontPoint.y - m_oOrigin.y,
m_oFrontPoint.z - m_oOrigin.z);
double x2 = oTranslatedFrontPoint.x * oTranslatedFrontPoint.x;
double y2 = oTranslatedFrontPoint.y * oTranslatedFrontPoint.y;
double radius = Math.Sqrt(x2 + y2 + oTranslatedFrontPoint.z * oTranslatedFrontPoint.z);
double s = Math.Sqrt(x2 + y2);
m_dPhi = Math.Acos(oTranslatedFrontPoint.z / radius);
m_dTheta = 0;
if (Math.Abs(s) > 1.0E-5)
{
if (oTranslatedFrontPoint.x >= 0)
{
m_dTheta = Math.Asin(oTranslatedFrontPoint.y / s);
}
else
{
m_dTheta = Math.PI - Math.Asin(oTranslatedFrontPoint.y / s);
}
}
Calculating Projections
Projection
Our first objective is to calculate the projected point a
at the plane S
.
How To Get This?
From my old math book again...
We can easily calculate the intersection between a line and a plane if we have both equations, and luckily again we do!
Our line equation in parametric form is r = p + t*v.
- p is any point at the line t is an scalar
- v is the line vector
- our p is the point a
- our v can be calculated from the Camera position to a as Cp-a
Using this parametric equation against the plane equation, we find the parameter t and so we do find the intersection point using
Code
public Point GetIntersection(Point p, Point Origin)
{
Vector v = new Vector(p, Origin);
double E = (v.x * A + v.y * B + v.z * C);
if (Math.Abs(E)<1.0E-5)
{
return null;
}
v = Algebra.Normalize(v.x, v.y, v.z);
double t = -(A * p.x + B * p.y + C * p.z + D) / (E);
return new Point(p.x + t * v.x, p.y + t * v.y, p.z + t * v.z);
}
Our last objective is to map this intersection point into our screen.
For this, we need to fit the point into our reference coordinate system using the calculated phi and theta...
Code
public Point LocalToCartesian(Point point)
{
point.x -= m_oOrigin.x;
point.y -= m_oOrigin.y;
point.z -= m_oOrigin.z;
rtPoint.RotZ(-m_dTheta, ref point.x, ref point.y);
rtPoint.RotY(m_dPhi, ref point.x, ref point.z);
return new Point(point.x, point.y, point.z);
}
Using the Code
All required code is in a project in the zip file at the top of the article, just download and compile.
Points of Interest
It is very challenging to keep improving the algorithms on my own and using only math instead of using libraries. Many people ask me why to create something which is already in many libraries. The response is that I like the challenge, the discovery, and it is very satisfactory to see the results by my own hands and effort.
History
Creating a camera class is a next step in the raytracing articles series, it will bring us to the next level and allow to start creating animations.