Skip to main content

Computer Graphics and Multimedia: Skyboxes and Shadow Maps

Computer Graphics and Multimedia
Skyboxes and Shadow Maps
    • Notifications
    • Privacy
  • Project HomeComputer Graphics and Multimedia
  • Projects
  • Learn more about Manifold

Notes

Show the following:

  • Annotations
  • Resources
Search within:

Adjust appearance:

  • font
    Font style
  • color scheme
  • Margins
table of contents
  1. Module 1
    1. Introduction to Computer Graphics with WebGL
    2. Assignment 1
    3. Assignment 2
  2. Module 2
    1. Working with WebGL and JavaScript
    2. Assignment 1
    3. Assignment 2
  3. Module 3
    1. Animation and Geometric Transformations
    2. Assignment 1
    3. Assignment 2
  4. Module 4
    1. Viewing and Projections
    2. Assignment 1
    3. Assignment 2
  5. Module 5
    1. Lighting and Shading
    2. Assignment 1
    3. Assignment 2
  6. Module 6
    1. Texture Mapping and Matrix Stacks
    2. Assignment 1
    3. Assignment 2
  7. Module 7
    1. Skyboxes and Shadow Maps
    2. Assignment 1
    3. Assignment 2
  8. Module 8
    1. Modeling and Hierarchy - Building Scenes
    2. Assignment 1
    3. Assignment 2

Skyboxes and Shadow Maps

A. drawElements vs drawArrays

When you did lab 2 you might have found that you repeated exactly the same vertex information multiple times while creating your drawings. This is wasteful, since all the data is repeated. It is common for one vertex to be repeated multiple times in a mesh. It would be better if we could create a vertex with all its associated attributes once and refer back to it using a simple number.

For example, given a mesh like this one:

Figure 1: A typical rectangular mesh. Notice that many vertices are part of several triangles. Inner vertices all belong to 6 triangles. Outer vertices may belong to 1, 2 or 3 triangles. This leads to much duplication

a call to drawArrays with the TRIANGLES primitive would repeat some vertices 6 times. Even trying to be efficient with TRIANGLE_STRIP would cause all vertices to be repeated twice. If you are repeating colours, texture coordinates and lighting information this can be rather expensive. Fortunately, you can use an index buffer to reduce repetition. Consider the cube from the transformations lab. It was represented like this:

var cubeVerts = [
	[ 0.5, 0.5, 0.5, 1], //0
	[ 0.5, 0.5,-0.5, 1], //1
	[ 0.5,-0.5, 0.5, 1], //2
	[ 0.5,-0.5,-0.5, 1], //3
	[-0.5, 0.5, 0.5, 1], //4
	[-0.5, 0.5,-0.5, 1], //5
	[-0.5,-0.5, 0.5, 1], //6
	[-0.5,-0.5,-0.5, 1], //7
];

var shapes = {
	wireCube: {Start: 0, Vertices: 30},
	solidCube: {Start: 30, Vertices: 36},
	axes: {Start: 66, Vertices: 6}
};

//Look up patterns from cubeVerts for different primitive types
var cubeLookups = [
//Wire Cube - use LINE_STRIP, starts at 0, 30 vertices
	0,4,6,2,0, //front
	1,0,2,3,1, //right
	5,1,3,7,5, //back
	4,5,7,6,4, //right
	4,0,1,5,4, //top
	6,7,3,2,6, //bottom
//Solid Cube - use TRIANGLES, starts at 30, 36 vertices
	0,4,6, //front
	0,6,2,
	1,0,2, //right
	1,2,3, 
	5,1,3, //back
	5,3,7,
	4,5,7, //left
	4,7,6,
	4,0,1, //top
	4,1,5,
	6,7,3, //bottom
	6,3,2,
];

Which is actually pretty compact. If we don't expand it in JavaScript like we did last week, and instead send the lookups as unsigned bytes to a WebGL buffer for drawing, the total data for this representation is:

vertex bytes = 8 vertices * 4 components * 4 bytes/component = 128 bytes

lookup bytes = 36 lookups * 1 byte/lookup = 36 bytes

Total = 194 bytes.

If we were to fully expand these arrays as was done in Lab 3, you would end up with 66 fully specified vertices:

66 vertices * 4 components * 4 bytes/component = 1056 bytes

That's 194 vs 1056 - using elements the result is 1/5 the size. This may not seem like much, but it can add up quickly - and all that data needs to be transferred from place to place.

Luckily WebGL provides an easy to use solution for situations like this. The cubeLookups can be loaded into a special buffer called an element array buffer. These buffers provide the indices for the regular ARRAY_BUFFER that you have become familiar with. They have one limitation that makes them a little less efficient than you might want though - the indices they specify refer to the same index in all regular array buffers. If you need the same position in blue and in green during the same draw, those will be duplicate positions at separate indices.

You can load the element array buffer with a regular arrays of integers, just like the cubeLookups array. To specify a buffer for the array, you create a buffer as usual, but bind it as ELEMENT_ARRAY_BUFFER. The buffer data must be copied as an integer type, so you can't use Dr. Angel's flatten() function to convert the array for use in the shader since it only produces 1D float array outputs. Instead, there are some simple copy constructors built into Javascript that do the job. For unsigned bytes, you can use Uint8Array.from() and for unsigned short integers you can use Uint16Array.from(). Which you use will depend on how many points there are in your mesh.

So, to load the cube's elements array you could use this code:

//cubeElements should probably so you can rebind it later as needed
cubeElements = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, cubeElements );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, Uint8Array.from(cubeLookups), gl.STATIC_DRAW );

Only one element array buffer can be bound to a shader at a time, so the equivalent to vertexAttribPointer() is folded into the the drawElements() function. Draw elements is specified as:

void drawElements( mode, count, type, indices);

Where:

  • mode is what primitive to draw with
  • count is how many vertices to draw.
  • type is the data type specified in the buffer
  • first is what vertex to start at in the array you loaded. The documentation calls it a pointer, but it is relative to the currently bound elements array buffer.

Here's how that would look in our render function:

gl.drawElements( gl.LINE_STRIP, shapes.wireCube.Vertices, gl.UNSIGNED_BYTE, shapes.wireCube.Start);

Using TRIANGLE_STRIP for Efficient Surfaces

As suggested in the previous section, you can use triangle strips to reduce the number of vertices required to specify a surface. They typically require n+2 vertices to fully specify an inline strip of triangles. The path through the vertices is usually backwards-N or backwards-Z shaped. For example following strip could be started at either end:

Figure 2: The N or sawtooth path that makes up a typical triangle strip. If the end points were reversed, so that the N's were forward, the triangles would be facing backward. To move upwards instead of sideways, rotate the N's as needed - now they should be backward Z's. If you can, try rotating your screen to see if you see any normal N's or Z's - it should be impossible without X-ray vision.

If you need to move to a new strip, you double each end point. This causes a pair of zero width degenerate triangles which won't be drawn. Your next triangle will need to haveIf used sparingly, this can still save a lot of space. You will find examples of this technique in this week's exercise.

Getting direction right can be tricky, so I've added a rule that makes the back of triangles bright green. That way, you'll spot your errors quickly.


Extended Resources
Save

Annotate

Next Chapter
Assignment 1
PreviousNext
Powered by Manifold Scholarship. Learn more at
Opens in new tab or windowmanifoldapp.org