Vertex Post-Processing

From OpenGL Wiki
Jump to navigation Jump to search

Vertex Post-Processing is the stage in the OpenGL Rendering Pipeline where the vertex outputs of the Vertex Processing undergo a variety of operations. Many of these are setup for Primitive Assembly and Rasterization stages.

After vertex processing, the following steps occur in the order they appear on this page.

Transform Feedback

Transform feedback is a way of recording values output from the Vertex Processing stage into Buffer Objects. Only the last vertex processing stage can perform transform feedback.

Primitive Assembly

Primitive assembly takes the vertex stream and converts it into a sequence of primitives, in accord with the Primitive type specified in the rendering command. Primitives can also be discarded at this point, to allow transform feedback without rendering anything.


Primitives generated by previous stages are collected and then clipped to the view volume. Each vertex has a clip-space position (the gl_Position output of the last Vertex Processing stage). The viewing volume for a vertex is defined by:

This volume can be modified by depth clamping as well as the addition of user-defined clip-planes. The total volume that primitives are clipped to, including user-defined clip planes, is the clipping volume.

The way primitives are clipped to this clipping volume depends on the basic Primitive type:

Points are not really "clipped". If a point is in any way outside of the clipping volume, then the primitive is discarded (ie: not rendered). Points can be bigger than one pixel, but the clipping remains; if the center of the point (the actual gl_Position value) is outside of the clipping range, it is discarded. Yes, this means that Point Sprites will disappear when the center moves off-screen.

Platform Issue (NVIDIA): These cards will not clip points "properly". That is, they will do what people generally want (only discard the point if it is fully outside the volume), rather than what the OpenGL specification requires. Be advised that other hardware does what OpenGL asks.

If the line is entirely outside of the volume, it is discarded. If the line is partially outside of the volume, then it is clipped; new vertex coordinates are computed for one or both vertices, as appropriate. The end-point of such a clipped vertex is on the boundary of the clipping volume.
A triangle is clipped to the viewing volume by generating appropriate triangles who's vertices are on the boundary of the clipping volume. This may generate more than 1 triangle, as appropriate. If a triangle is entirely outside of the viewing volume, it is culled.

When primitives are clipped, new per-vertex outputs must be generated for them. These are generated via linear interpolation (in clip-space) of the output values. Flat-shaded outputs don't get this treatment.

Depth clamping

The clipping behavior against the Z position of a vertex (ie: ) can be turned off by activating depth clamping. This is done with glEnable(GL_DEPTH_CLAMP). This will cause the clip-space Z to remain unclipped by the front and rear viewing volume.

Note: With perspective projections, you still get clipping with the sides of the viewing volume. Depth clamping turns a frustum into a pyramid, and pyramids still end at the tip. So objects that go behind the camera are still clipped. What is unclipped are objects between the projection near-plane and the camera.

The Z value computations will proceed as normal through the pipeline. After computing the window-space position, the resulting Z value will be clamped to the glDepthRange.

User-defined clipping

Additional clipping can be specified by the last active Vertex Processing shader stage, through the output array gl_ClipDistance.

Each array element represents a distinct condition for clipping a primitive. Each array element has to be enabled independently via glEnable(GL_CLIP_DISTANCEi), where i is the index of the clip distance in the array. The maximum number of clip distances is GL_MAX_CLIP_DISTANCES, which has a minimum value of 8.

For each enabled clip distance, the corresponding element in the gl_ClipDistance array is checked during the clipping stage. If the distance is non-negative, then the primitive is considered to be within that clipping area. If the distance is negative, then it is outside of that clipping area.

When rendering point primitives, if the point's vertex is outside of one of the enabled clip distances, then the point is culled. For lines and triangles, these primitives are clipped at the place where the interpolated value of the gl_ClipDistance goes from negative to non-negative. If multiple clip distances are enabled, then clipping works through the intersection of all such distances.

In order to write to the gl_ClipDistance array, you must first redeclare this array with an explicit size: the number of array elements you intend to use. If you have GLSL 4.10 or ARB_separate_shader_objects, you must redeclare the entire gl_PerVertex output Interface Block:

out gl_PerVertex
  vec4 gl_Position;
  float gl_ClipDistance[3];

Before GLSL 4.10 or ARB_separate_shader_objects, gl_ClipDistance must be redeclared as follows:

out float gl_ClipDistance[3];

