PDA

View Full Version : Having trouble with glDrawElements and triangles



paul_nicholls
23-07-2012, 05:45 AM
Hi all,
I have figured out how to use glDrawElements to render quads from an interleaved array of vertices to draw an internal video buffer of pixels at various sizes to the screen (see code below):


procedure TApplication.RenderScreen;type
TVertex = packed record
x,y,z : Single;
col : TRGBA;
end;


var
col : TRGBA;
buff : PVideoBuffer;
palette : TChip16Palette;
sx,sy : Integer;
dx,dy : Integer;


Vertices : array[0..(cVideoPixelCount * 4) - 1] of TVertex; // 4 x vertices per screen pixel (maximum possible per screen...)
Indices : array[0..(cVideoPixelCount * 4) - 1] of GLuint; // 4 x vertices per screen pixel (maximum possible per screen...)
VPointer : PByte; // vertices pointer
CPointer : PByte; // color pointer
VertexCount : Integer;
begin
palette := FChip16.Palette;


col := palette[FChip16.BGColor];
glClearColor(col.r/255,col.g/255,col.b/255,col.a/255);


glClear(GL_COLOR_BUFFER_BIT);


glDisable(GL_BLEND);


// draw video buffer to screen
buff := FChip16.VideoBuffer;


// enable and specify pointers to vertex arrays
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);


VPointer := @Vertices[0];
CPointer := @Vertices[0];
Inc(CPointer,3 * SizeOf(GLfloat));


glVertexPointer(3, GL_FLOAT , SizeOf(TVertex), VPointer);
glColorPointer (4, GL_UNSIGNED_BYTE, SizeOf(TVertex), CPointer);


VertexCount := 0;
dy := 0;
for sy := 0 to cVideoResY - 1 do
begin
dx := 0;
for sx := 0 to cVideoResX - 1 do
begin
col := palette[buff^[sy,sx]];


if buff^[sy,sx] <> cTransparentColor then
begin
Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx;
Vertices[VertexCount].y := dy;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);


Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx+FScale;
Vertices[VertexCount].y := dy;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);


Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx+FScale;
Vertices[VertexCount].y := dy+FScale;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);


Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx;
Vertices[VertexCount].y := dy+FScale;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);
end;
Inc(dx,FScale);
end;
Inc(dy,FScale);
end;


// draw number of elements
glDrawElements(GL_QUADS, VertexCount, GL_UNSIGNED_INT,@Indices);


glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays
glDisableClientState(GL_COLOR_ARRAY);
end;

Since I want to see if I can port this to Android, I need to use triangles instead of quads (is deprecated now...), but I just can't figure out how to place the vertices in the vertex array to draw triangles properly...

My screen is orientated with (0,0) at top left, and using Clockwise winding for triangles, quads, etc.

Any ideas?
cheers,
Paul

VilleK
23-07-2012, 11:02 AM
Here is a great page that helped me a lot when porting from OpenGL to ES:

http://pandorawiki.org/Porting_to_GLES_from_GL

paul_nicholls
23-07-2012, 01:05 PM
Thanks for the link :)
I figured out how to do the triangles ok now...it was only a small change - 6 vertices per pixel (2 triangles) instead of 4 (quad), slightly larger arrays, and GL_TRIANGLES of course :D

procedure TApplication.RenderScreen;type
TVertex = packed record
x,y,z : Single;
col : TRGBA;
end;


var
col : TRGBA;
buff : PVideoBuffer;
palette : TChip16Palette;
sx,sy : Integer;
dx,dy : Integer;


Vertices : array[0..(cVideoPixelCount * 6) - 1] of TVertex; // 6 x vertices per screen pixel (maximum possible per screen...)
Indices : array[0..(cVideoPixelCount * 6) - 1] of GLuint; // 6 x vertices per screen pixel (maximum possible per screen...)
VPointer : PByte; // vertices pointer
CPointer : PByte; // color pointer
VertexCount : Integer;
begin
palette := FChip16.Palette;


col := palette[FChip16.BGColor];
glClearColor(col.r/255,col.g/255,col.b/255,col.a/255);


glClear(GL_COLOR_BUFFER_BIT);


glDisable(GL_BLEND);


// draw video buffer to screen
buff := FChip16.VideoBuffer;


// enable and specify pointers to vertex arrays
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);


VPointer := @Vertices[0];
CPointer := @Vertices[0];
Inc(CPointer,3 * SizeOf(GLfloat));


glVertexPointer(3, GL_FLOAT , SizeOf(TVertex), VPointer);
glColorPointer (4, GL_UNSIGNED_BYTE, SizeOf(TVertex), CPointer);


