This project has moved and is read-only. For the latest updates, please go here.
Create a UV atlas for a mesh. This function performs the same operation as UVAtlasPartition then UVAtlasPack.

HRESULT UVAtlasCreate(
    _In_reads_(nVerts) const XMFLOAT3* positions, _In_ size_t nVerts,
    _When_(indexFormat == DXGI_FORMAT_R16_UINT,
    _When_(indexFormat != DXGI_FORMAT_R16_UINT,
        _In_reads_bytes_(nFaces*sizeof(uint32_t))) const void* indices,
    _In_ DXGI_FORMAT indexFormat, _In_ size_t nFaces,
    _In_ size_t maxChartNumber, _In_ float maxStretch,
    _In_ size_t width, _In_ size_t height,
    _In_ float gutter,
    _In_reads_(nFaces*3) const uint32_t *adjacency,
    _In_reads_opt_(nFaces*3) const uint32_t *falseEdgeAdjacency,
    _In_reads_opt_(nFaces*3) const float *pIMTArray,
    _In_opt_ std::function<HRESULT(float percentComplete)> statusCallBack,
    _In_ float callbackFrequency,
    _In_ DWORD options,
    _Inout_ std::vector<UVAtlasVertex>& vMeshOutVertexBuffer,
    _Inout_ std::vector<uint8_t>& vMeshOutIndexBuffer,
    _Inout_opt_ std::vector<uint32_t>* pvFacePartitioning = nullptr,
    _Inout_opt_ std::vector<uint32_t>* pvVertexRemapArray = nullptr,
    _Out_opt_ float *maxStretchOut = nullptr,
    _Out_opt_ size_t *numChartsOut = nullptr);


maxChartNumber: The maximum number of charts required for the atlas. If this is 0, it will be parameterized based solely on stretch. Note that this is not a hard limit, but the isochart will stop when a valid charting is found that is greater than or equal to this number.

maxStretch: The maximum amount of stretch. If 0.0, no stretching is allowed. If 1.0, then any amount of stretching is allowed.

width, height: The width and height of the texture the atlas will be used on.

gutter: The minimum distance, in texels between two charts on the atlas. This gets scaled by the width, so if gutter is 2.5, and it is used on a 512x512 texture, then the minimum distance will be 2.5 / 512 in u-v space.

falseEdgeAdjacency: a pointer to an array with 3 uint32_t per face, indicating at each face, whether an edge is a false edge or not (using the same ordering as the adjacency data structure). If this is nullptr, then it is assumed that there are no false edges. If not nullptr, then a non-false edge is indicated by -1 and a false edge is indicated by any other value (it is not required, but it may be useful for the caller to use the original adjacency value). This allows you to parameterize a mesh of quads, and the edges down the middle of each quad will not be cut when parameterizing the mesh.

pIMTArray: a pointer to an array with 3 floats per face, describing the integrated metric tensor for that face. This lets you control the way this triangle may be stretched in the atlas. The IMT passed in will be 3 floats (a,b,c) and specify a symmetric matrix (a b) that, given a vector (s,t), specifies the (b c) distance between a vector v1 and a vector v2 = v1 + (s,t) as sqrt((s, t) * M * (s, t)^T). In other words, this lets one specify the magnitude of the stretch in an arbitrary direction in u-v space.

For example if a = b = c = 1, then this scales the vector (1,1) by 2, and the vector (1,-1) by 0.

Note that this is multiplying the edge length by the square of the matrix, so if you want the face to stretch to twice its size with no shearing, the IMT value should be (2, 0, 2), which is just the identity matrix times 2.

This assumes you have an orientation for the triangle in some 2-D space. For UVAtlas, this space is created by letting S be the direction from the first to the second vertex, and T be the cross product between the normal and S.

See UVAtlasComputeIMTFromPerVertexSignal, UVAtlasComputeIMTFromSignal, UVAtlasComputeIMTFromTexture, UVAtlasComputeIMTFromPerTexelSignal

options: A combination of UVATLAS flags
  • UVATLAS_DEFAULT - By default, meshes with 25,000 or more faces default to "fast", otherwise they use "quality".
  • UVATLAS_GEODESIC_FAST - Uses approximations to improve charting speed at the cost of added stretch or more charts.
  • UVATLAS_GEODESIC_QUALITY - Provides better quality charts, but requires more time and memory than fast.
vMeshOutVertexBuffer, vMeshOutIndexBuffer: Resulting mesh data after UV atlasing which will have a different vertex order and new vertices due to duplication. The position data is replicated from the original vertices, but has unique uv data. The number of output faces is the same as the input positions array.

pvFacePartitioning: A pointer to an array of the final face-partitioning data. Can be nullptr.

pvVertexRemapArray: A pointer to the array of remapped vertices. Each array element identifies the original vertex each final vertex came from. The same original vertex can appear multiple times due to vertex splitting required, and the size of this vector is the same as the size of the vMeshOutVertexBuffer vector. Note: This is the reverse definition of DirectXMesh's vertex remap arrays, so you cannot use FinializeVB with this remap array. See UVAtlasApplyRemap.

maxStretchOut: Returns maximum stretch. Can be nullptr.

numChartsOut: Returns number of charts created. Can be nullptr.


std::unique_ptr<WaveFrontReader<uint16_t>> mesh( new WaveFrontReader<uint16_t>() );

if ( FAILED( mesh->Load( L"test.obj" ) ) )
   // Error

size_t nFaces = mesh->indices.size() / 3;
size_t nVerts = mesh->vertices.size();

std::unique_ptr<XMFLOAT3[]> pos( new XMFLOAT3[ nVerts ] );
for( size_t j = 0; j < nVerts; ++j )
   pos[ j ] = mesh->vertices[ j ].position;

std::unique_ptr<uint32_t[]> adj( new uint32_t[ mesh->indices.size() ] );
if ( FAILED( GenerateAdjacencyAndPointReps( &mesh->indices.front(), nFaces,
   pos.get(), nVerts, 0.f, nullptr, adj.get() ) ) )
   // Error

std::vector<UVAtlasVertex> vb;
std::vector<uint8_t> ib;
std::vector<uint32_t> remap;
HRESULT hr = UVAtlasCreate( pos.get(), nVerts,
   &mesh->indices.front(), DXGI_FORMAT_R16_UINT, nFaces,
   0, 0.f, 512, 512, 1.f,
   adj.get(), nullptr, nullptr,
   nullptr, &remap );
if (FAILED(hr))
   // Error

size_t nTotalVerts = vb.size();
std::unique_ptr<WaveFrontReader<uint16_t>::Vertex> newVB(
   new WaveFrontReader<uint16_t>::Vertex[ nTotalVerts ] );

hr = UVAtlasApplyRemap( &mesh->vertices.front(),
    nVerts, nTotalVerts,
    &remap.front(), newVB.get();
if (FAILED(hr))
   // Error

// Merge the UV data into the final VB
for( size_t j = 0; j < nTotalVerts; ++j )
    newVB[ j ].textureCoordinate = vb[ j ].uv;

auto newIB = reinterpret_cast<const uint16_t*>( &ib.front() );


UVAtlas can partition mesh geometry two ways:
  • Based on the number of charts
  • Based on the maximum allowed stretch. If the maximum allowed stretch is 0, each triangle will likely be in its own chart

Last edited Nov 20, 2014 at 8:01 PM by walbourn, version 30