Monday, November 14, 2016

[Binary Trees]: Binary Trees. -Each of the objects in a binary tree contains at least two pointers, and other types of data..

[References]
1. "Binary Trees in C++, from math.hws.edu."
2. ""
...
WE HAVE SEEN how objects can be linked into lists. When an object contains two pointers to objects of the same type, structures can be created that are much more complicated than linked lists. In this section, we'll look at one of the most basic and useful structures of this type: binary trees. Each of the objects in a binary tree contains two pointers, typically called left and right. In addition to these pointers, of course, the nodes can contain other types of data. For example, a binary tree of integers could be made up of objects of the following type:
           struct TreeNode {
              int item;         // The data in this node.
              TreeNode *left;   // Pointer to the left subtree.
              TreeNode *right;  // Pointer to the right subtree.
           };





The left and right pointers in a TreeNode can be NULL or can point to other objects of type TreeNode
  • A node that points to another node is said to be the parent of that node, and 
  • the node it points to is called a child
  • In the picture at the right, for example, node 3 is the parent of node 6, and nodes 4 and 5 are children of node 2. 


Not every linked structure made up of tree nodes is a binary tree. A binary tree must have the following properties: 
  • There is exactly one node in the tree which has no parent. This node is called the root of the tree. 
  • Every other node in the tree has exactly one parent. 
  • Finally, there can be no loops in a binary tree. That is, it is not possible to follow a chain of pointers starting at some node and arriving back at the same node.
  • A node that has no children is called a leaf
  • A leaf node can be recognized by the fact that both the left and right pointers in the node are NULL.
  • In the standard picture of a binary tree, the root node is shown at the top and the leaf nodes at the bottom -- which doesn't show much respect with the analogy to real trees. But at least you can see the branching, tree-like structure that gives a binary tree its name.

Consider any node in a binary tree. 
  • Look at that node together with all its descendents (that is, its children, the children of its children, and so on). This set of nodes forms a binary tree, which is called a subtree of the original tree. 
  • For example, in the picture, nodes 2, 4, and 5 form a subtree. This subtree is called the left subtree of the root. 
  • Similarly, nodes 3 and 6 make up the right subtree of the root. 

We can consider any non-empty binary tree to be made up of a root node, a left subtree, and a right subtree. Either or both of the subtrees can be empty. This is a recursive definition, matching the recursive definition of the TreeNode class. So it should not be a surprise that recursive functions are often used to process trees.

Consider the problem of counting the nodes in a binary tree. 
As an exercise, you might try to come up with a non-recursive algorithm to do the counting. The heart of problem is keeping track of which nodes remain to be counted. It's not so easy to do this, and in fact it's not even possible without using an auxiliary data structure. With recursion, however, the algorithm is almost trivial. Either the tree is empty or it consists of a root and two subtrees. If the tree is empty, the number of nodes is zero. (This is the base case of the recursion.) Otherwise, use recursion to count the nodes in each subtree. Add the results from the subtrees together, and add one to count the root. This gives the total number of nodes in the tree. Written out in C++:

int countNodes( TreeNode *root) {
    //count the nodes in the binary tree to which root points, and return the answer.
    if (NULL == root)
        return 0;//empty tree. it contains no nodes.
    else {
        int count = 1;
        count += countNodes(root->left);//
        count += countNodes(root->right);//
        return count;
    }
}//end countNodes

Or, consider the problem of printing the items in a binary tree. 
If the tree is empty, there is nothing to do. If the tree is non-empty, then it consists of a root and two subtrees. Print the item in the root and use recursion to print the items in the subtrees. Here is a function that prints all the items on one line of output:

     void preorderPrint( TreeNode *root ) {
           // Print all the items in the tree to which root points.
           // The item in the root is printed first, followed by the
           // items in the left subtree and then the items in the
           // right subtree.
        if ( root != NULL ) {  // (Otherwise, there's nothing to print.)
           cout << root->item << " ";      // Print the root item.
           preorderPrint( root->left );    // Print items in left subtree.
           preorderPrint( root->right );   // Print items in right subtree.
        }
     } // end preorderPrint()

This routine is called "preorderPrint" because it uses a preorder traversal of the tree. 
In a preorder traversal, 
  • the root node of the tree is processed first, 
  • then the left subtree is traversed, 
  • then the right subtree. 

