Stereoscopic camera settings in Blender Python

If you read my previous post about how to render dolly zooms in Blender using a Python script you may have been wondering whether you can make a stereoscopic 3D version. The answer is, of course, an emphatic yes! I decided I would add that as an extension of the other post as I wanted to write another post to discuss something not widely covered on the internet – how to adjust the stereoscopy camera settings in Python. And to help it make sense, especially if you’re new to this, we’ll assume the Blender camera provides an analogy of human sight, as in the diagram below. Basically, when we look at something the view is determined by how much our eyes rotate, which itself is determined by the convergence distance and the separation between our eyes (a.k.a. the interocular distance).

The main camera variables we need for stereoscopy
The main camera variables we need for stereoscopy.

But before I get onto the code I should mention a big difference between our Python code here and in the previous dolly zoom post, which can be very frustrating if we ignore it. Basically, the previous post found the camera through the bpy.data.objects Blender Python (BPY) structure, which is the correct way to do it. But to access the stereoscopic elements we need to use the bpy.data.cameras structure. And for extra confusion, the camera name we use in the data.cameras code is subtly different to the data.objects one. To overcome that we need to make sure we expand our camera in the Blender UI and rename the inner camera name (the one highlighted in blue below). I renamed the inner and outer names to be the same, which should allow me to easily access the camera using either BPY structure.

Changing the camera name in the Blender GUI
Changing the camera name in the Blender GUI.

With that said we can now look at how to access the stereoscopy settings for our camera in BPY (remember to enable stereoscopy for the rendering and camera in the Blender UI first). I’ve put an example below to show how to access what I consider the most important variables, which you can find in the ‘stereocamtest.py’ file on the Parth3D experiments Github repository, together with the rest of the resources used in this post. You can run it by opening ‘bpystereodollyzoom.blend’ in Blender, then opening the Python script in the scripting tab (the triangle icon lets you run it). The two distance settings we looked at in the diagram earlier, so they should make sense – I used the toe mode, and center pivot, options as they give what I consider a ‘normal’ stereoscopic image, but I’d encourage you to read the docs and experiment so you can get what you need instead.

import bpy

cst = bpy.data.cameras['Camera_STEREO'].stereo

print("Convergence distance (m): " + str(cst.convergence_distance)) # m
print("Convergence mode: " + cst.convergence_mode) # default OFFAXIS want TOE
print("Interocular distance (m): " + str(cst.interocular_distance)) # m e.g. 0.065
print("Pivot type: " + cst.pivot) # default Left want CENTER

So as an example of how to use these settings let’s use them to extend our previous dolly zoom code to output stereoscopic renders, which will use a camera previously set up in our Blender file to use stereoscopy – as you’ll have noticed above, it’s called ‘Camera_STEREO’. And we’ll access it in a modified Python file called ‘bpystereodollyzoom.py’ – you can just close the previous script in the Blender file and open this one to run the example.

I won’t cover the whole of that Python code as mostly it’s the same as previously. But I’ll cover some of the changes to hopefully help your transition. Perhaps the most important is the make_dfl_stereo_list function which calculates focal lengths for a list of distances, based on our initial camera settings. As you’ll see below it’s now extended to include the convergence distance and, very importantly, is based on the same ‘double the camera distance and double the focal length’ rule from the previous post, now doubling the convergence distance too. However, I didn’t change the eye separation as that would look unnatural (unless you have an inflatable head).

def make_dfl_stereo_list(dl, icd, ifl, icnd):
    """
    dl = list of distances (m)
    icd = initialisation camera distance (m)
    ifl = initialisation camera focal length (mm)
    icd = initial convergence distance (m)
    """
    dfl = []
    for c in range(0, len(dl)):
        fd = dl[c]
        fl = (fd / icd) * ifl
        cnd = (fd / icd) * icnd
        dfl.append([fd, fl, cnd])
    return dfl

The code also now retrieves the stereoscopic settings, as shown below. I set the convergence mode and pivot type too, just in case, but if you don’t change anything to do with that in the Blender UI it’s not strictly necessary as I set it for you in my blend file. Also, you’ll notice the interocular distance (i.e. the eye separation) isn’t included in the code. That’s because, as mentioned above, I didn’t want to change it. But you can easily add it if you want, as described in the code at the top of the page.

cst = bpy.data.cameras['Camera_STEREO'].stereo
st_cd = cst.convergence_distance # m
st_cm = cst.convergence_mode # default OFFAXIS want TOE
st_piv = cst.pivot # default Left want CENTER

cst.convergence_mode = "TOE"
cst.pivot = "CENTER";

The changes above mean we also have to call our list-making-function a little differently. But that’s just a case of changing the code as below, where all we’ve done is add st_cd as the last parameter.

dfl = make_dfl_stereo_list(dl, abs(cpy), sfl, st_cd)

Obviously we expect to need some changes to the rendering loop, but they’re actually quite minor. As shown below, we’re just accessing the convergence distance from our distance-based list, and updating the camera stereoscopy setting for it.

        cp, fl, cnvd = dfl[c]
        ...
        cst.convergence_distance = cnvd

Finally, we add to the end of the code to reset the changed Blender settings to their original values. I added the lines below to that and if you change the code to use the interocular distance (eye separation) you’ll likely want to include it here too to make sure it’s value is restored.

cst.convergence_distance = st_cd
cst.convergence_mode = st_cm
cst.pivot = st_piv

When you run the script you should end up, after waiting a while, with a ‘renders’ folder containing 360 individual renders, one for each frame of a twelve second movie. As previously, I used the FFMPEG command below, at the commandline in Windows, to join the renders into a video – so you may need to play about with it for Linux and Macs. And before I ran it I used the Photos3D Python library‘s batch processing script to add a mirrored version to the bottom of all the renders, so it can cover parallel and cross-eye viewing methods. That’s because the Blender file I provided on Github is set for parallel viewing, but you could change it to render cross-eyed. And using the Photos3D library, while not being overly fast, is a much quicker way of covering both viewing methods than separately rendering each in Blender.

ffmpeg.exe -framerate 30 -i ".\renders\frame-%%06d.png" -c:v libx264 -pix_fmt yuv420p movieout.mp4

After running that command you should end up with a movie file named ‘movieout.mp4’ in the same folder you ran it from. I’ve put it below so you can see how it will look – you should notice that the stereoscopic 3D effect is greatest when we’re zoomed in (wide angle lens viewing) and least when we’re zoomed out, just like when we look at near and far objects in real life. Of course, if you’re feeling adventurous, you could play with the code to create some weird effects using unnatural eye separation and convergence distance variations – sick bags at the ready!

The stereoscopic 3D dolly zoom movie.

So that’s that then, we now know how to control the main Python camera stereoscopy settings in BPY, allowing us to play with producing some quite exciting 3D movie effects 🙂