Reuse Same Normal Within One Triangle

Suppose I have a VBO with the below vertex struct, which is rendered as GL_TRIANGLES with a low-poly flat-shading style (normals are not interpolated across the triangle):

struct Vertex
    vec3 Position; (three floats)
    vec3 Normal; (three floats)


Gl.VertexAttribPointer(0, 3, VertexAttribType.Float, false, 24, IntPtr.Zero);
Gl.VertexAttribPointer(1, 3, VertexAttribType.Float, false, 24, (IntPtr)12);

Each triangle then consists of 3 Positions and 3 Normals, meaning the same Normal value is unnecessarily repeated 3 times.

Is it possible to redefine the Vertex struct like so, and modify the stride in glVertexAttribPointer to re-use the one normal vec3 across each triangle?

struct Vertex
    vec3 Position0;
    vec3 Position1;
    vec3 Position2;
    vec3 Normal;


Gl.VertexAttribPointer(0, 3, VertexAttribType.Float, false, 12, IntPtr.Zero);
Gl.VertexAttribPointer(1, 3, VertexAttribType.Float, false, 48, (IntPtr)36);

I’m not sure the above is valid, as a stride of 12 for position data means the normal will be interpreted as position data.

Is it possible to instead have two separate VBOs (one for position data and one for normal data) and use glVertexAttribDivisor to advance the normal buffer once for every 3 position vertices? Or is this function intended solely for instanced rendering?

If you want to do flat shading, declare the fragment shader input variable corresponding to the vertex colour with the flat qualifier. That will cause the vertex shader output for the last vertex of the triangle to be used for all fragments, rather than interpolating between the values for the three vertices.

You’ll need to set up the mesh so that each vertex is the last vertex of one triangle (or a group of triangles which share the same normal; e.g. if the mesh is made up of quads, every normal will be used by two triangles).

For an arbitrary triangle mesh, you’ll need as many vertices as there normals, which is typically around double the number of distinct vertex positions.

Alternatively, you can remove the normals from the vertex structure and compute them in the fragment shader with something like

in vec3 pos;
    vec3 normal = normalize(cross(dFdx(pos), dFdy(pos));

normal will be in the same coordinate system as pos.

No and yes respectively.

From what I understand glVertexAttribDivisor is only used to advance either in between vertices or instances as it says in the specification here

modifies the rate at which generic vertex attributes advance when rendering multiple instances of primitives in a single draw call

That is a good solution to solve the problem if we were to remove the normal data completely, but wouldn’t it be a litte more costly than using a precomputed set of 3 normals for each vertex even if they are redundant? Performance-wise and memory-wise the latter approach seems a bit better in comparison, isn’t it?

I have this in my shader at the moment, I’m just exploring methods of reducing the amount of data in my VBOs.

This is great! Thank you for this code snippet.

It’s hard to say how it compares against the costs of using normals in the vertex array.

Compared to a typical fragment shader with most of the Phong illumination model being calculated per vertex, it’s fairly trivial.

The main advantage is that it avoids having to allocate vertices to faces. Unless you’re dealing with a highly regular mesh (e.g. a rectangular grid), allocating vertices to faces while minimising the total number of vertices is a hard problem (possibly a NP-hard problem if you want the absolute minimum).

1 Like

Actually, looking at this again… won’t I need the position of two vertices in the triangle to calculate the normal?

Since it’s flat shading, I can compute this normal in the vertex shader which should have much overhead. I’ll have to benchmark it, it depends if the GPU is bottlenecked by memory bandwidth or processing.

If I calculate these normals in the vertex shader, it shouldn’t add much overhead.

The issue is that this must be done per Fragment as it says:

Available only in the fragment shader

So it would have to be computed in the fragment shader, like GClements said above

1 Like

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.