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
vtk Implementation
#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
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()
