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

1.6k Views Asked by At

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.

2

There are 2 best solutions below

1
On BEST ANSWER

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')
2
On

In the code you create two framebuffers. You mentioned you want a single image, and I assume you may want the models to be shown side by side.

Here is a correction to your code:

Full code:

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'])

# Matrices and Uniforms
# Framebuffers

fbo = ctx.framebuffer(
    ctx.renderbuffer((1024, 512)), # doubled width to get
    ctx.depth_renderbuffer((1024, 512)), # doubled width to get
)

# Rendering

fbo.use()
ctx.enable(ModernGL.DEPTH_TEST)
ctx.viewport = (0, 0, 1024, 512)  # x, y, width, height
ctx.clear(0.9, 0.9, 0.9)

ctx.viewport = (0, 0, 512, 512)  # x, y, width, height
texture.use()  # first texture
vao.render()

ctx.viewport = (512, 0, 512, 512)  # x, y, width, height
texture1.use()  # second texture
vao.render()

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


if __name__ == '__main__':
    import time

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