How to update 2d-shader coordinates when camera moves I'm drawing some lines using a 2D_UNIFORM_COLOR shader based on object location. When I rotate my view, the lines update only when the rotation is done: I thought that I can just put a function that updates my coordinates in modal() but this didn't work. So I wonder if there's a way to update the regions while the view is rotated?

P.S. I realise that a 3d shader would work better for drawing axes, but axes here is just an example
My code:
import bpy
from mathutils import Vector
import bgl
import gpu
from gpu_extras.batch import batch_for_shader
from bpy_extras.view3d_utils import location_3d_to_region_2d

def draw_callback_px(self, context):

    # do I really have to create a separate shader for each line? Ugh

    shader_X = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    shader_Y = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    shader_Z = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glLineWidth(2)
    
    v_x = (self.axes[0] - self.ob_location).normalized()
    v_y = (self.axes[1] - self.ob_location).normalized()
    v_z = (self.axes[2] - self.ob_location).normalized()
    batch_X = batch_for_shader(shader_X, 'LINE_STRIP', {&quot;pos&quot;: [self.ob_location, self.ob_location + v_x*100]})
    batch_Y = batch_for_shader(shader_Y, 'LINE_STRIP', {&quot;pos&quot;: [self.ob_location, self.ob_location + v_y*100]})
    batch_Z = batch_for_shader(shader_Z, 'LINE_STRIP', {&quot;pos&quot;: [self.ob_location, self.ob_location + v_z*100]})

    shader_X.bind()
    shader_X.uniform_float(&quot;color&quot;, (1, 0, 0, 0.2))
    batch_X.draw(shader_X)

    shader_Y.bind()
    shader_Y.uniform_float(&quot;color&quot;, (0, 1, 0, 0.2))
    batch_Y.draw(shader_Y)

    shader_Z.bind()
    shader_Z.uniform_float(&quot;color&quot;, (0, 0, 1, 0.2))
    batch_Z.draw(shader_Z)

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)

class ModalOperator(bpy.types.Operator):
    &quot;&quot;&quot;Move an object with the mouse, example&quot;&quot;&quot;
    bl_idname = &quot;object.modal_operator_test&quot;
    bl_label = &quot;Simple Modal Operator Test&quot;

    def modal(self, context, event):
        context.area.tag_redraw()
        
        self.update_region(context, event)
        
        # Navigation
        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'} or event.alt:        
            return {'PASS_THROUGH'}

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.ob = bpy.context.view_layer.objects.active
        if not self.ob:
            return {'CANCELLED'}
        self.update_region(context, event)

        args = (self, context)
        self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}
    
    def update_region(self, context, event):
        self.region = context.region
        self.rv3d = context.space_data.region_3d
        self.ob_location = location_3d_to_region_2d(self.region, self.rv3d, self.ob.location, default=None)
        self.axes = [location_3d_to_region_2d(self.region, self.rv3d, self.ob.location + Vector((1,0,0)), default=None),
                     location_3d_to_region_2d(self.region, self.rv3d, self.ob.location + Vector((0,1,0)), default=None),
                     location_3d_to_region_2d(self.region, self.rv3d, self.ob.location + Vector((0,0,1)), default=None)]
        
def register():
    bpy.utils.register_class(ModalOperator)


def unregister():
    bpy.utils.unregister_class(ModalOperator)

if __name__ == &quot;__main__&quot;:
    register()
Edit: as I wrote all that is below, I just realized the question was explicitly about 2D shader...
You just missed to call
self.update_region(context, None)

from inside the callback, which is itself called at each drawing.
Though, as it is the calculation is not correct. You should use object world matrix (see the previous answer).

Previous answer (I don't delete it as that could be useful):
As you want to move during the operator, you are in 3d, use:
gpu.shader.from_builtin('3D_UNIFORM_COLOR')

instead of:
gpu.shader.from_builtin('2D_UNIFORM_COLOR')

Also, in this case, use POST_VIEW, as indicated in the documentation.

draw_type (str) – Usually POST_PIXEL for 2D drawing and POST_VIEW for
3D drawing. In some cases PRE_VIEW can be used. BACKDROP can be used
for backdrops in the node editor.

self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_VIEW')

One shader is needed.
You can start from object axis and convert them in world space:
def draw_callback_px(self, context):

    obj = context.active_object
    mat = obj.matrix_world

    shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
    shader.bind()

    bgl.glEnable(bgl.GL_BLEND)
    bgl.glLineWidth(2)

    origin = mat @ Vector((0, 0, 0)) #Convert coordinates in world space
    x = mat @ Vector((2,0,0))
    y = mat @ Vector((0,2,0))
    z = mat @ Vector((0,0,2))
    batch_X = batch_for_shader(shader, 'LINES', {&quot;pos&quot;: [origin, x]}, indices = ((0, 1),) )
    batch_Y = batch_for_shader(shader, 'LINES', {&quot;pos&quot;: [origin, y]}, indices = ((0, 1),) )
    batch_Z = batch_for_shader(shader, 'LINES', {&quot;pos&quot;: [origin, z]}, indices = ((0, 1),) )

    shader.uniform_float(&quot;color&quot;, (1, 0, 0, 0.2))
    batch_X.draw(shader)

    shader.uniform_float(&quot;color&quot;, (0, 1, 0, 0.2))
    batch_Y.draw(shader)

    shader.uniform_float(&quot;color&quot;, (0, 0, 1, 0.2))
    batch_Z.draw(shader)

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)