Rendering a Sphere

In order to define our first shape, that can be used by the ray tracing renderer. We create a sub-class to Shape called Sphere. Enter the following text in your favourite editor under the file name Sphere.H:

#ifndef _Sphere_H
#define _Sphere_H

#include "Shape.H"

class Sphere : public Shape {
public:
    // Initialize sphere
    Sphere(const Transform *o2w,
           const Transform *w2o,
           bool ro,
           Float rad,
           Float z0,
           Float z1,
           Float phi);

    // Check if ray intersects shape
    virtual bool Intersect(const Ray &r,
                           Float *pT,
                           Float *pEpsilon,
                           OSL::ShaderGlobals *pSg) const;

private:
    // Private data
    Float radius;
    Float phimax;
    Float zmin;
    Float zmax;
    Float thetamin;
    Float thetamax;
};

#endif // _Shape_H

Before moving along with the implementation, we add one more conviniance function to Math.H. Nothig strance about this one, just make sure that a value is within a specified range:

// Clamp floating point value to range
inline Float Clamp(Float val, Float low, Float high)
{
    Float result;

    if (val < low)
    {
        result = low;
    }
    else if (val > high)
    {
        result = high;
    }
    else
    {
        result = val;
    }

    return result;
}

Now we are finnaly ready to define the sphere. In order for the ray tracer to know if an object is visible from the view plane, a series of rays are shoot out from a virtual eye in the world coordinates. If the ray hits an object, then a sample of a point on the objects surface is rendered. Therefor the most important method in the Shape class is the Intersect method. This method will answer the question wheter the ray hits the surface or not. If it is a hit, information on how to shade the surface will be stored in the ShaderGlobals data structure we talked about when learning how to use the Open Shading Language. The information we entered there then was meaningless, now we will have a little bit of more information put in there. The implementation of the Sphere class can be found in the file Sphere.C:

#include "Sphere.H"

// Initialize sphere
Sphere::Sphere(const Transform *o2w,
               const Transform *w2o,
               bool ro,
               Float rad,
               Float z0,
               Float z1,
               Float phi)
    : Shape(o2w, w2o, ro) {
    radius = rad;
    zmin = Clamp(std::min(z0, z1), -radius, radius);
    zmax = Clamp(std::max(z0, z1), -radius, radius);
    thetamin = acosf(Clamp(zmin / radius, -1.0f, 1.0f));
    thetamax = acosf(Clamp(zmax / radius, -1.0f, 1.0f));
    phimax = Radians(Clamp(phi, 0.0f, 360.0f));
}

// Check if ray intersects shape
bool Sphere::Intersect(const Ray &r,
                       Float *pT,
                       Float *pEpsilon,
                       OSL::ShaderGlobals *pSg) const
{
    Float phi;
    Vec3  phit;
    Ray   ray;

    // Transform ray to object space
    pWorldToObject->TransformRay(r, ray);

    // Compute quadratic sphere coefficients
    Float A = ray.d[0] * ray.d[0] +
              ray.d[1] * ray.d[1] +
              ray.d[2] * ray.d[2];
    Float B = (ray.d[0] * ray.o[0] +
               ray.d[1] * ray.o[1] +
               ray.d[2] * ray.o[2]) * 2.0f;
    Float C = ray.o[0] * ray.o[0] +
              ray.o[1] * ray.o[1] +
              ray.o[2] * ray.o[2] - radius * radius;

    // Solve quadratic equation for t values
    Float t0, t1;
    if (!Quadratic(A, B, C, &t0, &t1)) {
        return false;
    }

    // Compute intersection distance along ray
    if (t0 > ray.maxt || t1 < ray.mint) {
        return false;
    }

    Float thit = t0;
    if (t0 < ray.mint) {
        thit = t1;
        if (thit > ray.maxt) {
            return false;
        }
    }

    // Compute sphere hit position and phi
    phit = ray(thit);
    if (phit[0] == 0.0f && phit[1] == 0.0f) {
        phit[0] = 1e-5f * radius;
    }
    phi = atan2f(phit[1], phit[0]);
    if (phi < 0.0f) {
        phi += 2.0f * M_PI;
    }

    // Test sphere intersection against clipping parameters
    if ((zmin > -radius && phit[2] < zmin) ||
        (zmax <  radius && phit[2] > zmax) ||
        phi > phimax) {
        if (thit == t1) {
            return false;
        }

        if (t1 > ray.maxt) {
            return false;
        }
        thit = t1;

        // Compute sphere hit position and phi
        if (phit[0] == 0.0f && phit[1] == 0.0f) {
            phit[0] = 1e-5f * radius;
        }

        phi = atan2f(phit[1], phit[0]);
        if (phi < 0.0f) {
            phi += 2.0f * M_PI;
        }

        if ((zmin > -radius && phit[2] < zmin) ||
            (zmax <  radius && phit[2] > zmax) ||
            phi > phimax) {
            return false;
        }
    }

    // Find parametric representation of sphere hit
    Float u = phi / phimax;
    Float theta = acosf(Clamp(phit[2] / radius, -1.0f, 1.0f));
    Float v = (theta - thetamin) / (thetamax - thetamin);

    // Compute sphere dpdu and dpdv
    Float zradius = sqrtf(phit[0] * phit[0] +
                            phit[1] * phit[1]);
    Float invzradius = 1.0f / zradius;
    Float cosphi = phit[0] * invzradius;
    Float sinphi = phit[1] * invzradius;
    Vec3 dpdu(-phimax * phit[1], phimax * phit[0], 0.0f);
    Vec3 dpdv = (thetamax-thetamin) * Vec3(phit[2] * cosphi,
                                           phit[2] * sinphi,
                                           -radius * sinf(theta));

    // Initialize ShaderGlobals from parametric information
    const Transform &o2w = *pObjectToWorld;
    pSg->P    = o2w.TransformPoint(phit);
    pSg->u    = u;
    pSg->v    = v;
    pSg->dPdu = o2w.TransformVector(dpdu);
    pSg->dPdv = o2w.TransformVector(dpdv);

    // Store hit point for quadratic intersection
    *pT = thit;

    // Compute Epsilon for quadric intersection
    *pEpsilon = 5e-4f * *pT;

    return true;
}

As you can see we solve the quadratic equation to find the first parameter value for the ray on surface intersection, stored as t. Also notice that we estimate the magnitude of the error in this method as epsilon, this may later be used to
in order to avoid so called surface self intersection, when rendering shadows.

Some ideas for the sphere object has been "borrowed" from the the Physically Based Rendering Techniques Renderer, a.k.a pbrt, enjoy!

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License