In Review

In this tutorial, you have learned the following:

Further Study

Try doing these things with the given programs.

  • The first version of our impostors was a sphere approximation. It was not useful for relatively large spheres, but it could be useful for small ones. However, that approximation did not compute the depth of the fragment correctly. Make a version of it that does.

  • Change the geometry impostor tutorial to take another vertex input: the material to use. The vertex shader should pass it along to the geometry shader, and the geometry shader should hand it to the fragment shader. You can still use gl_PrimitiveID as the way to tell the fragment shader. Regardless of how you send it, you will need to convert the value to an integer at some point. That can be done with this constructor-like syntax: int(value_to_convert).

Further Research

This is an introduction to the concept of impostors. Indeed, the kind of ray tracing that we did has often been used to render more complex shapes like cylinders or quadratic surfaces. But impostors are capable of much, much more.

In effect, impostors allow you to use the fragment shader to just draw stuff to an area of the screen. They can be used to rasterize perfect circles, rather than drawing line-based approximations. Some have even used them to rasterize Bézier curves perfectly.

There are other impostor-based solutions. Most particle systems (a large and vibrant topic that you should investigate) use flat-cards to draw pictures that move through space. These images can animate, changing from one image to another based on time, and large groups of these particles can be used to simulate various phenomena like smoke, fire, and the like.

All of these subjects are worthy of your time. Of course, moving pictures through space requires being able to draw pictures. That means textures, which coincidentally is the topic for our next section.

GLSL Features of Note


This fragment shader-only directive will cause the outputs of the fragment to be ignored. The fragment outputs, including the implicit depth, will not be written to the framebuffer.


An input to the vertex shader of type int. This is the index of the vertex being processed.


An output from the fragment shader of type float. This value represents the depth of the fragment. If the fragment shader does not use this value in any way, then the depth will be written automatically, using gl_FragCoord.z. If the fragment shader writes to it somewhere, then it must ensure that all codepaths write to it.


A geometry shader output and the corresponding fragment shader input of type int. If there is no geometry shader, then this value will be the current count of primitives that was previously rendered in this draw call. If there is a geometry shader, but it does not write to this value, then the value will be undefined.


A geometry shader input. It is the current count of primitives previously processed in this draw call.

void EmitVertex();

Available on in the geometry shader, when this function is called, all output variables previously set by the geometry shader are consumed and transformed into a vertex. The value of those variables becomes undefined after calling this function.