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