In this chapter the book covers some more advanced shader topics that will allow you to use your programmable graphics hardware for more than simple polygon rendering.

In this post I will focus in the first part of the chapter. Here an entirely new shader stage is introduced: the geometry shader, which can process entire primitives and even generate new primitives on the fly. I’ll present 4 examples of simple geometry shader usage. They may not be very useful, but this is only introductory material ;).

The vertex and fragment shaders for the three first examples are the same, and they are…

#version 330 precision highp float; // Attributes per vertex: position and normal in vec4 vertex; in vec3 normal; uniform mat4 MVP; uniform mat4 modelview; uniform mat3 normalMatrix; uniform vec3 lightPos; out VertexData { vec4 color; vec3 normal; } vertexData; void main(void) { vec3 N = normalize (normalMatrix * normal); // Get vertex position in eye coordinates vec4 vertexPos = modelview * vertex; vec3 vertexEyePos = vertexPos.xyz / vertexPos.w; // Get vector to light source vec3 L = normalize(lightPos - vertexEyePos); // Dot product gives us diffuse intensity vertexData.color = vec4 (.3f, .3f, .9f, 1.f) * max (0.f, dot (N, L)); // Don't forget to transform the geometry! gl_Position = MVP * vertex; vertexData.normal = normal; }

#version 330 precision highp float; smooth in vec4 color; out vec4 outColor; void main (void) { outColor = color; }

And here are the examples:

# Culling

This example implements an “imaginary” viewpoint and culls the triangles that face backwards.

#version 330 precision highp float; layout (triangles) in; layout (triangle_strip, max_vertices = 3) out; in VertexData { vec4 color; vec3 normal; } vertexData[]; uniform vec3 viewpoint; smooth out vec4 color; void main(void) { // Calculate two vectors in the plane of the input triangle vec3 ab = gl_in[1].gl_Position.xyz - gl_in[0].gl_Position.xyz; vec3 ac = gl_in[2].gl_Position.xyz - gl_in[0].gl_Position.xyz; vec3 normal = normalize (cross (ac, ab)); // Calculate the transformed face normal and the view direction vector vec3 vt = normalize (viewpoint - gl_in[0].gl_Position.xyz); // Take the dot product of the normal with the view direction float d = dot (vt, normal); // Emit a primitive only if the sign of the dot product is positive if (d > 0.f) { for (int i = 0; i < gl_in.length (); ++i) { gl_Position = gl_in[i].gl_Position; color = vertexData[i].color; EmitVertex (); } EndPrimitive (); } }

# Explode

This example computes the triangles’ normals and displaces the triangles along them.

#version 330 precision highp float; layout (triangles) in; layout (triangle_strip, max_vertices = 3) out; in VertexData { vec4 color; vec3 normal; } vertexData[]; uniform float explode_factor; smooth out vec4 color; void main(void) { vec3 face_normal = normalize ( cross (gl_in[2].gl_Position.xyz - gl_in[0].gl_Position.xyz, gl_in[1].gl_Position.xyz - gl_in[0].gl_Position.xyz)); for (int i = 0; i < gl_in.length (); ++i) { color = vertexData[i].color; gl_Position = gl_in[i].gl_Position + vec4 (explode_factor * face_normal, 0.f); EmitVertex (); } EndPrimitive (); }

# Tessellate

This example creates a new vertex on every triangle that forms a cube and places it at the same distance as the rest of the vertices, then creates a triangle strip with the new vertex. We now have 2 triangles for each one that entered the geometry shader!

Note: For this example, the vertex positions in the vertex shader doesn’t have to be transformed. Just a passthrough. (Thanks Lezardong)

