Page MenuHome

Cycles: add experimental support for an Alembic procedural
AbandonedPublic

Authored by Kévin Dietrich (kevindietrich) on Mar 1 2018, 10:20 PM.

Details

Summary

This patch adds the concept of procedurals to Cycles as well as an Alembic procedural as demonstration, see T79174 for discussion. A procedural works by creating extra Nodes in the scene before rendering.

An Alembic Procedural is created for each CacheFile from Blender, and each Blender Object possessing a MeshSequenceCache modifier is added to list of objects of the right procedural. The procedural's parameters derive from the CacheFile's properties.

The animation (and constant) data of the objects inside the Alembic archive is loaded at once at the beginning of the render and kept inside a cache. At each frame change we simply update the right socket of the corresponding Cycles Node if the data is animated. This allows for fast playback in the viewport (depending on the scene size and compute power).

As data is not persistent for final renders and since we load all the data at the begining of the render session, the procedural is only available during viewport renders at the moment. When an Alembic procedural is rendered, data from the archive are not read on the Blender side.

The procedural currently only supports Curve and Mesh objects, as well as reading some custom attributes for meshes (multiple UVs, and Vertex colors). Shader assignments are made using FaceSets found in the Alembic archive, if any, similarly to how Blender does it, or if there isn't any FaceSet, we simply use the first shader specified on the Blender object.

The procedural is set as an experimental feature. Although it works great for real-time-ish play back of animated data, it is not so robust for playing back physics simulations if the vertex count changes, lacks support for more attribute and object types, needs better interaction when editing shaders in the viewport, and a cache management to avoid memory issues.

Diff Detail

