Draft: Subdivision Surface
This is a draft. Once the text and images are finalized the page will be moved to the official code documentation section.
This document gives a quick dive into the implementation of Subdivision Surface in Blender, using the OpenSubdiv library. This document only covers topics which are considered essential from Blender’s development perspective. For a more detailed documentation of OpenSubdiv, see the OpenSubdiv’s documentation.
This document aims at providing a top level view of the algorithm and its software implementation. Implementation details are documented in the source code through comments.
Important entities and terminology
Before diving into the algorithm, here is the terminology that will be used.
Coarse mesh: the mesh used as input for the subdivision surface process.
Mesh topology: vertices, edges, faces, and the relations between them. It also contains information about UV layers, UV islands and how they relate to coarse mesh loops, without information about actual positions of UV vertices.
Coarse coordinates: coordinates of vertices of a coarse mesh.
Grid: a special representation of high-res subdivided mesh in Blender. This is done by only storing coordinates of high-res vertices, without the full specification of edges and polygons between them.
Each coarse mesh polygon consists of as many grids as the number of loops in the polygon. All grids are met at the median of the polygon. The grids are placed as:
- One vertex is on the polygon median.
- Two vertices are on adjacent edge’s medians.
- One grid corner matches the polygon’s vertex.
The coordinate space of a grid starts at the center of the polygon.
- U axis of the grid points towards an edge connecting the current loop with the next loop.
- V axis of the grid points towards an edge connecting the previous loop with the current one.
PTex face: a quad face which comes from polygons of a coarse mesh. Quad coarse polygon consists of a single ptex face, Non-quad coarse polygon consists of n ptex faces, one for each loop of the polygon, the ptex indices within the polygon are aligned with loop indices. For the non-quad polygons, the ptex face vertices are placed as following:
- One vertex is on the polygon median.
- Two vertices are on adjacent edge’s medians.
- One ptex face vertex matches the polygon’s vertex.
Coordinate space within ptex face goes as following:
- U axis is pointing from the coarse polygon vertex in the direction of the next vertex of the polygon.
- V axis points in the direction of the previous vertex of the polygon.
For the quad polygons the origin of the ptex face coordinate system is at the first vertex of the polygon. For non-quad polygons the ptex coordinate system’s origin is at the ptex vertex which corresponds to the coarse polygon vertex.
Note that this is close to the idea of Blender’s Grids, but with different behavior for quad polygons and different coordinate systems within the grid/ptex face.
Limit surface: a surface which corresponds to the coarse mesh with an infinite number of subdivision steps applied.
Patch: a representation of a part of a limit surface in its localized area. PTex face can consist of one or multiple patches. Patches can be either B-Spline surface or Gregory surface. Patches are generated by the topology refiner of OpenSubdiv.
Topology refiner: a special entity in the OpenSubdiv which holds coarse mesh topology, analyzes it and creates subdivision surface patches needed for evaluation and/or visualization.
The typical subdivision surface process consists of the following steps:
- Create or re-use topology refiner for the input coarse mesh.
- Refine topology refiner with new coarse coordinates for vertices and UV layers.
- Evaluate a limit surface of the topology refiner and create an output.
1. Create/reuse topology refiner
The goal of this step is to provide a topology refiner which corresponds to the current topology of the coarse mesh in the input. This refiner exists inside of a Subdiv structure. Or, rather, Subdiv structure encapsulates all information and descriptors needed to perform subdivision surface evaluation.
A new topology refiner is created from an input using a converter abstraction. This converter is a simple structure which implements a minimal subset of accessors needed to provide topology from Blender side to OpenSubdiv’s topology refiner. Having this converter abstraction allows to minimize boilerplate code needed to create a subdivision surface from the DNA Mesh, or from a procedurally generated source.
Code reference: BKE_subdiv_new_from_converter()
Since the creation of a new topology refiner is an expensive operation, it is important to keep re-using the topology refiner whenever possible. For example, the Subdivision Surface modifier caches the topology refiner in its runtime field. For the cached topology refiner to be re-usable the following elements have to match:
- Topology of the coarse mesh
- Edge/vertex tags (meaning, attributes such as “crease” should match in order for topology refiner to be re-usable)
- Subdivision surface settings (such as Catmull-Clark vs. Simple subdivisions, Quality, and so on)
Code reference: openSubdiv_topologyRefinerCompareWithConverter() - comparison of cached topology refiner with new coarse mesh via Converter.
Code reference: BKE_subdiv_update_from_converter() - creates Subdiv descriptor for the given settings and converter. Will re-use given existing Subdiv descriptor when possible, or will destroy it and re-create if topology changed.
The actual creation of a topology refiner is pretty straight-forward, with a few caveats:
- OpenSubdiv expects manifold winding of polygons (meaning, if one polygon has vertices defined in clock-wice order, the adjacent one should do the same). Since this is not guaranteed in Blender, it is left to OpenSubdiv to do orientation. This is done by not providing information about edges, adjacency of edges to vertices, and adjacency of faces to vertices.
- Edge crease is squared prior to passing it to the OpenSubdiv library. This is mainly for compatibility reasons with old Blender’s subdivision surface implementation.
- OpenSubdiv does not deal with loose geometry, and it does lead to asserts and crashes in the OpenSubdiv code. So loose geometry is not provided to OpenSubdiv. Annoying outcome of this is a need to keep mapping of indices passed to OpenSubdiv and indices in the actual coarse mesh.
2. Refine for new coarse coordinates
This is the most straightforward step of the algorithm: new coordinates are passed to OpenSubdiv and the topology refiner is asked to refine the topology.
In the code this is happening in BKE_subdiv_eval_refine_from_mesh() this function.
After this the Subdiv descriptor is ready for CPU side evaluation.
3. Limit surface evaluation
One the CPU side,the BKE_subdiv_eval module is used to evaluate the limit surface. This module provides functionality to evaluate coordinates, derivatives, and normals of the limit surface at the given coordinate of a given ptex face.
Evaluation to Mesh
This code path is used when it’s needed to create a fully-specified high-res Mesh. It is used, for example, in the Subdivision Surface modifier.
This codepath utilizes BKE_subdiv_foreach module, which abstracts all logic needed to define high-res geometry (vertices, edges, loops, polygons) and connectivity between them (which edges are adjacent to which vertex, for example). The BKE_subdiv_foreach module does not perform actual evaluation, it just provides the information needed for it.
Roughly speaking, when fully-featured Mesh is to be created for subdivision surface, a set of callbacks is used via BKE_subdiv_foreach and these callbacks are using the BKE_subdiv_eval module to evaluate the positions of high-poly vertices.
Code reference: BKE_subdiv_to_mesh
Evaluation to CCG (Catmull-Clark Grids)
This code path is used when it’s needed to have CCG representation of high-res mesh. This representation is used by sculpt mode code in Multires, for example.
This is a somewhat more straight-forward code path: it simply maps grid coordinates to ptex face coordinates and evaluates a limit surface for them.
Code reference: BKE_subdiv_to_ccg_mesh
This is a special attachment to Subdiv evaluation, which allows to displace the limit surface. This is implemented as chaining limit surface and displacement evaluation. Basically, the evaluated limit surface position is passed to a displacement evaluator, which modifies the coordinate. The modified coordinate is then used as a final result.
This is how, for example, multiresolution modifier applies its sculpted displacement to the subdivided mesh.
Code references: BKE_subdiv_displacement_attach_from_multires(), BKE_subdiv_eval_displacement().
Subdivision surface as deformation modifier
Some algorithms in Blender need to be able to apply a modifier as deformation-only.4 For example, this is how crazy-space is being calculated (crazy space is used by painting modes to paint on a mesh with modifiers).
For subdivision surface modifiers this is done by creating a topology refiner from the mesh which is an input to the modifier and only evaluating new positions for the coarse vertices.
Code reference: BKE_subdiv_deform_coarse_vertices
Quality defines how well patches will represent a limit surface of the input coarse mesh. Higher quality will make more accurate representation, but will use more memory and will be slower to evaluate. Lower quality might not accurately represent semi-sharp edges of the model.
The OpenSubdiv repository contains some examples such as glViewer (or dxViewer for Windows) which can be used to analyze how various OpenSubdiv options affect a shape of the result. It also allows to visualize the limit surface and patches.