triangle strips and vertex arrays...

Hi,

I am trying to render a surface using triangle strips whose vertices are specified in Vertex Arrays (actually, Vertex Buffer Objects). What I would like to do is specify vertices in one or more vertex arrays and render them as TriangleStrips via glDrawArrays.

The problem that i am having is that the first vertex of the first triangle appears to be the origin!? The behavior I was expecting was that the first vertex I specify would be the first vertex of the first triangle.

I’m including a screen shot of a decimated, wire frame version of the surface which shows the first triangle includes the orgin (notice the black triangle pointing to the center).

Any thoughts on how to get around/modify this behavior? Or, am I stuck using alternatives to tirangle strips?

Thanks…

are you passing the byte offset of your vertex data in glVertexPointer? this is a common mistake.

for example, for an array of packed 3-vectors:

glVertexPointer(3,GL_FLOAT,0,(byte*)pStripData - (byte*)NULL);

if you have several terrain strips in a single vbo, each strip would have a different offset within that array. anyways, this is a common mistake, and might account for the origin being duplicated…

are you trying to render multiple adjacent strips in a single call without insserting degenrates? you may want to consider just sending triangles if degenerates are inconvenient.

hmmm. i’m out of guesses. can you post your vbo code so we can have a good looksy?

Ok… here is the code. This is Java using JOGL. The VertexBufferTerrain class is responsible for specifying the vertices to OpenGL. The Data class specified to the constructor maps a z-height to an x,y coordinate and the ColorMap class determines the color for a particular z-height.

What are degenerates? I was thinking that I need to terminate the end of one strip somehow. Is that what a degenerate is for? That wasn’t necessary using glBegin, glEnd, and glVertex. I tried splitting the triangle strips into different VBOs, but that didn’t seem to work.

Thanks…

  

import java.awt.Color;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import net.java.games.jogl.GL;

