Guide to GLSL lines_adjacency in Geometry Shaders
This article explains how the layout(lines_adjacency)
qualifier works in GLSL geometry shaders to access neighborhood mesh
data. It breaks down the primitive assembly process, illustrates the
mapping of the four input vertices, discusses drawing modes like line
strips, and outlines common rendering applications such as miter joins
and curve evaluation.
In standard line rendering, a geometry shader declared with
layout(lines) in; receives only two vertices defining a
discrete segment: the start point and the end point. Declaring
layout(lines_adjacency) in; expands this input stream to
provide four vertices per primitive within the built-in
gl_in[] array. The four vertices represent the current line
segment along with the immediate neighboring vertices before and after
it.
The topology passed into the shader is indexed as follows:
gl_in[0]: The adjacent vertex preceding the line segment.gl_in[1]: The start vertex of the actual line segment.gl_in[2]: The end vertex of the actual line segment.gl_in[3]: The adjacent vertex following the line segment.
To feed this topology into the pipeline, the application must issue
draw calls using either GL_LINES_ADJACENCY or
GL_LINE_STRIP_ADJACENCY.
When using GL_LINES_ADJACENCY, every segment requires
four explicit vertex indices. If rendering two disconnected segments
with adjacency, eight vertex indices are needed: indices 0–3 define the
first line with its neighbors, and indices 4–7 define the second.
When using GL_LINE_STRIP_ADJACENCY, vertices are shared
sequentially across segments. For a strip of \(N\) rendered line segments, the buffer must
contain \(N + 3\) vertices. For any
segment \(i\) (where \(i\) ranges from 0 to \(N-1\)):
gl_in[0]maps to vertex \(i\)gl_in[1]maps to vertex \(i + 1\)gl_in[2]maps to vertex \(i + 2\)gl_in[3]maps to vertex \(i + 3\)
The core segment processed is always the connection between
gl_in[1] and gl_in[2]. Vertices
gl_in[0] and gl_in[3] are purely auxiliary and
are not automatically rasterized.
Having access to gl_in[0] and gl_in[3]
solves several common algorithmic challenges in geometry processing:
Screen-Space Line Expansion and Miter Joins: To expand thin lines into screen-aligned quads with proper corner joins (such as miter, bevel, or round joints), the shader must know the direction vectors of the incoming segment (
gl_in[1] - gl_in[0]) and outgoing segment (gl_in[3] - gl_in[2]). Comparing these vectors with the main segment vector (gl_in[2] - gl_in[1]) allows for the calculation of the miter bisector and prevents gaps or overlapping artifacts at line joints.Cubic Spline Interpolation: Four continuous control points are required to compute cubic Hermite or Catmull-Rom splines directly on the GPU. With
lines_adjacency, the geometry shader can evaluate the curve betweengl_in[1]andgl_in[2]usinggl_in[0]andgl_in[3]as tangent anchors, outputting a finely tessellated series of sub-segments.Silhouette and Crease Detection: In wireframe and feature-edge rendering, knowing the positions of neighboring vertices allows the shader to detect edge curvature, sharp crease angles, or boundary transitions relative to the camera view.
For open paths where a start or end segment lacks a physical neighbor, the calling application must provide phantom or clamped vertices (often duplicating the first or last endpoint) to ensure the vertex count matches the four-vertex constraint required by the pipeline.