VertexCount := 0;
dy := 0;
for sy := 0 to cVideoResY - 1 do
begin
dx := 0;
for sx := 0 to cVideoResX - 1 do
begin
col := palette[buff^[sy,sx]];


if buff^[sy,sx] <> cTransparentColor then
begin
Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx;
Vertices[VertexCount].y := dy;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);


Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx+FScale;
Vertices[VertexCount].y := dy;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);


Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx+FScale;
Vertices[VertexCount].y := dy+FScale;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);


Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx+FScale;
Vertices[VertexCount].y := dy+FScale;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);


Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx;
Vertices[VertexCount].y := dy+FScale;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);


Vertices[VertexCount].col := col;
Vertices[VertexCount].x := dx;
Vertices[VertexCount].y := dy;
Indices[VertexCount] := VertexCount;
Inc(VertexCount);
end;
Inc(dx,FScale);
end;
Inc(dy,FScale);
end;


// draw number of elements
glDrawElements(GL_TRIANGLES, VertexCount, GL_UNSIGNED_INT,@Indices);


glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays
glDisableClientState(GL_COLOR_ARRAY);
end;

SilverWarior
23-07-2012, 07:17 PM
Why do you need 6 verticles? Wouldn't be 5 enough. Verticle which diagonally splits pixel in half can be shared by both triangles.

paul_nicholls
23-07-2012, 08:43 PM
Why do you need 6 verticles? Wouldn't be 5 enough. Verticle which diagonally splits pixel in half can be shared by both triangles.

I am pretty sure that with glDrawElements and triangles, that the vertices are separate in each triangle...
If I was using triangle strip or fan then it would reuse vertices wouldn't it?

If I am wrong please let me know!! :D

phibermon
23-07-2012, 10:11 PM
you'll find that most model formats intended to be rendered on a GPU will be stored as plain old triangles. (sometimes strips but fans only make sence with certain patches of geometry)

It was the case many years ago that some game engines stored models as a combination of tris/strips/fans so you were passing less data to the GPU, but :

A) Today in almost all practical 3D model cases, it's slower to make multiple calls to glDrawElements (splitting a model into tris/strips/fans) than it is to pass more data than is technically needed (just GL_TRIANGLES)

B) For various reasons such as using multiple textures/materials on a single model, you'll need to clone any verticies that are shared but have different normals/UVs. Strips/Fans share verticies by definintion so when using an index buffer, you can't have different UVs/Normals per face, for the same vertex.

--

So you'll often find you'll need to 'unwrap' shared verticies/uvs/normals. For example a vertex may be shared between two faces but those faces may have different materials or require lighting in unique ways (so different normals for a vertex, per face. think smoothing groups in the wavefront .obj format)

That's not to say there won't be shared vert/norm/uv sets. Basic rule of thumb is to identify any unique sets of vert/norm/uv and ensure that each unique combination has it's own index.

My engine (JenJin) has several model plugins that load into a generic data structure, depending on how a particular model format is stored I can either unwrap shared verts/uvs/normals as I load or do another pass after the data is loaded. (only for importing model formats of course, you don't want to be hogging the CPU in a game release, store your objects in a binary format with a layout that lets you get the data to the GPU with the bare minimum of pre-processing)

After unwrapping it's off to the GPU. It's normal to generate index buffers per material (I've not come across a method of drawing sub-sets of a shared index buffer with glDrawElements but if I'm being blind someone please enlighten me)

Be sure to replicate any per vertex data in this process, for example if you're loading bone weights they'll need to cloned along with the vertex.

--

Long story short : use GL_TRIANGLES for almost everything, it's your only practical choice for models using multiple materials.

Only use fans/strips for special geometry cases that use a single, continually mapped texture (a sky dome for example) but you'd have to be drawing a lot of this special case geometry to make it worth optimizing.

paul_nicholls
24-07-2012, 01:57 AM
Thanks guys :)

User137
24-07-2012, 09:56 PM
Basic rule of thumb is to identify any unique sets of vert/norm/uv and ensure that each unique combination has it's own index.
That's actually last thing i was working on with nxPascal. It wasn't ready yet at the time of the contest and you could see the vertex-sharing-done-wrong with the planet textures ;) Hopefully it's fixed now though, is rather nasty issue to deal with.

edit: Actually what made it even more complicated, was that the shared vertices may have exact same texture coordinates and normals. But when you go near the margin where texture coordinates wrap from 1.0 back to 0.0 side, you see 1 texture draw itself in a bugged manner. It may all look good and fine in a modelling program which doesn't share vertices when rendering, but not when going to vertex index mode.

Dan
25-07-2012, 12:31 AM
is rather nasty issue to deal with.
wait and see how nasty it gets when you add tangents and binormals to the equation;)