final class VertexBufferTerrain
  implements Terrain
{
  // CONSTRUCTORS
  //

  public VertexBufferTerrain( final Data imageData, final ColorMap colors )
  {
    this.vertexBufferID = new int[1];
    this.colorBufferID = new int[1];
    
    this.numVertices = 2*(imageData.getHeight()-1)*imageData.getWidth();
    this.vertexBuffer = FloatBuffer.allocate( numVertices*3 );
    this.colorBuffer = ByteBuffer.allocate( numVertices*3 );
    this.zScale = -20f;

    populateBuffers( imageData, colors );
  }

  private void populateBuffers( final Data data, final ColorMap colors )
  {
    int width = data.getWidth();
    int height = data.getHeight();
    
    // these offsets are used to center the surface at the origin...
    float xOffset = -width/2f;
    float yOffset = -height/2f;

    // the resulting x and y coordinates after the offsets are applied..
    float xCoord;
    float yCoord;

    // reuse these...
    float dataValue;
    Color color;
    
    for( int y=1; y<height; y++ )
    {
      int topYIndex = height-1-y;
      int botYIndex = height-y;

      for( int x=0; x<width; x++ )
      {
        xCoord = (x+xOffset); 
        yCoord = (y+yOffset); 
        
        vertexBuffer.put( xCoord );
        vertexBuffer.put( yCoord );
        dataValue = data.getValue( x, topYIndex );
        vertexBuffer.put( dataValue*zScale );
        
        color = colors.getColor( dataValue );
        colorBuffer.put( (byte)color.getRed() );
        colorBuffer.put( (byte)color.getGreen() );
        colorBuffer.put( (byte)color.getBlue() );
        
        yCoord = (y-1+yOffset); 
        vertexBuffer.put( xCoord );
        vertexBuffer.put( yCoord );
        dataValue = data.getValue( x, botYIndex );
        vertexBuffer.put( dataValue*zScale );
        
        color = colors.getColor( dataValue );
        colorBuffer.put( (byte)color.getRed() );
        colorBuffer.put( (byte)color.getGreen() );
        colorBuffer.put( (byte)color.getBlue() );
      }
    }
  }

  // METHODS
  //

  private void initialize( final GL gl )
  {
    // it would be nice to do this in the ctor, but we don't have a handle
    // to gl until the first time drawTerrain is invoked.  Perhaps just
    // move this into the interface...
  
    if( vertexBuffer != null )
    {
      // generate and bind the buffer
      //
      // 1) get the buffer ID
      gl.glGenBuffers( 1, vertexBufferID ); 

      // 2) bind the buffer (e.g. make it the active buffer).
      gl.glBindBuffer( GL.GL_ARRAY_BUFFER, vertexBufferID[0] ); 

      // load the data into the buffer...
      gl.glBufferData( 
        GL.GL_ARRAY_BUFFER, 
        vertexBuffer.capacity()*4, 
        vertexBuffer.array(), 
        GL.GL_STATIC_DRAW );
      
      // we don't need the geometry anymore... it's on the graphics card
      vertexBuffer.clear();
      vertexBuffer = null;
    }
    
    if( colorBuffer != null )
    {
      // generate and bind the buffer
      //
      // 1) get the buffer ID
      gl.glGenBuffers( 1, colorBufferID ); 
      
      // 2) bind the buffer (e.g. make it the active buffer).
      gl.glBindBuffer( GL.GL_ARRAY_BUFFER, colorBufferID[0] ); 
      
      // load the data into the buffer...
      gl.glBufferData( 
        GL.GL_ARRAY_BUFFER, 
        colorBuffer.capacity(), 
        colorBuffer.array(), 
        GL.GL_STATIC_DRAW );

      // we don't need the geometry anymore... it's on the graphics card
      colorBuffer.clear();
      colorBuffer = null;
    }
  }

  public void shutdown( final GL gl )
  {
    gl.glDeleteBuffers( GL.GL_ARRAY_BUFFER, vertexBufferID );
    gl.glDeleteBuffers( GL.GL_ARRAY_BUFFER, colorBufferID );
  }

  public void drawTerrain( final GL gl )
  {
    initialize( gl );
    
    gl.glBindBuffer( GL.GL_ARRAY_BUFFER, vertexBufferID[0] );
    gl.glVertexPointer( 
      3,           // num floats in a vertex (x,y,z)
      GL.GL_FLOAT, // the data type
      0,           // the stride between verteces in the array
      null         // this would be the vertex array, if using those...
      );

    gl.glBindBuffer( GL.GL_ARRAY_BUFFER, colorBufferID[0] );
    gl.glColorPointer(
      3,                   // num color components
      GL.GL_UNSIGNED_BYTE, // data type
      0,                   // the stride
      null                 // this would be the color array, if not using VBOs
      );
    
    // now, do the drawing...
    gl.glDrawArrays( GL.GL_TRIANGLE_STRIP, 0, numVertices );
  }


  // ATTRIBUTES
  //

  // RxTBD: we probably only need one array for the buffer IDs.
  private final int[] vertexBufferID;
  private final int[] colorBufferID;
  private FloatBuffer vertexBuffer;
  private ByteBuffer colorBuffer;
  private final int numVertices;
  private float zScale;

  // STATICS
  //
}

i had trouble reading the java code, but i think your problem is in the vertex arrangement.

i’m posting a working code snippet that i yanked out of a glut app which uses an index buffer instead to index triangle strips. this is genrally more efficient than plowing through vertex arrays. later, when you want to render parts of the terrain instead of the whole thing you’ll probably find this method handy.

if you’re bent upon using arrays, go over your vertex specification with a fine toothed comb for mistakes. it’s probably something simple…

anyways, the code is c++, but i hope it helps.

//
// Terrain vertex data
//
const int terSize = 64+1;
const int terGrid = 2;
float terVerts[terSize][terSize][3];
GLuint terVertexBuffer = 0;

