Scenefile Documentation

Scenefiles are the way we specify the information needed to generate 3D scenes in this class. The structure documented here is specific to the way our scene parser was designed. Once lab 5 is released, you can refer to it for complementary information/specific examples.

Basic Structure

The structure of the scenefile is organized as a declaration of the globalData, cameraData, templateGroups (optional), and groups. Scenes are specified in json format. The general format of the file is as follows:

{
  "name": "root",
  "globalData": {
    "..."
  },
  "cameraData": {
    "..."
  },
  "templateGroups": {
    "..."
  },
  "groups": [
    {
      "name": "Group Name",
      "translate": "...",
      "rotate": "...",
      "scale": "...",
      "lights": [
        {
          "..."
        }
      ],
      "primitives": [
        {
          "..."
        }
      ],
      "groups": [
        {
          "..."
        }
      ]
    }
  ]
}

Below we have a few of the most basic field-value pairings we use for attributes of the scenefile. This is mainly included so as to be referenced in later sections and so that you can get a feel for the syntax of the scenefile.

  • float — a single floating point number with syntax (ex: 10.2):
    { "field": 10.2 }
    
  • vec3 — three floating point numbers that represent a vector for a displacement or scaling factors in the x-y-z plane with syntax (ex: (10.2, 3.9, 8.6)):
    { "field": [10.2, 3.9, 8.6] }
    
  • vec4 - four floating point numbers that represent the axis of rotation (x, y, z) as well as the angle of rotation in degrees (w) with syntax (ex: (1.0, 0.0, 0.0), 90°):
    { "field": [1.0, 0.0, 0.0, 90] }
    
  • RGBSchema — three floating point numbers that represent a color in the red-green-blue (values 0-1) color space with syntax (ex: R: 0.5, G: 0.5, B: 0.5):
    { "field": [0.5, 0.5, 0.5] }
    
  • string - a string of characters that represents a file path, name, primitive/light type, with syntax (ex: "hello world"):
    { "field": "hello world" }
    
  • non-negative max-1 float - a single floating point number with minimum value 0.0 and maximum value 1.0 that represents coefficients for lighting properties (where returning >1 or <0 of incoming light would be illogical) with syntax (ex: 0.5):
    { "field": 0.5 }
    
  • Mat4Schema - a 4-length array containing 4-length arrays of float that represent a transformation with syntax:
    {
      "field": [
        [1.3, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1]
      ]
    }
    

While these represent the basic value types, there are more complex types that correspond to how exactly a light, primitive, and so forth are represented in the scene. These are explained in more depth below.

Global Data

Global data specifies certain constants used while rendering. Each of the coefficients has a certain place in the lighting equation; they are all non-negative max-1 float that are used to scale the diffuse, ambient, transparent, and specular components respectively. These constants are helpful for scenes with very few or very many lights. An example of globalData is as follows:

"globalData": {
    "ambientCoeff": 0.5,
    "diffuseCoeff": 0.7,
    "specularCoeff": 0.54,
    "transparentCoeff": 1
}

Camera Data

There can only be one camera in the scene. In a camera block, there must be listed the position (position), look vector (look), up vector (up), and height angle (heightAngle). Note that it is possible to replace the look vector with a focal point (denoted by the keyword focus), if so desired. The camera data must not contain both look and focus properties. Also note that the two fields for aperature and focal length are optional and only need to be included when implementing certain extra credit features. An example of a cameraData is as follows:

"cameraData": {
    "position": [0, 0, 5],
    "up": [0, 1, 0],
    "look": [0, 0, 1],
    "heightAngle": 45,
    "aperture": 3,         // optional
    "focalLength": 5       // optional
}

Groups

groups are the most substantial building block of scenefiles. Essentially, groups are wrappers that allow us to apply transformations to contained primitives, lights, or even other groups. Nested groups allow us to position objects relative to one another when helpful (as a contained group will still possess the transformations declared by the parent group).

A group optionally possesses a name (which become useful for templateGroups as explained later), as well as a transformation defined by fields of rotate, translate, scale which are of form vec4, vec3, and vec3 respectively. Alternatively, a transformation may be defined as a Mat4Schema matrix. Additionally, a group contains an array of primitive and/or light. Typically, a group will contain only a single light or a primitive as all primitives and objects within the same exact group possess the identical transformations and as a result would be stacked on top of one another. Finally, a group may also contain its own subgroups to take advantage of the recursive transformation process as discussed before in the groups field. An example group could be as follows (with the primitives array and groups array contents excluded):

{
  "name": "Group Name",
  "translate": [0, 0, 0],
  "rotate": [0, 0, 0],
  "scale": [1, 1, 1],
  "primitives": [
    {
      "..."
    }
  ],
  "groups": [
    {
      "..."
    }
  ]
}

Template Groups

templateGroups are identical to groups except that they require a name and are not rendered. Instead, templateGroups may be declared and then reused within the first real groups array as a manner by which to reduce the amount of repeated code. An example of a templateGroup is as follows:

