Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
ClusterLodGenerator.cpp
Go to the documentation of this file.
1
7
10#include "meshoptimizer.h"
11#include <algorithm>
12#include <limits>
13
14namespace EngineCore
15{
16
18 const std::vector<Vertex>& vertices,
19 const std::vector<uint32_t>& indices)
20{
21 if (indices.empty()) {
22 return glm::vec4(0.0f);
23 }
24
25 // Compute centroid
26 glm::vec3 center(0.0f);
27 for (uint32_t idx : indices) {
28 center += glm::vec3(vertices[idx].position);
29 }
30 center /= static_cast<float>(indices.size());
31
32 // Compute radius
33 float radiusSq = 0.0f;
34 for (uint32_t idx : indices) {
35 glm::vec3 pos = glm::vec3(vertices[idx].position);
36 float distSq = glm::dot(pos - center, pos - center);
37 radiusSq = std::max(radiusSq, distSq);
38 }
39
40 return glm::vec4(center, std::sqrt(radiusSq));
41}
42
44 const std::vector<Vertex>& vertices,
45 const meshopt_Meshlet& meshlet,
46 const std::vector<uint32_t>& meshletVertices)
47{
48 if (meshlet.vertex_count == 0) {
49 return glm::vec4(0.0f);
50 }
51
52 // Collect vertex positions for this meshlet
53 glm::vec3 center(0.0f);
54 for (uint32_t i = 0; i < meshlet.vertex_count; ++i) {
55 uint32_t vertexIndex = meshletVertices[meshlet.vertex_offset + i];
56 center += glm::vec3(vertices[vertexIndex].position);
57 }
58 center /= static_cast<float>(meshlet.vertex_count);
59
60 // Compute radius
61 float radiusSq = 0.0f;
62 for (uint32_t i = 0; i < meshlet.vertex_count; ++i) {
63 uint32_t vertexIndex = meshletVertices[meshlet.vertex_offset + i];
64 glm::vec3 pos = glm::vec3(vertices[vertexIndex].position);
65 float distSq = glm::dot(pos - center, pos - center);
66 radiusSq = std::max(radiusSq, distSq);
67 }
68
69 return glm::vec4(center, std::sqrt(radiusSq));
70}
71
73 const std::vector<Vertex>& srcVertices,
74 const std::vector<uint32_t>& srcIndices,
75 size_t targetIndexCount,
76 float targetError,
77 size_t maxVerticesPerMeshlet,
78 size_t maxTrianglesPerMeshlet)
79{
80 LodLevelData result;
81
82 // Simplify the mesh
83 result.indices.resize(srcIndices.size());
84 float resultError = 0.0f;
85
86 size_t newIndexCount = meshopt_simplify(
87 result.indices.data(),
88 srcIndices.data(),
89 srcIndices.size(),
90 &srcVertices[0].position.x,
91 srcVertices.size(),
92 sizeof(Vertex),
93 targetIndexCount,
94 targetError,
95 0, // options
96 &resultError
97 );
98
99 result.indices.resize(newIndexCount);
100 result.error = resultError;
101
102 if (newIndexCount == 0) {
103 return result;
104 }
105
106 // Remap vertices to remove unused ones
107 std::vector<uint32_t> remap(srcVertices.size());
108 size_t uniqueVertexCount = meshopt_generateVertexRemap(
109 remap.data(),
110 result.indices.data(),
111 result.indices.size(),
112 srcVertices.data(),
113 srcVertices.size(),
114 sizeof(Vertex)
115 );
116
117 result.vertices.resize(uniqueVertexCount);
118 meshopt_remapVertexBuffer(
119 result.vertices.data(),
120 srcVertices.data(),
121 srcVertices.size(),
122 sizeof(Vertex),
123 remap.data()
124 );
125
126 std::vector<uint32_t> remappedIndices(result.indices.size());
127 meshopt_remapIndexBuffer(
128 remappedIndices.data(),
129 result.indices.data(),
130 result.indices.size(),
131 remap.data()
132 );
133 result.indices = std::move(remappedIndices);
134
135 // Optimize for vertex cache
136 meshopt_optimizeVertexCache(
137 result.indices.data(),
138 result.indices.data(),
139 result.indices.size(),
140 result.vertices.size()
141 );
142
143 // Generate meshlets for simplified geometry
144 size_t maxMeshlets = meshopt_buildMeshletsBound(
145 result.indices.size(),
146 maxVerticesPerMeshlet,
147 maxTrianglesPerMeshlet
148 );
149
150 result.meshlets.resize(maxMeshlets);
151 result.meshletVertices.resize(maxMeshlets * maxVerticesPerMeshlet);
152 result.meshletTriangles.resize(maxMeshlets * maxTrianglesPerMeshlet * 3);
153
154 size_t meshletCount = meshopt_buildMeshlets(
155 result.meshlets.data(),
156 result.meshletVertices.data(),
157 result.meshletTriangles.data(),
158 result.indices.data(),
159 result.indices.size(),
160 &result.vertices[0].position.x,
161 result.vertices.size(),
162 sizeof(Vertex),
163 maxVerticesPerMeshlet,
164 maxTrianglesPerMeshlet,
165 0.5f // cone weight
166 );
167
168 // Trim to actual size
169 if (meshletCount > 0) {
170 const auto& lastMeshlet = result.meshlets[meshletCount - 1];
171 result.meshlets.resize(meshletCount);
172 result.meshletVertices.resize(lastMeshlet.vertex_offset + lastMeshlet.vertex_count);
173 result.meshletTriangles.resize(lastMeshlet.triangle_offset + lastMeshlet.triangle_count * 3);
174 } else {
175 result.meshlets.clear();
176 result.meshletVertices.clear();
177 result.meshletTriangles.clear();
178 }
179
180 return result;
181}
182
184 const std::vector<Vertex>& vertices,
185 const std::vector<uint32_t>& indices,
186 const std::vector<meshopt_Meshlet>& baseMeshlets,
187 const std::vector<uint32_t>& baseMeshletVertices,
188 const std::vector<uint8_t>& baseMeshletTriangles,
189 const LodGenerationConfig& config)
190{
192
193 // Check if mesh is large enough for LOD
194 if (indices.size() < config.minTrianglesForLod * 3) {
195 VS_LOG(LogLOD, Debug, "Mesh too small for LOD generation (indices: {}, min triangles: {})",
196 indices.size(), config.minTrianglesForLod);
197 return result;
198 }
199
200 VS_LOG(LogLOD, Info, "Generating LOD hierarchy: {} triangles, {} base meshlets",
201 indices.size() / 3, baseMeshlets.size());
202
203 // Store LOD level data for hierarchy building
204 std::vector<LodLevelData> lodLevels;
205
206 // LOD 0 = base level (finest)
207 LodLevelData baseLod;
208 baseLod.vertices = vertices;
209 baseLod.indices = indices;
210 baseLod.meshlets = baseMeshlets;
211 baseLod.meshletVertices = baseMeshletVertices;
212 baseLod.meshletTriangles = baseMeshletTriangles;
213 baseLod.error = 0.0f;
214 lodLevels.push_back(std::move(baseLod));
215
216 result.baseLevelClusterCount = baseMeshlets.size();
217
218 // Generate coarser LOD levels
219 size_t currentIndexCount = indices.size();
220 float accumulatedError = 0.0f;
221
222 for (size_t level = 1; level < config.maxLodLevels; ++level) {
223 size_t targetIndexCount = static_cast<size_t>(currentIndexCount * config.simplificationRatio);
224 targetIndexCount = (targetIndexCount / 3) * 3; // Round to triangles
225
226 if (targetIndexCount < 12) { // Minimum 4 triangles
227 break;
228 }
229
230 const auto& prevLevel = lodLevels.back();
232 prevLevel.vertices,
233 prevLevel.indices,
234 targetIndexCount,
235 config.targetError * static_cast<float>(level),
236 252, // Max vertices per meshlet
237 static_cast<size_t>(config.maxTrianglesPerCluster)
238 );
239
240 if (newLevel.indices.empty() || newLevel.meshlets.empty()) {
241 VS_LOG(LogLOD, Debug, "LOD level {} generation failed or produced empty result", level);
242 break;
243 }
244
245 accumulatedError += newLevel.error;
246 newLevel.error = accumulatedError;
247
248 VS_LOG(LogLOD, Debug, "LOD level {}: {} triangles, {} meshlets, error={:.4f}",
249 level, newLevel.indices.size() / 3, newLevel.meshlets.size(), newLevel.error);
250
251 currentIndexCount = newLevel.indices.size();
252 lodLevels.push_back(std::move(newLevel));
253
254 // Stop if we've reduced enough
255 if (newLevel.meshlets.size() <= 2) {
256 break;
257 }
258 }
259
260 result.lodLevelCount = lodLevels.size();
261
262 // Build cluster and group data structures
263 // Clusters: one per meshlet across all LOD levels
264 // Groups: link finer level clusters to coarser level representation
265
266 // First pass: count total meshlets and create cluster entries
267 size_t totalMeshlets = 0;
268 for (const auto& level : lodLevels) {
269 totalMeshlets += level.meshlets.size();
270 }
271
272 result.clusters.reserve(totalMeshlets);
273 result.groups.reserve(lodLevels.size() - 1); // One group per transition
274
275 // Track meshlet indices as we build the combined buffer
276 uint32_t globalMeshletIndex = 0;
277 std::vector<uint32_t> levelMeshletStartIndices;
278
279 for (size_t levelIdx = 0; levelIdx < lodLevels.size(); ++levelIdx) {
280 const auto& level = lodLevels[levelIdx];
281 levelMeshletStartIndices.push_back(globalMeshletIndex);
282
283 for (size_t meshletIdx = 0; meshletIdx < level.meshlets.size(); ++meshletIdx) {
284 const auto& meshlet = level.meshlets[meshletIdx];
285
286 ClusterLodData cluster;
288 level.vertices, meshlet, level.meshletVertices);
289 cluster.error = level.error;
290 cluster.meshletIndex = globalMeshletIndex;
291
292 // Link to parent group (coarser level alternative)
293 // Each cluster checks: "Is there a coarser representation that's good enough?"
294 // - If yes (parent error acceptable): don't render this cluster, use coarser
295 // - If no (parent error too high): render this cluster for finer detail
296 //
297 // Group k stores the error of LOD level k+1 (the coarser alternative)
298 // So LOD level k should check Group k to decide if LOD k+1 is sufficient
299 if (levelIdx < lodLevels.size() - 1) {
300 // Not the coarsest level - check if coarser level is sufficient
301 cluster.refinedGroupId = static_cast<int32_t>(levelIdx);
302 } else {
303 // Coarsest level - always render (no coarser alternative available)
304 cluster.refinedGroupId = -1;
305 }
306
307 result.clusters.push_back(cluster);
308 globalMeshletIndex++;
309 }
310 }
311
312 // Create groups linking each level to its coarser representation
313 for (size_t levelIdx = 0; levelIdx < lodLevels.size() - 1; ++levelIdx) {
314 const auto& coarserLevel = lodLevels[levelIdx + 1];
315
316 ClusterGroupData group;
317 // Group bounds encompass all meshlets in the coarser level
319 coarserLevel.vertices, coarserLevel.indices);
320 group.simplifiedError = coarserLevel.error;
321 group.depth = static_cast<int32_t>(lodLevels.size() - 1 - levelIdx); // Higher = finer
322
323 result.groups.push_back(group);
324 }
325
326 // Build combined meshlet data buffer
327 // Vertices and triangles need to be offset correctly
328 size_t totalVertices = 0;
329 size_t totalTriangles = 0;
330 for (const auto& level : lodLevels) {
331 for (const auto& meshlet : level.meshlets) {
332 totalVertices += meshlet.vertex_count;
333 totalTriangles += meshlet.triangle_count;
334 }
335 }
336
337 result.meshletData.meshlets.reserve(totalMeshlets);
338 result.unpackedData.vertices.reserve(totalVertices);
339 result.unpackedData.triangles.reserve(totalTriangles);
340
341 uint32_t vertexOffset = 0;
342 uint32_t triangleOffset = 0;
343
344 for (const auto& level : lodLevels) {
345 for (const auto& meshlet : level.meshlets) {
346 // Create unified meshlet with correct offsets
347 meshopt_Meshlet unifiedMeshlet;
348 unifiedMeshlet.vertex_offset = vertexOffset;
349 unifiedMeshlet.vertex_count = meshlet.vertex_count;
350 unifiedMeshlet.triangle_offset = triangleOffset;
351 unifiedMeshlet.triangle_count = meshlet.triangle_count;
352 result.meshletData.meshlets.push_back(unifiedMeshlet);
353
354 // Unpack vertices
355 for (uint32_t i = 0; i < meshlet.vertex_count; ++i) {
356 uint32_t srcVertexIndex = level.meshletVertices[meshlet.vertex_offset + i];
357 result.unpackedData.vertices.push_back(level.vertices[srcVertexIndex]);
358 }
359
360 // Pack triangles
361 for (uint32_t i = 0; i < meshlet.triangle_count; ++i) {
362 const uint8_t* triPtr = &level.meshletTriangles[meshlet.triangle_offset + i * 3];
363 result.unpackedData.triangles.push_back(
364 Ecs::PackedTriangle::pack(triPtr[0], triPtr[1], triPtr[2])
365 );
366 }
367
368 vertexOffset += meshlet.vertex_count;
369 triangleOffset += meshlet.triangle_count;
370 }
371 }
372
373 VS_LOG(LogLOD, Info, "LOD hierarchy generated: {} levels, {} clusters, {} groups",
374 result.lodLevelCount, result.clusters.size(), result.groups.size());
375
376 return result;
377}
378
379} // namespace EngineCore
::EngineCore::LogCategory LogLOD("LogLOD", ::EngineCore::LogVerbosity::Info, ::EngineCore::LogVerbosity::Verbose)
#define VS_LOG(Category, Verbosity, Format,...)
Log a message with category and verbosity.
Definition LogMacros.h:122
static glm::vec4 computeMeshletBoundingSphere(const std::vector< Vertex > &vertices, const meshopt_Meshlet &meshlet, const std::vector< uint32_t > &meshletVertices)
Compute bounding sphere for a meshlet.
static LodLevelData generateLodLevel(const std::vector< Vertex > &srcVertices, const std::vector< uint32_t > &srcIndices, size_t targetIndexCount, float targetError, size_t maxVerticesPerMeshlet, size_t maxTrianglesPerMeshlet)
static Ecs::LodHierarchyResult generate(const std::vector< Vertex > &vertices, const std::vector< uint32_t > &indices, const std::vector< meshopt_Meshlet > &baseMeshlets, const std::vector< uint32_t > &baseMeshletVertices, const std::vector< uint8_t > &baseMeshletTriangles, const LodGenerationConfig &config={})
Generate LOD hierarchy from mesh geometry.
static glm::vec4 computeBoundingSphere(const std::vector< Vertex > &vertices, const std::vector< uint32_t > &indices)
Compute bounding sphere for a set of vertices.
Log category system implementation.
@ Info
Important state changes.
Definition LogCategory.h:32
@ Debug
Development debugging.
Definition LogCategory.h:33
Result of LOD hierarchy generation for a mesh primitive. Contains per-cluster and per-group data for ...
Definition EcsData.h:234
std::vector< EngineCore::ClusterLodData > clusters
Per-cluster data (all LOD levels combined)
Definition EcsData.h:236
UnpackedMeshletData unpackedData
Unpacked GPU data for LOD meshlets.
Definition EcsData.h:243
std::vector< EngineCore::ClusterGroupData > groups
Definition EcsData.h:237
MeshletData meshletData
Meshlet data for all clusters (extends base meshlet data)
Definition EcsData.h:240
size_t baseLevelClusterCount
Clusters at LOD 0 (finest level)
Definition EcsData.h:245
size_t lodLevelCount
Number of LOD levels generated.
Definition EcsData.h:246
std::vector< meshopt_Meshlet > meshlets
Definition EcsData.h:179
static PackedTriangle pack(uint8_t i0, uint8_t i1, uint8_t i2)
Pack 3 local indices into a single uint32_t.
Definition EcsData.h:208
std::vector< PackedTriangle > triangles
Packed triangle indices (4 bytes each)
Definition EcsData.h:224
std::vector< Vertex > vertices
Unpacked vertex data per meshlet.
Definition EcsData.h:223
Per-group LOD data representing a simplified version of child clusters. Groups are created by merging...
Definition RenderData.h:480
int32_t depth
DAG depth (0 = coarsest level)
Definition RenderData.h:483
float simplifiedError
Error threshold for this group.
Definition RenderData.h:482
glm::vec4 simplifiedBoundsPositionAndRadius
xyz = local center, w = radius
Definition RenderData.h:481
Per-cluster LOD data for GPU-driven LOD selection. Each cluster maps to exactly one meshlet and conta...
Definition RenderData.h:461
glm::vec4 boundsPositionAndRadius
xyz = local center, w = radius
Definition RenderData.h:462
float error
Simplification error (mesh-local units)
Definition RenderData.h:463
uint32_t meshletIndex
Index into UnifiedMeshlet buffer.
Definition RenderData.h:465
int32_t refinedGroupId
Parent group index (-1 for base/finest geometry)
Definition RenderData.h:464
Generate a single LOD level by simplifying geometry.
float error
Simplification error for this level.
Configuration for LOD hierarchy generation.
size_t maxTrianglesPerCluster
Max triangles per cluster/meshlet.
float simplificationRatio
Reduction ratio per LOD level (0.5 = halve each level)
size_t minTrianglesForLod
Minimum triangles to consider LOD generation.
size_t maxLodLevels
Maximum LOD levels to generate.
float targetError
Target simplification error.
The fundamental building block of all meshes in this engine.
Definition Vertex.h:15