Dungeon Pipeline

dungeons
Author

Scott Milner

Dungeon Pipeline is a pipeline solution I wrote for my senior capstone short film Love & Dungeons (working title).

It includes customizable DCC launchers, import/export tools, Maya playblast tools, shot building tools for Maya and Houdini, and rendering and Tractor submission tools. The project is currently ongoing, thus Dungeon Pipeline is still under development. Dungeon Pipeline serves a team of ~50 artists, with the film slated to finish in May 2025.


Work on Dungeon Pipeline started in December of 2023. My goal was to take what I had learned from working on Accomplice Pipeline with Matthew Minson (@maminson) and do a clean rebuild from the ground up.

The Dungeon Pipline codebase is fully Black compliant and uses mypy for static type checking whenever possible.

Dungeon Pipeline has a lot of different components, read below to learn about some of the highlights!


Using Universal Scene Description (USD)

Dungeon Pipeline uses Pixar’s Universal Scene Description (USD) for all data transfer between DCCs and for rendering.

USD in Maya

Additional tooling is needed for efficient USD export from Maya. Since Maya does not yet come bundled with USD v24+, we cannot directly write USD files to network drives when on Windows. If needed, export scripts export files to tmp directories and copy them to their correct location.

A chaser script is used to post-process Maya exports as Maya itself provides minimal control over the structure of exported prims:

  • Exported character rigs are scaled down to Houdini scale and material bindings are moved to be adjacent to the geometry primitives so they can be referenced in easier
  • Exported cameras are located whereever they fall in the scene graph (ie previs cameras may be located in multiple places in a scene file), and moved to a conssistent location.
  • Exported animations scaled down to Houdini scale, then partitioned by namespace into layers and referenced back into the root layer. The static rig layers are also layerd in so that animation data is included in the scene via an Inherit arc on the static rig. Value Clips are used to partition the animation data into two time ranges so that shot preroll is only loaded when needed.

Environments are constructed in Solaris, then the animation shot build tools import the environment as a USD stage back into Maya for animation. The shot build tool sets the stage Edit Target to an override layer, allowing animators to make shot-specific tweaks to an environment without editing the original environment, such as moving and toggling visibility on set pieces. Environment artists can create Variant arcs so that entire groups of assets can be toggled on and off in Maya.

USD in Houdini

In Houdini, Dungeon Pipeline makes use of the Solaris Component Builder workflow for assembling and publishing assets. Python OnCreated scripts provide a set of custom defaults on SideFX’s component builder nodes. A default harness is provided inside of the Component Geometry Node to clean up the geometry and generate proxy geometry and packed UVs.

Asset with multiple variants

Geometry cleanup harness
Figure 1: Solaris Component Builder

Environments artists can drop in custom node setups that use context options to organize all of the assets in the scene graph to match the structure of the node graph.

Example nodegraph with the Scene Graph it generates

Nodegraph assembling an environment
Figure 2: Assembling environments in Solaris

Maya Playblasting

Dungeon Pipeline uses @abstractfactory’s excellent maya-capture script to standardize the export resolution and aspect, among other things. In the process of integrating maya-capture into Dungeon Pipeline, I made several contributions to maya-capture, adding the ability to playblast from Maya’s Camera Sequencer and fixing a bug that appeared when playblasting after configuring the Camera Sequencer.

With maya-capture providing the base playblast functionality, I added functionality for standardizing the HUDs (text burn-in) when playblasting, FFmpeg video export presets for different export destinations, and a Qt-based interface for artists to use, customizable to different departments.

Previs — many shots per file, camera sequencer option

Animation — one shot per file, pulls frame range from ShotGrid
Figure 3: Playblast dialog variants

Look Development Pipeline

For look development, artists create maps in Substance Painter then bring them into Solaris to author final materials. Dungeon Pipeline includes a custom Qt export UI to hide streamline this process. This UI allows artists to customize normal and displacement map source, and includes support for RenderMan’s PxrBumpRoughness normal map type. Artists can also override the default map resolution and export additional maps.

This export UI interfaces with ShotGrid to track asset material variants and keep variant paths consistent. Artists can add a new material variant if needed. After export, the exporter uses oiiotool to convert all of the maps to RenderMan standard .tex files.

Substance Painter custom Qt export dialog

Automatically generated material networks in Solaris
Figure 5: Textures from Substance Painter into Houdini

In Solaris, a custom material library HDA looks up the texture path based off of variant information from ShotGrid, then generates a template material with RenderMan and UsdPreviewSurface shaders for the artist to work off of. This detects which maps were exported in the most recent export and only adds the nodes necessary for those particular maps.

Artists then use one of the lighting rigs in the Lookdev HDA (based off of @looop45’s work for Accomplice Pipeline) to tweak their shaders and render out turnarounds for dailies.

HDA parameters

Character artist Landon Warnick, Groom artist Tiffani Cookson
Figure 6: Lookdev HDA

ShotGrid Database

Dungeon Pipeline uses ShotGrid/Flow Production Tracking as a centralized source of truth for tracking assets and shots. The ShotGrid Database module translates between the JSON-like objects returned by the ShotGrid API and the more Pythonic data structures used by Dungeon Pipeline.

To maximize performance in this small-scope project, a single batch call is made to ShotGrid when the database is initialized. This local copy is then referenced instead of making a call to ShotGrid for each access.

I used attrs and cattrs to define data structures. In addition to making type checking and IDE autocompletion easier, it also lets us do things like store a Python set[str] in a ShotGrid Text field, automatically handle nested Stub objects sent by ShotGrid, and write and read these Python objects to/from JSON files.

Maya shot open dialog

Maya asset publish dialog (geometry variants queried)

Houdini asset open dialog (no geometry variants)
Figure 7: ShotGrid database interaction UIs in various DCCs

Example usage:

# Import the database module and initialize it with the project ID and SG authentication credentials
>>> from pipe.db import DB
>>> from env_sg import DB_Config
>>> conn = DB.Get(DB_Config)

# now you can use the database connection
>>> asset = conn.get_asset_by_name("Test Asset (var1)")
>>> asset
Asset(code='Test Asset (var1)', id=7098, path='asset/environment/setdressing/testasset', name='testasset_var1', material_variants={'green', 'blue', 'purple', 'yellow', 'red'}, parent=AssetStub(id=7097, disp_name='Test Asset'))

# The fields in the Asset object match those found in the Asset Info tab of an asset page on the ShotGrid website
# Note that the `parent` attribute of this has the type `AssetStub`
# To expand an `AssetStub` to a full asset:
>>> parent_asset = conn.get_asset_by_stub(asset.parent)

# Entities can also be fetched by id
>>> conn.get_shot_by_id(8012)
Shot(assets=[AssetStub(id=7064, disp_name='Rayden'), AssetStub(id=7064, disp_name='Robin')], code='D_010', cut_in=1001, cut_out=1100, cut_duration=99, id=8012, path='shot/D_010', sequence=SequenceStub(id=7001, disp_name='D'), set=None)

# Lists of names and IDs can be fetched
>>> conn.get_shot_code_list()
['A_010', 'A_020', 'A_030', 'A_040', 'A_045', 'A_050', ..., 'G_430']

# Assets can be modified and updated 
>>> asset.material_variants
{'green', 'blue', 'purple', 'yellow', 'red'}
>>> asset.material_variants.add("pink")
>>> asset.material_variants
{'green', 'blue', 'purple', 'yellow', 'red', 'pink'}
>>> conn.update_asset(asset)
# The change is now reflected in ShotGrid