Repository
rB Blender
Branch
new_alembic_procedural_branch (branched from master)
Build Status
Buildable 9720
Build 9720: arc lint + arc unit

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
src/app/cycles_xml.cpp
611 ↗(On Diff #10136)

Debug prints should be removed.

650 ↗(On Diff #10136)

Code style:

}
else {
src/cmake/configure_build.cmake
15 ↗(On Diff #10136)

Unrelated change, can be submitted as a separate patch if it fixes issues you found.

src/cmake/precompiled_libs.cmake
36–39 ↗(On Diff #10136)

Unrelated change.

src/util/util_logging.h
43 ↗(On Diff #10136)

Unrelated change.

This revision now requires changes to proceed.Mar 2 2018, 3:43 PM

Thanks for all the comments. I'll split out the alembic code into it's own file and make the style notes you suggested. Is there a style guide online for Cycles developers?

r.e. The ProceduralNode comment, is this to help make alembic more of a first-class entity within Blender? I hadn't really thought of that much. For now why don't I reorganize my code a bit to make it more general purpose and not so tied to cycles_xml.

w.r.t Materials. I used the <state shader=""> syntax (was a little tricky to code!) simply because that was the current style and I wanted to be consistent. Certainly something like this is easier:

<alembic file="file.abc">
      <object path="*"  shader="redMtl" />
      <object path="/some/path/obj*" shader="blueMtl" />      // Override some objects
</alembic>

Is there some way I can replace the diff associated with this ticket?

Thanks for the explanation!

Maybe it's beyond the scope of this patch, but it's not actually that complicated. It would be best if there was a ProceduralNode base class, that would have a virtual void generate(Scene *scene) = 0; method. The scene would have a list of such procedural nodes, and before rendering call each of them to create nodes.

So then you'd have an AlembicNode with e.g. filepath and filter string sockets. The advantage of this would be that it's extensible with more procedurals, and file read/write would not need to know anything about Alembic. Then it's also easier to use another file format, send it over a network, inspect, etc.

<alembic filepath="myfile.abc" filter="path/to/object/*"/>

Material binding is also a complex topic on it's own, but perhaps the default way to do it should be to use the shader name from AbcMaterial? If you for example have a complex scene with many instances it's not so practical to sort them by material to put a <state> tag around. Ideally it's also possible to replace the .abc file with one from another frame, with perhaps some objects added or removed, and still have the XML file be valid. Having a way to override the shaders from the file could be good though, but maybe not with the <state> tag.

The "correct" way to do this is to lazy-load the alembic file, like other renderers do. The XML file would contain bounding box data and, after the first ray hits the bounding box, the alembic file would be loaded. I know nothing of cycles internals so don't have any idea how difficult this would be.

We don't support this, but note some renderers stopped doing this or don't advise to do it anymore. In practice global illumination rays fly all over the place and you end up loading everything when rendering the first pixels, which is slower than reading the meshes ahead of time.

Some minor comments left in the code, did not look in detail.

Is there a style guide online for Cycles developers?

We mostly follow the Blender code style, main difference is we don't use a space between if(.
https://wiki.blender.org/index.php/Dev:Doc/Code_Style

If in doubt look at surrounding Cycles code.

r.e. The ProceduralNode comment, is this to help make alembic more of a first-class entity within Blender? I hadn't really thought of that much. For now why don't I reorganize my code a bit to make it more general purpose and not so tied to cycles_xml.

It's not so much for Blender, we already have Alembic integration there and probably will continue to load Alembic files through that to ensure consistent results in the viewport and final render.

Having it as a node is about making Cycles itself easier to extend and maintain. If Alembic is not tied to XML files it's easier to change file formats, and also usable in IPR for example. Here is some code to implement the basics: P628. This should be enough to support Alembic in Cycles standalone.

Is there some way I can replace the diff associated with this ticket?

There's some explanation here, basically you upload the diff again and attach it to this revision. Or you can use arc o do it all from the command line.
https://wiki.blender.org/index.php/Dev:Doc/Tools/Code_Review#Upload_a_Diff

Is there some way I can replace the diff associated with this ticket?

There's some explanation here, basically you upload the diff again and attach it to this revision. Or you can use arc o do it all from the command line.
https://wiki.blender.org/index.php/Dev:Doc/Tools/Code_Review#Upload_a_Diff

I tend to use the 'Update Diff' function in the upper right corner of this page, you pretty much paste in a new diff and attach it to this revision.

I tend to use the 'Update Diff' function in the upper right corner of this page, you pretty much paste in a new diff and attach it to this revision.

Ah, much easier, I updated the wiki page to say that.

I tend to use the 'Update Diff' function in the upper right corner of this page, you pretty much paste in a new diff and attach it to this revision.

Ah, much easier, I updated the wiki page to say that.

might not hurt to update the wiki with a basic arc workflow, last time i tried to use it to update a patch, it submitted a new one without asking, haven't trusted it since , but we're getting awfully off topic here.

Update to Brecht's suggested organization. I still liked this syntax though:

<alembic filepath="file.abc" frame="5">
      <object path="/pTorus1/*" shader="blueMtl" />
       ...
</alembic>

rather than having the filters all on the alembic node as plugs. Let me know what you think.

I have 3 weeks off work so can continue to work in this for that time, and much less so after that.

I think nested object tags can work, but it should be in a way that can be represented by the nodes, like this:

class AlembicObject : public Node {
    ...
};

class AlembicProcedural {
    ...
    array<AlembicObject*> objects;
}

NODE_DEFINE(AlembicObject)
{
    NodeType* type = NodeType::add("alembic_object", create);
    ...
}

NODE_DEFINE(AlembicProcedural)
{
    ...
    SOCKET_NODE_ARRAY(objects, "Objects", AlembicObject::node_type);
}

We haven't used this so far by I want to do it for render passes as well. The thing I'm not entirely sure about is the syntax in the XML file, since it's not a natural fit. The most verbose syntax would be like this:

<alembic filepath="file.abc" frame="5">
      <objects>
          <alembic_object path="/pTorus1/*" shader="blueMtl" />
      </objects>
       ...
</alembic>

But we could leave out the <objects> tags for brevity, if we ever need to distinguish between two arrays with the same object type we could require then. The only difference then would be <alembic_object> instead of <object>.

I also noticed there is a bug in the code for SOCKET_NODE_ARRAY that can be fixed like this:

--- a/src/graph/node_type.h
+++ b/src/graph/node_type.h
@@ -217,7 +217,7 @@ const NodeType *structname::register_type()
 #define SOCKET_NODE_ARRAY(name, ui_name, node_type, ...) \
        { \
            static Node *defval = NULL; \
-               assert(SOCKET_SIZEOF(T, name) == sizeof(Node*)); \
+               assert(SOCKET_SIZEOF(T, name) == sizeof(array<Node*>)); \
                type->register_input(ustring(#name), ustring(ui_name), SocketType::NODE_ARRAY, SOCKET_OFFSETOF(T, name), &defval, NULL, node_type, ##__VA_ARGS__); \
        }
Brecht Van Lommel (brecht) requested changes to this revision.Mar 5 2018, 3:26 AM
Brecht Van Lommel (brecht) added inline comments.
src/render/procedural.cpp
69 ↗(On Diff #10147)

Function should be static.

78 ↗(On Diff #10147)

Function should be static.

90 ↗(On Diff #10147)

It would be good to do some error logging with VLOG on failures. Doesn't need to be done in first patch if it doesn't matter to you, but could added a comment about it then.

91 ↗(On Diff #10147)

Code style: factory.getArchive(filepath.c_str());

101 ↗(On Diff #10147)

Any reason not to specify frameTime in the XML file instead of frame? Or is there a way to get the framerate from the Alembic file? I don't think we should assume 24 fps, unless that's somehow part of the Alembic standard.

114 ↗(On Diff #10147)

I don't think there is an fnmatch on Windows. There is regex in C++11 though, maybe something in there can be used.

136 ↗(On Diff #10147)

I don't think we should be doing any up axis conversion normally. If the host app has a particular up axis convention it's best to let Cycles to use it as well to keep e.g. values in shader nodes and render passes working as expected for users.

There's not a lot of assumption about the up axis in Cycles, off the top of my head I can only think of the sky texture default axis, but that can be rotated.

170 ↗(On Diff #10147)

Do this before adding vertices, so you can reserve space for vertices and triangles in one go.

src/render/procedural.h
36 ↗(On Diff #10147)

Probably it's best to put this class in a separate alembic.h and alembic.cpp.

This revision now requires changes to proceed.Mar 5 2018, 3:26 AM

I've made most of these changes, and am testing. I started adding support for alembic Curves, adding them as Mesh curves, then noticed the curve.h/curve.cpp files in the render/ directory. This seems to be a more complete hair/fur system but it doesn't seem like the api is fully exposed in the standalone version of cycles yet. Is this correct? What's the best way to go about importing curves? Generally speaking, the curve control points should be treated as catmull-rom spline, interpolating the first and last control points and being either quadratic or cubic smooth in between.

I think nested object tags can work, but it should be in a way that can be represented by the nodes, like this:

class AlembicObject : public Node {
    ...
};

class AlembicProcedural {
    ...
    array<AlembicObject*> objects;
}

NODE_DEFINE(AlembicObject)
{
    NodeType* type = NodeType::add("alembic_object", create);
    ...
}

NODE_DEFINE(AlembicProcedural)
{
    ...
    SOCKET_NODE_ARRAY(objects, "Objects", AlembicObject::node_type);
}

We haven't used this so far by I want to do it for render passes as well. The thing I'm not entirely sure about is the syntax in the XML file, since it's not a natural fit. The most verbose syntax would be like this:

<alembic filepath="file.abc" frame="5">
      <objects>
          <alembic_object path="/pTorus1/*" shader="blueMtl" />
      </objects>
       ...
</alembic>

But we could leave out the <objects> tags for brevity, if we ever need to distinguish between two arrays with the same object type we could require then. The only difference then would be <alembic_object> instead of <object>.

I also noticed there is a bug in the code for SOCKET_NODE_ARRAY that can be fixed like this:

--- a/src/graph/node_type.h
+++ b/src/graph/node_type.h
@@ -217,7 +217,7 @@ const NodeType *structname::register_type()
 #define SOCKET_NODE_ARRAY(name, ui_name, node_type, ...) \
        { \
            static Node *defval = NULL; \
-               assert(SOCKET_SIZEOF(T, name) == sizeof(Node*)); \
+               assert(SOCKET_SIZEOF(T, name) == sizeof(array<Node*>)); \
                type->register_input(ustring(#name), ustring(ui_name), SocketType::NODE_ARRAY, SOCKET_OFFSETOF(T, name), &defval, NULL, node_type, ##__VA_ARGS__); \
        }

curves.h has a CurveSystemManager which contains some global quality settings for how curves are rendered, but these are settings that wouldn't be part of an Alembic file I think.

Curves are created in a mesh object mainly. There is an array of curve key points, and an array of curves. Each curve specifies which curve key point is the first point of that curve. So for example if you had curve key points ABCDEFGH and curves with first key 0 and 4, you'd have two curves ABCD and EFGH.

Cycles uses a cardinal spline with tension parameter c = 0.71, inherited from Blender. It might be possible to transform the curve key points to this basis (I think?), if not we'll have to add support in the kernel for some different types of basis.

This is a WIP diff. Let me know if you have problems applying/building it. I've included a new example scene for testing curve primitives, examples/scene_curves.xml. Brecht, if you're able to build it you'll see the resulting curves have issues when rendered: the joints at control points have cracks and the curves are also not smooth. They seem like simple linear tubes.

I couldn't build with this patch. It's not applying cleanly on the latest Cycles repository master, and referencing files named alembic.h and alembic.cpp which are not included?

It seems Cycles is rendering curves as line segments by default, you can change that by setting:

scene->curve_system_manager->primitive = CURVE_SEGMENTS;

We should change that.

Brecht Van Lommel (brecht) requested changes to this revision.Mar 11 2018, 6:15 PM
This revision now requires changes to proceed.Mar 11 2018, 6:15 PM

Still WIP; updated to latest repository. Added state option for curve primitive type.

Thanks for the updates, it builds for me at least but I did not look more deeply into the code since it's WIP anyway.

src/app/cycles_xml.cpp
409 ↗(On Diff #10173)

Make this 0.01f, we compile with double/float casting warnings by default.

src/render/alembic.cpp
103 ↗(On Diff #10173)

Add explicit (float)a[i][j] cast here

121 ↗(On Diff #10173)

24.0f

199 ↗(On Diff #10173)

0.1f.

Brecht Van Lommel (brecht) requested changes to this revision.Mar 15 2018, 11:33 PM
This revision now requires changes to proceed.Mar 15 2018, 11:33 PM

Quite a few changes. I've added motion blur support for the xml files and for alembic. I'm not happy with the syntax and would like to discuss alternatives. Currently it's like this:

<mesh Ppre="..." Pmid="..." Ppost="..." />

to match the MotionTransform class. It works but it clunky. How about this:

<mesh ... >
      <mesh_points P="..." />
      <mesh_points P="..." />
      ...
</mesh>

Can I ask some questions about how motion blur is done? It seems like the ATTR_STD_MOTION_VERTEX_POSITION attribute holds motion samples. It seems like, for 3 motion samples, the first and last sample are held in this attribute and the middle sample is in mesh->verts. Is this correct? Visually this seems to work for both meshes and curves. For rigid motion blur the MotionTransform on the object and camera nodes seems to be the thing to use.

Anyway, this version has basic motion blur support and a couple of example xml files added. Let me know, style-wise, what you think.

I know, long term nobody seems to want to use xml but any file format will need some way to specify multiple time samples for transform and vertex data.

You're correct about the way motion samples are stored.

Ideally we should have a more powerful attribute system that can support motion keys in a generic way. It possible to just do it on the XML side for now, with a syntax like <mesh P="..." motion_keys="3">. Then just store a longer array in P with all the motion keys and copy them to mesh->verts and ATTR_STD_MOTION_VERTEX_POSITION as needed.

Updated xml motion blur syntax:

<integrator motion_blur="true" />

<transform translate="-1 0 0  0 0 0  1 0 0" ...>
      ...
</transform

<mesh motion_steps="3" P=" 3X points " ... />

Deforming meshes must have an odd# of motion steps. The center set of vertices is copied to mesh->verts.

The transform node matrix, translate and rotate attributes can have either 1 or 3 entries, corresponding to MotionTransform's pre, mid and post matrices. There are new examples demonstrating the syntax.

Currently only Alembic curve primitives (hair) can have deformation blur. Even though, internally, cycles mesh objects contain both curves and meshes I'd like to suggest a new xml syntax:

<curves P="..." nverts="..." />

instead of exposing "curve_key" and "curve_first_key". I believe this is more consistent with the mesh syntax and more intuitive. Let me know if you agree.

Deforming meshes must have an odd# of motion steps. The center set of vertices is copied to mesh->verts.

Sounds good.

The transform node matrix, translate and rotate attributes can have either 1 or 3 entries, corresponding to MotionTransform's pre, mid and post matrices. There are new examples demonstrating the syntax.

Makes sense. Arbitrary number of motion steps for the transform matrix will be in the Cycles repository soon by the way

instead of exposing "curve_key" and "curve_first_key". I believe this is more consistent with the mesh syntax and more intuitive. Let me know if you agree.

Makes sense, I agree.

Last update for awhile. It's some cleanup plus the latest syntax for motion blurred vertices, plus adding the curves xml object. I won't be able to continue on this before summer.

Here's a link to the (WIP) Maya driver:

https://github.com/rjmercier1/CyclesForMaya

I will update this revision with the changes I made for reference and further discussion. The new patch does not contain any changes to the mesh API, it merely makes it compile with master and improve its behavior. However, I did not keep the changes for the XML interface. Instead of using Cycles standalone, it contains a way to load the Alembic files directly from the Mesh Sequence Cache modifier from Blender. This is mostly a hack to get things going and understand how procedurals would work in Cycles, but I could envision adding an option to the modifier to only load Alembic data at render time through such a procedural.

Kévin Dietrich (kevindietrich) edited the summary of this revision. (Show Details)
  • update alembic procedural patch to master, without standalone code
  • make use of the AlembicProcedural for Blender objects that have a mesh sequence cache
  • separated code from API patch
  • cleaned the code a bit and added some comments for things to be done
Kévin Dietrich (kevindietrich) retitled this revision from Add Alembic loader to cycles standalone to Cycles: work on an Alembic procedural.Aug 21 2020, 5:23 PM
Kévin Dietrich (kevindietrich) edited the summary of this revision. (Show Details)

I think the code is ready for review.

The procedural loads all the data in the archive in memory for all
frames, and data is looked up for each frame using the original
Alembic's TimeSampling. Supported types are meshes, curves, and
subdivision meshes, we could trivially add points when Cycles supports
points rendering.

The procedural has sockets to match Blender's CacheFile properties. One
procedural is created for each CacheFile in Blender, and Blender objects
possessing an Alembic cache are added to the procedural's list of
objects. A few more sockets and properties on the CacheFile were added
to control curves' (and points') radii as this data may be missing from
the archive. It also allows to customize the curves' look for rendering,
and matches similar procedurals in other render engines.

I remembered that Blender reads and writes Alembic face sets to use for
material assignments, so the Cycles procedural also uses those face sets
to assign per triangle shaders to meshes. This is not really the
intended use for face sets (which can be used for anything); I think it
would be better at some point to scope the face sets used for shaders,
to differentiate with other face sets used e.g. for simulations, and
also allow setting a custom Alembic property in the archive to do such
shader assignments.

The procedural is set as an experimental feature for now. Although it
works great for the use case of the sponsor's project (real-time
animations with no change in topology) it is not robust for other types
of production. Also, since data for all frames are loaded at once, this
will waste memory and processing time for final renders, maybe we could
make the procedurals persistent like images; which could also be
interesting for motion blur support. If this makes it to 2.92, it could
be a supported feature by 2.93.

There are some noted todos which are not in the scope of the sponsor's
use case; these will not likely be tackled in the next few weeks as I am
wrapping up the API work.

There is also partial support for interactive updates when editing
properties and shaders in the viewport, although this is well tested and
may be bug prone.

Kévin Dietrich (kevindietrich) retitled this revision from Cycles: work on an Alembic procedural to Cycles: add experimental support for an Alembic procedural.Dec 8 2020, 3:48 AM
Kévin Dietrich (kevindietrich) edited the summary of this revision. (Show Details)

I forgot to mention that during viewport renders I try to detect if there is a Cycles viewport enabled to avoid loading Alembic data on the Blender side. The approach I am using is not so nice, IMO, not sure of a better solution.

@Sybren A. Stüvel (sybren), setting you as a reviewer here, at least for the Blender side. The main changes are an option to the importer to add a modifier to every objects even if there are not animated so we can detect them through the Cycles Blender exporter, and a "default radius" for curves (and points in the future). The current code is setting an arbitrary radius of 1.0f to the curves if they do not have a widths property in the Alembic file. I guess it's better to parameterize this.

The main changes are an option to the importer to add a modifier to every objects even if there are not animated so we can detect them through the Cycles Blender exporter

What is the advantage of doing it like this? Is importing data from Alembic in Cycles faster than getting the same data from Blender?

and a "default radius" for curves (and points in the future).

Why is this necessary now, and not before?

The main changes are an option to the importer to add a modifier to every objects even if there are not animated so we can detect them through the Cycles Blender exporter

What is the advantage of doing it like this? Is importing data from Alembic in Cycles faster than getting the same data from Blender?

I would say it is bit faster to load as we don't go through the RNA, but not so much for static objects as they are not updated frame to frame anyway. There are a few reasons:

  • detect objects that have just an animated transforms, as I use the MeshSequenceCache modifier for this. I think I could add a detection through the constraints
  • support more features from Alembic as Blender is limiting and limited at the moment: instances, custom attributes, better subdivision support (corner crease, face holes, etc.).
  • it would allow to modify those objects in the archive, and have them updated in Blender or Cycles when reloading it, I know that this type of workflow is not so supported in Blender but we would have to start thinking about it at some point

and a "default radius" for curves (and points in the future).

Why is this necessary now, and not before?

I am pretty sure it was necessary before, for convenience to avoid having to manually set the radiuses on all objects. Maybe people don't care or complain. I cannot tell you why I haven't parameterized this when I worked on the importer though.

The bigger picture here is that there is Cycles standalone and integrated into other applications. Supporting direct loading of geometry through Alembic and USD is a standard feature for production renderers nowadays, also for supporting Hydra delegates. And with hdCycles we will get a USD procedural as well.

But we're not ready to expose this, so it's experimental. This may only really make sense when we have something like I/O collections or native Hydra support in Blender.

The current implementation using check_rendered_viewport_visible is clearly unreliable in general, but helps testing the functionality until we have a more complete integration. It should also be commented in the code/description that this is merely for testing.

I still need to review the code, both in Cycles and the Blender integration.

source/blender/blenkernel/BKE_scene.h
263 ↗(On Diff #31718)

Needs BKE_ prefix.

  • add BKE prefix to check_rendered_viewport_visible
  • cleanup, deduplicate logic to look for a cache modifier
  • add start and end frame sockets to the procedural, and activate during final renders
  • more robust way of detecting if we use the cycles procedural
  • add a scale parameter to the procedural to set the size of objects, accounting for different unit systems accross DCCs
  • fix wrong usage of getSubdivisionScheme
  • fix crashes when playing back an animated subdivided mesh
  • fix crash due to missing time sampling
  • fix potential buffer overflow

I would say it is bit faster to load as we don't go through the RNA, but not so much for static objects as they are not updated frame to frame anyway. There are a few reasons:

  • detect objects that have just an animated transforms, as I use the MeshSequenceCache modifier for this. I think I could add a detection through the constraints

Yes, IMO this should be done via the constraints system. There is no need to add a modifier to the object for this.

  • support more features from Alembic as Blender is limiting and limited at the moment: instances, custom attributes, better subdivision support (corner crease, face holes, etc.).

Personally I would suggest to improve Blender if such features are needed. But then again, my view is of course Blender-oriented and not Cycles-oriented, so I'll defer to you guys on this.

  • it would allow to modify those objects in the archive, and have them updated in Blender or Cycles when reloading it, I know that this type of workflow is not so supported in Blender but we would have to start thinking about it at some point

This would be nice to have for Blender as well, I know there's often requests for this.

I am pretty sure it was necessary before, for convenience to avoid having to manually set the radiuses on all objects. Maybe people don't care or complain. I cannot tell you why I haven't parameterized this when I worked on the importer though.

Fine by me to add such a thing. I'd do that in a separate patch, though, as I don't think it has much to do with Cycles per se.

I will split this patch up into a smaller parts, commit the Cycles side changes directly, and make new code reviews for the Blender side changes.