My favorites | English | Sign in

Shapes

This chapter describes how to specify 3D geometry using vertex data. It explains how to set up the objects in the transform graph and how to define arrays, buffers, fields, and streams for the geometric data.

The Hello, Cube sample discussed in this chapter creates a spinning red cube. To view the code for Hello, Cube in a new window, click here.

Contents

  1. Shapes and Primitives
  2. Buffers
  3. Streams
  4. Code Example: Hello, Cube Colors
  5. Next: Materials

Shapes and Primitives

A shape is a collection of primitives, which are the containers for the geometry and other vertex data for the shape. A given primitive can reference only a single material. The vertex data itself is stored in regular JavaScript arrays. These arrays are added to O3D buffers, described in the following section.

Buffers

A buffer is a collection of vertex data. Logically, a buffer is divided into fields, one field for each data type in the buffer. A field specifies the type of values it contains (FloatField, UInt32Field, or UByteNField) and the number of components in each logical unit. For example, a buffer that contains vertex positions would contain a single field of type FloatField, with 3 components (x, y, and z):

 var positionsField = positionsBuffer.createField('FloatField', 3);

In addition to positions, buffers can store colors, normals, texture coordinates, tangents, and other per-vertex information for the primitive.

A buffer can contain multiple, interleaved types of data—for example, positions and texture coordinates for each vertex in the primitive. In this case, you create a field for each type of data in the buffer. Here is how you would create a buffer to hold both position and texture data:

// Create buffers containing the vertex data.
var vertexBuffer = g_pack.createObject('VertexBuffer');
var positionsField = vertexBuffer.createField('FloatField', 3);
var texCoordsField = vertexBuffer.createField('FloatField', 2);
vertexBuffer.set(interleavedPositionsAndTexcoordArray);

Streams

A field is associated with a stream, which tells the system how to interpret the buffer data. A stream associates a field with the input to a vertex shader, as follows:

Variable Description Values
semantic Specifies what semantic this stream will be mapped to in the vertex shader. POSITION, NORMAL, TANGENT, BINORMAL, COLOR, TEXCOORD
semantic_index Corresponds to the number on the semantic in the shader. In other words, a stream with a semantic of NORMAL and an index of 3 corresponds to the shader semantic NORMAL3. A number from 0 to n
field The field that supplies the data for this stream. FloatField, UByteNField UInt32Field
start_index The first element to use in the field. A number from 0 to n

Shaders use the semantic to find the appropriate vertex stream to process (POSITION, NORMAL, TEXCOORD, and so on).

All the vertex streams for a given primitive are associated with one stream bank.

Index Buffer: A Special Case

The index buffer is associated directly with the primitive. (It is not part of a stream bank.) This buffer describes how to connect the triangle indices to create the geometry of the primitive. The index buffer is treated separately from the other buffers so that the vertex data can be re-indexed in different ways.

The index buffer is optional. If you do not provide one, the primitive is not indexed.

Here is the code that creates the index buffer, sets the indices array, and associates the indexBuffer with the primitive:

var indexBuffer = g_pack.createObject('IndexBuffer');
indexBuffer.set(indicesArray);
cubePrimitive.indexBuffer = indexBuffer;

Back to top

Code Example: Hello, Cube Colors

This section describes the general structure of Hello, Cube Colors. The major tasks are as follows:

  1. Create the shape and primitive(s).
  2. Specify the vertex data.
  3. Add the data to a buffer, specifying fields.
  4. Set vertex streams and index buffer to the primitive.

Create the Shape, Primitive, and Stream Bank

The createCube() function creates the shape, primitive, and stream bank and associates the primitive with the shape and stream bank. It also assigns a material to the primitive, as follows:

function createCube(material) {
  var cubeShape = g_pack.createObject('Shape');
  var cubePrimitive = g_pack.createObject('Primitive');
  var streamBank = g_pack.createObject('StreamBank');

  cubePrimitive.material = material;
  cubePrimitive.owner(cubeShape);
  cubePrimitive.streamBank = streamBank;
  .
  .
  .

Later, this branch of objects will be attached to the transform graph.

Specify the Vertex Data

The Hello, Cube example specifies a cube primitive using an array of 8 vertices that are referenced by indices in an index buffer that defines triangles. The first 3 points in the indices array are used to create the first triangle, the second 3 points are used to create the second triangle, and so on.

Specify Primitive Attributes

The cube in this example is constructed from 12 triangles, 2 for each face. First, the attributes for the primitive (cubePrimitive) are set as follows:

  cubePrimitive.primitiveType = g_o3d.Primitive.TRIANGLELIST;
  cubePrimitive.numberPrimitives = 12; // 12 triangles
  cubePrimitive.numberVertices = 8;    // 8 vertices in total
  cubePrimitive.createDrawElement(g_pack, null);   // Create the draw element for this primitive.

Specify the Positions

Here is how you create an array of vertices for the 8 corners of the cube in this example:

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

Add the Array to a Buffer

The positionArray is now ready to add to the positionsBuffer. First, though, you need to create a field within the buffer to describe the type of data (FloatField) and the number of components in each logical unit (3):

// Create buffers containing the vertex data.
var positionsBuffer = g_pack.createObject('VertexBuffer');
var positionsField = positionsBuffer.createField('FloatField', 3);
positionsBuffer.set(positionArray);

Associate a Field with a Vertex Shader Input

Now, you're ready to set a vertex stream on the stream bank to supply data to a vertex shader. The setVertexStream() function associates fields with vertex shader inputs:

// Associate the positions Buffer with the StreamBank.
streamBank.setVertexStream(
  g_o3d.Stream.POSITION, // semantic: This stream stores vertex positions
  0,                     // semantic index: First (and only) position stream
  positionsField,        // field: the field this stream uses.
  0);                    // start_index: How many elements to skip in the field.

Specify the Index Data

Here is how you create an array of indices that specifies how to connect the vertices in the positionArray to form the 6 faces of the cube (each face is composed of 2 triangles):

var indicesArray = [
      0, 1, 2,  // face 1
      2, 1, 3,
      2, 3, 4,  // face 2
      4, 3, 5,
      4, 5, 6,  // face 3
      6, 5, 7,
      6, 7, 0,  // face 4
      0, 7, 1,
      1, 7, 3,  // face 5
      3, 7, 5,
      6, 0, 4,  // face 6
      4, 0, 2
  ];

As described earlier in Index Buffer: A Special Case, the indices array is special. It is added directly to the IndexBuffer, which is set on the primitive directly.

var indexBuffer = g_pack.createObject('IndexBuffer');
indexBuffer.set(indicesArray);
cubePrimitive.indexBuffer = indexBuffer;

Next: Materials

Materials describes how to create a material, an effect, and a shader. It also introduces you to transform semantics, which are based on NVidia's Standard Annotations and Semantics (SAS) and are used by O3D shaders to describe standard transformation matrices.

Back to top