{
  "name": "root",
  "..."
  "templateGroups": {
    {
      "name": "Template Group 1",
      "translate": [0, 0, 0],
      "rotate": [0, 0, 0],
      "scale": [1, 1, 1],
      "primitives": [
        {
          "..."
        }
      ]
    }
  },
  "groups": [
    {
      "name": "Group Name",
      "translate": "...",
      "rotate": "...",
      "scale": "...",
      "groups": [
        {
          "name": "Template Group 1"
        }
      ]
    }
  ]
}

As you can see, templateGroups are instantiated by the reusing the name of the templateGroup within the groups array of the root object, as well as any subsequent groups array. Note that templateGroups may also contain other templateGroups within their groups array.

Lights

The specific behavior of a light is defined by the type field contained within itself that is of type string. The type field may be one of the following: point, directional, or spot. Regardless of type, a light may possess a name and must possess color of RGBSchema type. Regarding specific light types...

  • point lights must contain a attenuationCoeff of vector type.
  • directional lights must contain a direction of vector type.
  • spot lights must contain a direction of vector type as well as a penumbra and angle of float type.

An example of a light is as follows:

{
  "name": "Point Light 1",
  "type": "point",
  "color": [0.5, 0.5, 0.5],
  "attenuationCoeff": [0.5, 0.5, 0.5]
}

Note that with regards to directional lights, ALL translations applied are ignored (as a directional light is simulated as a point inifinitely far away in the given direction). Similarly, spot and point lights are affected by translations, but not rotation or scaling operations.

Primitives

A Primitive is the most basic object and are at the lowest level what the scene is comprised of. Primitives are those which represent three-dimensional shapes, namely, cube, cylinder, cone, sphere, and mesh which are specified by the type field of type string.

Primitives contain in their block a combination of optional surface characteristic definitions: diffuse color diffuse, ambient color ambient, reflected color reflective, specular color specular, specular exponent shininess, transparency transparent. There is also the ability to provide texture to an object using a bitmap or to use a bumpmap. Both of these fields have optional u,v coefficient fields for the scaling of the texture which both default to a value of 1 if not specified. Primitives may also have an optional name. All fields of primitive may be represented as follows:

{
  "name": "Primitive Name",
  "type": "cube",
  "diffuse": [0.5, 0.5, 0.5],
  "specular": [0.5, 0.5, 0.5],
  "reflective": [0.5, 0.5, 0.5],
  "transparent": [0.5, 0.5, 0.5],
  "shininess": 10,
  "texture": "path/to/texture.png", // optional (with U,V)
  "textureU": 0.5,
  "textureV": 0.5,
  "bumpMap": "path/to/bumpmap.png", // optional (with U,V)
  "bumpMapU": 0.5,
  "bumpMapV": 0.5,
  "meshFile": "path/to/mesh.obj" // if type: mesh
}

Note that the above primitive would never be seen in the wild. For example, a meshFile may only be provided if the primitive is of type mesh. Similarly, textureU and textureV ought only be provided if texture is provided. The same goes for bumpMapU and bumpMapV. The following is a more realistic example of a primitive:

{
  "name": "Primitive Name",
  "type": "cube",
  "diffuse": [0.5, 0.5, 0.5],
  "specular": [0.5, 0.5, 0.5],
  "reflective": [0.5, 0.5, 0.5],
  "shininess": 10,
  "texture": "path/to/texture.png",
  "textureU": 0.5,
  "textureV": 0.5
}
Comprehensive Sample List

Below we have a simple scenefile composed of just a single directional light and single cube.

{
  "name": "root",
  "globalData": {
    "ambientCoeff": 0.5,
    "diffuseCoeff": 0.5,
    "specularCoeff": 0.5,
    "transparentCoeff": 0.5
  },
  "cameraData": {
    "position": [3, 3, 3],
    "up": [0, 1, 0],
    "heightAngle": 30,
    "focus": [0, 0, 0]
  },
  "groups": [
    {
      "name": "root",
      "lights": [
        {
          "type": "directional",
          "color": [1, 1, 1],
          "direction": [-3, -2, -1]
        }
      ],
      "primitives": [
        {
          "type": "cube",
          "diffuse": [1, 0, 0],
          "specular": [1, 1, 1],
          "shininess": 25
        }
      ]
    }
  ]
}

Below is a more complex scene featuring 2 point lights, 1 directional light, 2 cones, 1 cube, 1 cylinder, and 1 sphere with a nested group that applies inherited transformations.

