Search code examples
pythonopenglpython-moderngl

How do I render multiple objects with different textures in python-moderngl


I am trying to create a headless renderer which takes 2 models with different textures and then renders them both into a single image

    texture = ctx.texture(texture_image.size, 3, texture_image.tobytes())
    texture.build_mipmaps()

    texture1 = ctx.texture(texture_image1.size, 3, texture_image1.tobytes())
    texture1.build_mipmaps()
    # Vertex Buffer and Vertex Array

    vbo = ctx.buffer(vertex_data)
    vao = ctx.simple_vertex_array(
        prog, vbo, *['in_vert', 'in_text', 'in_norm'])
    for i in range(2):


        # Matrices and Uniforms

        # Framebuffers

        fbo = ctx.framebuffer(
            ctx.renderbuffer((512, 512)),
            ctx.depth_renderbuffer((512, 512)),
        )

        # Rendering

        fbo.use()
        ctx.enable(ModernGL.DEPTH_TEST)
        ctx.clear(0.9, 0.9, 0.9)
        texture.use()

        vao.render()

        vbo = ctx.buffer(cylinder)
        vao = ctx.simple_vertex_array(
            prog, vbo, *['in_vert', 'in_text', 'in_norm'])

        fbo.use()
        ctx.enable(ModernGL.DEPTH_TEST)
        # ctx.clear(0.9, 0.9, 0.9)
        texture1.use()

        vao.render()

        # Loading the image using Pillow

        data = fbo.read(components=3, alignment=1)
        img = Image.frombytes('RGB', fbo.size, data, 'raw', 'RGB', 0, -1)
#         del data
#         del img

    img.save(f'output{i}.png')


if __name__ == '__main__':
    import time

    start = time.time()
    main()
    end = time.time()
    print(end-start)

In this only the second model is finally exported and the initial model is overwritten. I tried a lot of resources to find an answer to this but couldn't find any.

Edit- Added an image of what I am trying to create. The first model i.e. the cylinder and then the watch over it with a different texture.

The watch and the cylinder behind it are the two models.


Solution

  • Here is a standalone example you can run. It renders two textured quads to a framebuffer and saves the framebuffer contents to a png file.

    If you are making a draw loop of some kind it's imporant that you don't create resources inside that loop unless you know what that implies.

    If this doesn't work for your models the problem is probably somewhere in the code you did not share like in the shader or the model itself (possibly placement or projection)

    When working with OpenGL it's almost always a good idea to create a minimal version first and gradually add the content. This makes it easier to identify where the problem is located.

    Output

    enter image description here

    import moderngl
    from array import array
    from PIL import Image
    
    ctx = moderngl.create_context(standalone=True)
    framebuffer_size = (512, 512)
    
    texture1 = ctx.texture((2, 2), 3, array('B', [200, 0, 0] * 4))
    texture2 = ctx.texture((2, 2), 3, array('B', [0, 200, 0] * 4))
    
    fbo = ctx.framebuffer(
        ctx.renderbuffer(framebuffer_size),
        ctx.depth_renderbuffer(framebuffer_size),
    )
    
    program = ctx.program(
        vertex_shader="""
        #version 330
    
        in vec2 in_pos;
        in vec2 in_uv;
        out vec2 uv;
    
        void main() {
            gl_Position = vec4(in_pos, 0.0, 1.0);
            uv = in_uv;
        }
        """,
        fragment_shader="""
        #version 330
    
        uniform sampler2D texture0;
        out vec4 fragColor;
        in vec2 uv;
    
        void main() {
            fragColor = texture(texture0, uv);
        }
        """,
    )
    
    buffer1 = ctx.buffer(array('f',
        [
            # pos xy    uv
            -0.75,  0.75, 1.0, 0.0,
            -0.75, -0.75, 0.0, 0.0,
             0.75,  0.75, 1.0, 1.0,
             0.75, -0.75, 1.0, 0.0,
        ]
    ))
    
    buffer2 = ctx.buffer(array('f',
        [
            # pos xy    uv
            -0.5,  0.5, 1.0, 0.0,
            -0.5, -0.5, 0.0, 0.0,
             0.5,  0.5, 1.0, 1.0,
             0.5, -0.5, 1.0, 0.0,
        ]
    ))
    
    
    vao1 = ctx.vertex_array(program, [(buffer1, '2f 2f', 'in_pos', 'in_uv')])
    vao2 = ctx.vertex_array(program, [(buffer2, '2f 2f', 'in_pos', 'in_uv')])
    
    # --- Render ---
    # Make a loop here if you need to render multiple passes
    
    fbo.use()
    fbo.clear()
    
    # draw quad with red texture
    texture1.use()
    vao1.render(mode=moderngl.TRIANGLE_STRIP)
    
    # draw quad with green texture
    texture2.use()
    vao2.render(mode=moderngl.TRIANGLE_STRIP)
    
    ctx.finish()
    
    img = Image.frombytes('RGB', fbo.size, fbo.read())
    img.save('output.png')