We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Incorrect behavior with multipe clipping planes 
I was checking up on that nice trick where the clipping planes logic is done in the vertex shader and then interpolated to the fragment shader, with the intention of applying it in pygfx too. However, I found that this trick does not work in the case of multiple clipping planes.

This can be shown with the following example:
```py
import numpy as np
from vispy import app, scene, io
from vispy.visuals.filters.clipping_planes import PlanesClipper

canvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True)
view = canvas.central_widget.add_view()

cube = scene.visuals.Box(100, 100, 100, color=(1, 0, 0, 1), parent=view.scene)

view.camera = scene.cameras.TurntableCamera(parent=view.scene, fov=60)

clip_center = (0, 20, 60)
clipping_planes = np.concatenate(
    [ np.array([[clip_center, [1, 0, 0]]]), np.array([[clip_center, [0, 1, 0]]])]
)

clipper = PlanesClipper()
clipper.clipping_planes = clipping_planes
cube.attach(clipper)

if __name__ == '__main__':
    app.run()
```

If you turn the camera to look from above, you'll see this:

![image](https://user-images.githubusercontent.com/3015475/135463433-c3a23bf3-842b-4982-ae78-7f0a3634300b.png)


I think this can be explained with the following figure:

![image](https://user-images.githubusercontent.com/3015475/135464082-98220a3e-7177-4a7e-9b18-cad18c8b67d1.png)

The black lines indicate two clipping planes (the shaded side is where they clip). The two blue dots represent two vertices with a line or polygon interpolating between them. Both dots are of equal distance two a plane, one on the + side and one on the - side. Now if the `min_plane_distance` (or whatever we ended up calling it :D ) is interpolated, it will have its zero point (the point where it starts clipping) in the middle.

cc @brisvag 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `vispy/visuals/filters/clipping_planes.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # Copyright (c) Vispy Development Team. All Rights Reserved.
3 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
4 
5 from functools import lru_cache
6 
7 import numpy as np
8 
9 from ..shaders import Function, Varying
10 from .base_filter import Filter
11 
12 
13 class PlanesClipper(Filter):
14     """Clips visual output based on arbitrary clipping planes.
15 
16     Parameters
17     ----------
18     cliping_planes : ArrayLike
19         Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)
20     coord_system : str
21         Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)
22 
23     """
24 
25     VERT_CODE = """
26     void clip() {
27         // Transform back to visual coordinates and clip based on that
28         $v_distance_from_clip = $clip_with_planes($itransform(gl_Position).xyz);
29     }
30     """
31 
32     FRAG_CODE = """
33     void clip() {
34         if ($v_distance_from_clip < 0.)
35             discard;
36     }
37     """
38 
39     def __init__(self, clipping_planes=None, coord_system='scene'):
40         tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']
41         if coord_system not in tr:
42             raise ValueError(f'Invalid coordinate system {coord_system}. Must be one of {tr}.')
43         self._coord_system = coord_system
44 
45         super().__init__(
46             vcode=Function(self.VERT_CODE), vhook='post', vpos=1,
47             fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,
48         )
49 
50         v_distance_from_clip = Varying('v_distance_from_clip', 'float')
51         self.vshader['v_distance_from_clip'] = v_distance_from_clip
52         self.fshader['v_distance_from_clip'] = v_distance_from_clip
53 
54         self.clipping_planes = clipping_planes
55 
56     @property
57     def coord_system(self):
58         """
59         Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)
60         """
61         # unsettable cause we can't update the transform after being attached
62         return self._coord_system
63 
64     def _attach(self, visual):
65         super()._attach(visual)
66         self.vshader['itransform'] = visual.get_transform('render', self._coord_system)
67 
68     @staticmethod
69     @lru_cache(maxsize=10)
70     def _build_clipping_planes_func(n_planes):
71         """Build the code snippet used to clip the volume based on self.clipping_planes."""
72         func_template = '''
73             float clip_planes(vec3 loc) {{
74                 float distance_from_clip = 3.4e38; // max float
75                 {clips};
76                 return distance_from_clip;
77             }}
78         '''
79         # the vertex is considered clipped if on the "negative" side of the plane
80         clip_template = '''
81             vec3 relative_vec{idx} = loc - $clipping_plane_pos{idx};
82             float distance_from_clip{idx} = dot(relative_vec{idx}, $clipping_plane_norm{idx});
83             distance_from_clip = min(distance_from_clip{idx}, distance_from_clip);
84             '''
85         all_clips = []
86         for idx in range(n_planes):
87             all_clips.append(clip_template.format(idx=idx))
88         formatted_code = func_template.format(clips=''.join(all_clips))
89         return Function(formatted_code)
90 
91     @property
92     def clipping_planes(self):
93         """Get the set of planes used to clip the mesh.
94         Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)
95         """
96         return self._clipping_planes
97 
98     @clipping_planes.setter
99     def clipping_planes(self, value):
100         if value is None:
101             value = np.empty([0, 2, 3])
102         self._clipping_planes = value
103 
104         clip_func = self._build_clipping_planes_func(len(value))
105         self.vshader['clip_with_planes'] = clip_func
106 
107         for idx, plane in enumerate(value):
108             clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])
109             clip_func[f'clipping_plane_norm{idx}'] = tuple(plane[1])
110
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/vispy/visuals/filters/clipping_planes.py b/vispy/visuals/filters/clipping_planes.py
--- a/vispy/visuals/filters/clipping_planes.py
+++ b/vispy/visuals/filters/clipping_planes.py
@@ -24,14 +24,15 @@
 
     VERT_CODE = """
     void clip() {
-        // Transform back to visual coordinates and clip based on that
-        $v_distance_from_clip = $clip_with_planes($itransform(gl_Position).xyz);
+        // pass position as varying for interpolation
+        $v_position = gl_Position;
     }
     """
 
     FRAG_CODE = """
     void clip() {
-        if ($v_distance_from_clip < 0.)
+        float distance_from_clip = $clip_with_planes($itransform($v_position).xyz);
+        if (distance_from_clip < 0.)
             discard;
     }
     """
@@ -47,9 +48,9 @@
             fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,
         )
 
-        v_distance_from_clip = Varying('v_distance_from_clip', 'float')
-        self.vshader['v_distance_from_clip'] = v_distance_from_clip
-        self.fshader['v_distance_from_clip'] = v_distance_from_clip
+        v_position = Varying('v_position', 'vec4')
+        self.vshader['v_position'] = v_position
+        self.fshader['v_position'] = v_position
 
         self.clipping_planes = clipping_planes
 
@@ -63,7 +64,7 @@
 
     def _attach(self, visual):
         super()._attach(visual)
-        self.vshader['itransform'] = visual.get_transform('render', self._coord_system)
+        self.fshader['itransform'] = visual.get_transform('render', self._coord_system)
 
     @staticmethod
     @lru_cache(maxsize=10)
@@ -102,7 +103,7 @@
         self._clipping_planes = value
 
         clip_func = self._build_clipping_planes_func(len(value))
-        self.vshader['clip_with_planes'] = clip_func
+        self.fshader['clip_with_planes'] = clip_func
 
         for idx, plane in enumerate(value):
             clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])

{"golden_diff": "diff --git a/vispy/visuals/filters/clipping_planes.py b/vispy/visuals/filters/clipping_planes.py\n--- a/vispy/visuals/filters/clipping_planes.py\n+++ b/vispy/visuals/filters/clipping_planes.py\n@@ -24,14 +24,15 @@\n \n     VERT_CODE = \"\"\"\n     void clip() {\n-        // Transform back to visual coordinates and clip based on that\n-        $v_distance_from_clip = $clip_with_planes($itransform(gl_Position).xyz);\n+        // pass position as varying for interpolation\n+        $v_position = gl_Position;\n     }\n     \"\"\"\n \n     FRAG_CODE = \"\"\"\n     void clip() {\n-        if ($v_distance_from_clip < 0.)\n+        float distance_from_clip = $clip_with_planes($itransform($v_position).xyz);\n+        if (distance_from_clip < 0.)\n             discard;\n     }\n     \"\"\"\n@@ -47,9 +48,9 @@\n             fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,\n         )\n \n-        v_distance_from_clip = Varying('v_distance_from_clip', 'float')\n-        self.vshader['v_distance_from_clip'] = v_distance_from_clip\n-        self.fshader['v_distance_from_clip'] = v_distance_from_clip\n+        v_position = Varying('v_position', 'vec4')\n+        self.vshader['v_position'] = v_position\n+        self.fshader['v_position'] = v_position\n \n         self.clipping_planes = clipping_planes\n \n@@ -63,7 +64,7 @@\n \n     def _attach(self, visual):\n         super()._attach(visual)\n-        self.vshader['itransform'] = visual.get_transform('render', self._coord_system)\n+        self.fshader['itransform'] = visual.get_transform('render', self._coord_system)\n \n     @staticmethod\n     @lru_cache(maxsize=10)\n@@ -102,7 +103,7 @@\n         self._clipping_planes = value\n \n         clip_func = self._build_clipping_planes_func(len(value))\n-        self.vshader['clip_with_planes'] = clip_func\n+        self.fshader['clip_with_planes'] = clip_func\n \n         for idx, plane in enumerate(value):\n             clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])\n", "issue": "Incorrect behavior with multipe clipping planes \nI was checking up on that nice trick where the clipping planes logic is done in the vertex shader and then interpolated to the fragment shader, with the intention of applying it in pygfx too. However, I found that this trick does not work in the case of multiple clipping planes.\r\n\r\nThis can be shown with the following example:\r\n```py\r\nimport numpy as np\r\nfrom vispy import app, scene, io\r\nfrom vispy.visuals.filters.clipping_planes import PlanesClipper\r\n\r\ncanvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True)\r\nview = canvas.central_widget.add_view()\r\n\r\ncube = scene.visuals.Box(100, 100, 100, color=(1, 0, 0, 1), parent=view.scene)\r\n\r\nview.camera = scene.cameras.TurntableCamera(parent=view.scene, fov=60)\r\n\r\nclip_center = (0, 20, 60)\r\nclipping_planes = np.concatenate(\r\n    [ np.array([[clip_center, [1, 0, 0]]]), np.array([[clip_center, [0, 1, 0]]])]\r\n)\r\n\r\nclipper = PlanesClipper()\r\nclipper.clipping_planes = clipping_planes\r\ncube.attach(clipper)\r\n\r\nif __name__ == '__main__':\r\n    app.run()\r\n```\r\n\r\nIf you turn the camera to look from above, you'll see this:\r\n\r\n![image](https://user-images.githubusercontent.com/3015475/135463433-c3a23bf3-842b-4982-ae78-7f0a3634300b.png)\r\n\r\n\r\nI think this can be explained with the following figure:\r\n\r\n![image](https://user-images.githubusercontent.com/3015475/135464082-98220a3e-7177-4a7e-9b18-cad18c8b67d1.png)\r\n\r\nThe black lines indicate two clipping planes (the shaded side is where they clip). The two blue dots represent two vertices with a line or polygon interpolating between them. Both dots are of equal distance two a plane, one on the + side and one on the - side. Now if the `min_plane_distance` (or whatever we ended up calling it :D ) is interpolated, it will have its zero point (the point where it starts clipping) in the middle.\r\n\r\ncc @brisvag \n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) Vispy Development Team. All Rights Reserved.\n# Distributed under the (new) BSD License. See LICENSE.txt for more info.\n\nfrom functools import lru_cache\n\nimport numpy as np\n\nfrom ..shaders import Function, Varying\nfrom .base_filter import Filter\n\n\nclass PlanesClipper(Filter):\n    \"\"\"Clips visual output based on arbitrary clipping planes.\n\n    Parameters\n    ----------\n    cliping_planes : ArrayLike\n        Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)\n    coord_system : str\n        Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)\n\n    \"\"\"\n\n    VERT_CODE = \"\"\"\n    void clip() {\n        // Transform back to visual coordinates and clip based on that\n        $v_distance_from_clip = $clip_with_planes($itransform(gl_Position).xyz);\n    }\n    \"\"\"\n\n    FRAG_CODE = \"\"\"\n    void clip() {\n        if ($v_distance_from_clip < 0.)\n            discard;\n    }\n    \"\"\"\n\n    def __init__(self, clipping_planes=None, coord_system='scene'):\n        tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']\n        if coord_system not in tr:\n            raise ValueError(f'Invalid coordinate system {coord_system}. Must be one of {tr}.')\n        self._coord_system = coord_system\n\n        super().__init__(\n            vcode=Function(self.VERT_CODE), vhook='post', vpos=1,\n            fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,\n        )\n\n        v_distance_from_clip = Varying('v_distance_from_clip', 'float')\n        self.vshader['v_distance_from_clip'] = v_distance_from_clip\n        self.fshader['v_distance_from_clip'] = v_distance_from_clip\n\n        self.clipping_planes = clipping_planes\n\n    @property\n    def coord_system(self):\n        \"\"\"\n        Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)\n        \"\"\"\n        # unsettable cause we can't update the transform after being attached\n        return self._coord_system\n\n    def _attach(self, visual):\n        super()._attach(visual)\n        self.vshader['itransform'] = visual.get_transform('render', self._coord_system)\n\n    @staticmethod\n    @lru_cache(maxsize=10)\n    def _build_clipping_planes_func(n_planes):\n        \"\"\"Build the code snippet used to clip the volume based on self.clipping_planes.\"\"\"\n        func_template = '''\n            float clip_planes(vec3 loc) {{\n                float distance_from_clip = 3.4e38; // max float\n                {clips};\n                return distance_from_clip;\n            }}\n        '''\n        # the vertex is considered clipped if on the \"negative\" side of the plane\n        clip_template = '''\n            vec3 relative_vec{idx} = loc - $clipping_plane_pos{idx};\n            float distance_from_clip{idx} = dot(relative_vec{idx}, $clipping_plane_norm{idx});\n            distance_from_clip = min(distance_from_clip{idx}, distance_from_clip);\n            '''\n        all_clips = []\n        for idx in range(n_planes):\n            all_clips.append(clip_template.format(idx=idx))\n        formatted_code = func_template.format(clips=''.join(all_clips))\n        return Function(formatted_code)\n\n    @property\n    def clipping_planes(self):\n        \"\"\"Get the set of planes used to clip the mesh.\n        Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)\n        \"\"\"\n        return self._clipping_planes\n\n    @clipping_planes.setter\n    def clipping_planes(self, value):\n        if value is None:\n            value = np.empty([0, 2, 3])\n        self._clipping_planes = value\n\n        clip_func = self._build_clipping_planes_func(len(value))\n        self.vshader['clip_with_planes'] = clip_func\n\n        for idx, plane in enumerate(value):\n            clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])\n            clip_func[f'clipping_plane_norm{idx}'] = tuple(plane[1])\n", "path": "vispy/visuals/filters/clipping_planes.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) Vispy Development Team. All Rights Reserved.\n# Distributed under the (new) BSD License. See LICENSE.txt for more info.\n\nfrom functools import lru_cache\n\nimport numpy as np\n\nfrom ..shaders import Function, Varying\nfrom .base_filter import Filter\n\n\nclass PlanesClipper(Filter):\n    \"\"\"Clips visual output based on arbitrary clipping planes.\n\n    Parameters\n    ----------\n    cliping_planes : ArrayLike\n        Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)\n    coord_system : str\n        Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)\n\n    \"\"\"\n\n    VERT_CODE = \"\"\"\n    void clip() {\n        // pass position as varying for interpolation\n        $v_position = gl_Position;\n    }\n    \"\"\"\n\n    FRAG_CODE = \"\"\"\n    void clip() {\n        float distance_from_clip = $clip_with_planes($itransform($v_position).xyz);\n        if (distance_from_clip < 0.)\n            discard;\n    }\n    \"\"\"\n\n    def __init__(self, clipping_planes=None, coord_system='scene'):\n        tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']\n        if coord_system not in tr:\n            raise ValueError(f'Invalid coordinate system {coord_system}. Must be one of {tr}.')\n        self._coord_system = coord_system\n\n        super().__init__(\n            vcode=Function(self.VERT_CODE), vhook='post', vpos=1,\n            fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,\n        )\n\n        v_position = Varying('v_position', 'vec4')\n        self.vshader['v_position'] = v_position\n        self.fshader['v_position'] = v_position\n\n        self.clipping_planes = clipping_planes\n\n    @property\n    def coord_system(self):\n        \"\"\"\n        Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)\n        \"\"\"\n        # unsettable cause we can't update the transform after being attached\n        return self._coord_system\n\n    def _attach(self, visual):\n        super()._attach(visual)\n        self.fshader['itransform'] = visual.get_transform('render', self._coord_system)\n\n    @staticmethod\n    @lru_cache(maxsize=10)\n    def _build_clipping_planes_func(n_planes):\n        \"\"\"Build the code snippet used to clip the volume based on self.clipping_planes.\"\"\"\n        func_template = '''\n            float clip_planes(vec3 loc) {{\n                float distance_from_clip = 3.4e38; // max float\n                {clips};\n                return distance_from_clip;\n            }}\n        '''\n        # the vertex is considered clipped if on the \"negative\" side of the plane\n        clip_template = '''\n            vec3 relative_vec{idx} = loc - $clipping_plane_pos{idx};\n            float distance_from_clip{idx} = dot(relative_vec{idx}, $clipping_plane_norm{idx});\n            distance_from_clip = min(distance_from_clip{idx}, distance_from_clip);\n            '''\n        all_clips = []\n        for idx in range(n_planes):\n            all_clips.append(clip_template.format(idx=idx))\n        formatted_code = func_template.format(clips=''.join(all_clips))\n        return Function(formatted_code)\n\n    @property\n    def clipping_planes(self):\n        \"\"\"Get the set of planes used to clip the mesh.\n        Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)\n        \"\"\"\n        return self._clipping_planes\n\n    @clipping_planes.setter\n    def clipping_planes(self, value):\n        if value is None:\n            value = np.empty([0, 2, 3])\n        self._clipping_planes = value\n\n        clip_func = self._build_clipping_planes_func(len(value))\n        self.fshader['clip_with_planes'] = clip_func\n\n        for idx, plane in enumerate(value):\n            clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])\n            clip_func[f'clipping_plane_norm{idx}'] = tuple(plane[1])\n", "path": "vispy/visuals/filters/clipping_planes.py"}]}