How to Generate Adjacency Index Buffers for Geometry Shaders and Tessellation

Use meshopt_generateAdjacencyIndexBuffer to create triangle-list-with-adjacency data for silhouette detection, or meshopt_generateTessellationIndexBuffer to build PN-AEN patches for crack-free displacement mapping, both implemented in src/indexgenerator.cpp of the zeux/meshoptimizer library.

The zeux/meshoptimizer library provides optimized algorithms for mesh processing, including dedicated functions to generate adjacency index buffers required by modern GPU pipelines. These functions construct specialized index buffers that expose edge-adjacency information, enabling geometry shaders to access neighboring triangles or tessellation shaders to perform crack-free displacement. Understanding how to generate adjacency index buffers correctly ensures your rendering pipeline can implement advanced effects like silhouette outlining and adaptive subdivision.

Adjacency vs. Tessellation Index Buffers

The library exposes two distinct generators in src/meshoptimizer.h that serve different GPU pipeline requirements.

Geometry-Shader Adjacency (meshopt_generateAdjacencyIndexBuffer)

This function produces a triangle-list-with-adjacency layout containing 6 indices per input triangle. The output format follows [v0, a0, v1, a1, v2, a2], where v* represents the original triangle vertices and a* represents the vertices opposite each corresponding edge. This format enables geometry shaders to access neighboring vertices for effects like silhouette detection or wireframe rendering. The function declaration appears at lines 59–69 in src/meshoptimizer.h.

PN-AEN Tessellation (meshopt_generateTessellationIndexBuffer)

This function generates a 12-index patch per input triangle designed for Position-Normal Approximate Catmull-Clark with Edge Normals (PN-AEN) tessellation. The output includes original vertices, opposing edge pairs, and dominant vertices for each corner to prevent cracking during displacement mapping. This requires index_count * 4 storage space and is declared at lines 71–84 in src/meshoptimizer.h.

Algorithm Implementation Details

Both functions share a common three-stage algorithm implemented in src/indexgenerator.cpp, differing only in the final patch generation step.

Stage 1: Canonical Position Remap

The algorithm begins by calling buildPositionRemap to eliminate duplicate positions caused by split attribute streams. A fast hash-based lookup table maps every vertex index to a canonical vertex index representing the unique 3D position. This step executes at lines 92–95 for adjacency generation and lines 63–66 for tessellation generation.

Stage 2: Edge Set Construction

The code constructs an edge_table hash set containing every directed edge in the mesh. Each entry stores the opposite vertex—the vertex not belonging to that edge. The hash key uses a 64-bit composite (i0 << 32) | i1 where i0 → i1 follows triangle winding. The population loops run from lines 106–126 (adjacency) and lines 74–88 (tessellation).

Stage 3: Patch Generation

For each input triangle, the algorithm looks up the opposite edge for all three sides:

  • Adjacency (lines 128–148): Writes a 6-element patch mapping each edge to its opposing vertex.
  • Tessellation (lines 91–119): Writes a 12-element patch containing original vertices, opposite edge pairs, and dominant corner vertices.

Input Requirements and Constraints

Both functions enforce specific prerequisites to ensure correct adjacency calculation:

  • Topology: The input index buffer must contain triangles (index_count % 3 == 0).
  • Vertex Layout: Positions must be tightly packed as float3 (first 12 bytes) with a stride between 12 and 256 bytes, aligned to sizeof(float).
  • Buffer Sizing: Destination buffers require index_count * 2 elements for adjacency (6 indices per triangle) or index_count * 4 for tessellation (12 indices per triangle).
  • Determinism: The algorithms operate solely on position data, requiring no additional per-vertex attributes.

GPU Pipeline Integration

Feed the generated buffers directly to your graphics API:

  • Adjacency topology: Use VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY in Vulkan or GL_TRIANGLES_ADJACENCY in OpenGL.
  • Tessellation topology: Use VK_PRIMITIVE_TOPOLOGY_PATCH_LIST with patch size 12 in Vulkan, or GL_PATCHES in OpenGL after calling glPatchParameteri(GL_PATCH_VERTICES, 12).

