Latest addition

I plan to put my code out there in its entirety as a Visual Studio C++ MFC project when I'm good and ready.

Read elsewhere why I chose to not use e.g. OpenGL etc. and just have the fun of doing my own brain work ;-)

I've a lot of hard ray tracing problems that I need to tackle still... many of which will likely remain untackled, but not all.

Soft and expensive

Poisson distribution

I've been working on distribution ray tracing techniques to try and improve e.g. Depth Of Field, Soft Shadows, Glossy Surfaces etc. by implementing poisson distribution.

One of the problems with most distribution techniques in ray tracing is to make a 'randomly enough' distribution of points.

It's easy enough to write a routine that 'randomly' fills e.g. a circle or a quadrant with points... thing is, the points seem to 'cluster' in some areas, while leaving other areas unoccupied.

When doing distributed ray tracing this is known to create artefacts in the images so a more intelligent distribution technique is called for to counteract it.

To explain the problem, think of how we produce soft shadows.

One way of producing soft shadows is to a lot of 'random' rays at a given target area, representing the light source, as seen from the observer (the point we shadow test). This area may be any form, e.g. a circular disk, quadrant or even complex volume.

Often, as in my example below with soft shadows, the area will represent something as seen through the eyes of the observer. This could be the shape of a lamp, as seen from a specific point on a surface.

We want to test if the point we hit is in shadow. If the light source is a represented by only a single point 3D coordinate, we shoot a ray from the point we want to test to the light source.

If the ray hits any object we have shadow.

Otherwise we do not.

And we're done.

Area light sources

If however, the light source has any area or volume things get more complicated. We can't just shoot a single ray and be done because the light source is no longer just represented by a single point we can aim at.

If the ilght source is an old fashioned bolb type of lamp, it's shape will look alot like a circular disk if you look at it from a distance.

Then, if you shoot a ray at one point of the light bolb, the ray may be obstructed by an object but if you shoot it at another part of the bolb, it is not obstructed.

So one very common and popular method of doing soft shadows is to shoot a LOT of rays in a random pattern representing the shape of the light source we're testing against.

The problem with the random method is that points do not seem to do random in a natural way... they seem to cluster in some areas and leave unnatural empty spaces in other.

One approach is to implement poisson distribution of points in a given area. This method tries to mimic natures 'randomness' of e.g. rain drops falling on your cars wind screen.

I've added a class for creating poisson distribution of points based on a 3D center point, a normal vector for direction and (for now) only a radius defining the disk and the desired density/resolution or number of points.

So the method takes the above arguments plus an array, fills the array and returns it with a set of points.

Previously I created the points in a more primitive way, but still quite effectively by defining 2D disk in 3D space with a normal vector for direction and a radius.

So sort of like a DVD-disk positioned somewhere in the scene, it's center being the center of e.g. the light source.

The ray from the hit point to the center of the light source was the normal of the disk.

 

TO BE CONTINUED :-)

 

So, again I've had a pause in development.

However... I managed to squeese in a little development I've wanted to implement for a long time, namely poisson-distribution.

This comes in handy when I want to do distribution ray tracing, e.g. soft shadows, depth of field etc., where multiple ray need to be fired.

My current approach to creating soft shadows at the moment is to create a set of random points on a 3D disk of a given radius by:

1) The position of the ligth source

2) The radius of the light source (disk or sphere)

3) A vector from the hit point in the scene to the center of the light source

Heres how I do the soft shadow right now:

