PDA

View Full Version : textures and VBOs



asheridan
07-07-2005, 11:11 AM
Hi.

I'm trying to use a 2D texture to color a terrain. The geometry (triangle strips) and texture coordinates are stored in VBOs and drawing is achieved using glVertexPointer, glTexCoordPointer, and glDrawArrays.

I'm running into a peculiar problem: when I render all strips the first ??? of strips are colored (e.g. texture mapped) correctly and the remaining are munged. To verify that the coordinates are correctly specified in the coordinate buffer, I've rendered different portions of the terrain (e.g. the first 50 strips, the next 50 strips, the last 50 strips) and these strips appear to render correctly. Here are some snapshots of those tests:

http://asheridan.net/FullRendering.png
http://asheridan.net/FirstFifty.png
http://asheridan.net/NextFifty.png
http://asheridan.net/LastFifty.png

The code is below. It is written in Java. The populateBuffers() method populates the buffers (java.nio.Buffer instances) with geometry, texture coordinates, and texture image data. The initialize(GL) method puts those buffers in VBOs and creates the texture. Finally, the drawTerrain(GL) method does the drawing. The colorBuffer isn't being used (because we are using the texture for colors), so please disregard that buffer.

Since the constructor specified classes aren't shown here: the Data class determines the z-coordinate for a given x,y on the terrain and the ColorMap class determines the color for a give z-coordinate.

Any thoughts? Am I setting up my texture coordinate buffer incorrectly?

Thanks,
Andrew




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 // package private!
implements Terrain
{
// CONSTRUCTORS
//

public VertexBufferTerrain( final Data imageData, final ColorMap colorMap )
{
this.data = imageData;
this.colors = colorMap;

// RxTBD: we probably only need one array for the buffer IDs, but this
// is more clear so I'm keeping it for now.
this.vertexBufferID = new int[1];
this.colorBufferID = new int[1];
this.texCoordBufferID = new int[1];

int numStrips = imageData.getHeight() - 1;
int numDegenerateVerticesPerStrip = 4;
int numVerticesPerStrip =
2*imageData.getWidth() + numDegenerateVerticesPerStrip;

this.numVertices = numVerticesPerStrip*numStrips;

int bufferCapacity = (numVertices*3);
this.vertexBuffer = FloatBuffer.allocate( bufferCapacity );
this.colorBuffer = ByteBuffer.allocate( bufferCapacity );
this.texCoordBuffer = FloatBuffer.allocate( bufferCapacity );

// The texture buffer needs a width and height that are the next power
// of two greater than the max of the width and the height of the
// image.
//
// RxTBD: width and height can be independent...
textureBufferSize =
lumeniq.util.math.MathUtils.findNextPowerOfTwo(
Math.max( imageData.getWidth(), imageData.getHeight() ) );
this.textureBuffer =
ByteBuffer.allocateDirect( textureBufferSize*textureBufferSize );

populateBuffers();
}

// METHODS
//

private void populateBuffers()
{
int width = data.getWidth();
int height = data.getHeight();
int numStrips = height - 1;

// the coordinates for the top left corner of the terrain
final float xStart = -width/2f;
final float yStart = height/2f;

// each triangle strip has a top and bottom y (duh!).
int topImageY = 0;
int bottomImageY = 0;

// reuse these...
float dataValue = 0f;
Color color = Color.BLACK;
float xCoord = 0f;
float yCoord = 0f;
final float texCoordDenominator = textureBufferSize-1;

for( int strip = 0; strip < numStrips; ++strip )
{
topImageY = strip;
bottomImageY = strip+1;

// start with a degenerate...
dataValue = data.getValue( 0, bottomImageY );
degenerate( xStart, yStart-bottomImageY, dataValue, Color.BLUE,
0, bottomImageY/texCoordDenominator );

for( int x = 0; x < width; ++x )
{
xCoord = x+xStart;
yCoord = yStart-bottomImageY;
dataValue = data.getValue( x, bottomImageY );
color = colors.getColor( dataValue );
vertex( xCoord, yCoord, dataValue, color,
x/texCoordDenominator, bottomImageY/texCoordDenominator );

yCoord = yStart-topImageY;
dataValue = data.getValue( x, topImageY );
color = colors.getColor( dataValue );
vertex( xCoord, yCoord, dataValue, color,
x/texCoordDenominator, topImageY/texCoordDenominator );
}

// end with a degenerate...
degenerate( xCoord, yCoord, dataValue, Color.BLUE,
(width-1)/texCoordDenominator,
topImageY/texCoordDenominator );
}

// fill the textureBuffer with the colors...
for( int y = 0; y < height; ++y )
{
for( int x = 0; x < width; ++x )
{
textureBuffer.put(
(byte)colors.getColor( data.getValue( x, y ) ).getRed() );
}

// fill the remainder of this row in the textureBuffer with 0s...
for( int i = 0; i < textureBufferSize - width; ++i )
textureBuffer.put( (byte)0 );
}
}

private void vertex( float x, float y, float z, Color color,
float textureX, float textureY )
{
vertexBuffer.put( x );
vertexBuffer.put( y );
vertexBuffer.put( z );
colorBuffer.put( (byte)color.getRed() );
colorBuffer.put( (byte)color.getGreen() );
colorBuffer.put( (byte)color.getBlue() );
texCoordBuffer.put( textureX );
texCoordBuffer.put( textureY );
}

private void degenerate( float x, float y, float z, Color color,
float textureX, float textureY )
{
vertex( x,y,z,color, textureX, textureY );
vertex( x,y,z,color, textureX, textureY );
}

public void initialize( final GL gl )
{
gl.glEnableClientState( GL.GL_VERTEX_ARRAY );
//gl.glEnableClientState( GL.GL_COLOR_ARRAY );
gl.glEnableClientState( GL.GL_TEXTURE_COORD_ARRAY );

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 colors anymore...
colorBuffer.clear();
colorBuffer = null;
}

if( texCoordBuffer != null )
{
// texture stuph...
int numTextures = 1;
textureID = new int[numTextures];
gl.glGenTextures( numTextures, textureID );
gl.glBindTexture( GL.GL_TEXTURE_2D, textureID[0] );

// RxTBD: asheridan: are these correct?
gl.glTexParameteri(
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE );
gl.glTexParameteri(
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE );

gl.glTexParameteri(
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR );
gl.glTexParameteri(
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR );

// now, specify the friggin' texture...
gl.glTexImage2D(
GL.GL_TEXTURE_2D,
0, // not mipmapping
1, // only one color component in the texture
textureBufferSize,
textureBufferSize,
0, // no border
GL.GL_LUMINANCE, //replicates single value across RGB, alpha is 1
GL.GL_UNSIGNED_BYTE, // textureBuffer has unsigned byte values...
textureBuffer
);

// generate and bind the buffer
//
// 1) get the buffer ID
gl.glGenBuffers( 1, texCoordBufferID );

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

// load the data into the buffer...
gl.glBufferData(
GL.GL_ARRAY_BUFFER,
texCoordBuffer.capacity(),
texCoordBuffer.array(),
GL.GL_STATIC_DRAW );

// we don't need the coords anymore...
texCoordBuffer.clear();
texCoordBuffer = null;
}
}

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

