2
$\begingroup$

Please, I would like to know the following:

My target is to have a color picking for the OpenGL shapes in the viewport using unique custom color for each generated object (If there is any other way please guide me) In the example the script (I assembled some scripts online to do this test) draws a simple 3d cube with shader color values(0.7585456, 0.5684187, 0, 1) - Line 41 but when I try to pick the color back using the color_read method I get these values [0.7529412508010864, 0.5647059082984924, 0.0, 1.0] which only matches the first two floats number but no exactly the long float which we passed as the shader color earlier.

Here is the code example

import bpy
import gpu
from gpu_extras.batch import batch_for_shader



indices = (
    (0, 1), (0, 2), (1, 3), (2, 3),
    (4, 5), (4, 6), (5, 7), (6, 7),
    (0, 4), (1, 5), (2, 6), (3, 7))
    
coords = (
    (-1, -1, -1), (+1, -1, -1),
    (-1, +1, -1), (+1, +1, -1),
    (-1, -1, +1), (+1, -1, +1),
    (-1, +1, +1), (+1, +1, +1))   
    
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')

# Draw function which copies data from the 3D View
def draw(self, context):
    gpu.state.line_width_set(5)

    # draw the geo data into the opengl shader
    batch = batch_for_shader(
                            shader=shader, 
                            type='LINES', 
                            content={
                                "pos": coords,

                                    },
                            indices=indices
                            )
                            
    gpu.state.depth_test_set('LESS_EQUAL')
    gpu.state.depth_mask_set(True)

    shader.bind()
    
    # here I set the shader color to a particular float values and later I was to picking this exact color from read_color method
    shader.uniform_float("color", (0.7585456, 0.5684187, 0, 1))


    batch.draw(shader)

    gpu.state.depth_mask_set(False)


# Modal operator for controlled redrawing of the image object
# NOTE: This code is only for a more conveniant testing of the draw function
#       If you want to stop the test, press 'ESC'

class ModalFramebufferCopy(bpy.types.Operator):
    bl_idname = "view3d.modal_framebuffer_copy"
    bl_label = "Draw 3D View Framebuffer"

    # modal operator for controlled redraw of the image
    def modal(self, context, event):
        # stop the execution of this example code if 'ESC' is pressed
        if event.type in {'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
            return {'CANCELLED'}


        elif event.type == 'LEFTMOUSE':
            self.framebuffer = gpu.state.active_framebuffer_get()
            self.framebuffer.clear()
            self.framebuffer.bind()
            
            # get information on current viewport 
            self.viewport_info = gpu.state.viewport_get()
            self.width = self.viewport_info[2]
            self.height = self.viewport_info[3]
            
            # obtain pixels from the framebuffer
            self.pixelBuffer = self.framebuffer.read_color(event.mouse_x, event.mouse_y, 1, 1, 4, 0, 'FLOAT')
            
            # here I print the returned values from the color picking but it doesn't match the shader input colors
            print (self.pixelBuffer.to_list()[0])

            self.modal_redraw = True            

        else:

            # set draw variable to update:
            # This is here to prevent excessive redrawing
            self.modal_redraw = True

        return {'PASS_THROUGH'}



    def invoke(self, context, event):
        self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw, (self, context), 'WINDOW', 'POST_VIEW') # this draws the grid alone (without objects)
        
        # updates the viewport after an new options is selected
        bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(ModalFramebufferCopy)


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


if __name__ == "__main__":
    register()

    # Invoke modal operator for the example code
    bpy.ops.view3d.modal_framebuffer_copy('INVOKE_DEFAULT')

Please, notice that once you run the script and move the cursor into position in the viewport it will print the color data info. Also, I'm using Blender 3.4

Thanks in advance

[Additional info from comments:]

I'm drawing custom shapes in the viewport and connect them to an empty object as a holder of them and I need to select the empty object once I select any of these custom shapes. like what happens when you select a wireframe obj it detects which object you selected then select it in the outliner.

$\endgroup$
8
  • $\begingroup$ Why do you expect the values to match? Don't they go through shader, whatever rendering process, color management and what not? $\endgroup$ Mar 9 at 10:22
  • $\begingroup$ Thanks for the comment Martynas. if so . Is there a way to get the raw color before all these calculations happens or if there any other way to select a GPU drawn object ? $\endgroup$ Mar 9 at 12:03
  • $\begingroup$ Could you provide more context? What is this for? Why do you need the color picking functionality? $\endgroup$ Mar 9 at 14:26
  • 1
    $\begingroup$ Sure. I'm drawing a custom shapes in the viewport and connect them to an empty object as a holder of them and I need to select the empty object once I select any of these custom shapes. like what happens when you select a wireframe obj it detects which object you selected then select it in the outliner. $\endgroup$ Mar 9 at 16:17
  • $\begingroup$ The color space in the shader is quite special, try to take a screenshot and check the color with other software, you will know that your picking is correct. $\endgroup$
    – X Y
    Mar 9 at 16:44

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.