#version 330 precision highp float; layout (triangles) in; layout (triangle_strip, max_vertices = 6) out; in VertexData { vec4 color; vec3 normal; } vertexData[]; uniform mat4 MVP; smooth out vec4 color; void main(void) { vec3 a = normalize(gl_in[0].gl_Position.xyz); vec3 b = normalize(gl_in[1].gl_Position.xyz); vec3 c = normalize(gl_in[2].gl_Position.xyz); vec3 d = normalize(b + c); gl_Position = MVP * vec4 (b, 1.f); color = vec4 (1.f, 0.f, 0.f, 1.f); EmitVertex (); gl_Position = MVP * vec4 (d, 1.f); color = vec4 (0.f, 1.f, 0.f, 1.f); EmitVertex (); gl_Position = MVP * vec4 (a, 1.f); color = vec4 (0.f, 0.f, 1.f, 1.f); EmitVertex (); gl_Position = MVP * vec4 (c, 1.f); color = vec4 (1.f, 0.f, 1.f, 1.f); EmitVertex (); }

# Normals

The last example uses a slightly different vertex shader to create a new effect. It renders the polygon’s normals. The geometry shader transforms the triangles into lines representing vertices and faces normals. To do this, the vertex shader doesn’t have to transform the geometry, that’s why the following vertex shader is just a pass-through for the geometry.

#version 330 precision highp float; // Attributes per vertex: position and normal in vec4 position; in vec3 normal; out VertexData { vec3 normal; } vertexData; void main(void) { // Pass through the data, the Geometry Vertex will transform it gl_Position = position; vertexData.normal = normal; }

#version 330 precision highp float; layout (triangles) in; layout (line_strip, max_vertices = 8 ) out; in VertexData { vec3 normal; } vertexData[]; uniform mat4 MVP; smooth out vec4 color; void main(void) { // Normals for the triangle vertices for (int i = 0; i < gl_in.length (); ++i) { color = vec4 (1.f, .3f, .3f, 1.f); gl_Position = MVP * gl_in[i].gl_Position; EmitVertex (); color = vec4 (0.f); gl_Position = MVP * (gl_in[i].gl_Position + vec4 (vertexData[i].normal * .05f, 0.f)); EmitVertex (); EndPrimitive (); } // Triangle face normal (from triangle's centroid) vec4 cent = (gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position) / 3.f; vec3 face_normal = normalize ( cross (gl_in[1].gl_Position.xyz - gl_in[0].gl_Position.xyz, gl_in[2].gl_Position.xyz - gl_in[0].gl_Position.xyz)); gl_Position = MVP * cent; color = vec4 (.3f, 1.f, .3f, 1.f); EmitVertex (); gl_Position = MVP * (cent + vec4 (face_normal * .1f, 0.f)); color = vec4 (0.f); EmitVertex (); EndPrimitive (); }

#version 330 precision highp float; smooth in vec4 color; out vec4 outColor; void main (void) { outColor = color; }

And finally, here are some cool screenshots!

Very helpful!!

Very useful and simple, thanks!

There’s one thing that’s bothering me, in the tessellation example, by adding a new vertex the resulting two triangles lie in the same plane as the original one, so they have the same normal and thus they are equally affected by light and indistinguishable from each other, is that right?

Thank you!

No, they are not in the same plane (d is not in the same than (b+c)/2). You are right in the rest, though; they would be indistinguishable. But then, why bother tessellating? :)

That was exactly what was confusing me, gracias!

If normals were recalculated using this new vertex then the tessellation would be noticeable, right?

In this case, the position is the normal as well :), but I’m applying no lighting, I only output colors. You can tell which vertex is which by its color.

I added some lighting to see the results and got some strange outputs, that confused me more.

This post has been a great introduction to geometry shaders, thanks again.

Hello !

This post has more than one year now, hope you are still here.

For the tesselation shader, why do you multiply the vertex position by the MVP matrix two times ?

In the vertex shader : gl_Position = MVP * vertex;

Then in the geometry shader : gl_Position = MVP * vec4(a, 1.f);

Maybe mistake ?

Congrats for your article.

Lezardong

Hi!

I don’t update the blog anymore, but I still receive emails ;)

Indeed, you’re right. I forgot to mention that the vertex position in the vertex shader doesn’t have to be transformed in this case.

Thanks!

Marc