/**
Returns:
0.0 if in full shadow
1.0 if no shadow
0.0-1.0 if partially in shadow (soft shadows)
*/
// -----------------------------------------------------------------------------------------
auto Render::CheckForShadowingShape(Ray& ray, Light* light, Vector_3D& toLight)
{
    auto shadowingShapeFound = false;

    if (/*ray.hitPoly && */(light != nullptr))
    {
        if (light->softShadows)
        {
            // Create a number random points within the DOF_ApertureDiscRadius and eye as the center.
            // The DOF_ApertureDisc is NOT in a plane parallel with the image plane.
            // It has its own normal, which is almost never parallel with the image plane's normal

            /*
            Since the direction is almost _never_ perpendicular to the image plane
            we cannot use e.g. the upVector of the image plane to compute a point in the disc.
            We need a vector that lies in a plane which has the direction as a normal. The direction
            of the vector doesn't matter, as long as it is in that plane.
            Such a vector can be calculated by crossing the direction with a vector from the eye
            to on of the corners in the image plane.
            */

            Vector_3D arbitraryFromLightCenter;
            Vector_3D vCrossed;

            arbitraryFromLightCenter.x = light->position.x + 10000000000000000000.0;
            arbitraryFromLightCenter.y = light->position.y + 10000000000000000000.0;
            arbitraryFromLightCenter.z = light->position.z + 10000000000000000000.0;
            VNormalize(arbitraryFromLightCenter);
            vCrossed = toLight ^ arbitraryFromLightCenter;
            VNormalize(vCrossed);

            Point_3D p;
            Ray shadowRay;
            double radius;
            double ang;
            auto rMax       = light->radius * 2.0;
            auto allTrue    = true;
            auto allFalse   = true;
            auto sample     = 0;
            auto doContinue = true;

            shadowFunctionCallS++;

            // Now fire rays into the scene based on the points generated above:
            do
            {
                scene->thread_stats.shadowRays++;

                radius           = light->radius - ((double)rand() / (RAND_MAX + 1) * rMax);
                ang              = (double)rand() / (RAND_MAX + 1) * 360.0;
                p.x              = light->position.x + radius * vCrossed.ux;
                p.y              = light->position.y + radius * vCrossed.uy;
                p.z              = light->position.z + radius * vCrossed.uz;
                discP[sample]    = Point_3D::rotPointFromFormula(light->position, toLight, p, ang);
                shadowRay.origin = ray.intersect;

                VSetByPoints(shadowRay.dir, ray.intersect, discP[sample]);
                VNormalize(shadowRay.dir);

                shadowRay.fromShape = ray.hitShape;
                shadowRay.hitPoly   = ray.hitPoly;
                shadow[sample]      = TestShadowRay(ray, shadowRay, light);

                if ((shadow[sample] == true) && allTrue)
                {
                    allFalse = false;
                    if (sample > light->softShadowsSamplesThreshold)
                    {
                        doContinue = false;
                        scene->thread_stats.shadowNofTimesMinSamplesReached++;
                    }
                }
                else if ((shadow[sample] == false) && allFalse)
                {
                    allTrue = false;
                    if (sample > light->softShadowsSamplesThreshold)
                    {
                        doContinue = false;
                        scene->thread_stats.shadowNofTimesMinSamplesReached++;
                    }
                }

                sample++;
            } while( (sample < light->softShadowSamples) && doContinue );

            scene->thread_stats.shadowAvgSamples = (double)scene->thread_stats.shadowRays / shadowFunctionCallS;

            // 'sample' is the actual number of samples that were taken. 'sample' may be smaller
            // than "light->softShadowSamples", if the sampling was discontinued due to threshold
            auto nofSamples  = sample;
            auto nofInShadow = 0;

            for (auto k = 0; k < nofSamples; k++)
            {
                if (shadow[k])
                {
                    nofInShadow++;
                }
            }

            if (nofSamples > scene->thread_stats.shadowMaxSamples)
            {
                scene->thread_stats.shadowMaxSamples = nofSamples;
            }

            auto lightFct = 1.0 - ((double)nofInShadow / (double)nofSamples);
            return lightFct;
        }
        else
        {
            Ray shadowRay;
            shadowRay.origin    = ray.intersect;
            shadowRay.dir       = toLight;
            VNormalize(shadowRay.dir);
            shadowRay.fromShape = ray.hitShape;
            shadowRay.hitPoly   = ray.hitPoly;
            shadowingShapeFound = TestShadowRay(ray, shadowRay, light);

            scene->thread_stats.shadowRays++;
            if (shadowingShapeFound)
            {
                return 0.0;
            }
            return 1.0;
        }
    }
    return 0.0;
}

----------------------------------------------------------------

So if you study that code you'll se that I create kind of a disk in space.

  • The center of the disk is the center of the ligth source

  • The normal of the disk is represented by the vector from the hit point to the light

It has the center of the light source, the vector from the hit point in the scene, and the radius of the 'lamp'.

This approach has actually produced some stunningly beautiful results so I've been retluctant to spend time on changing my stuff.

However, I do like the idea of space being used as effeciently as possible; welcome poisson.

I havent implemented the thing yet, but I suspect I will have a possin class, which I hand:

PoissonDisk( double diskRadius, int nofPoints, Vector_3D diskNormal, Point_3D diskCenter, DISK_SHAPE diskShape );

It will return me a series of points at which to shoot my shadow rays.