# pixelated artifacts in native vtk implementation compared to pyvista

**URL:** https://discourse.vtk.org/t/pixelated-artifacts-in-native-vtk-implementation-compared-to-pyvista/16207
**Category:** Support
**Tags:** python
**Created:** [January 4, 2026, 6:36pm UTC](https://discourse.vtk.org/t/pixelated-artifacts-in-native-vtk-implementation-compared-to-pyvista/16207 "2026-01-04T18:36:06Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![jonaspleyer](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jonaspleyer/32/10057_2.png) [@jonaspleyer](https://discourse.vtk.org/u/jonaspleyer)
#### Post date: [January 4, 2026, 6:36pm UTC](https://discourse.vtk.org/t/pixelated-artifacts-in-native-vtk-implementation-compared-to-pyvista/16207/1 "2026-01-04T18:36:06Z")

</div>

Hey everyone,

I am having trouble, figuring out the reason for why I am seeing pixelated artifacts in one of my renders. I have a pipline which works well for my purpose and is implemented in `pyvista`. I wanted to recreate this pipeline in native vtk to obtain better performance. The performance is considerably better but the artifacts make the results unusable for me.

## Comparison of pyvista (left) and vtk (right) results

 ![Figure_1](https://discourse.vtk.org/uploads/default/original/2X/7/7a6e76d9817eac6d4e5c56f46e5b21cf08818df4.png)

## vtk Implementation

```cpp
#include <cmath>

#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkCompositeDataGeometryFilter.h>
#include <vtkCylinderSource.h>
#include <vtkExtractEdges.h>
#include <vtkGraphicsFactory.h>
#include <vtkMultiBlockDataSet.h>
#include <vtkNamedColors.h>
#include <vtkOpenGLRenderer.h>
#include <vtkNew.h>
#include <vtkPolyDataMapper.h>
#include <vtkPNGWriter.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSphereSource.h>
#include <vtkTransform.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkWindowToImageFilter.h>
#include <vtkImageExport.h>

#include "render.h"

#define M_PI 3.14159265358979323846

namespace {
    vtkSmartPointer<vtkDataSet> CreateSphereDataSet(Vertex center, double radius)
    {
        double c[3] = {center.x, center.y, center.z};
        vtkNew<vtkSphereSource> sphere;
        sphere->SetCenter(c);
        sphere->SetRadius(radius);
        sphere->SetPhiResolution(30);
        sphere->SetThetaResolution(30);
        sphere->Update();
        return sphere->GetOutput();
    }

    vtkSmartPointer<vtkDataSet> CreateCylinderDataSet(
        double center[3],
        double direction[3],
        double radius,
        double height
    ) {
        double c[3] = {center[0], center[1], center[2]};
        vtkNew<vtkCylinderSource> cylinder;
        cylinder->SetHeight(height);
        cylinder->SetRadius(radius);
        cylinder->SetResolution(100);
        cylinder->Update();

        // Transform it
        vtkNew<vtkTransform> transform;
        transform->Identity();
        // Default orientation
        double orientation[3] = {0,1,0};
        double angle = std::acos(
            direction[0]*orientation[0] + direction[1]*orientation[1] + direction[2]*orientation[2]
        ) / 2 / M_PI * 360;
        double cross[3] = {
            orientation[1]*direction[2] - orientation[2]*direction[1],
            orientation[2]*direction[0] - orientation[0]*direction[2],
            orientation[0]*direction[1] - orientation[1]*direction[0]
        };
        transform->RotateWXYZ(angle, cross);
        transform->PostMultiply();
        transform->Translate(c);
        transform->Update();

        vtkNew<vtkTransformPolyDataFilter> transformPD;
        transformPD->SetTransform(transform);
        transformPD->SetInputData(cylinder->GetOutput());
        transformPD->SetInputConnection(cylinder->GetOutputPort());
        transformPD->Update();

        return transformPD->GetOutput();
    }
} // namespace

vtkNew<vtkMultiBlockDataSet> create_mesh(Agent agent) {
    // Create cell surfaces
    vtkNew<vtkMultiBlockDataSet> mesh;

    Vertex* vertices = agent.positions;
    double radius = agent.radius;

    mesh->SetBlock(0, CreateSphereDataSet(vertices[0], radius));
    for (int i=1; i<agent.n_vertices; i++) {
        double center[3] = {
            0.5 * (vertices[i].x + vertices[i-1].x),
            0.5 * (vertices[i].y + vertices[i-1].y),
            0.5 * (vertices[i].z + vertices[i-1].z)
        };
        double direction[3] = {
            vertices[i].x - vertices[i-1].x,
            vertices[i].y - vertices[i-1].y,
            vertices[i].z - vertices[i-1].z
        };
        double height = std::sqrt(
            (direction[0]*direction[0] + direction[1]*direction[1] + direction[2]*direction[2])
        );
        direction[0] /= height;
        direction[1] /= height;
        direction[2] /= height;
        mesh->SetBlock(2*i-1, CreateCylinderDataSet(center, direction, radius, height));
        mesh->SetBlock(2*i, CreateSphereDataSet(vertices[i], radius));
    }
    return mesh;
}

void render_img(Agent* agents, int n_agents, Camera camera, void* buffer)
{
    vtkNew<vtkRenderer> aren;
    aren->RemoveAllLights();
    aren->ResetCamera(0, camera.size_x, 0, camera.size_y, 0, camera.distance_z);
    aren->UseFXAAOff();
    aren->UseSSAOOff();
    aren->SetAmbient(0, 0, 0);
    aren->SetNearClippingPlaneTolerance(0.001);
    aren->SetAllocatedRenderTime(5000);
    aren->SetAutomaticLightCreation(false);
    aren->SetBackground(0, 0, 0);

    auto cam = aren->GetActiveCamera();
    cam->SetParallelProjection(true);
    cam->SetPosition(camera.size_x/2.0, camera.size_y/2.0, camera.distance_z);
    cam->SetFocalPoint(camera.size_x/2.0, camera.size_y/2.0, 0);
    cam->OrthogonalizeViewUp();

    cam->SetParallelScale(std::min(camera.size_x, camera.size_y) / 2);
    cam->SetClippingRange(0, 2 * camera.distance_z);
    aren->ResetCameraClippingRange(0, camera.size_x, 0, camera.size_y, 0, camera.distance_z);

    for (int i=0; i<n_agents; i++) {
        auto mesh = create_mesh(agents[i]);

        vtkNew<vtkExtractEdges> edges;
        edges->SetInputData(mesh);

        vtkNew<vtkCompositeDataGeometryFilter> polydata;
        polydata->SetInputConnection(edges->GetOutputPort());

        vtkNew<vtkPolyDataMapper> mapper;
        mapper->SetInputConnection(0, polydata->GetOutputPort(0));

        vtkNew<vtkActor> actor;
        actor->SetMapper(mapper);
        actor->GetProperty()->SetLineWidth(0);
        actor->GetProperty()->SetColor(agents[i].color);
        actor->GetProperty()->SetSpecular(0);
        actor->GetProperty()->SetSpecularPower(0);
        actor->GetProperty()->SetAmbient(1.0);
        actor->GetProperty()->SetLighting(false);
        actor->GetProperty()->SetInterpolationToFlat();
        actor->GetProperty()->SetPointSize(5);
        actor->GetProperty()->SetRepresentationToSurface();
        actor->GetProperty()->SetBaseIOR(0.0);
        actor->GetProperty()->SetMetallic(0.0);
        actor->GetProperty()->SetRoughness(0.0);
        actor->GetProperty()->SetAnisotropy(0.0);
        actor->GetProperty()->SetCoatIOR(0.0);
        actor->GetProperty()->SetCoatStrength(0.0);
        actor->GetProperty()->SetDiffuse(0.0);
        actor->GetProperty()->SetEdgeVisibility(false);
        actor->GetProperty()->SetVertexVisibility(false);
        actor->UseBoundsOff();
        aren->AddActor(actor);
    }

    int size_x = camera.size_x * camera.resolution;
    int size_y = camera.size_y * camera.resolution;

    vtkNew<vtkRenderWindow> win;
    win->AddRenderer(aren);
    win->SetOffScreenRendering(1);
    win->SetSize(size_x, size_y);
    win->SetMultiSamples(0);
    win->Render();

    vtkNew<vtkWindowToImageFilter> windowToImageFilter;
    windowToImageFilter->SetInputBufferTypeToRGB();
    windowToImageFilter->SetInput(win);
    windowToImageFilter->Update();

    vtkNew<vtkImageExport> exporter;
    exporter->SetInputConnection(windowToImageFilter->GetOutputPort());
    exporter->SetInputData(windowToImageFilter->GetOutput());
    exporter->Update();
    exporter->Export(buffer);

    vtkNew<vtkPNGWriter> writer;
    writer->SetFileName("screenshot.png");
    writer->SetInputConnection(windowToImageFilter->GetOutputPort());
    writer->Write();
}

```

## pyvista Implementation

```python
def __create_cell_surfaces(
    cells: dict[CellIdentifier, tuple[RodAgent, CellIdentifier | None]],
):
    for ident in cells.keys():
        meshes = []
        p = cells[ident][0].pos
        r = cells[ident][0].radius

        meshes.append(pv.Sphere(center=p[0], radius=r))
        for j in range(max(p.shape[0] - 1, 0)):
            # Add sphere at junction
            meshes.append(pv.Sphere(center=p[j + 1], radius=r))

            # Otherwise add cylinders
            pos1 = p[j]
            pos2 = p[j + 1]
            center = 0.5 * (pos1 + pos2)
            direction = pos2 - center
            height = float(np.linalg.norm(pos1 - pos2))
            cylinder = pv.Cylinder(
                center=center, direction=direction, radius=r, height=height
            )
            meshes.append(cylinder)
        # Combine all together
        mesh = pv.MultiBlock(meshes).extract_geometry()
        yield (ident, mesh)

def main_routine():
    plotter = pv.Plotter(
        off_screen=True,
        window_size=[
            int(np.round(domain_size[0] * ppm[0])),
            int(np.round(domain_size[1] * ppm[1])),
        ],
    )

    # Get camera info
    p1 = np.array([0, 0, 0])
    p2 = np.array([domain_size[0], domain_size[1], 0])
    p3 = np.array([0, domain_size[1], 0])
    rect = pv.Rectangle([p1, p2, p3])
    plotter.add_mesh(rect)
    bounds = (0.0, domain_size[0], 0.0, domain_size[1], 0.0, 0)
    pv.Plotter.view_xy(plotter, bounds=bounds)
    pv.Plotter.enable_parallel_projection(plotter)
    plotter.camera.tight(padding=0)
    plotter.camera.position = (*plotter.camera.position[:2], render_distance)
    camera = plotter.camera.copy()
    plotter.clear_actors()

    pv.Plotter.set_background(plotter, [render_settings.bg_brightness] * 3)

    for ident, cell in __create_cell_surfaces(cells):
        color = [render_settings.cell_brightness] * 3
        if colors is not None:
            color = colors[ident]
        actor = plotter.add_mesh(
            cell,
            show_edges=False,
            color=color,
            diffuse=render_settings.diffuse,
            ambient=render_settings.ambient,
            specular=render_settings.specular,
            specular_power=render_settings.specular_power,
            metallic=render_settings.metallic,
            pbr=render_settings.pbr,
            lighting=render_settings.lighting,
        )
        actor.UseBoundsOff()

    if not render_settings.render_mask:
        pv.Plotter.enable_ssao(plotter, radius=render_settings.ssao_radius)
        plotter.enable_anti_aliasing()
    else:
        plotter.disable_anti_aliasing()

    plotter.camera = camera
    img = np.array(plotter.screenshot())
    plotter.close()

```

---

<div class="post-metadata">

### Author: ![dgobbi](https://discourse.vtk.org/user_avatar/discourse.vtk.org/dgobbi/32/18_2.png) [@dgobbi](https://discourse.vtk.org/u/dgobbi)
#### Post date: [January 5, 2026, 1:32am UTC](https://discourse.vtk.org/t/pixelated-artifacts-in-native-vtk-implementation-compared-to-pyvista/16207/2 "2026-01-05T01:32:12Z")

</div>

The use of vtkExtractEdges in the C++ code is suspicious. Is it there intentionally? It will cause the mesh to be rendered as a wireframe, which doesn’t seem like what you want.

Regarding the mesh, you don’t need the spheres, because vtkCylinderSource has options to automatically cap the ends a cylinder with hemispheres:

```c++
        cylinder->CappingOn();
        cylinder->CapsuleCapOn();

```

---

<div class="post-metadata">

### Author: ![jonaspleyer](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jonaspleyer/32/10057_2.png) [@jonaspleyer](https://discourse.vtk.org/u/jonaspleyer)
#### Post date: [January 5, 2026, 1:40pm UTC](https://discourse.vtk.org/t/pixelated-artifacts-in-native-vtk-implementation-compared-to-pyvista/16207/3 "2026-01-05T13:40:11Z")

</div>

Thanks that actually solved it. It also improved performance considerably.
