With our latest open movie 'Charge', we were aiming for high fidelity realism using Eevee with a strong visual impact. One big challenge to giving emotional impact to our main character’s expressions is making the deformation of his facial skin feel fleshy and realistic. Having stiff facial expressions can ruin the immersion in an emotional moment of the film and push you into the uncanny valley very quickly.
So something that we wanted to have since early in the production were wrinkle maps that would best be triggered automatically using tension maps on the deformed mesh and ideally these wrinkle maps would even be fully procedural and omni-directional. This would grant us full freedom in the wrinkles generated from deformation of the mesh with minimal manual iteration on them. Important to note is that these wrinkles would not replace our entire system for realistic face deformation. They would only be complementary to what we achieve in a more directed manual approach and add another layer of detail and interaction.
In this blog post I want to share our results, talk about the journey it took to get there and how it works in more technical detail.
In the end we opted for using this technique only for the micro detail, as we already had the lower detail levels covered with manually sculpted displacement/bump maps that are triggered by the rig. So in our final result the effect is quite subtle and only shows in close-up shots. So we used it only for the highest level of detail on a skin cell level to create fine directional bump under compression. It does help a lot with the fleshiness and making the skin feel like it is actually being compressed, rather than just moving around.
The same technique can also be used for medium level detail. We decided against this for our character, as we already had a working, more directable solution by the time I got around to the procedural system and manually revised details are usuallt more accurate than a fully procedural approach. For the micro level we needed the procedural solution though.
One indication of successful wrinkle map that you can observe is how a lot of the procedurally generated wrinkles follow the same flow as the sculpted, broader wrinkles. This is a testament to how accurately the skin deformations have been considered in the rig but it also shows how, when used right, this method can give great results with little effort once put into place. In other areas the flow does not quite match (e.g. around the mouth). This is where it would have been useful to have this technique as a tool already in the creation of the facial shapes to monitor unwanted shifting of skin tissue. Seeing the wrinkles already appear in the sculpting process of the shapes gives very useful feedback that helps with consistency.
There are some issues with it that could be iterated over, for us we ended up putting the emphasis on the micro detail. Manual iteration for something like this will always be better than a fully procedural solution, so the combination of both is probably the most efficient solution for a nice result.
The most important thing to understand for a good result with this technique though, is that because the wrinkles are fully procedural, it will only display the actual deformation of the geometry. So if the shifting of the tissue is fundamentally off on a geometry level, the wrinkles will only emphasize this, rather than fix it. That said, this also makes this a useful tool, to investigate how the tissue is deforming on a detailed level, while working on the shapes.
Find a simple example file with the setup here.
Very early on into the production when we were doing some RnD to see what techniques we could possibly use to achieve a high fidelity facial performance. So I was investigating ways to retrieve tension information from the mesh deformation with geometry nodes. The first results were looking relatively promising but it was quickly clear that I'd need to spend a lot more time to figure out a system that is high-quality and flexible enough to fit our needs.
At this stage we decided to focus on manually sculpted expressions that we blend in the rig as shapekeys, displacement maps and bump maps. We would need manual control over the broader shapes regardless of how procedural wrinkle maps would end up and this would give us the highest level of actually controlling each shape.
That way procedural wrinkles based on a tension map would be a nice touch on top down the road but we wouldn't rely on it.
But how does this actually work now?
Well, there are multiple different ideas combined to achieve this. First I’m comparing the final, deformed mesh with an undeformed reference mesh to calculate two individual tension maps using Geometry Nodes. I then pass the resulting maps as attributes into the shader where I use them to generate the wrinkle map that I use as a bump map. This way every difference between the reference mesh and the deformed mesh will be reflected in the wrinkle map. This includes also the blended displacement shapes that have been manually sculpted, as well as any animation on the base mesh performed by the armature.
The first of the tension maps is relatively simple. It’s just a comparison of the face area before and after deformation A_Base/Deformed using a division and mapping the result so that it ranges from 0 for full compression to 1 for infinite stretching, while 0.5 means an equal face area. The formula used for this is:
1 - 2^( - ADeformed / ABase )
The directional tension map to retrieve the stretch direction to generate the wrinkles is a bit more tricky.
The base concept is to compare the length of the edges of the deformed and the undeformed mesh and then looking up the direction of the edge in a given UV space. When an edge got shorter it has been compressed and the wrinkle should appear orthogonal to it, when it is longer it has been stretched and the wrinkles should run parallel. By weighting the corresponding direction by how the length of the edge changed and averaging the vectors of all edges connected to each point we should get a resulting direction map. There is one crucial issue though.
The problem with these directional vectors is that the direction of an edge has two possibilities and for the wrinkles we don't care about left/right, we just care about horizontal/vertical (and anything in between). But if we just add the vectors together as they are, opposing directions will cancel each other out. So when generating the stretch map we need to use a vector space where both vectors for a direction and its opposite mean the same thing. The solution for this issue is to use the periodicity of polar coordinate space and simple multiply the angle component of the polar coordinates by 2. That way, two vectors that are exactly opposite in euclidean 2D space result in the same vector of this alternative vector space.
Now, when we average the vectors of the edges connecting to a point, vectors that were initially opposing are now aligned and don't cancel each other out, but add up instead. This exactly what we need in this case.
Another challenge was the fact that restricting yourself to the edges of a mesh will leave some deformations unaccounted for and lead to inaccuracies. A square for example can be deformed into a rhombus shape, without changing the length of its edges. So far we are not accounting for this type of deformation.
The way I solved this in our case was to triangulate the meshes in two ways and repeating the same described operation on the triangulated meshes. And then averaging everything together on the vertices. Here is where the current implementation gets a bit limiting, as it only works with meshes that fully consist only of quads. It also comes at a noticeable performance cost for high resolution meshes, so this is definitely something to be improved in the future.
Now all deformations should be accounted for and the resulting directional tension map can be output as an attribute to be used in the shader. It's important to note that this is not to be interpreted as an euclidean direction vector but still using the adjusted vector space. That means it needs to be transformed back before it is used. But that should be done in the shader, rather than on the vertices, because otherwise face interpolation would mess things up.
To generate the wrinkle map itself I knew would be difficult to pull off in a way that both looks believable and holds up with the animation of the skin tissue over time, as strength and direction of the compression change. On top of that it shouldn’t be all too draining on the performance of the shader, although that was less of a priority in our case. This could also be done by manually creating wrinkle maps for different directions and blending them together, I went with a fully procedural approach.
The way I ended up doing it was to divide the UV space into cells and within each cell squash a perlin noise according to the direction that the tension vector is giving us. This noise was then randomized and repeated 4 times to blend between different grid alignments and hide the seams that the cells create. This method creates a result that is quite nice, while it isn't perfect, as it cannot create longer wrinkles that continue through multiple cells. But I found it to be the method with the best behavior for animation.
One very important thing to keep in mind about this technique is how important the actual deformation of the geometry is, as all the information used comes directly from that. So if a system like this is already in place when the facial shapes are being sculpted that can be of enormous help to always have a reference of how the tissue is deforming and where it is compressing. That is useful both for the accuracy of the generated wrinkles, but also for easier iteration on the shapes themselves as there is a direct reference point without having to eyeball the shift of skin tissue.
Some notes about potential adjustments to this technique:
Overall I'm really quite happy that we were able to achieve what we set out to in terms of facial performance. Though the wrinkle only contributed to a small extent I am also really happy about the tech that came out of this and can hopefully be helpful for us in the future, as well as others.
Hi Simon, this remind me of the paper "Skin Stretch : Simulating Dynamic Skin Microgeometry" @ Sigg2015. https://youtu.be/tuZMMZ8vbNk I thought this could only be possible in game engine or proprietary software at big studio. Now seems to me it is doable in eevee/cycle with GN.
@Miles Cheng Very interesting! Yes, this is the same goal and idea. It looks like the way they are solving it is mainly different in the way that the texture is created. I opted for the procedural approach where they are doing convolotion on actual displacement maps.
@Simon Thommes I want to give it a try using micro skin texture, need a compression map to increase the colour contrast. A flow map to adjust the map flow and a Directional tension map to blur the texture. About the algorithm to retrieve the directional tension map, Could you give some hints on the face corner angle method you mentioned?
@Miles Cheng Not sure what exactly you need hints for. I laid everything out in this blog post. The file with the setup is also available, so you can try adapting it for what you need.
@Simon Thommes Could you explain more on how to replace two -way triangulation by using face corner angle?
@Miles Cheng Ah, I see. That's something I didn't explore yet. But it's about analysing the shape of the polygons via the face corner angles instead of the edge length.
This is amazing! I work in motion graphics and have been testing your technique combined with cloth simulation. I love Blender but it's Cloth/soft body sim becomes really cumbersome at higher poly counts and had been making me think maybe it's time i move back to Cinema. But your wrinkle map technique combined with a low poly mesh allow my sims to be near real time but have really high fidelity and control. Thank you for sharing such a thorough explanation too, this doesn't get half as much attention atm as it deserves.
@Thomas Groves Awesome, glad it helps you! It will also be interesting how physics simulation capabilities will develop in the near future with geometry nodes simulation, so stay tuned for that :)
Superb article ! Your know your stuff :-) I wish Blender could be ship with more geo nodes systems as these
Hello Simon, just curious if this case study that have been developed for charge would be revisited or developed more? Like you mention making the procedural wrinkle becoming a helper tool to sculpt by giving a preview. Also is there on the realistic human workflow I see there are still issue with utilizing multires to bake out the sculpted details into height maps, and it is also somewhat solved using a geonode setup. Will this baking also have GN solutions in near future? Or would it be a development on the feature of multires modifier. Just curious. Again amazing work from you and team.
@B3dtest Hay, thanks! For us at the studio this depends on the type of film we're making. If we're doing a cartoony production that is not going to be a topic, so I'm planning on iterating on this more in the near future.
In terms of Blender development there are already plans to move more away from multires, but rather towards texture based displacement sculpting. That would also helps with layering different levels of detail more freely and not locking you in to the topology entirely. But the only issue that this technique has when used while sculpting is that you need to compare two states of deformation. So you always need both the deformed and undeformed state and they need to have matching topology. But if that sculpted deformation is only done via a modifier you can easily get that. I'm not sure to what degree something like this could be a builtin tool. But with some manual; work of setting things up, already quite a bit is possible.
@Simon Thommes Hello Simon, Thank you for the reply. So where can I follow this development of texture based displacement sculpting? Is it like manually painting a height map? Do you mean if I am using a modifier to sculpt the deformation I can simply get the comparison of two state of deformation? I don't mind doing some manual work If there is a clear steps and predictability in the workflow. I also agree that chasing wrinkle realism should not be a focus of a creative animation studio. Just would be nice to solidify a clear workflow for wrinkle map or any detail baking from manual sculpt that later can be triggered via rig deformation just like in Einer and your post about procedural wrinkles.
@B3dtest The idea is to keep the user's interaction with the sculpting tools exactly the same. But the way the data is stored is not directly on the geometry via the multires modifier, but in a texture instead. So basically as vector displacement, but you don't have to draw the map, you just sculpt is normally.
I'm not sure where on the roadmap of the sculpting module this is. I'm pretty sure, that it's still quite far down the line. But there is ongoing development of the new texture painting, which will have to be done before that is being tackled.
You can follow general Blender development topics and meeting notes here https://devtalk.blender.org/ and the sculpting module's development tasks are visible here https://developer.blender.org/tag/sculpt_paint_texture/
@Simon Thommes Thank you for the answers and help. I understand that the tool development going to take some time and, in the meantime, do you think Julien Kaspar method of baking out sculpt details using Geo node could be optimally explored as well? I see a lot of Einer wrinkle workflow involves geo nodes and some custom settings to handle limitation of the workflow. I am curious how a middle aged less wrinkled face would look if it were made using Einer workflow. Have you guys maybe consider revisiting this topic again like how you guys refine stylized human by making snow after learning a lot from making rain. Just something I got in mind, hope my questions doesn't confuse you in any way.
@B3dtest I see a lot of Einer wrinkle workflow involves geo nodes
Yea that might be my doing to a large degree :D I'm using geondoes a lot to explore workflows and work around some of the shortcomings of some Blender tools. That also helps with development though, I think. As we have working prototypes already that have been proven in a production workflow.
Julien is heavily invested in the sculpting module, so he definitely has an eye on that. I'm pretty sure also that the idea is to revisit the documentation on realistic character creation as Blender gets more features to make that easier. I'm not sure though how much time we'll have for that unless we actually work on another project that involves realistic faces.
@Simon Thommes Yes thank you for the answer, so it is kind of uncertain since it revolves around so many factors. I also not really pushing for a super realistic wrinkle, but rather want to see how this automated wrinkle can be used in any style of production. Like animated shaders in sprite fright. But I guess that is going too far. Anyway, thanks for the support for this long chat of mine. Probably my last question: Are you going to release the example for einer face with procedural wrinkle like in the example on this blog post? Like the face rig version?
@B3dtest The setup is used in the released version of the rig. But only in the capacity of the rendered closeup of the eye, not the grey shaded example. Going from one to the other is basically just a matter of controlling the noise scale and bump strength.
@Simon Thommes Thank you for your time and effort on answering all my question. It is very kind of you. Wish the best for the team future works.
Join to leave a comment.