Raytrace File Format

File Format: Overview and Philosophy

The input file for the raytracer consists of a single JSON file, which defines the scene to be rendered, the lights to use, as well as several parameters to configure the raytracing process. The root of the JSON file is an object/dictionary, with several keys:

  • "scene" maps to another JSON Object that defines the scene graph, as described below (default: a red sphere)
  • "lighting" maps to another JSON Object that defines the lighting of the scene, as described below (default: basic lighting)
  • "camera" contains the desired position of the camera, as a vector (default: (0,0,0))
  • "view" contains the view direction of the camera, as a vector (default: (0,1,0))
  • "background" contains the color to be used when a ray does not hit any object (default: gray(128)
  • "reflections" contains the maximum number of reflective rays to be used (default: 0)
  • "fov" defines the field of view of the camera (default: 90)

As can be seen, every key has a default value, and could therefore be omitted. Indeed, the shortest valid scene file would be just {} (an empty object), which results in a red sphere to be rendered with basic lighting:

As can be seen below, default values are available for almost every possible value, allowing for very succinct files. The files provided with the framework, however, are more complete than necessary (unless otherwise noted) to serve as a basis for further modification. Before getting to the description of the scene graph and lighting information, I will first briefly describe how some of the more fundamental data structures are defined.

Vectors and Colors

3-dimensional vectors and colors (in RGB-format) are the two most common composite data types used by our raytracer. Both are represented as JSON objects with 3 entries. For vectors we have "x", "y" and "z", each with a default value of 0, and for colors we have "red" (or just "r"), "green" (or "g"), and "blue" (or "b"), also with default values of 0. The color (255,0,0) (i.e. red) could be represented as {"red": 255}, or {"r": 255", "g": 0, "b": 0} (or a combination thereof). Note that if both "red" and "r" are present as keys, the former takes precedence (and likewise for the other abbreviations). For colors, it is also possible to define gray-scale values by using the key "gray" (or "color") instead of RGB values. A color of {"gray": 128} (or {"color": 128}) would be equivalent to a color of {"r": 128, "g": 128, "b": 128}, which is a medium gray. The key "gray" takes precedence over the key "color", which takes precedence over the keys "red"/"r", "green"/"g", and "blue"/"b".

Some examples:

  • {"x": 0, "y": -100, "z": 0} and {"y": -100} both define the vector (0,-100,0).
  • {"x": 0, "y": 1} and {"y": 1} both define the vector (0,1,0).
  • {"r": 64, "g": 64", "b": 64}, {"gray": 64}, {"color": 64} are all equivalent ways of writing the color (64,64,64), a dark gray.
  • {"r": 255, "b": 255} and {"red": 255, "green": 0, "blue": 255} both define the color (0,255,255)/purple.

Polymorphism

You should be familiar with the concept of polymorphism from an object-oriented programming perspective: The ability to use objects of different types as long as they have the same interface. It so happens that in our raytracer there will be several different object types that have similar interfaces, but may have different attributes, and we want to store them in our JSON file. The file format takes a generic approach to this: Each time there are multiple possible types that could occur in a certain position, there is a key "type" that defines what other keys the object may have. For example, as we will see in more detail below, spheres and cylinders are both scene objects, and may show up wherever a scene object may show up. A JSON object encoding either of them will therefore have a key "type" with the value "sphere" or "cylinder" (as appropriate), and if the type is "sphere" there will be other keys like "center" and "radius", whereas if the "type" is "cylinder" the other keys are "radius" and "height". The documentation below will note which keys are applicable for which "type". As with other keys, "type" has a default value in all cases.

Materials

Before we discuss the actual scene graph description, we need to be able to define “materials”. These materials define how the surface of our objects interacts with the lighting in the scene. There are three "type"s of materials: "default" (unsurprisingly, also the default value for the "type" key), "textured" and "procedural". Default materials only carry a surface color, while textured materials apply an image to the surface, and procedural materials are able to execute arbitrary code to determine surface colors. All material types share the attributes/keys:

  • "ka": For the strength that ambient light is reflected with (default: 0.5)
  • "kd": For the strength that light is used for the diffuse component (default: 0.3)
  • "ks": For the strength that light is used for the specular component (default: 0.2)
  • "alpha": For the “shininess” constant that controls the size of the specular highlights (default: 3)
  • "reflectiveness": Defines how much of the incoming ray should be reflected vs. use the surface color. Ranges from 0 (no reflection) to 1 (perfect mirror), with a default of 0.
  • "transparency": Defines how much of the incoming ray should “go through” the object, vs. use the surface color. Ranges from 0 (completely opaque) to 1 (completely transparent), with a default of 0.
  • "refractionIndex": Defines how rays are refracted (“bent”/redirected) when they enter a transparent material. The refraction index of the outside/air is assumed to be 1, which is also the default value for this property.

The material "type"s only differ in one attribute. Materials with "type" "default" additionally have the attribute value:

  • "color", which defines the surface color (default: (255,0,0)/red)

Materials with "type" "textured", on the other hand, have the an additional attribute values:

  • "texture" which contains a file name referring to a texture file located in the sketch’s data directory. The default value for this property is "default.png", which is a file containing a blue/white chessboard pattern.

Finally, materials with "type" "procedural" have the additional attribute:

  • "name", which is one of the defined (as code) procedural materials. The framework comes with two example materials, named "SineWave" and "Lava" (the default value), but you can add your own for bonus points.

Three example materials:

{
   "ka": 0, 
   "kd": 0.6, 
   "ks": 0.2, 
   "alpha": 2, 
   "color": {"r": 255, "g": 255, "b": 255}
}

This material defines a white surface (using the "default" material type), that ignores ambient lighting (ka is 0), and is neither reflective nor transparent (the default values for both of these properties are 0).

   {
    "type": "textured"
    "ka": 0.2, 
    "kd": 0.8, 
    "ks": 0, 
    "texture": "earth.png",
    "transparency": 0.99, 
    "refractionIndex": 1.33
   }

This material defines a textured surface ("type" is "textured") that puts an image of the "earth" (the value of "texture") onto the surface and does not have any specular highlights ("ks" is 0). However, the material is almost entirely transparent ("transparency" is 0.99), and it will therefore mostly act as a lens, and refract the light.

{
   "type": "procedural",
   "name": "Lava"
}

This material will use the code in LavaMaterial to determine the surface of the object it is applied to, and uses all default values for the material properties (so "ka" is 0.5, "kd" is 0.3, etc.).

Scene Graph

To actually render anything with our raytracer, we need to define the object or objects that exist in the world. This is done using a scene graph, which is a (rooted) tree. There are three larger groups of node types in this tree:

  • Leaf nodes represent individual geometric primitives like spheres and cylinders.
  • Constructive Solid Geometry (CSG) nodes represent set operations that combine multiple child-nodes into a larger scene
  • Transformation nodes define movement and rotation of a single child node

A “scene” is such a tree where the root can be any of the node types, with children as appropriate. For example, it would be possible to have a scene that consists of a single sphere, or a scene that consists of the union of a sphere and the intersection of a cylinder and a plane. The "type" key, as usual, defines which type of node we have (default: "sphere").

Primitives

Each geometric primitive has its own node "type". Each of the primitives listed below has a key "material" that contains a material definition as described above, and additional attributes depending on its type. The supported primitive types are:

  • "sphere", which has the additional attributes "radius" (float, default: 1), and "center" (vector as described above, default (0,5,0))
  • "plane", which has the additional attribute "center" (vector, default (0,0,0)) and "normal" (vector, default (0,1,0)) to define the plane, as well as "scale" to define the size of the texture grid
  • "triangle", which has the additional attributes "v1", "v2", and "v3" representing the three vertices the triangle is defined by (in counterclockwise order!), as well as "tex1", "tex2" and "tex3" for the corresponding (2D) texture coordinates; the default values for these texture coordinates are (1,0) for "tex1", (0,1) for "tex2" and (0,0) for "tex3", for reasons also explained on the textures page. However, note that the default value for each coordinate is 0, i.e. {"tex1": {"y": 0}} will result in "tex1" being (0,0). Only if "tex1" is omitted entirely will the value be (1,0).
  • "cylinder", which has the additional attributes "radius" (float, default: 1), and "height" (float, default -1, where a negative value means “infinite height”)
  • "cone", "paraboloid", "hyperboloidone", "hyperboloidtwo" corresponding to cones, paraboloids, hyperboloids of one sheet and hyperboloids of two sheets, respectively; all of these use the attribute "scale" to define the size of the texture grid

As you will notice, some primitives (like the cylinder) can not be repositioned or rotated. This is done in order to make the raytracing equations easier, but of course we would like to be able to have cylinders that are not just at the origin and upright. For this, the transformation operations, described in the next section, are useful.

Transformation Operations

There are two transformation operators, one that combines movement and rotation, and another for scaling. The "type" for the former is "moverotation", and it additionally has the following attributes:

  • "movement": A vector that describes how many units the child shall be moved in each of the three cardinal directions.
  • "rotation": A vector that describes how many degrees the child shall be rotated around each of the principal coordinate axes.
  • "child": A single child scene object to which the transformation is applied (which may, in turn, be a primitive, one of the CSG operations described below, or even another transformation, although the usefulness of the latter is limited, as the two could be combined into one).

Note that the semantics of this transformation are: First the child is rotated around the z-axis, then around the x-axis, then around the y-axis, and then the movement is applied in the global coordinate system. This means, if an object is centered at the origin, the rotation will be applied first (in z,x,y-order), resulting in the object still being at the origin but in the desired orientation, before it is moved to the desired location.

The scaling operator has the "type" "scaling" and one other attribute "scaling" which is a three-dimensional vector that contains the scaling in each dimension. Note: In contrast to other vectors, the default value for each entry in this vector is 1, i.e. no scaling.

CSG Operations

Finally, scenes can also be composed of multiple objects. We will support three constructive solid geometry (CSG) operations: "union", "intersection" and "difference". Each of these operations is related to the set-operation of the same name: The union of a list of children consists of all points that are in any of the children, while the intersection of a list of children consists of all points that are in all of these children. The difference, finally, can only be applied to exactly two objects a and b, and consists of all points that are in a but not in b (note that b itself could be composite, though, e.g. the union of multiple other objects).

For union and intersection, the "type" value should be "union" or "intersection", respectively, and the object has one additional key "children", which is a list of further scene objects, of which the union or intersection is to be taken.

For the difference operation, the "type" value should be "difference", and there are two additional keys "a" and "b", resulting in "a" without/minus "b".

Note: Unlike most other keys, neither "children" nor "a" and "b" have default values.

Lighting

For lighting, our raytracer has two options: The basic lighting model is, as the name implies, just a simple way to get some 3-dimensional appearance in the output. The “actual” lighting model we want to use is the phong lighting/reflection model that consists of ambient, diffuse and specular parts. As with other objects, the "type"-key defines the type of the lighting model: "basic" or "phong". Both lighting models use a key "lights" which is a list of light objects. Each light object contains a "position" (default: (-3, -2, -5); it is offset from the origin to provide nice lighting to the quadrics that may sit at the origin), a "color" (or "diffuse", which takes precedence) with default value color(128), and a "specular" color (if not present, it will use the same color as "diffuse"). Note that if no "lights"-key is present, a single light object with default values (located at (-3, -2, -5) with color(128) for diffuse and specular) will be created. If, for some reason, you don’t want any lights in your scene you can add the "lights" key with an empty list as the value.

The "phong" lighting model has two additional keys: "ambient" defines the ambient color (default: color(128)) and "shadows" is a boolean value indicating whether shadow rays should be used or not (default: false).

Raytracing Parameters

Finally, the input file contains several keys at the root level that control how raytracing is performed:

  • "reflections" defines how many levels of reflection should be performed when a ray hits a reflective surface (default: 0)
  • "background" defines the color to be used when a ray does not hit any object (default: color(128))
  • "camera" defines the position of the camera (default: (0,0,0))
  • "view" defines the forward direction of the camera, i.e. the direction of the center ray (default: (0,1,0)). Note that in order to define the viewport uniquely, another direction also needs to be defined; in our case, the “left” direction is the cross product of the global z-axis and the forward direction, and the “up” direction follows from these two. The case where view is (0,0,1) is left undefined.
  • "fov" defines the angular field of view (in degrees) of the camera (default: 90). Note that our images are square, and the field of view is the same in vertical and horizontal direction.