Tile rendering with Cinema 4D
-
Dear community,
Is there a recommended way to do tile rendering—dividing a large image into smaller pieces that can be rendered quickly and then reassembled into one final image?
Current Approach:
We currently use the "Render Tiles" camera to divide an image into smaller pieces, then assemble them using software like FFmpeg or OpenImageIO.
Here's our detailed step-by-step procedure: https://github.com/aws-deadline/deadline-cloud-for-cinema-4d/blob/mainline/docs/tile_rendering/tile_rendering.md based on this old article.Alternative approach:
But while going over some of the render settings, I stumbled on "RDATA_RENDERREGION_LEFT" (similarly for left, top and bottom).
Could we use this approach instead—rendering specific regions of the image separately and then assembling them into the final image?Has anyone implemented a similar solution or can provide guidance on whether this approach is feasible?
Thank you for your assistance.
-
I was able to get tile rendering done by running a script like this:
import c4d import os doc: c4d.documents.BaseDocument op: c4d.BaseObject | None def main() -> None: # --- Configure these --- tiles_x = 2 # columns tiles_y = 2 # rows output_dir = os.path.join(os.path.expanduser("~"), "Desktop", "tiles") # ------------------------ os.makedirs(output_dir, exist_ok=True) base_rd = doc.GetActiveRenderData() full_w = int(base_rd[c4d.RDATA_XRES]) full_h = int(base_rd[c4d.RDATA_YRES]) tile_w = full_w // tiles_x tile_h = full_h // tiles_y for ty in range(tiles_y): for tx in range(tiles_x): rd = base_rd.GetClone() left = tx * tile_w top = ty * tile_h right = left + tile_w bottom = top + tile_h # Region at full resolution rd[c4d.RDATA_RENDERREGION] = True rd[c4d.RDATA_RENDERREGION_LEFT] = left rd[c4d.RDATA_RENDERREGION_TOP] = top rd[c4d.RDATA_RENDERREGION_RIGHT] = right rd[c4d.RDATA_RENDERREGION_BOTTOM] = bottom # Full-res bitmap — C4D renders region into this bmp = c4d.bitmaps.MultipassBitmap(full_w, full_h, c4d.COLORMODE_RGB) bmp.AddChannel(True, True) result = c4d.documents.RenderDocument( doc, rd.GetData(), bmp, c4d.RENDERFLAGS_EXTERNAL | c4d.RENDERFLAGS_SHOWERRORS, ) if result != c4d.RENDERRESULT_OK: print(f"Tile ({tx}, {ty}) failed with code: {result}") continue # Crop the tile region out of the full bitmap tile_bmp = c4d.bitmaps.BaseBitmap() tile_bmp.Init(tile_w, tile_h) for y in range(tile_h): for x in range(tile_w): r, g, b = bmp.GetPixel(left + x, top + y) tile_bmp.SetPixel(x, y, r, g, b) path = os.path.join(output_dir, f"tile_{tx}_{ty}.png") tile_bmp.Save(path, c4d.FILTER_PNG) print(f"Saved {path}") c4d.gui.MessageDialog( f"Done! {tiles_x * tiles_y} tiles saved to:\n{output_dir}" ) if __name__ == "__main__": main() -
Hmm, I was able to reassemble the tiles within Cinema 4D as well. Here's the script with reassembly.
Feel free to chime in if we should not be doing this (performance impact, does not work with different renderers etc)
import c4d import os doc: c4d.documents.BaseDocument op: c4d.BaseObject | None def main() -> None: """Renders the scene as a grid of tiles, then reassembles into the final image.""" # --- Configure these --- tiles_x = 2 tiles_y = 2 output_dir = os.path.join(os.path.expanduser("~"), "Desktop", "tiles") final_path = os.path.join(output_dir, "final_assembled.png") # ------------------------ os.makedirs(output_dir, exist_ok=True) base_rd = doc.GetActiveRenderData() full_w = int(base_rd[c4d.RDATA_XRES]) full_h = int(base_rd[c4d.RDATA_YRES]) tile_w = full_w // tiles_x tile_h = full_h // tiles_y tile_bmps = {} for ty in range(tiles_y): for tx in range(tiles_x): rd = base_rd.GetClone() left = tx * tile_w top_ = ty * tile_h right = left + tile_w bottom = top_ + tile_h rd[c4d.RDATA_RENDERREGION] = True rd[c4d.RDATA_RENDERREGION_LEFT] = left rd[c4d.RDATA_RENDERREGION_TOP] = top_ rd[c4d.RDATA_RENDERREGION_RIGHT] = right rd[c4d.RDATA_RENDERREGION_BOTTOM] = bottom bmp = c4d.bitmaps.MultipassBitmap(full_w, full_h, c4d.COLORMODE_RGB) bmp.AddChannel(True, True) print(f"Rendering tile ({tx}, {ty}) region=({left},{top_})-({right},{bottom})") result = c4d.documents.RenderDocument( doc, rd.GetData(), bmp, c4d.RENDERFLAGS_EXTERNAL | c4d.RENDERFLAGS_SHOWERRORS, ) if result != c4d.RENDERRESULT_OK: print(f"Tile ({tx}, {ty}) failed with code: {result}") return # Crop tile — use same coords as region tile_bmp = c4d.bitmaps.BaseBitmap() tile_bmp.Init(tile_w, tile_h) for y in range(tile_h): for x in range(tile_w): r, g, b = bmp.GetPixel(left + x, top_ + y) tile_bmp.SetPixel(x, y, r, g, b) tile_path = os.path.join(output_dir, f"tile_{tx}_{ty}.png") tile_bmp.Save(tile_path, c4d.FILTER_PNG) print(f"Saved {tile_path}") tile_bmps[(tx, ty)] = tile_bmp # --- Reassemble: place each tile at its matching position, no flip --- print("Assembling final image...") final_bmp = c4d.bitmaps.BaseBitmap() final_bmp.Init(full_w, full_h) for ty in range(tiles_y): for tx in range(tiles_x): tile_bmp = tile_bmps[(tx, ty)] dst_x = tx * tile_w dst_y = ty * tile_h for y in range(tile_h): for x in range(tile_w): r, g, b = tile_bmp.GetPixel(x, y) final_bmp.SetPixel(dst_x + x, dst_y + y, r, g, b) final_bmp.Save(final_path, c4d.FILTER_PNG) print(f"Saved assembled image to {final_path}") c4d.bitmaps.ShowBitmap(final_bmp) c4d.gui.MessageDialog( f"Done! {tiles_x * tiles_y} tiles rendered and assembled.\n{final_path}" ) if __name__ == "__main__": main()