Note that, despite redeclaring the output, you still have to glEnable the specific clip distances you intend to use. If you enable a clip distance, but do not write to it from the last vertex processing stage, you get undefined behavior.


User-defined clipping can be used for many things. The classic use is to clip all vertices in a scene that are beyond a certain plane. The clip distance in this case is the distance to the plane, with the visible side of the plane providing non-negative distances.

Such distances are easy to compute in vertex shaders. The traditional plane equation consists of the equation , where A, B, and C are a unit normal for the plane, with D being the distance from that plane to the origin in that coordinate system.

Given such a plane, one can compute the clip distance with a simple dot product. Given a 4D vector position (with W = 1) in the same space as the plane equation, you simply compute dot(position, plane).

For example, in GUI elements, it is often desired to ensure that objects inside a GUI window cannot be drawn outside of that window. There are several tools one could use to ensure this, including changing employing Scissor Testing.

However, most of these solutions have one important downside: the parameters can only be changed between rendering calls. Which means, if you have a large batch of work that you want rendered, where parts of that batch are in different windows, you must do break it up into multiple rendering calls. This decreases performance.

User-defined clipping can help. These parameters can be per-vertex values, sampled from vertex arrays. The Vertex Shader, or other final processing stage, will be able to use them to set the gl_ClipDistance outputs.

The key is to set the clipping parameters based on the requested clipping region and the world-space position of the vertices.

There are some downsides to this approach.

First, it requires an additional per-vertex attribute, who's values will typically be shared among many vertices. And this attribute would likely have to be a full 2D clipping region: an XY position and width+height. Storing such positions would probably require 16-bits per component, as 8 bits would not provide enough resolution for most displays. So that's a total of 8 bytes added to each vertex.

It is possible to mitigate this by using a UBO to store an array of clipping regions, with each vertex simply specifying an integer index into that array.

Second, when user-defined clipping is used together with Multisampling, GUI elements may be drawn outside of the clipping region. User-defined clipping happens at the vertex level, not the fragment level. So it remains possible for multisampled Rasterization to generate Fragments outside of the pixel area assigned by the user-defined clipping region.

Perspective divide

The clip-space positions returned from the clipping stage are transformed into normalized device coordinates (NDC) via this equation:

Viewport transform

The viewport transform defines the transformation of vertex positions from NDC space to window space. These are the coordinates that are rasterized to the output image.

The viewport is defined by a number of viewport parameters. These parameters are set by these functions:

void glViewport(GLint x​, GLint y​, GLsizei width​, GLsizei height​);

void glDepthRange(GLdouble nearVal​, GLdouble farVal​);

void glDepthRangef(GLfloat nearVal​, GLfloat farVal​);

The second two functions set the same parameters, the near and far values of the depth range.

Given the viewport parameters, we compute the window-space coordinates via these equations:

Where x​, y​, width​, height​, nearVal​, and farVal​ are the viewport parameters.

Viewport array

Viewport Arrays
Core in version 4.6
Core since version 4.1
Core ARB extension ARB_viewport_array

Multiple viewports can be used in OpenGL. The specific viewport for a particular primitive can be set by the Geometry Shader. If the GS does not specify a viewport, then viewport number 0 is selected. The computation works as above, except where it says "the viewport parameters", it means "the viewport parameters for the primitive's viewport index".

There are sets of viewports, indexed on the half-open range [0, GL_MAX_VIEWPORTS). Each index has its own depth range and viewport coordinates. The previously defined functions will only set the value for viewport index 0.

To set the viewport parameters for a particular index, use this pair of functions:

void glViewportIndexedf(GLuint index​, GLfloat x​, GLfloat y​, GLfloat w​, GLfloat h​)

void glViewportIndexedfv(GLuint index​, const GLfloat *v​)

void glDepthRangeIndexed(GLuint index​, GLdouble nearVal​, GLdouble farVal​)

The index​ is the viewport index to set the parameters for. glViewportIndexedfv takes an array of 4 floats, in the same orders as the parameters for glViewportIndexedf.

Multiple viewport indices can be set with a single function, via these APIs:

void glViewportArrayv(GLuint first​, GLsizei count​, const GLfloat *v​)

void glDepthRangeArrayv(GLuint first​, GLsizei count​, const GLdouble *v​)

The first​ index is the first viewport index to set. count​ is the number of viewport indices to be set by the function. v​ is an array of viewport values, which contains count​ * 4 or 2 values, depending on the function being called. The values for a single viewport index are in the same order as the arguments in the regular function calls.

See also