K3D-tools / K3D-jupyter

K3D lets you create 3D plots backed by WebGL with high-level API (surfaces, isosurfaces, voxels, mesh, cloud points, vtk objects, volume renderer, colormaps, etc). The primary aim of K3D-jupyter is to be easy for use as stand alone package like matplotlib, but also to allow interoperation with existing libraries as VTK.
MIT License
921 stars 123 forks source link

Drawing a sphere #350

Closed nathanielvirgo closed 2 years ago

nathanielvirgo commented 2 years ago

This is partly an issue and partly a question. (I didn't see anywhere else to ask questions.)

I would like to draw a set of points on the surface of a sphere. I tried this code, using a k3d.points with a large point size to draw the sphere:

import numpy as np
import k3d

points = np.random.standard_normal((1000,3))
points = points / np.linalg.norm(points,axis=1)[:,None]

plot = k3d.plot()

plot += k3d.points([0, 0, 0],point_size = 2,color=0xeeeeee)
plot += k3d.points(points, point_size=0.01,color=0x000000)

plot.display()

However, this doesn't seem to work very well. If I zoom out the black points get absorbed inside the white sphere, while if I zoom in they seem to be above its surface.

This might be a bug, so I'm reporting it here, but it also might be that k3d.points isn't supposed to be used this way.

If setting point_size this big isn't supported, is there a canonical way to draw a sphere in k3d? Triangulating my own mesh seems like overkill, given that some perfectly nice sphere generating code exists already in k3d_points, so I'm wondering if there's another way to do it.

nathanielvirgo commented 2 years ago

Some other things to note on this:

  1. in the k3D panel in the plot, if I change the FOV it affects the point cloud, but it doesn't affect the size of the big white point. Presumably this is related to the mismatch in perspective that I noted above.

  2. in in k3D panel, if I change the shader of the white point to "mesh" then its perspective matches that of the point cloud, and its size is affected by the FOV setting. However, if I set it to "3D", "3D specular" or "flat" then the same problem happens.

Unfortunately setting it to "mesh" doesn't solve the problem for me, because the mesh is quite coarse and doesn't look great. So it looks like rolling my own sphere mesh is the only solution for now, assuming the other shaders can't be changed to resolve the issue.

artur-trzesiok commented 2 years ago

Hi @nathanielvirgo

k3d.points are highly-optimized versions for viewing like 1kk points in real-time. That's why by default K3D uses billboarding technique. Every point in fact is a 2d plane (2 triangles) dynamically oriented by vertex-shader perpendicular to camera. 3d-like feeling is achieved by custom-made pixel-shader. So a lot of tricks to be super-fast :)

Trade-off from that is an accuracy especially when we have a variety of sizes or intersections between points. For that case k3d has shader='mesh'. In that case, every point is a "proper mesh sphere". You can control detail of the approximation by mesh_detail option. Please look at code below:

import numpy as np
import k3d

points = np.random.standard_normal((1000,3))
points = points / np.linalg.norm(points,axis=1)[:,None]

plot = k3d.plot()

plot += k3d.points([0, 0, 0],point_size = 2,color=0xeeeeee, shader='mesh', mesh_detail=8)
plot += k3d.points(points, point_size=0.01,color=0x000000, shader='mesh')

plot.display()

That will produce:

image
nathanielvirgo commented 2 years ago

Thank you, that's great, really helpful!

After playing around with it a bit, I do think it should be considered a bug that the custom shaders don't respect the FOV setting. It means that even when drawing normal-sized points, their size changes inconsistently when changing the field of view.

For example, take this code, the same as yours except that the black points are drawn with the default shader:

import numpy as np
import k3d

points = np.random.standard_normal((1000,3))
points = points / np.linalg.norm(points,axis=1)[:,None]

plot = k3d.plot()

plot += k3d.points([0, 0, 0],point_size = 2,color=0xeeeeee, shader='mesh', mesh_detail=8)
plot += k3d.points(points, point_size=0.01,color=0x000000)

plot.display()

at the default FOV of 60 degrees it looks like this:

image

but if I set the FOV to 20 degrees and zoom out it looks like this:

image

Of course I can fix this by drawing the black points with the mesh shader as you did, but I can imagine it being awkward sometimes that the fast shader doesn't respect the FOV setting.

Thanks again for your help.

artur-trzesiok commented 2 years ago
artur-trzesiok commented 2 years ago

So preparation of new k3d is here https://github.com/K3D-tools/K3D-jupyter/pull/351

artur-trzesiok commented 2 years ago

Please download new version of k3d@2.14.0:

https://pypi.org/project/k3d/