In a postorder traversal
  • the left subtree is traversed, 
  • then the right subtree, and 
  • then the root node is processed. 

And in an inorder traversal
  • the left subtree is traversed first, 
  • then the root node is processed, 
  • then the right subtree is traversed. 


Printing functions that use postorder and inorder traversal differ from preorderPrint only in the placement of the statement that outputs the root item:

     void postorderPrint( TreeNode *root ) {
           // Print all the items in the tree to which root points.
           // The items in the left subtree are printed first, followed 
           // by the items in the right subtree and then the item in the
           // root node.
        if ( root != NULL ) {  // (Otherwise, there's nothing to print.)
           postorderPrint( root->left );    // Print items in left subtree.
           postorderPrint( root->right );   // Print items in right subtree.
           cout << root->item << " ";       // Print the root item.
        }
     } // end postorderPrint()


     void inorderPrint( TreeNode *root ) {
           // Print all the items in the tree to which root points.
           // The items in the left subtree are printed first, followed 
           // by the item in the root node, followed by the items in
           // the right subtree.
        if ( root != NULL ) {  // (Otherwise, there's nothing to print.)
           inorderPrint( root->left );    // Print items in left subtree.
           cout << root->item << " ";     // Print the root item.
           inorderPrint( root->right );   // Print items in right subtree.
        }
     } // end inorderPrint()

Each of these functions can be applied to the binary tree shown in the illustration at the beginning of this section. The order in which the items are printed differs in each case:
          preorderPrint outputs:   1  2  4  5  3  6
       
          postorderPrint outputs:  4  5  2  6  3  1
       
          inorderPrint outputs:    4  2  5  1  3  6

In preorderPrint, for example, the item at the root of the tree, 1, is output before anything else. But the preorder printing also applies to each of the subtrees of the root. The root item of the left subtree, 2, is printed before the other items in that subtree, 4 and 5. As for the right subtree of the root, 3 is output before 6. A preorder traversal applies at all levels in the tree. The other two traversal orders can be analyzed similarly.


Binary Sort Trees

One of our examples using a list was a linked list of strings, in which the strings were kept in increasing order.

  • While a linked list works well for a small number of strings, it becomes inefficient for a large number of items. 
  • When inserting an item into the list, searching for that item's position requires looking at, on average, half the items in the list. 
  • Finding an item in the list requires a similar amount of time. 
  • If the strings are stored in a sorted array instead of in a linked list, then searching becomes more efficient because binary search can be used. 
  • However, inserting a new item into the array is still inefficient since it means moving, on average, half of the items in the array to make a space for the new item.
  •  A binary tree can be used to store an ordered list of strings, or other items, in a way that makes both searching and insertion efficient. A binary tree used in this way is called binary sort tree.

A binary sort tree is a binary tree with the following property:

  • For every node in the tree, 
  • the item in that node is greater than every item in the left subtree of that node, 
  • and it is less than or equal to all the items in the right subtree of that node. 
  • Here for example is a binary sort tree containing items of type string. (In this picture, I haven't bothered to draw all the pointer variables. Non-NULL pointers are shown as arrows.)


Binary sort trees have this useful property: 
  • An inorder traversal of the tree will process the items in increasing order. 
  • In fact, this is really just another way of expressing the definition. 
  • For example, if an inorder traversal is used to print the items in the tree shown above, then the items will be in alphabetical order
  • The definition of an inorder traversal guarantees that all the items in the left subtree of "judy" are printed before "judy", and all the items in the right subtree of "judy" are printed after "judy". 
  • But the binary sort tree property guarantees that the items in the left subtree of "judy" are precisely those that precede "judy" in alphabetical order, and all the items in the right subtree follow "judy" in alphabetical order. So, we know that "judy" is output in its proper alphabetical position. But the same argument applies to the subtrees. "Bill" will be output after "alice" and before "fred" and its descendents. "Fred" will be output after "dave" and before "jane" and "joe". And so on.

Suppose that we want to search for a given item in a binary search tree. 
  • Compare that item to the root item of the tree. If they are equal, we're done. 
  • If the item we are looking for is less than the root item, then we need to search the left subtree of the root -- the right subtree can be eliminated because it only contains items that are greater than or equal to the root. 
  • Similarly, if the item we are looking for is greater than the item in the root, then we only need to look in the right subtree. 
  • In either case, the same procedure can then be applied to search the subtree. 

Inserting a new item is similar: 
  • Start by searching the tree for the position where the new item belongs. 
  • When that position is found, create a new node and attach it to the tree at that position.

Searching and inserting are efficient operations on a binary search tree, provided that the tree is close to being balanced
A binary tree is balanced if for each node, the left subtree of that node contains approximately the same number of nodes as the right subtree. In a perfectly balanced tree, the two numbers differ by at most one. Not all binary trees are balanced, but if the tree is created randomly, there is a high probability that the tree is approximately balanced. During a search of any binary sort tree, every comparison eliminates one of two subtrees from further consideration. If the tree is balanced, that means cutting the number of items still under consideration in half. This is exactly the same as the binary search algorithm, and the result is a similarly efficient algorithm.

The sample program SortTreeDemo.cc is a demonstration of binary sort trees. The program includes functions that implement inorder traversal, searching, and insertion. We'll look at the latter two functions below. The main()routine tests the functions by letting you type in strings to be inserted into the tree. Here is an applet that simulates this program:
(Applet "SortTreeConsole" would be displayed here
if Java were available.)
In this program, nodes in the binary tree are represented using the following class, including a simple constructor that makes creating nodes easier:
      struct TreeNode {
              // An object of type TreeNode represents one node
              // in a binary tree of strings.

         string item;      // The data in this node.
         TreeNode left;    // Pointer to left subtree.
         TreeNode right;   // Pointer to right subtree.

         TreeNode(string str) {
                // Constructor.  Make a node containing str.
            item = str;
            left = NULL;
            right = NULL;
         }

      };  // end struct Treenode

(Note: I am following the Java-like convetion of defining functions and constructors inside the class declaration. Usually, in C++, the definitions are in a separate implementation file. However, it's legal to do things this way in C++, even though it's not recommended.)
A variable in the main program of type pointer-to-TreeNode points to the binary sort tree that is used by the program:

      TreeNode *root;  // Pointer to the root node in the tree.
      root = NULL;     // Start with an empty tree.

A recursive function named treeContains is used to search for a given item in the tree. This routine implements the search algorithm for binary trees that was outlined above:

          bool treeContains( TreeNode *root, string item ) {
                 // Return true if item is one of the items in the binary
                 // sort tree to which root points.   Return false if not.
             if ( root == NULL ) {
                   // Tree is empty, so it certainly doesn't contain item.
                return false;
             }
             else if ( item == root->item ) {
                   // Yes, the item has been found in the root node.
                return true;
             }
             else if ( item < root->item ) {
                   // If the item occurs, it must be in the left subtree.
                return treeContains( root->left, item );
             }
             else {
                   // If the item occurs, it must be in the right subtree.
                return treeContains( root->right, item );
             }
          }  // end treeContains()

When this routine is called in the main() function, the first parameter is the local variable root, which points to the root of the entire binary sort tree.
It's worth noting that recursion is not really essential in this case. A simple, non-recursive algorithm for searching a binary sort tree just follows the rule: Move down the tree until you find the item or reach a NULL pointer. Since the search follows a single path down the tree, it can be implemented as a while loop. Here is non-recursive version of the search routine:

      bool treeContainsNR( TreeNode *root, string item ) {
             // Return true if item is one of the items in the binary
             // sort tree to which root points.   Return false if not.
         TreeNode *runner;  // For "running" down the tree.
         runner = root;     // Start at the root node.
         while (true) {
            if (runner == NULL) {
                  // We've fallen off the tree without finding item.
               return false;  
            }
            else if ( item == runner->item ) {
                  // We've found the item.
               return true;
            }
            else if ( item < runner->item ) {
                  // If the item occurs, it must be in the left subtree,
                  // So, advance the runner down one level to the left.
               runner = runner->left;
            }
            else {
                  // If the item occurs, it must be in the right subtree.
                  // So, advance the runner down one level to the right.
               runner = runner->right;
            }
         }  // end while
      } // end treeContainsNR();

A recursive function for inserting a new item into the tree is similar to the recursive search function. If the tree is empty, then we have to set the tree equal to a new tree, consisting of a single node that holds the item. Otherwise, we test whether the new item is less than or greater than the item in the root node. Based on this test, we decide whether to insert the new item into the left subtree or into the right subtree. In either case, we do the inserting by calling the same function recursively. Note that since the value of the parameter, root, can change in the function, this parameter must be passed by reference. Here is the definition of the insertion function. A non-recursive version is possible, but it would be much more complicated:

       void treeInsert(TreeNode *&root, string newItem) {
              // Add the item to the binary sort tree to which the parameter
              // "root" refers.  Note that root is passed by reference since
              // its value can change in the case where the tree is empty.
          if ( root == NULL ) {
                 // The tree is empty.  Set root to point to a new node containing
                 // the new item.  This becomes the only node in the tree.
             root = new TreeNode( newItem );
                     // NOTE:  The left and right subtrees of root
                     // are automatically set to NULL by the constructor.
                     // This is important!
             return;
          }
          else if ( newItem < root->item ) {
             treeInsert( root->left, newItem );
          }
          else {
             treeInsert( root->right, newItem );
          }
       }  // end treeInsert()


Expression Trees

Another application of trees is to store mathematical expressions such as 15*(x+y) or sqrt(42)+7 in a convenient form. 
Let's stick for the moment to expressions made up of numbers and the operators +, -, *, and /. Consider the expression 3*((7+1)/4)+(17-5). This expression is made up of two subexpressions, 3*((7+1)/4) and (17-5), combined with the operator "+". When the expression is represented as a binary tree, the root node holds the operator +, while the subtrees of the root node represent the subexpressions 3*((7+1)/4) and (17-5). Every node in the tree holds either a number or an operator. A node that holds a number is a leaf node of the tree. A node that holds an operator has two subtrees representing the operands to which the operator applies. The tree is shown in the illustration below. I will refer to a tree of this type as an expression tree.
Given an expression tree, it's easy to find the value of the expression that it represents. Each node in the tree has an associated value. If the node is a leaf node, then its value is simply the number that the node contains. If the node contains an operator, then the associated value is computed by first finding the values of its child nodes and then applying the operator to those values. The process is shown by the red arrows in the illustration. The value computed for the root node is the value of the expression as a whole. There are other uses for expression trees. For example, a postorder traversal of the tree will output the postfix form of the expression.










...

...

Sunday, November 13, 2016

[Binary Trees]: This article introduces the basic concepts of binary trees, and then works through a series of practice problems.

[References]
1. "Binary Trees, from stanford.edu"
2.

...

Binary Trees

by Nick Parlante
This article introduces the basic concepts of binary trees, and then works through a series of practice problems with solution code in C/C++ and Java. Binary trees have an elegant recursive pointer structure, so they are a good way to learn recursive pointer algorithms.

Contents

Section 1. Binary Tree Structure -- a quick introduction to binary trees and the code that operates on them 
Section 2. Binary Tree Problems -- practice problems in increasing order of difficulty 
Section 3. C Solutions -- solution code to the problems for C and C++ programmers 
Section 4. Java versions -- how binary trees work in Java, with solution code

Stanford CS Education Library -- #110

This is article #110 in the Stanford CS Education Library. This and other free CS materials are available at the library (http://cslibrary.stanford.edu/). That people seeking education should have the opportunity to find it. This article may be used, reproduced, excerpted, or sold so long as this paragraph is clearly reproduced. Copyright 2000-2001, Nick Parlante, nick.parlante@cs.stanford.edu.


Section 1 -- Introduction To Binary Trees

A binary tree is made of nodes, 

  • where each node contains a "left" pointer
  • a "right" pointer, and 
  • a data element
  • The "root" pointer points to the topmost node in the tree. 
  • The left and right pointers recursively point to smaller "subtrees" on either side. 
  • A null pointer represents a binary tree with no elements -- the empty tree. 
  • The formal recursive definition is: a binary tree is either empty (represented by a null pointer), or is made of a single node, where the left and right pointers (recursive definition ahead) each point to a binary tree





A "binary search tree" (BST) or "ordered binary tree" is

  • a type of binary tree where the nodes are arranged in order: 
  • for each node, all elements in its left subtree are less-or-equal to the node (<=), and 
  • all the elements in its right subtree are greater than the node (>)

  • The tree shown above is a binary search tree -- the "root" node is a 5, and its left subtree nodes (1, 3, 4) are <= 5, and its right subtree nodes (6, 9) are > 5. Recursively, each of the subtrees must also obey the binary search tree constraint: in the (1, 3, 4) subtree, the 3 is the root, the 1 <= 3 and 4 > 3. Watch out for the exact wording in the problems -- a "binary search tree" is different from a "binary tree".
  • The nodes at the bottom edge of the tree have empty subtrees and are called "leaf" nodes (1, 4, 6) while the others are "internal" nodes (3, 5, 9).

Binary Search Tree Niche

Basically, binary search trees are fast at insert and lookup. 
The next section presents the code for these two algorithms. 

  • On average, a binary search tree algorithm can locate a node in an N node tree in order lg(N) time (log base 2). 
  • Therefore, binary search trees are good for "dictionary" problems where the code inserts and looks up information indexed by some key. 
  • The lg(N) behavior is the average case -- it's possible for a particular tree to be much slower depending on its shape.

Strategy

Some of the problems in this article use plain binary trees, and some use binary search trees. In any case, the problems concentrate on the combination of pointers and recursion. (See the articles linked above for pointer articles that do not emphasize recursion.)
For each problem, there are two things to understand...
  • Node/pointer structure that makes up the tree and the code that manipulates it
  • The algorithm, typically recursive, that iterates over the tree
When thinking about a binary tree problem, it's often a good idea to draw a few little trees to think about the various cases.

Typical Binary Tree Code in C/C++

As an introduction, we'll look at the code for the two most basic binary search tree operations -- lookup() and insert(). The code here works for C or C++. Java programers can read the discussion here, and then look at the Java versions in Section 4.
In C or C++, the binary tree is built with a node type like this...
struct node {
    int data;
    struct node* left;
    struct node* right;
}

...


































.....

Saturday, November 5, 2016

[Mapping]: Reflect and Refract Light of Static Environment Mapping

[References]
1. "Cube Maps: Sky Boxes and Environment Mapping, from Anton's OpenGL 4.0 Tutorial"
2. "Chapter 7, Environment Mapping Techniques, from developer.nvidia"
3.

[KeyWords] 
                       Cube Maps; Reflect and Refract Light;                        Static Environment Maps; Dynamic Environment Maps;
...

Cube Maps: Sky Boxes and Environment Mapping

Anton Gerdelan. Last Updated 2 October 2016

OpenGL has a special kind of texture for cubes that allows us to pack 6 textures into it.

  • It has a matching sampler in GLSL that takes a 3d texture coordinate - with R, S, and T, components. 
  • The trick here is that we use a 3d direction vector for the texture coordinates. 
  • The component with the biggest magnitude tells GL which of the 6 sides to sample from, and the balance between the remaining 2 components tells GL where on that 2d texture to sample a texel from.


Two popular techniques that make use of cube maps are

  • sky boxes, which provide the illusion of a 3d background behind the scenery, 
  • environment mapping, which can make a very-shiny surface (think lakes, chrome, or car paint) appear to reflect the environment scenery. 
  • We can also use it to approximate refraction for translucent materials like water.

Sky Box

This technique effectively replaces the GL background colour with a more interesting 3d "painted backdrop". The idea with the sky box technique is to have a big box encase the camera as it moves around. If the box appears to be the same distance even when the camera is moving, then we get a parallax effect - the illusion of it being relatively very far away. We don't rotate it as the camera rotates though - this allows us to pan around. If the texture has been well constructed then we won't notice the seams and it will have been projected onto the box so that it appears as if we are inside a sphere when we sample it with our view 3d vector. We can actually use a dome or a sphere instead of a box, but boxes are easier to texture map.
The size of the box is not going to affect how big it looks. Think about it - if it's moving with the camera it will look exactly the same close up as far away. The size only matters if it intersects with the clip planes - in this case you will see a plane cutting off part of your box. We will draw the box before anything else, and turn off depth-masking. Remember that this means that it doesn't write anything into the depth buffer - any subsequent draws to the same pixel will draw on top of it. Then it is never drawn in front of your scenery, even if it is closer to the camera.

Make a Big Cube

We need to make a big cube and put it into a vertex buffer. We don't need to add texture coordinates. We are going to be inside the cube, so make sure that you get the winding order of the vertices so that the "front" faces are on the inside. You can load a cube from a mesh file if you like, but I think that's cheating.

Create a Cube-Map Texture

Now, we need to make or find a suitable set of textures for the sky box. There are some great examples at Humus' (Emil Persson's) site:http://www.humus.name/index.php?page=Textures, and many older games will have sky box textures that you can experiment with. You might need to swap the "front" and "back" textures around if they are intended for the opposing coordinate system. Cube maps are sometimes stored inside a single texture file, but more often than not, as 6 separate textures. I'll assume that we are loading from 6 separate textures here. Note that we don't generate mip-maps for a sky box because it's always going to be much the same distance from the camera, and we can just choose an appropriately-sized texture. To test my cube I drew a different coloured border onto each textured face to show where the seams were and check if I needed to swap the front and back textures.
I have a create_cube_map() function which takes file names for the 6 separate image files, and generates the opengl texture. This will call another function to load the separate image for each of the 6 sides. Note that each side is going to bind to the same texture that we generated - we will pass this as a parameter. Finally we apply some texture filtering to the texture, and the texture handle is passed back as a function parameter
**********
The texture filtering parameters are interesting. Make sure that you set "clamp to edge" for all three components. If you don't clamp to edge then you might get a visible seam on the edges of your textures (we'll see why shortly). We have some bi-linear filtering to clean up any minor aliasing when the camera rotates. The function that loads each image is very similar to the one that we wrote in the texture mapping tutorial, and also uses Sean Barrett's image loader (stb_image):
**************
The code is almost identical to creating a single 2d texture in OpenGL, except that we bind a texture of type GL_TEXTURE_CUBE_MAP instead of GL_TEXTURE_2D. Then glTexImage2D() is called 6 times, with the macro for each side as the first parameter, rather than GL_TEXTURE_2D.

