How to Generate an Adjacency Index Buffer for Silhouette Detection with `meshopt_generateAdjacencyIndexBuffer`

meshopt_generateAdjacencyIndexBuffer creates an expanded index buffer that provides three indices per original triangle vertex—enabling geometry shaders to detect silhouette edges in a single GPU pass.

This function in the zeux/meshoptimizer library generates the adjacency data required by DirectX 12 and Vulkan when rendering with adjacency primitives for edge-based effects like outlining or shadow volumes. It operates entirely on the CPU, scanning the mesh to link neighboring triangles without modifying the original geometry.

What Is an Adjacency Index Buffer?

An adjacency index buffer is a specialized index format used by graphics APIs to support silhouette detection. For every triangle vertex, it stores:

  • The original vertex index
  • The two neighboring vertices that share the edge with the adjacent triangle

This 3× expansion (from index_count to 3 * index_count) allows the GPU's geometry shader to access adjacent triangles directly. Without this data, the shader would need complex texture lookups or multiple passes to determine edge visibility.

According to the meshoptimizer source code, the function builds a triangle adjacency structure that records, for each triangle edge, the opposite vertex of the neighboring triangle (or the same vertex if the edge is a boundary).

Prerequisites for Using meshopt_generateAdjacencyIndexBuffer

Before calling the function, prepare these inputs:

  1. Destination buffer – Allocate 3 * index_count unsigned int values to hold the expanded adjacency data.
  2. Source index buffer – A flat array of triangle indices (unsigned int*). The function expects 32-bit indices; convert 16-bit indices if necessary.
  3. Vertex positions – An array of float positions (x, y, z per vertex) with the vertex count and stride in bytes. A typical stride is sizeof(float) * 3 for tightly packed positions.

How to Call meshopt_generateAdjacencyIndexBuffer

The function signature in src/meshoptimizer.h (line 169) is:

void meshopt_generateAdjacencyIndexBuffer(
    unsigned int* destination,
    const unsigned int* indices,
    size_t index_count,
    const float* vertex_positions,
    size_t vertex_count,
    size_t vertex_positions_stride);

Basic Usage Example

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

// Input mesh data
std::vector<unsigned int> indices = { 0, 1, 2,  2, 1, 3 };  // Two triangles
std::vector<float> positions = {
    -1.0f, -1.0f, 0.0f,  // Vertex 0
     1.0f, -1.0f, 0.0f,  // Vertex 1
    -1.0f,  1.0f, 0.0f,  // Vertex 2
     1.0f,  1.0f, 0.0f   // Vertex 3
};
size_t vertexCount = positions.size() / 3;
size_t indexCount = indices.size();

// Allocate destination buffer (3x the original size)
std::vector<unsigned int> adjacency(3 * indexCount);

// Generate adjacency buffer
meshopt_generateAdjacencyIndexBuffer(
    adjacency.data(),
    indices.data(),
    indexCount,
    positions.data(),
    vertexCount,
    sizeof(float) * 3);

// adjacency now contains the adjacency index buffer ready for GPU upload

C-Style API Example

For C compatibility or manual memory management:

#include "meshoptimizer.h"
#include <stdlib.h>

// Assuming idx, idxCount, pos, and posCount are already initialized
unsigned int* adj = (unsigned int*)malloc(3 * idxCount * sizeof(unsigned int));

meshopt_generateAdjacencyIndexBuffer(
    adj,
    idx,
    idxCount,
    pos,
    posCount,
    sizeof(float) * 3);

// Upload adj to GPU as an adjacency index buffer, then cleanup
free(adj);

Using the Buffer for Silhouette Detection

Once generated, bind the adjacency buffer using your graphics API's adjacency primitive type:

  • DirectX 12: Use D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE with adjacency, or D3D12_IBV_FORMAT with the appropriate strip adjacency flag
  • Vulkan: Use VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY

In the geometry shader, you receive six vertices per triangle (the original three plus three adjacent vertices). Compare the face normal of the current triangle with the adjacent triangle's normal to detect silhouette edges—edges where the dot product of normals changes sign or crosses the view vector.

Implementation Details

The adjacency generation logic resides in src/indexgenerator.cpp (function meshopt_generateAdjacencyIndexBuffer around line 580).

Internally, the function:

  1. Builds a TriangleAdjacency structure (defined in src/vcacheoptimizer.cpp around line 34) that maps edges to their opposite vertices
  2. Scans all triangles to link shared edges
  3. Outputs the expanded buffer where each original vertex is followed by its two edge neighbors

The public API declaration in src/meshoptimizer.h provides the stable interface, while the underlying TriangleAdjacency utilities handle the edge-to-edge matching.

Summary

  • meshopt_generateAdjacencyIndexBuffer expands an index buffer to 3× its size, adding adjacent vertex data for each edge.
  • The function requires 32-bit indices, vertex positions, and proper stride calculation to generate valid adjacency data.
  • The resulting buffer enables geometry shader-based silhouette detection by providing neighboring triangle vertices directly to the GPU.
  • Implementation spans src/meshoptimizer.h (API), src/indexgenerator.cpp (generation logic), and src/vcacheoptimizer.cpp (adjacency structures).

Frequently Asked Questions

What is the difference between a regular index buffer and an adjacency index buffer?

A regular index buffer stores one index per triangle corner. An adjacency index buffer stores three indices per corner: the original vertex plus the two vertices of the neighboring triangle that share the edge. This expansion allows geometry shaders to access adjacent primitives without additional texture lookups or compute passes.

Does meshopt_generateAdjacencyIndexBuffer modify the original mesh data?

No. The function operates entirely on the CPU and does not modify the input indices or vertex positions. It only writes to the provided destination buffer, leaving the source data unchanged for subsequent processing.

Why do I need to provide vertex positions if the function only manipulates indices?

The function uses vertex positions to determine geometric edge equivalence. By comparing positions, it can identify when two triangles share the same edge in 3D space even if their index values differ (due to welding, duplication, or index remapping). This ensures correct adjacency generation for meshes with welded or split vertices.

Can I use 16-bit indices with meshopt_generateAdjacencyIndexBuffer?

The function signature requires 32-bit (unsigned int) indices for both source and destination. If your mesh uses 16-bit indices, you must convert them to 32-bit before calling the function, or manually expand the buffer using the library's other utilities.

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 →