A typical residential ArchViz project delivers 6–12 still images — each from a different camera angle. Rendering these manually means sitting at your workstation, starting a render, waiting 15–25 minutes for completion, saving the output, switching to the next camera, and repeating this cycle for hours. It is tedious, error-prone (wrong camera, forgotten render element, incorrect output path), and wastes production time that could be spent on the next project.
Batch rendering automates this entire cycle. Configure the queue once, click start, and walk away. Set it running at 6 PM, and your 8-camera project is finished by midnight with zero artist attention required. This article provides a production-grade MaxScript batch system handling per-camera settings, output organization, error recovery, and render time logging.
The Core Batch Render Script
This MaxScript collects all cameras matching a naming prefix, configures the output path for each, renders sequentially, and produces a detailed log file. It handles both V-Ray and Corona and preserves render element output paths.
MaxScript-- RenderVault: Production Batch Render System
-- Renders all cameras with organized output and logging
(
-- CONFIGURATION
local outputRoot = @"E:\Renders\CurrentProject\"
local projectName = "Penthouse_R03"
local imageFormat = ".png"
local renderW = 3840
local renderH = 2160
local cameraPrefix = "CAM_"
local skipExisting = true
-- Create output directory
local outputDir = outputRoot + projectName + "\\"
if not doesDirectoryExist outputDir do makeDir outputDir all:true
-- Collect cameras matching prefix
local renderCams = for cam in cameras \
where (classof cam == VRayPhysicalCamera or \
classof cam == Physical or \
classof cam == FreeCamera or \
classof cam == TargetCamera) and \
matchPattern cam.name pattern:(cameraPrefix + "*") \
collect cam
if renderCams.count == 0 do (
messageBox "No cameras found matching prefix."
return undefined
)
-- Create log file
local logPath = outputDir + projectName + "_render_log.txt"
local logFile = createFile logPath
format "=== Batch Render: % | % cameras | % ===\n\n" \
projectName renderCams.count (localTime) to:logFile
-- Set render resolution
renderWidth = renderW
renderHeight = renderH
local totalTime = 0
local successCount = 0
local failCount = 0
format "\n▸ Batch render: % cameras queued\n" renderCams.count
for i = 1 to renderCams.count do (
local cam = renderCams[i]
local outputPath = outputDir + projectName + "_" + cam.name + imageFormat
if skipExisting and doesFileExist outputPath do (
format " [%/%] SKIPPED: % (exists)\n" i renderCams.count cam.name
continue
)
format " [%/%] Rendering: %..." i renderCams.count cam.name
-- Set camera and output
viewport.setCamera cam
rendOutputFilename = outputPath
-- Set render element output paths
local rm = maxOps.GetCurRenderElementMgr()
for e = 0 to (rm.NumRenderElements() - 1) do (
local el = rm.GetRenderElement e
local elDir = outputDir + "Elements\\" + el.elementName + "\\"
if not doesDirectoryExist elDir do makeDir elDir all:true
rm.SetRenderElementFilename e \
(elDir + projectName + "_" + cam.name + "_" + el.elementName + imageFormat)
)
-- Render with timing
local startTime = timestamp()
try (
render camera:cam outputFile:outputPath vfb:false
local elapsed = (timestamp() - startTime) / 1000.0
totalTime += elapsed
successCount += 1
format " Done (%.1fs)\n" elapsed
format "[OK] % — %.1f sec\n" cam.name elapsed to:logFile
) catch (
failCount += 1
format " FAILED\n"
format "[FAIL] % — %\n" cam.name (getCurrentException()) to:logFile
)
)
-- Summary
format "\n=== COMPLETE ===\n"
format "Success: % | Failed: % | Time: %.1f min\n" \
successCount failCount (totalTime / 60.0)
format "\nSuccess: % | Failed: % | Total: %.1f min\n" \
successCount failCount (totalTime / 60.0) to:logFile
close logFile
format "Log: %\n" logPath
)
Camera Naming Convention
The batch system relies on consistent camera naming. We use this convention across all studio projects:
Camera Naming StandardFormat: CAM_[Room]_[Angle]_[Version]
Examples:
CAM_LivingRoom_Wide_v01
CAM_Kitchen_Island_v01
CAM_Bedroom_Master_v01
CAM_Exterior_Front_v01
Version suffixes:
_v01 = initial position
_v02 = revised after client feedback
_v03 = final approved
Non-render cameras (excluded from batch by prefix):
REF_ClientAngle_01 (reference cameras)
TEST_LightingCheck (test cameras)
The naming convention serves three purposes: the CAM_ prefix identifies renderable cameras for the batch script, the room and angle segments create self-documenting output filenames, and the version suffix enables revision tracking without renaming files. Output files are named Penthouse_R03_CAM_Kitchen_Island_v01.png — readable, sortable, and immediately identifiable by anyone on the team.
Per-Camera Resolution Overrides
Some projects require different resolutions per camera — a hero living room shot at 8K for a billboard, standard views at 4K for web, and a quick overhead plan view at 2K for the architect. This extended script reads resolution overrides from camera User Defined Properties:
MaxScript-- RenderVault: Per-Camera Resolution Override Reader
-- Add custom properties to cameras: "RenderW=7680" and "RenderH=4320"
(
fn getCameraResolution cam defaultW defaultH = (
local w = defaultW
local h = defaultH
-- Read User Defined Properties
local userData = getUserPropBuffer cam
-- Parse width
local wMatch = matchPattern userData pattern:"*RenderW=*"
if wMatch do (
local lines = filterString userData "\r\n"
for line in lines do (
if matchPattern line pattern:"RenderW=*" do (
local val = substituteString line "RenderW=" ""
w = val as integer
)
)
)
-- Parse height
local hMatch = matchPattern userData pattern:"*RenderH=*"
if hMatch do (
local lines = filterString userData "\r\n"
for line in lines do (
if matchPattern line pattern:"RenderH=*" do (
local val = substituteString line "RenderH=" ""
h = val as integer
)
)
)
#(w, h)
)
-- Usage: Before rendering each camera in the batch loop:
-- local res = getCameraResolution cam 3840 2160
-- renderWidth = res[1]
-- renderHeight = res[2]
format "Add User Defined Properties to cameras:\n"
format " RenderW=7680\n RenderH=4320\n"
format "Cameras without properties use the default resolution.\n"
)
To set a camera's override: select the camera → Properties panel → User Defined tab → add RenderW=7680 and RenderH=4320 on separate lines. The batch script reads these values and adjusts resolution per camera, falling back to the default when no override is specified.
Output Directory Structure
The batch script creates a clean output structure that separates beauty renders from render elements:
Output DirectoryE:\Renders\CurrentProject\
└── Penthouse_R03\
├── Penthouse_R03_CAM_LivingRoom_Wide_v01.png
├── Penthouse_R03_CAM_Kitchen_Island_v01.png
├── Penthouse_R03_CAM_Bedroom_Master_v01.png
├── Penthouse_R03_render_log.txt
└── Elements\
├── VRayReflection\
│ ├── Penthouse_R03_CAM_LivingRoom_Wide_v01_VRayReflection.png
│ └── ...
├── VRayLighting\
│ └── ...
└── VRayZDepth\
└── ...
Render elements are organized into subdirectories by element type, not by camera. This structure makes it easy to batch-process a specific element type in Photoshop (e.g., drag all Reflection elements into a single action) without hunting through camera-mixed folders.
Overnight Render Monitoring
For overnight batch renders, add this completion notification that plays a system sound and optionally sends an email notification when the batch finishes:
MaxScript-- RenderVault: Batch Completion Notification
-- Add at the end of the batch render script
(
-- Play system sound
sysInfo.SoundNotification = true
-- Create completion summary file (for monitoring tools)
local summaryPath = outputDir + "BATCH_COMPLETE.txt"
local summaryFile = createFile summaryPath
format "Batch render completed at %\n" (localTime) to:summaryFile
format "Success: % | Failed: %\n" successCount failCount to:summaryFile
format "Total render time: %.1f minutes\n" (totalTime / 60.0) to:summaryFile
close summaryFile
-- Optional: Power management after completion
-- Uncomment ONE of the following:
-- sysInfo.Shutdown() -- Shut down the PC
-- sysInfo.Hibernate() -- Hibernate
-- (do nothing) -- Leave running (default)
format "\n🔔 Batch render complete. Summary: %\n" summaryPath
)
Error Recovery: Re-Rendering Failed Cameras
When a batch render encounters a crash or memory overflow on one camera, the script logs the failure and continues to the next camera. After the batch completes, use this script to re-render only the failed cameras:
MaxScript-- RenderVault: Re-Render Failed Cameras from Log
-- Parses the render log and re-queues only failed cameras
(
local logPath = @"E:\Renders\CurrentProject\Penthouse_R03\Penthouse_R03_render_log.txt"
local logContent = ""
local logStream = openFile logPath mode:"r"
while not eof logStream do
logContent += readLine logStream + "\n"
close logStream
-- Find failed camera names
local failedCams = #()
local lines = filterString logContent "\n"
for line in lines do (
if matchPattern line pattern:"*[FAIL]*" do (
-- Extract camera name from "[FAIL] CAM_Name — error"
local parts = filterString line " "
if parts.count >= 2 do
append failedCams parts[2]
)
)
if failedCams.count == 0 then (
format "No failed cameras found in log. All renders succeeded.\n"
) else (
format "Found % failed cameras. Re-rendering:\n" failedCams.count
for camName in failedCams do
format " → %\n" camName
-- Re-run batch on these cameras only
)
)
Key Takeaways
Batch rendering transforms multi-camera projects from hours of manual supervision into a single-click overnight process. Use the CAM_ prefix convention to separate renderable cameras from reference and test cameras. Organize outputs with per-element subdirectories for efficient post-production. Set per-camera resolution overrides through User Defined Properties when mixed-resolution deliverables are needed. And always enable the render log — it provides accountability, timing data for future project estimates, and the failure information needed for targeted re-rendering.
Using a different batch workflow or render manager? Share your setup — we compare reader batch systems in our studio workflow features.