Basic Cube-Map Shaders

Our vertex shader just needs position the cube and output texture coordinates. In this case a 3d coordinate as a vec3. This should be a direction, but I can use the actual local vertex points as a direction because it's a cube. If you think about it, the points along the front face are going to be interpolated with varying values ofx and y, but all will have z value -10.0 (or the biggest size in the cube). You don't need to normalise these first - it will still work. The only problem is that the pointsexactly on the corners are not going to have a "biggest" component, and pixels may appear black as the coordinate is invalid. This is why we enabled clamp-to-edge.
Note: I've seen other tutorials that manipulate the Z and W components ofgl_Position such that, after perspective division the Z distance will always be 1.0; maximum distance in normalised device space, and therefore always in the background. This is a bad idea. It will clash, and possibly visibly flicker or draw all-black, when depth testing is enabled. It's better to just disable depth masking when drawing, as we will do shortly, and make sure that it's the first thing that you draw in your drawing loop.
**************
The P and V matrices are my camera's projection, and view matrices, respectively. The view matrix here is a special version of the camera's view matrix that does not contain the camera translation. Inside my main loop I check if the camera has moved. If so I build a very simple view matrix and update its uniform for the cube map shader. This camera only can only rotate on the Y-Axis, but you might use a quaternion to generate a matrix for a free-look camera. Remember that view matrices use negated orientations so that the scene is rotated around the camera. Of course if you are using a different maths library then you will have different matrix functions here.