//
// Terrain index data
// NB: Unsigned shorts are generally more efficient...
//
const int terIndexSize = (terSize+2)*terSize*2;
GLuint terIndices[terIndexSize];
GLuint terIndexBuffer = 0;


void drawTerrain()
{
	if( terVertexBuffer == 0 )
	{
		//
		// Generate a terrain in the xz plane
		//
		for( int z = 0; z < terSize; z++ )
		{
			for( int x = 0; x < terSize; x++ )
			{
				terVerts[z][x][0] = (x - terSize/2)*terGrid;
				terVerts[z][x][2] = (z - terSize/2)*terGrid;
				terVerts[z][x][1] = 0; 
			}
		}

		//
		// Create the vertex buffer
		//
		glGenBuffers( 1, &terVertexBuffer );       
		glBindBuffer( GL_ARRAY_BUFFER, terVertexBuffer );      
		glBufferData( GL_ARRAY_BUFFER, terSize*terSize*3*sizeof(float), terVerts, GL_STATIC_DRAW );      
	

		//
		// Create the tristrip index buffer
		//
		GLuint* index = terIndices;
		for( int z = 0; z < terSize-1; z++ )
		{
			for( int x = 0; x < terSize; x++ )
			{
				*index++ = x + z*terSize;
				*index++ = x + (z+1)*terSize;
			}
			//
			// Add a degenerate triangle at the end
			// for warp to other side.
			// Modern harware will recognize the degenerate case and 
			// do away with it, so this only costs us an additional
			// 2 indices per row.
			// 
			// The alternative is to index triangles, 
			// which is not bad at all if your vertices are 
			// arranged in a cache friendly way (they are in this case).
			//
			*index++ = x-1 + (z+1)*terSize;
			*index++ = 0 + (z+1)*terSize;
		}
		glGenBuffers( 1, &terIndexBuffer );       
		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, terIndexBuffer );      
		glBufferData( GL_ELEMENT_ARRAY_BUFFER, terIndexSize*sizeof(GLuint), terIndices, GL_STATIC_DRAW );      
	}

	// Set render state
	glEnable( GL_CULL_FACE );
	glColor3f(1,0,0);
	//glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

	// Bind vertex buffer
	glBindBuffer( GL_ARRAY_BUFFER, terVertexBuffer );      
	glVertexPointer( 3, GL_FLOAT, 0, 0 );
	glEnableClientState( GL_VERTEX_ARRAY );

	// Bind index buffer
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, terIndexBuffer );      

	// Draw the entire terrain
	glDrawElements( GL_TRIANGLE_STRIP, terIndexSize, GL_UNSIGNED_INT, 0 );
	
	// Tidy up (if necessary)
	glDisableClientState( GL_VERTEX_ARRAY );
	glBindBuffer( GL_ARRAY_BUFFER, 0 );      
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );      
}

What are degenerates? I was thinking that I need to terminate the end of one strip somehow. Is that what a degenerate is for?
sorry, i missed this part. yeah, degenerate triangles are triangles with 0 area. this is just the thing to fix those cases at the end.

ooops…correction to code above:
const int terIndexSize = (terSize+1)*(terSize-1)*2;

Thanks… I’ll try throwing in a couple of degenerates to fix the end cases.

FYI… adding degenerates to the beginning and end of each row seemed to fix my problem. Thanks for the help.

my pleasure. glad it helped.

i’m a bit curious about having to add a degen at the beginning. are you sure about this?

maybe it’s because your zigzag starts at z+1 rather than z? that would mean your last vertex on a row is at z. in this case, i think the degenerate would be (lastX,z) (lastX,z+1), followed by (0,z+1), (0,z+1). maybe that’s what you are seeing and that would make sense.

the trick is to get from point a to point b with 3 consecutive vertices in a line. so you could either flip your zigzag pattern, which leaves you in good shape to start a fresh strip on the other side, or live with the 2 extra degenerate triangles and keep things the way they are.