Skip to content

A Dynamic Fully Fledged Raytracing Engine Built in C++

Notifications You must be signed in to change notification settings

mrrosoff/Monte-Carlo-RayTracing-Engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

98 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Monte Carlo Raytracing Engine

A Dynamic Fully Fledged Raytracing Engine Built in C++

About Project

An CPU based engine built to render complex scenes with all the capabilities of real world private engines. Images are outputted in the PPM format and use a simple driver file system for creation.

The engine uses a Monte Carlo technique to render images. This process randomly fires rays on contact with Lambertian materials. This creates much more accurate lighting than traditional Ray Tracing. The downside is of course speed. The engine can handle simple Lambertian surfaces, metal surfaces with total reflection, and refractive surfaces such as glass. The engine also produces accurate shadows and highlights and has complex material control.

Example Images

SquareSpheres

Shown below are the resulting images when running the engine with the driver "SquareSpheres". This driver file consists of only spheres.

Number of Samples Resulting Image
10 Samples 10 Samples
100 Samples 100 Samples
1,000 Samples 1000 Samples
10,000 Samples 10000 Samples
100,000 Samples 100000 Samples

Spheres

Shown below are the resulting images when running the engine with the driver "Spheres".

Number of Samples Resulting Image
10 Samples 10 Samples
100 Samples 100 Samples
1,000 Samples 1000 Samples
10,000 Samples 10000 Samples

Mando

Shown below are the resulting images when running the engine with the driver "Mando". Due to the high triangle count of this driver file, rendering times are very long.

Number of Samples Resulting Image
10 Samples 10 Samples
100 Samples 100 Samples

Installation

Building the Executable

After downloading the repository, navigate to the directory where the repository is stored.

From the command line, enter the following commands.

mkdir build
cmake .. && make

At this time, we do not support Windows or MacOS.

Running the Engine

After You Have Built the Executable, Run The Program Using the Command

./raytracer [Driver File] [Ouput File] [Samples]

The output format for the image is the PPM model. You can open such images in most image viewers and applications such as Adobe Photoshop. The number of Samples directly coresponds to the amount of noise in the image. The higher the samples, the higher quality of an image.

Node Driver

In addition, you can build this project as a Node.js package. Simply install the package with the following command.

npm install

Or if you are in another project use the following command.

npm install raytracer-node

Here are a couple of example functions that utilize the library (written with ES6 syntax). Please note that node is single threaded, and as such, this library will appear to "crash" your thread. It will complete its task given time. If you wish to use a rendering engine with this program, I would recommend looking into experimental node features or using worker windows in something like electron to hide the slowdown.

function runRaytracer() {
    createImage(
        [
            "0 0.25 -30", 
            "0 0 1", 
            "0 1 0", 
            "-10", 
            "-10 10 -10 10", 
            "400 400", 
            "100"
        ],
		[
			"0 0 -10000000030 10000000000 0.3 0.3 0.8",
			"0 0 10000000020 10000000000 0.3 0.3 0.8",
			"0 -10000000020 0 10000000000 0.9 0.9 0.9",
			"0 10000000020 0 10000000000 0.9 0.9 0.9",
			"0 59.5 0 40 1.5 1.5 1.5 light",
			"-10000000020 0 0 10000000000 0.3 0.8 0.3",
			"10000000020 0 0 10000000000 0.8 0.3 0.3",
			"-7 -17.1 0 3 0.8 0.8 0.8 glass",
			"5 -17.1 5 3 0.8 0.8 0.8 mirror"
		])
}
import raytracer from 'raytracer-node';

var lastCall = 0;

function createImage(sceneData, sphereData) {
    const emitter = new EventEmitter()

	emitter.on('start', (file) => console.log(file));
	emitter.on('progress', (percent, time) =>
	{
        let splitTime = time.split(" ");
        splitTime[3] = parseFloat(splitTime[3]).toFixed(2);

        let fixedString = splitTime.join(" ");

        if (new Date() - lastCall < 500)
        {
            return false;
        }

        lastCall = new Date();
        console.log("Percent Complete", parseInt(percent.substring(0, 2)), "Time Remaining", fixedString);
    });
    emitter.on('finish', (time) => console.log(time));
    const imageString = raytracer(emitter.emit.bind(emitter), sceneData, sphereData);

    emitter.removeListener('start', () => {});
	emitter.removeListener('progress', () => {});
	emitter.removeListener('finish', () => {});

    return imageString;
}
function generateHTMLElement(imageString) {
    let imageDataLines = outputImageString.split("\n");
    let imageDataLinesBroken = [];

    imageDataLines.forEach((line) =>
    {
        imageDataLinesBroken.push(line.split(" "));
    });

    let width = imageDataLinesBroken[1][0];
    let height = imageDataLinesBroken[1][1];

    let c = document.createElement("canvas");
    let ctx = c.getContext("2d");

    c.width = width;
    c.height = height;

    let myImageData = ctx.createImageData(width, height);
    let counter = 0;

    let imageData = myImageData.data;

    for (let i = 2; i < imageDataLinesBroken.length; i++)
    {
        for (let j = 0; j < imageDataLinesBroken[i].length; j++)
        {
            imageData[counter] = parseInt(imageDataLinesBroken[i][j]);
            counter++;

            if ((j + 1) % 3 === 0)
            {
                imageData[counter] = 255;
                counter++;
            }
        }
    }

    ctx.putImageData(myImageData, 0, 0);
    return c.toDataURL("image/png");
}

Creating Driver Files

For your convenience, example driver files have been provided under the folder titled ExampleDriverFiles. These driver files are all fully featured to create interesting and dramatic images that showcase the features of the engine.

If you wish to create your own driver files, the following fields are required for each element:

eye X Y Z  
look X Y Z  
up X Y Z  
d Distance  
bounds Left Right Bottom Top  
res Width Height  
sphere X Y Z Radius AlbedoRed AlbedoGreen AlbedoBlue  
model RX RY RZ RTheta ScaleFactor TX TY TZ SmoothingTheta FilePath

For Spheres and for Model Material Files, optional fields exist. Appending light, mirror, or glass to the end of a sphere line or material description will activate the corresponding property for that section of the image. See reference driver files for examples. Make sure that the bounds and resolution share an aspect ratio. This is important to ensure no artifacts.

For a node install, you directly input the fields above into the function call. The first arguement to the library will be an emitter, followed by the scene data, and then the sphere data. You must also append the number of samples required to the scene data, as shown in the example above.

Where to Find OBJ Files

Finding obj files is inconsequential. Simply look up ".obj files" on the internet and choose one that fits your liking. Make sure that you create the proper material file using the format showcases in the example files. Files with Triangle counts of over 10K will result in images that take a significant time to render.