The fragment shader uses a special sampler; samplerCube which accepts the 3d texture coordinate. Other than that there is nothing special about the fragment shader. Use the texture() function, but older GL implementations may prefertextureCube().

Rendering

Use your shaders, and bind your VAO as usual. Remember that we should bind the texture into an active slot before rendering. This time it has a different texture type; GL_TEXTURE_CUBE_MAP. With depth-masking disabled it won't write anything to the depth buffer; allowing all subsequently drawn scenery to be in front of the sky box.


 
I found this set of sky box textures on Emil Perssons' website, which have a Creative Commons Attribution licence, so they are excellent for demos. (top) points to a corner of the box, viewing the left and front textures, (bottom) points to another corner, viewing the front, and right textures. I suggest testing it with a camera that can rotate.


Reflection and Refraction
with Static Environment Maps

So far we have only used specular highlights to show if a surface is highly reflective, which appears as a brightly-coloured dot. Suppose we want to reflect a more detailed image of the sky box or part of the environment onto the surface. If we have a cube map with an image of a brightly-coloured square window on it, then we can use this to create a much more interesting reflection of light on the surface.

Reflecting the Sky Box

The most common type of environment map just reflects a scene's sky box onto a surface. We can say that this is a "static" environment map because it won't reflect any moving pieces of the scene. It can produce quite a convincing effect as you can see that the sky box matches the reflection as you rotate the view. You don't need to have a sky box displayed for this to work though - an indoor scene might not have any visible sky, but you might have a cube map that is not displayed, but contains an indoor-like cube map which you only use for reflections.