{
  "name": "root",
  "globalData": {
    "ambientCoeff": 0.2,
    "diffuseCoeff": 0.5,
    "specularCoeff": 0.5,
    "transparentCoeff": 1
  },
  "cameraData": {
    "position": [0.0, 0.0, 5.0],
    "up": [0.0, 1.0, 0.0],
    "look": [0, 0, 0],
    "heightAngle": 45.0
  },
  "groups": [
    {
      "lights": [
        {
          "type": "directional",
          "color": [1.0, 1.0, 1.0],
          "direction": [-2.0, -4.0, -6.0]
        }
      ]
    },
    {
      "translate": [9.0, -3.0, 1.0],
      "lights": [
        {
          "type": "point",
          "color": [0.5, 0.5, 0.5],
          "attenuationCoeff": [1, 0, 0]
        }
      ]
    },
    {
      "translate": [-9.0, -3.0, 1.0],
      "lights": [
        {
          "type": "point",
          "color": [0.5, 0.5, 0.5],
          "attenuationCoeff": [1, 0, 0]
        }
      ]
    },
    {
      "groups": [
        {
          "translate": [0.0, 0.0, -5.0],
          "scale": [1.5, 1.5, 1.5],
          "primitives": [
            {
              "type": "cylinder",
              "ambient": [0.0, 0.5, 0.0],
              "diffuse": [0.0, 1.0, 0.0],
              "specular": [1.0, 1.0, 1.0],
              "shininess": 30.0
            }
          ]
        },
        {
          "translate": [0.0, 0.0, -8.0],
          "rotate": [0.0, 0.0, 1.0, 90.0],
          "groups": [
            {
              "translate": [4.0, 0.0, 0.0],
              "scale": [3.0, 3.0, 3.0],
              "primitives": [
                {
                  "type": "sphere",
                  "ambient": [0.0, 0.5, 0.5],
                  "diffuse": [0.0, 1.0, 1.0],
                  "specular": [1.0, 1.0, 1.0],
                  "shininess": 30.0
                }
              ]
            },
            {
              "translate": [-4.0, 0.0, 0.0],
              "scale": [3.0, 3.0, 3.0],
              "primitives": [
                {
                  "type": "cone",
                  "ambient": [0.5, 0.5, 0.0],
                  "diffuse": [1.0, 1.0, 0.0],
                  "specular": [1.0, 1.0, 1.0],
                  "shininess": 30.0
                }
              ]
            },
            {
              "translate": [0.0, 4.0, 0.0],
              "scale": [3.0, 3.0, 3.0],
              "primitives": [
                {
                  "type": "cube",
                  "ambient": [0.5, 0.0, 0.5],
                  "diffuse": [1.0, 0.0, 1.0],
                  "specular": [1.0, 1.0, 1.0],
                  "shininess": 30.0
                }
              ]
            },
            {
              "translate": [0.0, -4.0, 0.0],
              "scale": [3.0, 3.0, 3.0],
              "primitives": [
                {
                  "type": "cone",
                  "ambient": [0.5, 0.5, 0.5],
                  "diffuse": [1.0, 1.0, 1.0],
                  "specular": [1.0, 1.0, 1.0],
                  "shininess": 30.0
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Finally, below we have a scenefile that features 1 point lights and 1 templateGroup containing a sphere with numerous material properties as well as a normal group that creates another sphere as well as instantiates that templateGroup.

{
  "name": "root",
  "globalData": {
    "ambientCoeff": 0.5,
    "diffuseCoeff": 0.7,
    "specularCoeff": 0.54,
    "transparentCoeff": 1
  },
  "cameraData": {
    "position": [10, 4.1, 16],
    "up": [0, 1, 0],
    "heightAngle": 49.5,
    "look": [-9, -3.2, -16]
  },
  "templateGroups": [
    {
      "name": "level 1",
      "translate": [0, 0, 0],
      "scale": [3, 3, 3],
      "primitives": [
        {
          "type": "sphere",
          "reflective": [0.75, 1, 0.75],
          "diffuse": [0.75, 1, 0.75],
          "shininess": 25,
          "specular": [1, 1, 1],
          "textureFile": "image/marsTexture.png",
          "textureU": 1,
          "textureV": 1,
          "blend": 0.75
        }
      ]
    }
  ],
  "groups": [
    {
      "name": "Point Light",
      "translate": [10, 10, 10],
      "lights": [
        {
          "type": "point",
          "color": [1, 1, 1],
          "attenuationCoeff": [0, 0, 0]
        }
      ]
    },
    {
      "name": "level 0",
      "translate": [0, 0, 0],
      "scale": [6, 6, 6],
      "primitives": [
        {
          "type": "sphere",
          "reflective": [1, 0.75, 0.75],
          "diffuse": [1, 0.75, 0.75],
          "shininess": 25,
          "specular": [1, 1, 1],
          "textureFile": "image/marsTexture.png",
          "textureU": 1,
          "textureV": 1,
          "blend": 0.75
        }
      ]
    },
    {
      "translate": [-4.5, 0, 0],
      "rotate": [0, 0, 1, 90],
      "groups": [
        {
          "name": "level 1"
        }
      ]
    }
  ]
}

This format is not as all-encompassing as it could be, but this is intentional as one of the major aims is for it to be simple to parse.