3
$\begingroup$

I'm using Python gpu module to render some simple rectangles in a 2D image.

The script retrieves current render settings:

def render_size( scene ):
    render = scene.render
    percent = render.resolution_percentage * 0.01
    dim_x = render.resolution_x * percent
    dim_y = render.resolution_y * percent
    return round(dim_x), round(dim_y)

Then calculates triangles based on some coordinates data and uses the following to make an offscreen rendering and get the result as pixel buffer:

def render_offscreen( dim_x, dim_y, vertices, indices, color = (0, 0.5, 0.5, 1.0) ):

    offscreen = gpu.types.GPUOffScreen( dim_x, dim_y )

    with offscreen.bind():

        shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
        batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)

        shader.bind()

        shader.uniform_float("color", color)
        batch.draw(shader)

        buffer = bgl.Buffer(bgl.GL_BYTE, dim_x * dim_y * 4)
        bgl.glReadBuffer(bgl.GL_BACK)
        bgl.glReadPixels(0, 0, dim_x, dim_y, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)

    offscreen.free()

    return buffer

Last, it transfers the buffer into an image:

def buffer_to_image( dim_x, dim_y, image_name, buffer ):

    if not image_name in bpy.data.images:
        bpy.data.images.new(image_name, dim_x, dim_y)

    image = bpy.data.images[image_name]
    image.scale(dim_x, dim_y)
    image.pixels = [v / 255 for v in buffer]

    return image

The problem is coordinates are shifted in the result. What should be here (1821, 981) is (1881, 1002), and that seems to be around 3.x% of the input values (I mean, this shift seems to be proportional along the axis):

enter image description here

What am I missing?

The complete code is here:

$\endgroup$
1
$\begingroup$

I was lucky enough to be drived to the solution by dr.sybren, hypersomniac and mano_wii!!

The solution is to setup the projection matrix appropriately to translate wanted coordinates in pixels (in my case 1920x1080) to normalized coordinated between -1 and 1.

So before running the batch, we need to reduce the scale by half the dimensions and translate the projection by -1 in x and y:

projection_matrix = Matrix.Diagonal( (2.0 / dim_x, 2.0 / dim_y, 1.0) )
projection_matrix = Matrix.Translation( (-1.0, -1.0, 0.0) ) @ projection_matrix.to_4x4()
gpu.matrix.load_projection_matrix(projection_matrix)

The complete code part is:

def render_offscreen( dim_x, dim_y, vertices, indices, color = (0, 0.5, 0.5, 1.0) ):

    offscreen = gpu.types.GPUOffScreen( dim_x, dim_y )

    with offscreen.bind():

        with gpu.matrix.push_pop():            
            projection_matrix = Matrix.Diagonal( (2.0 / dim_x, 2.0 / dim_y, 1.0) )
            projection_matrix = Matrix.Translation( (-1.0, -1.0, 0.0) ) @ projection_matrix.to_4x4()

            gpu.matrix.load_projection_matrix(projection_matrix)

            shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
            batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)

            shader.bind()
            shader.uniform_float("color", color)
            batch.draw(shader)

            buffer = bgl.Buffer(bgl.GL_BYTE, dim_x * dim_y * 4) 
            bgl.glReadBuffer(bgl.GL_BACK)

            bgl.glReadPixels(0, 0, dim_x, dim_y, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)

    offscreen.free()

    return buffer
$\endgroup$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.