public void drawTerrain( final GL gl )
{
gl.glEnable( GL.GL_TEXTURE_2D );
gl.glTexEnvf( GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE );

gl.glActiveTexture( GL.GL_TEXTURE0 );
gl.glBindTexture( GL.GL_TEXTURE_2D, textureID[0] );


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
// );

gl.glBindBuffer( GL.GL_ARRAY_BUFFER, texCoordBufferID[0] );
gl.glTexCoordPointer(
2,
GL.GL_FLOAT,
0,
null // the pointer is the currently bound buffer...
);

// now, do the drawing...
gl.glDrawArrays( GL.GL_TRIANGLE_STRIP, 0, numVertices );

//JoglUtils.checkError( "drawTerrain", gl, null );

gl.glDisable( GL.GL_TEXTURE_2D );
}


// ATTRIBUTES
//

private final Data data;
private final ColorMap colors;

/**
* The total number of vertices, including degenerates (used to separate
* triangle strips).
*/
private final int numVertices;

// RxTBD: we probably only need one array for the buffer IDs.
private final int[] vertexBufferID;
private final int[] colorBufferID;
private final int[] texCoordBufferID;


private FloatBuffer vertexBuffer;
private ByteBuffer colorBuffer;
private FloatBuffer texCoordBuffer;

/**
* This buffer will be the next power of two greater than the max of the
* width and the height of the image.
*/
private ByteBuffer textureBuffer;

/**
* The width and the height of the texture buffer (the next power of two
* greater than the max of the image width and height).
*/
private final int textureBufferSize;

/** The ID (or "name", in OpenGL terms) of the texture. */
private int[] textureID;

// STATICS
//
}

07-07-2005, 07:04 PM
gl.glBufferData( GL.GL_ARRAY_BUFFER, texCoordBuffer.capacity(), texCoordBuffer.array(), GL.GL_STATIC_DRAW );You do realize that the size parameter is in machine units (bytes). I don't know what capacity() returns (number of elements?).

And make sure your texcoords make sense with your wrap. CLAMP_TO_EDGE will clamp (s,t) to [1/(2n), 1 - 1/(2n)].

Hlz

asheridan
07-08-2005, 10:27 AM
Thank you! texCoordBuffer.capacity() was returning the number of floats. Simply multiplying this by 4 fixed my problem.

I'll have to attribute this to a copy and paste error on my part :-( Notice I was correctly specifying the size param for the vertexBuffer, but forgot to add this to the texCoordBuffer...

Thanks for finding my bug...

Andrew

07-08-2005, 08:33 PM
My pleasure.

Made me forget about my own bugs for a moment ;-)

Hlz