# Using Level of Detail (LOD) with vtkGlyph3DMapper for Large Numbers of Spheres

**URL:** <https://discourse.vtk.org/t/using-level-of-detail-lod-with-vtkglyph3dmapper-for-large-numbers-of-spheres/15913>\
**Category:** Development\
**Tags:** code\
**Created:** [August 1, 2025, 3:58pm UTC](https://discourse.vtk.org/t/using-level-of-detail-lod-with-vtkglyph3dmapper-for-large-numbers-of-spheres/15913 "2025-08-01T15:58:58Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Shravan](https://discourse.vtk.org/letter_avatar_proxy/v4/letter/s/ea666f/32.png) [@Shravan](https://discourse.vtk.org/u/Shravan)\
**Post date:** [August 1, 2025, 3:58pm UTC](https://discourse.vtk.org/t/using-level-of-detail-lod-with-vtkglyph3dmapper-for-large-numbers-of-spheres/15913/1 "2025-08-01T15:58:59Z")

</div>

### **Objective**

I’m rendering `N` instances of a sphere within a bounding box using `vtkGlyph3DMapper`. The positions are generated procedurally to fill the box, and this part works well.

### **Problem**

When `N` becomes large (e.g., tens of thousands), performance drops significantly — especially during interaction. To prevent extreme cases, I compute the box’s capacity and limit the number of spheres accordingly. However, even within this capped number, rendering can be slow due to the volume of geometry.

I want to implement **Level of Detail (LOD)** to improve interaction performance while still showing all glyphs, just at a lower resolution when necessary.

### **What I’ve Tried**

- I noticed `vtkGlyph3DMapper` has methods for enabling LOD (`SetLODEnabled`, etc.), but when I use them, **only a subset of the glyphs are visible** , and the rest disappear. My expectation was to see **all glyphs, but with simpler geometry**.
- I also tried using `vtkLODActor` and ‘vtkLODProp3D’, providing a low-resolution version of the sphere as an additional mapper. But during interaction, I **see no change in visual fidelity or performance** , and it seems the LOD is never triggered — even when rendering becomes slow.

### **Questions**

1. **Is `vtkGlyph3DMapper`’s built-in LOD system meant to be used together with `vtkLODActor`** , or are they separate approaches?
2. **When using `vtkLODActor`** , is there a way to **control the threshold** at which it switches to lower-res mappers? The `SetLODMapperRenderTime()` method exists, but its documentation suggests it shouldn’t be used, and render time is hard to estimate accurately.
3. Is there a **recommended way to efficiently render a very large number of identical glyphs** with dynamic LOD switching in VTK?

* * *

I’ll attach code to demonstrate the setup I’m using. Any suggestions on best practices or alternatives would be greatly appreciated.

```auto
#include <QApplication>
#include <QSurfaceFormat>
#include <QVTKOpenGLNativeWidget.h>

#include <vtkGlyph3DMapper.h>
#include <vtkInformation.h>
#include <vtkInformationVector.h>
#include <vtkInteractorStyleTrackballActor.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkPolyDataAlgorithm.h>
#include <vtkPolyData.h>
#include <vtkRenderer.h>
#include <vtkSphereSource.h>
#include <vtkSmartPointer.h>

#include <random>

class SphereLocationSource : public vtkPolyDataAlgorithm
{
public:
    static SphereLocationSource *New();
    vtkTypeMacro(SphereLocationSource, vtkPolyDataAlgorithm);

    ~SphereLocationSource() = default;

    vtkSetMacro(NumSpheres, int);

protected:
    SphereLocationSource() { this->SetNumberOfInputPorts(0); }

    int RequestData(vtkInformation *vtkNotUsed(request), vtkInformationVector **vtkNotUsed(inputVector), vtkInformationVector *outputVector) override
    {
        vtkInformation *outInfo = outputVector->GetInformationObject(0);
        vtkPolyData *output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

        vtkNew<vtkPolyData> polyData;
        auto points = vtkSmartPointer<vtkPoints>::New();

        // Ideal spacing between centers (2 * radius + some space between particles)
        double minSpacing = 2.25;
        int nx = std::cbrt(NumSpheres);
        int ny = nx;
        int nz = nx;
        int nTotal = nx * ny * nz;

        double dx = 100 / nx;
        double dy = 50 / ny;
        double dz = 40 / nz;

        double offsetX = dx / 2;
        double offsetY = dy / 2;
        double offsetZ = dz / 2;

        int count = 0;
        for (int i = 0; i < nx; ++i)
        {
            for (int j = 0; j < ny; ++j)
            {
                for (int k = 0; k < nz; ++k)
                {
                    if (count >= nTotal)
                        break;
                    double x = offsetX + i * dx;
                    double y = offsetY + j * dy;
                    double z = offsetZ + k * dz;
                    points->InsertNextPoint(x, y, z);
                    ++count;
                }
            }
        }

        for (int i = 0; count < nTotal; ++i)
        {
            int xi = i % (nx - 1);
            int yj = (i / (nx - 1)) % (ny - 1);
            int zk = (i / ((nx - 1) * (ny - 1))) % (nz - 1);

            double x = offsetX + (xi + 0.5) * dx;
            double y = offsetY + (yj + 0.5) * dy;
            double z = offsetZ + (zk + 0.5) * dz;
            points->InsertNextPoint(x, y, z);
            ++count;
        }

        // Adding some jitter to the locations so the randomization is implied.
        std::mt19937 rng(42); // Fixed seed = reproducible
        std::uniform_real_distribution<double> jitter(-2 * minSpacing, 2 * minSpacing);

        for (vtkIdType i = 0; i < points->GetNumberOfPoints(); ++i)
        {
            double p[3];
            points->GetPoint(i, p);
            p[0] += jitter(rng);
            p[1] += + jitter(rng);
            p[2] += jitter(rng);
            points->SetPoint(i, p);
        }

        polyData->SetPoints(points);
        output->ShallowCopy(polyData);

        return 1;
    }

private:
    int NumSpheres = 10;
};
vtkStandardNewMacro(SphereLocationSource);

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication app(argc, argv);
    QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat());

    vtkNew<vtkSphereSource> sphere;
    vtkNew<vtkSphereSource> lowResSphere;
    lowResSphere->SetPhiResolution(4);
    lowResSphere->SetThetaResolution(4);

    vtkNew<SphereLocationSource> locations;
    locations->SetNumSpheres(100000);

    vtkNew<vtkGlyph3DMapper> mapper;
    mapper->ScalarVisibilityOff();
    mapper->SetInputConnection(locations->GetOutputPort());
    mapper->SetSourceConnection(sphere->GetOutputPort());

    // mapper->SetCullingAndLOD(true);
    // mapper->SetLODColoring(true);
    // mapper->SetNumberOfLOD(2);
    // mapper->SetLODDistanceAndTargetReduction(0, 5.0, 0.0);
    // mapper->SetLODDistanceAndTargetReduction(1, 10.0, 0.8);

    vtkNew<vtkGlyph3DMapper> lowResMapper;
    lowResMapper->ScalarVisibilityOff();
    lowResMapper->SetInputConnection(locations->GetOutputPort());
    lowResMapper->SetSourceConnection(lowResSphere->GetOutputPort());

    // Actor trial 1
    vtkNew<vtkActor> actor;
    actor->SetMapper(mapper);

    
    // Actor trial 2
    // vtkNew<vtkLODActor> glyphActor;
    // actor->SetMapper(mapper);
    // glyphActor->AddLODMapper(lowResMapper);

    // Actor trial 3
    // vtkNew<vtkLODProp3D> glyphActor;
    // glyphActor->AddLOD(mapper, 0.1);
    // glyphActor->AddLOD(lowResMapper, 0.0001);
    // glyphActor->AutomaticLODSelectionOn();

    vtkNew<vtkRenderer> renderer;
    renderer->AddActor(actor);
    renderer->SetBackground(0.1, 0.2, 0.4);
    renderer->ResetCamera();

    vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
    renderWindow->AddRenderer(renderer);

    QVTKOpenGLNativeWidget vtkWidget;
    vtkWidget.setRenderWindow(renderWindow);
    
    // vtkWidget.renderWindow()->GetInteractor()->SetStillUpdateRate(0.001);
    // vtkWidget.renderWindow()->GetInteractor()->SetDesiredUpdateRate(60);

    vtkWidget.resize(800, 600);
    vtkWidget.show();

    return app.exec();
}

```
