Smooth (Gouraud) shading disappears in 8.2

I have an application working fine with python and VTK 8.1.2 (https://gitlab.com/Jellby/Pegamoid).

Now I wanted to try something else (TTK), and I installed a new conda environment with vtk 8.2. The application still works, but something happens with the normal interpolation applied to a contour filter. Sometimes it works as intended (Gouraud), sometimes it shows as Flat, and at least in some cases it is dependent on the isovalue level set for the contour. Example:

The only thing that changes is the “Value” slider: anything smaller than 5.3e-5 shows as Flat, anything larger than 5.57e-5 shows as Gouraud. For different objects the threshold is somewhere else.

Is this some kind of new parameter or feature in 8.2? Or is it a bug in 8.2, in my conda installation, or my application?

(By the way, even though I explicitly set “Gouraud”, I have the feeling what I actually see is Phong, see Is there any difference between Gouraud and Phong?)

Sounds like a bug, but I have no clear idea where in the software stack this might be. You might try exporting the suspect mesh in both good and bad forms and coming up with a minimal vtk level (cxx or python doesn’t matter) program the exhibits the difference. This would isolate the problem enough so that a bug report on the issue tracker would be tractable.

hth

Maybe someone can double-check, before I create a bug report, in case I’m doing something wrong or stupid. The script is very simple, and the “orb4.vti” file can be found (temporarily) at https://filebin.net/sns8xwp3h8k20cff/orb4.vti The problem seems like vtkAppendPolyData losing normals when it combines two sources. With value=5e-4 there’s only one source (pos is empty) and it looks smooth, with value=5e-5 there are two sources and it looks faceted, but if I comment out pos, it looks smooth again. With vtk 8.1.2 it always looks smooth.

#!/usr/bin/env python

import vtk

# create a rendering window and renderer
ren = vtk.vtkRenderer()
renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(ren)

# create a renderwindowinteractor
iren = vtk.vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)

# read the data
reader = vtk.vtkXMLImageDataReader()
reader.SetFileName('orb4.vti')

# create contours
# try 5e-5, 5e-4
value = 5e-5
contour = vtk.vtkContourFilter()
contour.SetInputConnection(reader.GetOutputPort())
contour.SetNumberOfContours(2)
contour.SetValue(0, -value)
contour.SetValue(1, value)
# patchwork with positive and negative parts
pos = vtk.vtkClipPolyData()
pos.SetInputConnection(contour.GetOutputPort())
neg = vtk.vtkClipPolyData()
neg.InsideOutOn()
neg.SetInputConnection(contour.GetOutputPort())
rvneg = vtk.vtkReverseSense()
rvneg.SetInputConnection(neg.GetOutputPort())
rvneg.ReverseCellsOn()
rvneg.ReverseNormalsOn()
tot = vtk.vtkAppendPolyData()
tot.AddInputConnection(pos.GetOutputPort())
tot.AddInputConnection(rvneg.GetOutputPort())

# mapper and actor
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(tot.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetDiffuseColor(1,1,1)
actor.GetProperty().SetDiffuse(1)
actor.GetProperty().SetSpecular(1)
actor.GetProperty().SetSpecularPower(30.0)
actor.GetProperty().SetInterpolationToGouraud()
#actor.GetProperty().SetInterpolationToPhong()

# assign actor to the renderer
ren.AddActor(actor)

light = vtk.vtkLight()
light.SetPosition(10, 10, 10)
light.SetColor(1.0, 1.0, 1.0)
light.SetLightTypeToCameraLight()
ren.AddLight(light)

# enable user interface interactor
print(vtk.vtkVersion.GetVTKVersion())
iren.Initialize()
iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
renWin.Render()
iren.Start()

I don’t fully understand what’s going on with the two contours, turning them inside out, and merging them - but if I apply a vtkPolyDataNormal on the polydata before setting it in the mapper then it seems to work well (blobs appear smooth).

What is it you don’t understand, the reason for doing it or the code under the hood? As for the reason, without that the wireframe representation looks inside out, as can be seen with:

mapper.SetInputConnection(contour.GetOutputPort())
actor.GetProperty().SetRepresentation(1)

And vtkPolyDataNormals helps (left), but it’s nowhere as good as the original (right), I guess because the original computes the normals from the volume data, while the filter can only compute the normals from the triangles in the contour. Or am I missing something?
Screenshot

I just mean that the process of creating the polydata is not trivial and I did not have the time to understand well enough to assess if it might violate some of VTK’s assumptions. The fact that vtkPolyDataNormals restores smooth rendering indicates that the normal “got lost” at some point in the pipeline. By inspecting normal array at each filter output you should be able to find out which filter does not behave well.

To improve performance, default precision was changed in some filters from double to single. There is a slight chance that if you force filters to use double precision then it will work better.

How would I do that?

Depends on the filter. First you need to find out which filter swallows the normal vectors.

OK, vtkAppendPolyData discards the normals, but that’s (probably) because vtkReverseSense discards the array name. In 8.1.2 the array it kept even if it has different name in the two inputs, in 8.2 it isn’t.

8.1.2
** pos
Values
Normals
** rvneg
Values
None
** tot
Values
None

8.2.0
** pos
Values
Normals
** rvneg
Values
None
** tot
Values

So… quick and dirty fix:

rvneg.Update()
for i in range(rvneg.GetOutput().GetPointData().GetNumberOfArrays()):
  rvneg.GetOutput().GetPointData().GetArray(i).SetName(neg.GetOutput().GetPointData().GetArrayName(i))
1 Like

Note that the above fix only works for static data, as soon as something changes upstream (e.g. the contour value), the vtkReverseSense filter recreates the normals array and removes its name again. Since I couldn’t find a way to rename arrays “on the fly”, I had to create a programmable filter:

fix_normals = vtk.vtkProgrammableFilter()
def rename_array():
  input = fix_normals.GetInput()
  output = fix_normals.GetOutput()
  output.ShallowCopy(input)
  output.GetPointData().GetArray(1).SetName('Normals')
fix_normals.SetExecuteMethod(rename_array)
...
#tot.AddInputConnection(rvneg.GetOutputPort())
fix_normals.SetInputConnection(rvneg.GetOutputPort())
tot.AddInputConnection(fix_normals.GetOutputPort())

Do you think something should be changed in VTK?

Append filter behavior looks reasonable to me. Maybe reverse sense filter should be changed to preserve array names?

Yes, I definitely think vtkReverseSense should keep array names. I created an issue (https://gitlab.kitware.com/vtk/vtk/issues/17715)

2 Likes

And merge request that has been merged to master, even better! Thank you, @Jellby!

Now I’m a VTK author! I want a share in the profits! :grin: :stuck_out_tongue: