Website powered by

Invincible Presents: Atom Eve - The Anatomy of the Shaders

One of my roles on Invincible Presents: Atom Eve, was to take our existing shader system (made by the wonderfully talented Chris Zuko) and expand on the pipeline to bring all the character art made by our art team into engine for the animation team to use with new features and artistic direction that the project required.
https://store.steampowered.com/app/2060870/Invincible_Presents_Atom_Eve/

Hello! In this post, I'll be covering some of the methodology on how we tackled different features we wanted in Invicible Presents: Atom Eve on the shading side that might alleviate the workload for others.

Our characters are built up on a series of atlases on a grid that's evenly divided, and if overlapped, would have the same relative positions for every facial feature.

This lets me control the facial feature expression to use on the rig side through the use of custom primitive indexes and math to "zoom in" on a specific cell of the grid.

This lets me control the facial feature expression to use on the rig side through the use of custom primitive indexes and math to "zoom in" on a specific cell of the grid.

Here's an example of how a cell gets selected from the atlas; in this example it's the basic face shape which has the mouth and nose.

Here's an example of how a cell gets selected from the atlas; in this example it's the basic face shape which has the mouth and nose.

We wanted the animators to be able to control each individual facial feature so that they could mix and match for nuance of expression. This meant that we repeat the cell selection process for each facial feature and couple that with additional controls.

We wanted the animators to be able to control each individual facial feature so that they could mix and match for nuance of expression. This meant that we repeat the cell selection process for each facial feature and couple that with additional controls.

I won't go into detail on how every facial feature is constructed, since the general methodology is the same per piece and we work around any feature-specific problems that might crop up within material functions designated to handle that.

I won't go into detail on how every facial feature is constructed, since the general methodology is the same per piece and we work around any feature-specific problems that might crop up within material functions designated to handle that.

Controlling the selection of cells and adjustment of position through blueprints using custom primitive indexes is super easy and flexible. It means the rig can easily access the parameters on the shader.

Controlling the selection of cells and adjustment of position through blueprints using custom primitive indexes is super easy and flexible. It means the rig can easily access the parameters on the shader.

This means on the rig side, we use an enum that simply dictates what the values of the primitive data should be.

Stepping away from the primitive data, we needed to come up with a way to handle the eyes and the back eyebrow so that they would be occluded by the face.

 To do so, we make a mask that I could break down in the shader. Red would signify sclera area, green would signify where the back brow would be occluded. I make use of the blue channel on characters with goggles or masks to occlude both eye brows.

To do so, we make a mask that I could break down in the shader. Red would signify sclera area, green would signify where the back brow would be occluded. I make use of the blue channel on characters with goggles or masks to occlude both eye brows.

Then it's just a very simple matter of layering the masks appropriately using lerps.

Then it's just a very simple matter of layering the masks appropriately using lerps.

Next, since we were on a tight timeline,   we opted for a shader based solution to both enemy color variation and for blood splatter.

Next, since we were on a tight timeline, we opted for a shader based solution to both enemy color variation and for blood splatter.

To make the variations, I created a mask that delineates certain parts of the body we wanted to split out.

To make the variations, I created a mask that delineates certain parts of the body we wanted to split out.

I then desaturate the base texture and multiply in the variation colors we setup previously. This means anyone can go in and fine tune the color choices with ease!

I then desaturate the base texture and multiply in the variation colors we setup previously. This means anyone can go in and fine tune the color choices with ease!

Then, once variations are setup, we can bring the result into the next material function which is what handles the blood effect on characters.

Then, once variations are setup, we can bring the result into the next material function which is what handles the blood effect on characters.

I have two more masks for the blood (this is the last of the masks!) The one on the left defines where the blood can land. The purpose for this is that we didn't want blood covering the enemy faces poorly, like awkwardly overlapping an eye or the mouth.

I have two more masks for the blood (this is the last of the masks!) The one on the left defines where the blood can land. The purpose for this is that we didn't want blood covering the enemy faces poorly, like awkwardly overlapping an eye or the mouth.

The purpose of the right mask is to delineate areas of distortion, hence why I name it the distortion mask. In this example, the procedural blood would've landed flat across the glove and crowbar, but with the distortion mask it gets broken up.

The purpose of the right mask is to delineate areas of distortion, hence why I name it the distortion mask. In this example, the procedural blood would've landed flat across the glove and crowbar, but with the distortion mask it gets broken up.

It's very simple in setup, simply taking the red and green values of the distortion mask and adding them to the UV input of our blood splatter texture. This means whatever pixels that would fall in this overlap that I want to avoid, would get nudged away

It's very simple in setup, simply taking the red and green values of the distortion mask and adding them to the UV input of our blood splatter texture. This means whatever pixels that would fall in this overlap that I want to avoid, would get nudged away

And finally, the last step: Fading in and out. We couldn't use transparency because of how a lot of characters are built up of overlapping body pieces. Instead, we opted to use dithering.

I add in the glue mask to the process in case a glued enemy gets killed, the glue gets dithered along with them.

I add in the glue mask to the process in case a glued enemy gets killed, the glue gets dithered along with them.

The reason we lerp it to the original texture, instead of the DitherTemporalAA node, is that if we left it on the DitherTemporalAA node even at an alpha of 1, some pointillism would be leftover

The reason we lerp it to the original texture, instead of the DitherTemporalAA node, is that if we left it on the DitherTemporalAA node even at an alpha of 1, some pointillism would be leftover

Sorry this was a lot! Thank you for reading, I had a ton of fun on this project with some fun problems to solve and feel like I grew a ton as an artist as a result. Hope you enjoyed!

Sorry this was a lot! Thank you for reading, I had a ton of fun on this project with some fun problems to solve and feel like I grew a ton as an artist as a result. Hope you enjoyed!