Implementation Examples

C++ Usage

#include "meshoptimizer.h"
#include <vector>
#include <cassert>

// Geometry shader adjacency
void buildAdjacency(const std::vector<unsigned int>& indices,
                    const std::vector<float>& positions,
                    std::vector<unsigned int>& adjIndices)
{
    assert(indices.size() % 3 == 0);
    size_t indexCount = indices.size();
    size_t vertexCount = positions.size() / 3;
    
    adjIndices.resize(indexCount * 2);
    
    meshopt_generateAdjacencyIndexBuffer(
        adjIndices.data(),
        indices.data(),
        indexCount,
        positions.data(),
        vertexCount,
        sizeof(float) * 3
    );
}

// PN-AEN tessellation
void buildTessellation(const std::vector<unsigned int>& indices,
                       const std::vector<float>& positions,
                       std::vector<unsigned int>& tessIndices)
{
    assert(indices.size() % 3 == 0);
    size_t indexCount = indices.size();
    size_t vertexCount = positions.size() / 3;
    
    tessIndices.resize(indexCount * 4);
    
    meshopt_generateTessellationIndexBuffer(
        tessIndices.data(),
        indices.data(),
        indexCount,
        positions.data(),
        vertexCount,
        sizeof(float) * 3
    );
}

JavaScript WebAssembly Usage

function generateAdjacency(indices, positions) {
    const indexCount = indices.length;
    const vertexCount = positions.length / 3;
    const adj = new Uint32Array(indexCount * 2);
    
    instance.exports.meshopt_generateAdjacencyIndexBuffer(
        adj, indices, indexCount, positions, vertexCount, 12
    );
    
    return adj;
}

Summary

  • meshopt_generateAdjacencyIndexBuffer creates 6-index patches for geometry shaders requiring triangle adjacency data.
  • meshopt_generateTessellationIndexBuffer creates 12-index patches for PN-AEN tessellation workflows.
  • Both functions reside in src/indexgenerator.cpp and use a three-stage algorithm: canonical position remapping, edge hash table construction, and patch emission.
  • Input meshes must use float3 positions with 12-byte stride alignment, and output buffers must be sized to index_count * 2 or index_count * 4 respectively.
  • The resulting buffers bind directly to GL_TRIANGLES_ADJACENCY or GL_PATCHES (with 12 vertices) primitives.

Frequently Asked Questions

What is the difference between adjacency and tessellation index buffers?

Adjacency index buffers contain 6 indices per triangle exposing only the single vertex opposite each edge, used for geometry shader operations like silhouette detection. Tessellation index buffers contain 12 indices per triangle including dominant vertices and full edge pairs required for PN-AEN displacement mapping to prevent cracks at T-junctions.

Why does the algorithm need to remap positions canonically?

The buildPositionRemap step ensures that vertices sharing the same 3D position but different attribute indices (common in UV seams or hard edge splits) are treated as the same vertex for adjacency calculations. Without this step, edges that are geometrically adjacent but indexed separately would not be recognized as neighbors.

Can I use these functions on indexed meshes with arbitrary vertex attributes?

Yes, provided the vertex positions are stored as the first 12 bytes of each vertex structure. The stride parameter allows the functions to skip over additional attributes (normals, UVs, tangents) when reading positions, as long as the stride is between 12 and 256 bytes and aligned to 4 bytes.

How do I verify the output buffer size requirements?

For meshopt_generateAdjacencyIndexBuffer, allocate index_count * 2 unsigned integers (6 indices per triangle ÷ 3 original indices = 2x multiplier). For meshopt_generateTessellationIndexBuffer, allocate index_count * 4 unsigned integers (12 indices per triangle ÷ 3 original indices = 4x multiplier). Both functions assume the input index_count is divisible by 3.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →