Repository: vispy/vispy

Issue:
Text isn't occluded properly
When using the Text visual in a 3D scene graph, occlusion of text isn't handled properly. 
All visuals that were created after the text was added to the scene will "occlude" the text even if the text is in front.
All visuals that were created before the text cannot occlude the text and the text remains visible regardless.

It seems that the text is not properly incorporated into the 3D scene and instead it is just painted over.

I created a demo scene to illustrate the issue. Note how the numbers are always visible through the green plane, even when the numbers are behind the green plane. The red plane always hides the numbers, even when numbers are in front of the plane.

``` python
import numpy as np
import vispy
import vispy.scene

canvas = vispy.scene.SceneCanvas(keys='interactive', bgcolor=(1,1,1))
canvas.show()

view = canvas.central_widget.add_view()
view.camera = vispy.scene.TurntableCamera(up='z', fov=60)

view.camera.set_range(x=(-5, 5), y=(-5, 5), z=(-5, 5))

x_coords = np.array((-5,5))
y_coords = np.array((-5,5))
z_data = np.array([(3,3),(3,3)])

item = vispy.scene.visuals.SurfacePlot(x=x_coords, y=y_coords, z=z_data, color=(0, 1, 0, 1))
view.add(item)

for i in range(-5, 5):
    item = vispy.scene.Text(text="%i" % i, color='black', bold=True, font_size=200, pos=(0,0,i))
    view.add(item)

item = vispy.scene.visuals.SurfacePlot(x=x_coords, y=y_coords, z=-z_data, color=(1, 0, 0, 1))
view.add(item)

vispy.app.run()
```



Discussion:
@djhoese 
This is a drawing order issue. It can be worked around by forcing the drawing order with `.order` on each of the visuals. Here is the result:

![image](https://user-images.githubusercontent.com/1828519/142741570-4cb21a0f-3293-49d9-8d53-027aecfde827.png)

When I set the first surface to `.order = 3`, the text to `.order = i`, and the last surface to `.order = -3`.
Given the age of this issue I'm going to close this assuming that I probably won't get a response and that I provided a reasonable workaround.
@djhoese you still get a response. Even though I'm not using vispy anymore in my current projects.

The workaround wouldn't have helped though. Because the issue remains that the text occlusion isn't calculated correctly. All you've done is to manually work out the occlusion for this particular scene and camera angle. In the example posted here, if you start moving the camera to a new position, e.g. looking from below the red plane, it will be all wrong again.

The app I was working on at the time had vehicles driving around and their name was hovering above each vehicle. You could then see the name of vehicles even when they where behind a mountain or similar which was rather odd and ugly. Working out the occlusion manually in such a setting would have been about impossible.

But like I said, I'm no longer working on this so you can leave the issue closed - unless you actually want to solve the occlusion issue properly. :-)
Ugh you are absolutely right. I thought I had checked looking from the red surface. We definitely want to make this easier or just fix it all around, but there are also other similar issues. I think I'll reopen this as it is a pretty simple and clear example compared to some of the others I've seen.
Could it be as simple that the text visual is not writing to the depth buffer?
Very likely. I don't have much experience with the text visual or writing to the gl_FragDepth. @almarklein any chance you have a little time to throw together a PR?
Probably not this week. I'll keep the notification open in my mailbox, so I can see if I can find some time next week or so. 
@djhoese 
Is there anyway we can display a 3D text in a view? TextVisual currently can only display 2D text, as far as I've seen! Please correct me if I'm wrong.
I (finally!) took some time to look into this. It's not that the depth is not set, but the depth-test is disabled. It looks like this is deliberate:
https://github.com/vispy/vispy/blob/f0af63083b25c27bd02ce3778bd9e62096c3ccd1/vispy/visuals/text/text.py#L433-L434

If I enable it, and add `if (alpha <= 0) discard;` right before [this line](https://github.com/vispy/vispy/blob/f0af63083b25c27bd02ce3778bd9e62096c3ccd1/vispy/visuals/text/text.py#L230) then I get this:

<img width="561" alt="image" src="https://user-images.githubusercontent.com/3015475/165965236-7e94e780-fee1-4cb7-8357-c33df1d76bd5.png">

This looks more like what (I think) was intended, except for the edges due to the inproper blending (yes, transparency is hard!). This effect can be removed by selecting a dark background.

I don't think it would be a good idea to simply turn on the depth test, since people might rely on it behaving like an overlay. My suggestion would be to make it optional. 


Resolving patch:
diff --git a/vispy/visuals/text/text.py b/vispy/visuals/text/text.py
--- a/vispy/visuals/text/text.py
+++ b/vispy/visuals/text/text.py
@@ -227,6 +227,8 @@ def get_font(self, face, bold=False, italic=False):
             alpha = (alpha + 0.5 * asum) / 3.0;
         }
 
+        if (alpha <= 0) discard;
+
         gl_FragColor = vec4(v_color.rgb, v_color.a * alpha);
     }
     """
@@ -395,6 +397,10 @@ class TextVisual(Visual):
         The 'gpu' method should produce higher quality results.
     font_manager : object | None
         Font manager to use (can be shared if the GLContext is shared).
+    depth_test : bool
+        Whether to apply depth testing. Default False. If False, the text
+        behaves like an overlay that does not get hidden behind other
+        visuals in the scene.
     """
 
     _shaders = {
@@ -405,7 +411,7 @@ class TextVisual(Visual):
     def __init__(self, text=None, color='black', bold=False,
                  italic=False, face='OpenSans', font_size=12, pos=[0, 0, 0],
                  rotation=0., anchor_x='center', anchor_y='center',
-                 method='cpu', font_manager=None):
+                 method='cpu', font_manager=None, depth_test=False):
         Visual.__init__(self, vcode=self._shaders['vertex'], fcode=self._shaders['fragment'])
         # Check input
         valid_keys = ('top', 'center', 'middle', 'baseline', 'bottom')
@@ -430,7 +436,7 @@ def __init__(self, text=None, color='black', bold=False,
         self.rotation = rotation
         self._text_scale = STTransform()
         self._draw_mode = 'triangles'
-        self.set_gl_state(blend=True, depth_test=False, cull_face=False,
+        self.set_gl_state(blend=True, depth_test=depth_test, cull_face=False,
                           blend_func=('src_alpha', 'one_minus_src_alpha'))
         self.freeze()
 


Test patch:
diff --git a/vispy/visuals/tests/test_text.py b/vispy/visuals/tests/test_text.py
--- a/vispy/visuals/tests/test_text.py
+++ b/vispy/visuals/tests/test_text.py
@@ -81,4 +81,15 @@ def test_face_bold_italic():
         assert font1 is font4
 
 
+def test_text_depth_test():
+    t = Text(depth_test=False)
+    assert not t._vshare.gl_state["depth_test"]
+
+    t = Text(depth_test=True)
+    assert t._vshare.gl_state["depth_test"]
+
+    t = Text()  # Default is false
+    assert not t._vshare.gl_state["depth_test"]
+
+
 run_tests_if_main()