{"id":28075410,"url":"https://github.com/nvpro-samples/nv_cluster_lod_builder","last_synced_at":"2025-05-13T00:57:03.166Z","repository":{"id":276294154,"uuid":"921074219","full_name":"nvpro-samples/nv_cluster_lod_builder","owner":"nvpro-samples","description":"continuous level of detail mesh library","archived":false,"fork":false,"pushed_at":"2025-04-04T19:20:26.000Z","size":362,"stargazers_count":269,"open_issues_count":0,"forks_count":5,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-05-13T00:56:58.497Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nvpro-samples.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.txt","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-23T09:38:51.000Z","updated_at":"2025-05-10T04:31:21.000Z","dependencies_parsed_at":"2025-02-07T11:34:16.633Z","dependency_job_id":"fc93b258-4991-463e-bf62-abad14354912","html_url":"https://github.com/nvpro-samples/nv_cluster_lod_builder","commit_stats":null,"previous_names":["nvpro-samples/nv_cluster_lod_builder"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvpro-samples%2Fnv_cluster_lod_builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvpro-samples%2Fnv_cluster_lod_builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvpro-samples%2Fnv_cluster_lod_builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nvpro-samples%2Fnv_cluster_lod_builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nvpro-samples","download_url":"https://codeload.github.com/nvpro-samples/nv_cluster_lod_builder/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253850883,"owners_count":21973672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-13T00:57:02.603Z","updated_at":"2025-05-13T00:57:03.151Z","avatar_url":"https://github.com/nvpro-samples.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nv_cluster_lod_builder \u003c!-- omit from toc --\u003e\n\n![](doc/lod_stitch.svg)\n\n**nv_cluster_lod_builder** is a _continuous_ level of detail (LOD) mesh library.\nContinuous LOD allows for fine-grained control over geometric detail within a\nmesh, compared to traditional discrete LOD. Clusters of triangles are carefully\nprecomputed by decimating the original mesh in a way that they can be seamlessly\ncombined across different LOD levels. At rendering time, a subset of these\nclusters is selected to adaptively provide the required amount of detail as the\ncamera navigates the scene.\n\nKey features of continuous LOD systems include:\n\n- **Fast rendering with more detail:** Triangles are allocated where they are\n  most needed.\n- **Reduced memory usage with geometry streaming:** Particularly beneficial for\n  ray tracing applications.\n\nThis library serves as a quick placeholder or learning tool, demonstrating the\nbasics of creating continuous LOD data. For a reference implementation of the\nrendering system, see\nhttps://github.com/nvpro-samples/vk_lod_clusters.\n\n**Input:** a triangle mesh with millions of triangles\n\n**Output:**\n\n1. [nvclusterlod/nvclusterlod_mesh.h](include/nvclusterlod/nvclusterlod_mesh.h) - decimated clusters of the\n   original mesh, with groupings and relations to other groups\n2. [nvclusterlod/nvclusterlod_hierarchy.h](include/nvclusterlod/nvclusterlod_hierarchy.h) - a spatial\n   hierarchy of *cluster groups* to improve performance of runtime cluster\n   selection for rendering\n\nTo render, select *cluster groups* where:\n\n- Detail or decimation error of the group is small enough, relative to the camera\n- Detail or decimation error of the group's decimated geometry is not small enough\n\nThis is the gist, but the library also does some massaging of the values that\nfeed into these checks to make sure multiple LODs do not render over the top of\neach other. See below.\n\nGeometry can be streamed in when needed to save memory.\n\n**Table of Contents**\n\n- [How it works](#how-it-works)\n  - [Building LODs](#building-lods)\n  - [Selecting Clusters](#selecting-clusters)\n  - [Spatial Hierarchy](#spatial-hierarchy)\n  - [Streaming](#streaming)\n  - [References](#references)\n- [Usage Example](#usage-example)\n- [Build Integration](#build-integration)\n  - [Dependencies](#dependencies)\n- [License](#license)\n- [Limitations](#limitations)\n\n## How it works\n\nThe key to continuous LOD is a decimation strategy that allows regular\nwatertight LOD transitions across a mesh. Such transitions require borders that\nmatch on both sides, and are obtained by keeping the border of the triangle\nedges fixed during decimation. Since these edges do not change, successive\niterations of decimation must choose different borders and fix new edges to let\nthe old ones decimate.\n\nTo explain why, consider forming groups of triangles and decimating triangles\nwithin. Then grouping the decimated groups and decimating again *recursively*\nuntil there is just one root group. In this case, some of the vertices would\nremain fixed across the entire hierarchy, and would be decimated only when the\nlast two groups are grouped and decimated to form the coarsest LOD. To avoid\nthis, new groups must instead be allowed to cross any border, and in fact\nencouraged to.\n\nThis library makes groups of geometry and decimates within *groups*. Decimated\ngeometry is then re-grouped, encouraging crossing the old group's borders when\nforming new groups. Groups are made from clusters of triangle rather than just\ntriangles for performance reasons. A group is a cluster of clusters of\ntriangles. Whole triangle clusters are swapped in and out at runtime for detail\ntransitions.\n\n### Building LODs\n\n\u003cimg src=\"doc/lod_generate.jpg\" width=\"600\"\u003e\n\nThe image above shows the process that is repeated to create LODs until there is\njust a single cluster representing the whole mesh:\n\n1. Make clusters [, within old borders]\n\n   This library uses\n   [nv_cluster_builder](https://github.com/nvpro-samples/nv_cluster_builder)'\n   segmented API to make clusters of a fixed size from triangles within groups\n   of the previous iteration, or globally for the first iteration.\n\n2. Group clusters [, crossing old borders]\n\n   This is just making clusters of clusters, but with a catch. Border edges\n   cannot decimate so it is important to encourage grouping clusters in a way to\n   keep old borders internal to the group. Then the previously locked edges are\n   free to decimate. This is done by adding a connection and weight between\n   clusters sharing many vertices (locked in particular) and optimizing for a\n   *minimum cut* when making cluster groups with\n   [nv_cluster_builder](https://github.com/nvpro-samples/nv_cluster_builder).\n\n   If there is only one cluster in one group, the operation is complete.\n\n3. Decimate within groups, keep border\n\n   Vertices shared between groups are computed and locked before using\n   [meshoptimizer](https://github.com/zeux/meshoptimizer)'s `simplify` to\n   decimate each cluster group. The aim is to halve the number of triangles.\n   These become the input to the next iteration.\n\nThe code is documented and intended to be read too. These steps can be found in\n`nvclusterlodMeshCreate()` at the bottom of\n[`nvclusterlod_mesh.cpp`](src/nvclusterlod_mesh.cpp).\n\nWhen decimating, the generating group is tracked. This is the geometry each\ncluster was decimated from. A cluster's group is one of many groups generated by\ndecimating its generating group. Similarly a group has many generating groups.\nClusters will be selected in intersections of groups and generating groups -\nperhaps something to optimize the decision making with. The term *parent* is\navoided due to possible confusion between the originating geometry and the\ndirection to the root node.\n\n![](doc/lod_dag.svg)\n\nThe image above shows an example 2D illustration with colored groups, their\nclusterings and relationships. Notably, two groups of clusters may produce\ndecimated clusters that are both part of a new group. This allows group borders\nto be decimated after each iteration. The relationships form a directed acyclic\ngraph (DAG), i.e. not a tree, with the constraint that relationships don't skip\nlevels - but maybe that could help with uneven detail? LOD transitions may only\nhappen across group borders, which places a limit on the rate of LOD change.\n\nThe output data are:\n\n- Clusters of triangles, referencing vertices in the original mesh\n- Groupings of clusters and their relationships:\n  - Generating geometry, input to decimation\n  - Generated geometry, decimation output\n- Group bounding spheres\n- Group decimation quadric error\n\n### Selecting Clusters\n\nThe first step is to pick the goal. A couple of examples are:\n\n1. Pixel-sized triangles?\n2. Sub-pixel-sized geometric error?\n\nThe latter may be more efficient if for example large triangles give the same\nvisual result. This may be more challenging to quantify particularly if\ndecimation introduces error not captured by the metric. For the moment this\nlibrary uses [*quadric\nerror*](https://www.cs.cmu.edu/~./garland/Papers/quadrics.pdf), an approximate\nmeasure of the object-space distance between the decimated mesh and the original\nhigh-resolution mesh. Inaccuracies from decimating vertex attributes such as\nnormals and UVs are currently ignored.\n\nA conservative maximum vertex position error is maintained for all cluster\ngroups. This is the farthest any vertex may be from representing the original\nsurface. When rendering we ask, \"what is the largest possible angular error from\nthe camera?\" for a particular group. We then want to render geometry when its\nerror is just less than a threshold, but not any overlapping geometry.\n\n![](doc/arcsin_angular_error.svg)\n\nThe farthest a decimated vertex may be incorrectly representing geometry is the\nquadric error. This will be bigger in screen space nearer the camera so the\nnearest point on the group's bounding sphere is chosen. The largest possible\nangular error from the camera is then the angular size of a sphere with quadric\nerror radius at that point. Convenient and simple: the arcsine of the error\ndivided by the distance to the closest point on the bounding sphere. A target\nthreshold can be chosen based on a single pixel's FOV at the center of the\nprojection - to keep any geometric error less than the size of a pixel. This\navoids varying the threshold across the image, which would further complicate a\nproblem yet to solve.\n\n![](doc/graph_cut.svg)\n\nWe have a target goal and a way to compute it, but how can we guarantee a single\nunique continuous surface? I.e. no holes and no overlaps. An ideal solution\nwould be to pick clusters that satisfy the angular error threshold but constrain\nthe rest to only making a single LOD transition per group. That would require\ntraversing the graph with its adjacency information, visualized above. The term\nis a making graph cut and it would be challenging to do quickly and in parallel\non a GPU.\n\nWe ideally want to test whether to render a cluster independently. We could\nrender geometry where its error is the first below the threshold, i.e. its\ndecimated error is greater. Just that would actually guarantee no holes, but\nthere would still be overlaps. E.g. two clusters that represent the same surface\nbeing drawn at once. This can happen when the bounding sphere of a decimated\ngroup is so far from the camera that its conservative angular error is smaller\nthan a group's angular error that it was decimated from.\n\nThe solution implemented by this library is to artificially increase the size of\nthe bounding spheres such that the nearest point to the camera is always nearer\nthan that on a bounding sphere of its generating geometry. In short, make\nbounding spheres bound generating geometry too. Once done, a single watertight\nmesh can be stitched together from independent parallel decisions. In general,\nthe angular error, or whatever metric is compared to a threshold, must never\ndecrease with each level of decimation. The failure above was a decrease due to\nthe size distortion of a perspective projection.\n\nOne derivation glossed over so far is why store bounding spheres and errors per\ngroup. The simple answer is that for LOD transitions to work, the entire group\nmust change LOD at the same time, so all clusters in a group must share the same\nvalues.\n\n### Spatial Hierarchy\n\nThis library provides a spatial hierarchy of bounding spheres to search for\ncluster groups of the right LOD in the right spatial region relative to the\ncamera.\n\nOne way to think of this is there are many high-detailed clusters and few low\ndetail. If an object is far away, only the low-detailed clusters should be\nchecked. That is, the search can exit early if it is known that all remaining\nclusters are too detailed.\n\n![](doc/spatial_selection.svg)\n\nAnother way of thinking about this is at a certain distance range from the\ncamera, as shown in the image above, only clusters with certain bounding sphere\nradii and quadric error ranges should be rendered. Thus, the search space can be\nreduced by conservatively searching only that region. An r-tree could work well\nhere too.\n\nThe hierarchy is actually a set of hierarchies - one for each LOD level. For\nconvenience, per-level roots are merged since the application would need to\nsearch all levels anyway, or at least their roots.\n\nLeaf nodes point to cluster groups and are initialized with the group's\ndecimated cluster maximum quadric error (i.e. from the next level\\*) and the\ngroup's bounding sphere. The hierarchy is built by recursively spatially\nclustering nodes - not fast, but it works and it isn't a bottleneck yet.\nInternal nodes are given the maximum quadric error and bounding sphere of their\nchildren.\n\n\\*The group quadric error is the error of the generated group's clusters, i.e.\nafter decimation, not the error in the group's clusters. This avoids\nunnecessarily storing a per-cluster error.\n\n![](doc/hierarchy_selection.svg)\n\nThe tree can be traversed using the same angular error check as for cluster\ngroups, exiting when the node's error is less than the threshold. The trees for\nLODs with too fine detail will exit early. The blue crosses in the above image\nshow an example - those nodes are already below the threshold. Since traversed\nleaf nodes have already been checked to be above the threshold, and they are\ninitialized with cluster's generated group's error, their clusters only need to\ncheck that they are below the threshold in order to select them for rendering.\nNote that the entire group may not necessarily be drawn. For example, two of the\nyellow clusters were not below the threshold (red cross). This same check is\nmade by the blue group's leaf node and blue clusters are drawn instead.\n\nWhile it is possible to exit early from a tree with too coarse detail, it may\ninterfere with streaming, depending on how dependencies are implemented.\n\n### Streaming\n\nThis is not a definitive how-to, but outlines some ideas for getting started\nwith streaming continuous LOD.\n\nThe first feature needed for streaming is indirection - e.g. pointers to cluster\ngroups that are initially null and can be populated over time (outside of\ncluster selection and rendering). Then, cluster groups in leaf nodes encountered\nduring hierarchy traversal must be marked and streamed in. Finally, minor\nchanges are needed for selecting clusters:\n\n- Obviously, don't traverse leaf nodes whose groups have not been loaded yet\n- Consider clusters to be below the threshold if their generating group has not been loaded\n\nChoosing to keep lower detail geometry loaded greatly simplifies things. That\nis, making sure decimated geometry is loaded first. This happens naturally due\nto traversal order, but tracking dependencies host side may be needed if\nstreaming less than everything-at-once from traversal.\n\nInitially, streaming at the granularity of cluster groups and using the\ngenerated group indices directly as dependencies is straight forward. Cluster\ngroups could also be combined for coarser streaming granularity, with a new set\nof dependencies.\n\n**Simple Streaming**\n\nCompute per-group needed flags during traversal. Emit load/unload events on\nrising/falling edges. Fulfil those events in whole and set or unset the pointers\nto the new data between traversal+rendering. Some filtering such as per-group\nframe age may be useful to avoid frequently unloading and reloading groups.\n\n**Continuous Streaming**\n\nThe simple streaming above has less control over the amount streamed per batch.\nThis can be improved by adding batch size limits and queues. Note that by\npartially streaming will require manually resolving dependency orders. Some\nideas are:\n\n1. Limit the number of load/unload events emitted per frame\n2. Add a global event queue\n   - Delay unloads and ignore pulses by comparing events at the front of the\n     queue with the most recent events inserted into the back of the queue.\n   - Prioritise events by a detail metric so geometry loads evenly on screen\n3. Set a fixed memory limit (memory pool even) and/or fixed cluster/group count\n   - Prioritise loading until memory exhausted\n   - Then only unload until memory reclaimed\n4. To maintain dependency loading in topological order, expand events after the global queue\n   - Recursively load generated groups first\n   - Unload groups only if it is not a dependency of another group\n   - The order of dependency resolution must not be changed after this step in the pipeline, but batches can still be formed\n5. Form batches during dependency loading\n   - The memory limit may be hit during dependency expansion\n   - Must not include load/unload for same item in batch, assuming batches are\n     executed in parallel\n\n### References\n\n- [(1989) A pyramidal data structure for triangle-based surface description](https://ieeexplore.ieee.org/document/19053)\n- [(1995) On Levels of Detail in Terrains](https://citeseerx.ist.psu.edu/document?repid=rep1\u0026type=pdf\u0026doi=3fa8c74a44f02aaaa18fe2d3cfdedfc9b8dbc50a)\n- [(1998) Efficient Implementation of Multi-Triangulations](https://dl.acm.org/doi/10.5555/288216.288222)\n- [(2001) Visualization of Large Terrains Made Easy](https://ieeexplore.ieee.org/document/964533)\n- [(2005) Batched Multi Triangulation](https://ieeexplore.ieee.org/document/1532797)\n- [(2021) A Deep Dive into Unreal Engine's 5 Nanite](https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf) ([video](https://www.youtube.com/watch?v=eviSykqSUUw))\n- [(2023) Real-Time Ray Tracing of Micro-Poly Geometry with Hierarchical Level of Detail](https://www.intel.com/content/www/us/en/developer/articles/technical/real-time-ray-tracing-of-micro-poly-geometry.html) ([video](https://www.youtube.com/watch?v=Tx32yi_0ETY))\n\n## Usage Example\n\nFor a complete usage example, see https://github.com/nvpro-samples/vk_lod_clusters.\n\nTo create LOD data with this library:\n\n```cpp\n#include \u003cnvclusterlod/nvclusterlod_hierarchy.h\u003e\n#include \u003cnvclusterlod/nvclusterlod_hierarchy_storage.hpp\u003e\n#include \u003cnvclusterlod/nvclusterlod_mesh.h\u003e\n#include \u003cnvclusterlod/nvclusterlod_mesh_storage.hpp\u003e\n\n...\n\n// Create contexts for running operations\nnvcluster::Context context;\nnvcluser::ContextCreateInfo contextCreateInfo{};\nnvclusterCreateContext(\u0026contextCreateInfo, \u0026context);\n\nnvclusterlod::Context lodContext;\nnvclusterlod::ContextCreateInfo lodContextCreateInfo{.clusterContext = context};\nnvclusterlodCreateContext(\u0026lodContextCreateInfo, \u0026lodContext);\n\n// Input mesh\nstd::vector\u003cuint32_t\u003e indices   = ...;\nstd::vector\u003cvec3\u003e     positions = ...;\n\n// Create decimated clusters\nconst nvclusterlod::MeshInput meshInput{\n    // Mesh data\n    .indices      = indices.data(),\n    .indexCount   = static_cast\u003cuint32_t\u003e(indices.size()),\n    .vertices     = reinterpret_cast\u003cconst float*\u003e(positions.data()),\n    .vertexOffset = 0,\n    .vertexCount  = static_cast\u003cuint32_t\u003e(positions.size()),\n    .vertexStride = sizeof(vec3),\n    // Use default configurations and decimation factor:\n    .clusterConfig = {},\n    .clusterGroupConfig = {},\n    .decimationFactor = 0.5,\n};\n\nnvclusterlod::LocalizedLodMesh mesh;\nnvclusterlod::generateLocalizedLodMesh(lodContext, meshInput, mesh);\n\n// Build a spatial hierarchy for faster selection\nconst nvclusterlod::HierarchyInput hierarchyInput {\n    .clusterGeneratingGroups = mesh.lodMesh.clusterGeneratingGroups.data(),\n    .groupQuadricErrors      = mesh.lodMesh.groupQuadricErrors.data(),\n    .groupClusterRanges      = mesh.lodMesh.groupClusterRanges.data(),\n    .groupCount              = static_cast\u003cuint32_t\u003e(mesh.lodMesh.groupClusterRanges.size()),\n    .clusterBoundingSpheres  = mesh.lodMesh.clusterBoundingSpheres.data(),\n    .clusterCount            = static_cast\u003cuint32_t\u003e(mesh.lodMesh.clusterBoundingSpheres.size()),\n    .lodLevelGroupRanges     = mesh.lodMesh.lodLevelGroupRanges.data(),\n    .lodLevelCount           = static_cast\u003cuint32_t\u003e(mesh.lodMesh.lodLevelGroupRanges.size())\n};\n\nnvclusterlod::LodHierarchy hierarchy;\nnvclusterlod::generateLodHierarchy(lodContext, hierarchyInput, hierarchy);\n\n// Upload mesh and hierarchy to the GPU. These are both simple structures of arrays.\n...\n```\n\n**Rendering whole levels of detail**\n\n```cpp\n// For each LOD level (highest detail first)\nfor(size_t lod = 0; lod \u003c mesh.lodMesh.lodLevelGroupRanges.size(); lod++)\n{\n    const nvcluster::Range\u0026 lodLevelGroupRange = mesh.lodMesh.lodLevelGroupRanges[lod];\n    glBegin(GL_TRIANGLES);  // Naive OpenGL immediate mode just for illustration\n\n    // For each group\n    for(uint32_t groupIndex = lodLevelGroupRange.offset; groupIndex \u003c lodLevelGroupRange.offset + lodLevelGroupRange.count; groupIndex++)\n    {\n        const nvcluster::Range\u0026 groupClusterRange = mesh.lodMesh.groupClusterRanges[groupIndex];\n\n        // For each cluster\n        for(uint32_t clusterIndex = groupClusterRange.offset; clusterIndex \u003c groupClusterRange.offset + groupClusterRange.count; clusterIndex++)\n        {\n            const nvcluster::Range\u0026 clusterTriangleRange = mesh.lodMesh.clusterTriangleRanges[clusterIndex];\n            const nvcluster::Range\u0026 clusterVertexRange   = mesh.clusterVertexRanges[clusterIndex];\n\n            // Can use this to pre-compute a per-cluster vertex array\n            const uint32_t* clusterVertexGlobalIndices = \u0026mesh.vertexGlobalIndices[clusterVertexRange.offset];\n\n            // For each triangle\n            for(uint32_t triangleIndex = clusterTriangleRange.offset; triangleIndex \u003c clusterTriangleRange.offset + clusterTriangleRange.count; triangleIndex++)\n            {\n                // For each triangle vertex\n                for (uint32_t vertex = 0; vertex \u003c 3; ++vertex)\n                {\n                    uint32_t localVertexIndex  = mesh.lodMesh.triangleVertices[3 * triangleIndex + vertex];\n                    uint32_t globalVertexIndex = clusterVertexGlobalIndices[localVertexIndex];\n                    glVertex3fv(glm::value_ptr(positions[globalVertexIndex]));\n                }\n            }\n        }\n    }\n    glEnd();\n}\n```\n\n**Selecting clusters**\n\nIt is intended clusters are rendered based on their quadric error, a measure of\ngeometric accuracy. A threshold in error over distance [to the camera] is chosen\n- the arcsine of which would be the angular error. This could be converted to a\nscreen space pixel size, but for ray tracing where there are shadows and\nreflections behind the camera, a pure distance metric is a good start.\n\nTo form a single unique surface with clusters of the right LOD, render clusters\nwhere:\n\n```cpp\nerrorOverDistance(\n        objectToEyeTransform,\n        hierarchy.groupCumulativeBoundingSpheres[clusterGroup],\n        hierarchy.groupCumulativeQuadricError[clusterGroup]\n    ) \u003e= threshold\n\u0026\u0026\nerrorOverDistance(\n        objectToEyeTransform,\n        hierarchy.groupCumulativeBoundingSpheres[clusterGeneratingGroup],\n        hierarchy.groupCumulativeQuadricError[clusterGeneratingGroup]\n    ) \u003c threshold\n```\n\nThe `clusterGeneratingGroup` is the group from which a cluster was generated by\ndecimation. E.g. decimating the \"generating\" group of clusters *generates*\nanother a new smaller set of clusters.\n\nThe `groupCumulativeQuadricError` is actually the error after its geometry is\ndecimated, not the error of the group itself's clusters. This value doesn't\nexist at the group level, which is the reason for the surprise. The above\nconditions gives a band in which cluster are chosen. Their group's decimated\ngeometry (first check) is closest to but not exceeding the threshold. Their\ngeometry (second check) does exceed the threshold so they are first past the\nthreshold. This holds true given some massaging of the bounding spheres to\nguarantee the decimated geometry will always pass the threshold before the\ngeometry itself.\n\nThe `groupCumulativeBoundingSpheres` conservatively include their generating\ngroup's bounding spheres. This guarantees that clusters from multiple levels\ncannot be rendered at once.\n\n**Spatial hierarchy**\n\nPerforming a test per cluster would be expensive. Even only testing every unique\ngroup--generating-group pair. This library creates a spatial hierarchy of\nbounding spheres to reduce the search space.\n\n`hierarchy.nodes` contains a tree of all clusters. The first node is the root\nnode. Simply descend while the following condition holds and check all cluster\nrange nodes when found. It's actually a combination of hierarchies for each LOD\nlevel. The way it works is described below.\n\n```cpp\nerrorOverDistance(\n        objectToEyeTransform,\n        node.boundingSphere,\n        node.maxClusterQuadricError\n    ) \u003e= threshold\n```\n\n## Build Integration\n\nThis library uses CMake and requires C++20. It is currently a static\nlibrary, designed with C compatibility in mind with data passed as a structure\nof arrays and output allocated by the user. Integration has been verified by\ndirectly including it with `add_subdirectory`:\n\n```cmake\nadd_subdirectory(nv_cluster_lod_builder)\n...\ntarget_link_libraries(my_target PUBLIC nv_cluster_lod_builder)\n```\n\nIf there is interest, please reach out for CMake config files (for\n`find_package()`) or any other features. GitHub issues are welcome.\n\n### Dependencies\n\nnv_cluster_lod_builder depends upon\n[nv_cluster_builder](https://github.com/nvpro-samples/nv_cluster_builder) and\n[meshoptimizer](https://github.com/zeux/meshoptimizer), which are submodules. To\ndownload them, run\n\n```\ngit submodule update --init --recursive\n```\n\n## License\n\nThis library and\n[nv_cluster_builder](https://github.com/nvpro-samples/nv_cluster_builder) are\nlicensed under the [Apache License\n2.0](http://www.apache.org/licenses/LICENSE-2.0).\n\nThis library uses third-party dependencies, which have their own:\n\n- [meshoptimizer](https://github.com/zeux/meshoptimizer), licensed under the\n  [MIT License](https://github.com/zeux/meshoptimizer/blob/47aafa533b439a78b53cd2854c177db61be7e666/LICENSE.md)\n\n## Limitations\n\nThis library is intended to enable a quick start to continuous LOD. It\ndemonstrates the basics for use as a learning tool or a placeholder.\n\nCluster and cluster group quality includes the limitations outlined in\n[nv_cluster_builder](https://github.com/nvpro-samples/nv_cluster_builder).\n\nThe number of triangles per cluster is configurable, but the vertex count is\nunconstrained. There are plans to address this, but for now it is possible that\nthe 256 vertex limit of `VK_NV_cluster_acceleration_structure` may be exceeded.\n\nThe decimation step uses [meshoptimizer](https://github.com/zeux/meshoptimizer)\nfor its lightweight convenience. This step is internal and not configurable.\nTexture seams are not preserved and in general vertex attributes are yet to be\nplumbed through.\n\nPerformance is limited by the clustering and decimation algorithms that run on\nthe CPU, although there is some parallelization.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnvpro-samples%2Fnv_cluster_lod_builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnvpro-samples%2Fnv_cluster_lod_builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnvpro-samples%2Fnv_cluster_lod_builder/lists"}