Visualizing a 3D volume in Makie: Lighting, smoothness

In plotting discrete 3D arrays using Makie, how can one modify the lighting and smoothness properties?

Here is an example of a thick, extruded cross geometry constructed as a binary 3D array. (Figure on left) The lighting on the horizontal slab, the vertical slab, and along the slab thickness is very different. How can this be made more consistent?

(Figure on right) There is also some surface roughness, although this is only obvious when zooming in and less of an issue.

I am plotting these binary 3D arrays with the volume call and isovalue = 1.0 property.

I notice that when plotting analytical 3D data, the lighting and surface roughness issues are gone. Below is an example of a Schwarz “G” surface, also plotted with the volume and isovalue calls, similar to the Makie gallery example here.

I am open to using Plots or other packages, but I’ve not found a better solution than Makie.



The roughness is almost unavoidable - or you can just trade it in with other artifacts.
You can also try to visualize it as a mesh, which should have sharp edges:

To make the lighting smoother, you may want to change the light position (by the keyword light = Vec3f0(1, 2, 3)) or disable shading entirely (shading = false).

shading changes nothing in my volume plot, perhaps because it is not a mesh.

Is the light keyword part of the volume call or something else? I input several different values for the light vector and it produces no change.

I’ve added an example in the latest docs for direct function meshing.

If you want smooth normals you may want to use MarchingTetrahedra() rather than MarchingCubes(). I haven’t tested Meshing on Boolean arrays, so I am not sure what the expected behavior should be, but it should still work.

The issue with visualizing a mesh is missing ends or “isocaps”.

This results from meshing a binary array (0’s and 1’s) and using iso = 0.5 in the MarchingTetrahedra call.


Here is a hackish example which gives Isocaps:

using Meshing
using GeometryTypes

gyroid(v) = cos(v[1])*sin(v[2])+cos(v[2])*sin(v[3])+cos(v[3])*sin(v[1])
gyroid_shell(v) = max(gyroid(v)-0.4,-gyroid(v)-0.4)

xr,yr,zr = ntuple(_->LinRange(0,pi*4,50),3)

A = [gyroid_shell((x,y,z)) for x in xr, y in yr, z in zr]
A[1,:,:] .= 1e10
A[:,1,:] .= 1e10
A[:,:,1] .= 1e10
A[end,:,:] .= 1e10
A[:,end,:] .= 1e10
A[:,:,end] .= 1e10

gy_mesh = GLNormalMesh(A, MarchingCubes())

# view with Makie
import Makie
using LinearAlgebra
Makie.mesh(gy_mesh, color=[norm(v) for v in gy_mesh.vertices])


Last time I tried this kind of thing I found that interpolating the underlying isosurface to generate the normals using Interpolations.gradient worked better than relying on the normals generated by GLNormalMesh (which are based on the intermediate mesh).

For code, see the example I posted here:

(code available at Makie.jl based visualization of 3D 1/f noise · GitHub)

1 Like