With the incident direction vector from the camera to the surface position, I, we calculate a reflection R around the surface normal N. We use R to sample the cube map, and the texel is then applied to the fragment of the surface.
The principle is very simple. We work out a direction vector from the view point (camera position) to the surface. We then reflect that direction off the surface based on the surface normal. We use the reflected direction vector to sample the cube map. GLSL has a built-in reflect() function which takes an input vector and a normal, and produces the output vector. I had some trouble getting this to compile on all drivers, so I prefer to code it manually.
I loaded a mesh of the Suzanne monkey head, and created a vertex simple shader for it. I calculate the vertex positions and normals in eye space.


The fragment shader calculates the direction vector from the camera to the surface as incident_eye; the incident direction in eye space. Note that this is normally vec3 dir = normalize(to - from) but the "from" here is the camera position (0,0,0) in eye space. The built-in reflection function then gets the reflected vector, which is converted to world space and used as the texture coordinates.

You can reduce the number of instructions in the shader if you compute the reflection in world space, but then you need a camera world position uniform.
 
Environment maps reflecting the sky box onto a mesh. Using a highly detailed cube map texture will significantly improve the quality of the effect.

Common Mistakes

  • My reflection is upside-down or the wrong side! - Check the direction of your incident and normal vectors. Normalise them.
  • My mesh reflections are not smooth like your monkey! - Remember to use interpolated surface normals (set all faces to smooth normals in Blender).

Refraction


With the incident direction vector from the camera to the surface position, I, we calculate a refraction R which modifies the incident vector based on the surface normal, N, and the ratio of refractive indices, which is the first index (1.0 for air), divided by the second index (1.3333 for water). With the above example, you might use a cube map that displays the interior of a swimming pool, rather than the sky box.
Translucent objects should refract light. This means that it doesn't go straight through; the ray bends when it hits the surface and changes material. Looking into a swimming pool, for example. This works much the same way as reflection, except that we perturb the eye-to-surface vector about the surface normal, instead of reflecting it. The actual change in direction is governed by Snell's Law. GLSL also has a built-in refract() function with a vector-based version of Snell's Law. You can modify the previous fragment shader to use this instead of vec3 reflected:


Where the ratio is the ratio between the refractive index of the first material (air, 1.0), and the second material (water, 1.3333). You can find some indices for common materials on Wikipedia. Note that the monkey head is single-sided; we are ignoring the effect of the light traveling through the back of the head, where it should refract a second time. So you can say that it's not very realistic, but fine for simple surfaces.
 


Refraction using indices for air (top), water (second), and cubic zirconia (bottom two).
You can imagine that with the right combination of Phong, vertex displacement, and refraction environment map, that you could make a pretty convincing water visualisation.

Common Mistakes

  • My refracted image looks magnified or blocky! - Try using higher-resolution cube map textures.
  • My image is upside-down! - This is probably correct. Look at photos of refractions of real, similar surfaces (i.e. sphere, pool). Check that your index ratio is correct (first / second).


And For Experts...
Dynamic Environment Maps

It is possible to use the entire scene, including all the moving objects, in an environment map. If you are already comfortable with binding textures to framebuffers (see later tutorials) then you can bind 6 textures as target outputs from 6 framebuffers. You will render the scene 6 times from the point of view of the object that is going to be environment-mapped. Once pointing up, once down, once left, etc. The output textures will be combined into the cube map. The perspective matrix should have a field of view of 90 degrees so that the 6 frustums line up exactly into a cube.
  1. Create 6 colour textures
  2. Create 6 depth textures
  3. Create 6 framebuffers
  4. Attach a colour texture and depth map to each framebuffer
  5. When rendering bind each framebuffer and draw in 1 of 6 fixed directions
  6. Create a cube map from 6 textures
We looked at how to manually create a cube-map at the beginning of the article. It's also possible to attach all 6 depth and colour maps to a single framebuffer and render all 6 views in a single pass. To do this you will need to use a geometry shader that redirects the vertex positions to separate gl_Layers. This might give you a small computational improvement if you are computing dynamic environment maps in real-time, but I find geometry shaders to be unstable across the range of drivers and would not recommend this option.
...
...