Advice for high-quality anti-aliased lines + volume rendering

Hi all,

We’re running with multi-sampling disabled since it’s problematic when combined with volume rendering on Intel GPUs (GL errors/black screen).

Instead we use FXAA with default settings to at least get some anti-aliasing for our polygonal geometry.

However, thin lines really don’t look that good, especially when drawn at near 45 degree angle.

Example of a line with width 2.0 rendered at near 45 degree angle with FXAA at default settings:

FXAA

I’ve tried to play around some with the FXAA options based on the blog post at https://blog.kitware.com/new-fxaa-anti-aliasing-option-in-paraviewvtk/ , but haven’t been able to improve the result much.

Is there any way to do volume rendering + nice anti-aliased polygonal geometry (especially thin lines) that works reliably on Intel/NVIDIA cross platform?

1 Like

FXAA is bad at handling thin lines, but this looks especially wrong. Are you configuring the render window without an alpha channel? The “steps” of the anti-aliased line edge should be dropping off in opacity to give a smooth appearance, but they seem to be maintaining uniform opacity. What is the output of renWin->Print(std::cout); (or print(renWin) on python)?

If MSAA and FXAA aren’t working, there’s also vtkSSAAPass that does super-sampled anti-aliasing. It’s an expensive option, but might work better for you.

Hi Allison,

At some angles it looks OKish:

okayish

It’s certainly better than no AA:

no-aa

It’s just that at some angles it looks a bit ragged like in my first post.

The render window has an alpha channel for sure, because we’re using transparent polydatas. Here’s the printout:

vtkGenericOpenGLRenderWindow (0x1f5f0f0)
  Debug: Off
  Modified Time: 2961
  Reference Count: 3
  Registered Events: 
    Registered Observers:
      vtkObserver (0x1f2a6d0)
        Event: 91
        EventName: WindowIsCurrentEvent
        Command: 0x1f5ea30
        Priority: 0
        Tag: 2
      vtkObserver (0x1f8e060)
        Event: 92
        EventName: WindowFrameEvent
        Command: 0x1f5ea30
        Priority: 0
        Tag: 3
      vtkObserver (0x1f8e100)
        Event: 3
        EventName: StartEvent
        Command: 0x1f5ea30
        Priority: 0
        Tag: 4
      vtkObserver (0x1f8d780)
        Event: 8
        EventName: StartPickEvent
        Command: 0x1f5ea30
        Priority: 0
        Tag: 5
      vtkObserver (0x1f8e240)
        Event: 8
        EventName: StartPickEvent
        Command: 0x1f5ea30
        Priority: 0
        Tag: 6
      vtkObserver (0x1f2a700)
        Event: 51
        EventName: CursorChangedEvent
        Command: 0x1f5ea30
        Priority: 0
        Tag: 7
      vtkObserver (0x201fd70)
        Event: 90
        EventName: WindowMakeCurrentEvent
        Command: 0x1f5ea30
        Priority: 0
        Tag: 1
  Erase: On
  Window Name: Visualization Toolkit - OpenGL
  Position: (0, 0)
  Size: (0, 0)
  Mapped: 0
  OffScreenRendering: 0
  Double Buffered: 1
  DPI: 72
  TileScale: (1, 1)
  TileViewport: (0, 0, 1, 1)
  Borders: On
  IsPicking: Off
  Double Buffer: On
  Full Screen: Off
  Renderers:
    Debug: Off
    Modified Time: 2964
    Reference Count: 1
    Registered Events: (none)
    Number Of Items: 2
  Stereo Capable Window Requested: No
  Stereo Render: Off
  Point Smoothing: Off
  Line Smoothing: Off
  Polygon Smoothing: Off
  Abort Render: 0
  Current Cursor: 0
  Desired Update Rate: 0.0001
  In Abort Check: 0
  NeverRendered: 1
  Interactor: 0x1d4eaf0
  Swap Buffers: On
  Stereo Type: RedBlue
  Number of Layers: 2
  AccumulationBuffer Size 0
  AlphaBitPlanes: On
  UseSRGBColorSpace: Off
  AnaglyphColorSaturation: 0.65
  AnaglyphColorMask: 4 , 3
  MultiSamples: 8
  StencilCapable: False
  DefaultFrameBufferId: 0

Not sure why that printout says MultiSamples: 8 BTW. We have the following as the very first thing in our main:

    // Default to multi-sampling off.
    auto format = QVTKOpenGLWidget::defaultFormat();
    format.setSamples(0);
    QSurfaceFormat::setDefaultFormat(format);

And using e.g. 8 there, the volume rendering breaks on at least some Intel GPUs (like the one in my laptop).

We’re using VTK 8.2.0 and vtkOpenGLNativeWidget.

Perhaps it’s https://gitlab.kitware.com/vtk/vtk/issues/17154 we’re running into?

Applying this patch to the Line example to disable multisampling and enable FXAA, and set the line with to 2.0

--- Line.cxx.orig       2019-11-26 10:02:17.617513296 +0100
+++ Line.cxx    2019-11-26 10:00:10.440851355 +0100
@@ -30,13 +30,15 @@
   vtkSmartPointer<vtkActor> actor = 
     vtkSmartPointer<vtkActor>::New();
   actor->SetMapper(mapper);
-  actor->GetProperty()->SetLineWidth(4);
+  actor->GetProperty()->SetLineWidth(2);
   actor->GetProperty()->SetColor(colors->GetColor3d("Peacock").GetData());
 
   vtkSmartPointer<vtkRenderer> renderer = 
     vtkSmartPointer<vtkRenderer>::New();
+  renderer->SetUseFXAA(true);
   vtkSmartPointer<vtkRenderWindow> renderWindow = 
     vtkSmartPointer<vtkRenderWindow>::New();
+  renderWindow->SetMultiSamples(0);
   renderWindow->AddRenderer(renderer);
   renderWindow->SetWindowName("Line");
   vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =

Then rotating the camera a little:

line-example

With line width 3:

So it’s possible to reproduce with just plain VTK (no QVTKOpenGLNativeWidget).

Ok, taking a closer look with a screen magnifier, the opacity gradient is present, so it looks like things are working as expected.

We’ll have to just chalk this one up to “FXAA sucks at anti-aliasing lines.” Because of the way the algorithm works, the approximation errors are accentuated when a feature is just a few pixels wide.

Since it looks like these lines are being rendered as an overlay, you may try moving the actor that’s drawing the line to another renderer and configure that one with MSAA.

Ah alright. Yea I guess it’s just a limitation in the FXAA approach.

Hm, multisampling is a render window property though, isn’t it? So even if I moved these actors to a separate renderer, I’d need to activate multisampling on the render window (which breaks the volume rendering). Or is there a way to control multisampling per renderer? (on the bus with my phone, will check when I’m home)

Ack, you’re right. It’s been a while since I worked on our rendering :slight_smile:

You could use a separate renderer configured with a vtkSSAAPass on the lines, that should give a decent result.

@ken-martin may have some other ideas, perhaps there’s a way to enable MSAA on a single FBO that is later blended into the displayed render context?

Alright, will try a new renderer + vtkSSAAPass and see if the performance is OK. I only showed a single line here, but we’re going to do vector field visualization using vtkGlyph3DMapper and may have on the order of 10000 lines.

Will also take some syncing of camera positions since the lines are in the same 3D universe as the volume rendering.

Anyway, thanks for the tip!

Also, ideally these lines would be shown inside the volume, and not as an overlay on top, so this would be a compromise and it may turn out to look too strange.

(If you look closely at my very first screenshot, you’ll see that the line really is inside the volume, it is darkened in some spots by intermediate voxels)