Francisco Garcia-Obledo shared the way he and his colleagues improved the blood in Assassin’s Creed 3 Remastered.
Francisco Garcia-Obledo shared the way he and his colleagues improved the blood in Assassin’s Creed 3 Remastered.
The purpose of this post is to show how we improved the blood from AC3 with the latest tech changes we implemented in Anvil. This is how the blood looks like in the final game.
Here is a comparison. In the original game, the blood was simulated with just one particle emitter with a generic texture (the same texture map was used to simulate dirt, snow bits, etc). The nice touch it had was the use of a threshold alpha animation which is a common technique nowadays and used in a wide range of effects. My approach here was to use the same technique but with a more realistic base texture plus adding more emitters and better dynamics (gravity, dampening, etc). I ended up having 4 emitters: thick drops, smaller drops, core, and spray.
Original vs Remaster:
TEXTURE CREATION
I’m going to explain how I created the core blood effect (the other ones are basically the same but changing the shape of the blood). The purpose of this breakdown is to show my workflow in which I mix both procedural and traditional techniques.
I added Blender into my pipeline when I was working at Gears 4 and we didn’t have any fluid simulation software and I’ve used it since then on other projects, from Red Dead Redemption 2 to Candy Crush.
The scene setup is very basic, it just consists of one geometry acting as fluid emitter and other being the collider. There is some animation on them to achieve (after many attempts) the look I was looking for.
Scene setup | Emission | Collision | Animation |
When we choose a frame we are happy with we are ready to isolate the fluid mesh and place a camera to take renders:
This is how the fluid looks after the collision and isolating it from the rest of the geometries. By default, you get not very realistic results, the texture looks too “blobby” | You can work with the fluid geometry as if it was a “normal” geometry. I applied here basically 2 modifiers, Displace (to get rid of the blobbiness) and Smooth (to clean the geometry) | Render of the normal map ready to be imported into the engine | Render of the opacity map |
IMPORT TO ENGINE (1)
We can move to our engine, import the textures and create the shader we will use.
Basic shader |
PBR values |
Fake reflection
I always like to add fake reflection to liquid textures so that we can still observe some specular highlights and preserve the liquid feel (it is especially noticeable on the darkest bits of the image) | Cubemap texture |
CREATION OF THRESHOLD TEXTURE MAP
At this moment we have a basic blood sprite with which we can start to work on the dynamics of the effect. But, the disappearance of this blood splat will be a classic alpha fade out and, in my opinion, this is outdated now for things like liquids. Let’s move on and create the threshold map. For this, I incorporated into my pipeline Substance Designer.
- First, we have to import the opacity map we got from Blender and invert it:
- After that, we are ready to add the Distance node. I don’t really know the maths behind it or what other people use it for but let me explain it the way I understand it which will make this process easier to understand. This node takes the intersection between black and white pixels and creates a gradient inwards defined by the Maximum Distance parameter. I see it as an inverted push (3dsmax)/ displace (Blender) modifier:
There is one thing here to pay attention to. This node works with absolute values so a value of, let’s say 20, works for the thicker parts but will create almost no gradient in thinner parts (such as the squirts). To solve that, I divided the image in 2, the center and the squirts (the maximum distance was set to a much lower value for these)
- When we are happy with our threshold map, one thing we have to be mindful of is that this texture doesn’t have to be necessarily “nice”, we have to think of the values of it. So, as this is a grayscale texture that will define the animation of the opacity, we need as many values as we can. So, one common mistake I’ve seen out there is having threshold maps without a smooth histogram. With that, I mean that the histogram should have pixels filling up the whole range, from 0 to 255. To do that I added the Auto Levels node:
After some tweaks here and there, we will get our resultant threshold map.
Note: You can simulate the threshold animation with the threshold adjustment layer in Photoshop.
Texture map | Animation |
You can see that although the animation is smooth and will look ok like this, it looks unnatural. Especially, it creates some hard angles that you never see in liquids. To fix that you can modify the texture as if it was a standard texture, adding filters to it but being careful that you don’t break the shape of it and, more importantly, you keep the whole range of grays in the histogram.
Texture map | Animation |
IMPORT TO ENGINE (2)
Taking the shader from where we left it we will add the alpha threshold animation (also called erosion). It depends on the engine we are using so here I’ll link some references to this topic. So, let’s add the threshold map we created into our shader, this is how it will look up to this point:
Looks cool but you can appreciate what made me create this whole pipeline for when the liquid is untouched the borders look nice with some height but as long as the threshold animation starts to act, those borders are lost and the liquid loses the feeling of something “thick”. To me, this looks as if you were cutting paper. Here you can see the normals of the image in which you will see what I’m referring to:
Somehow we need to change the normal map over time and sync it with the threshold animation. There was a cool tutorial explaining how they did it on The Last of Us. I will explain here my method with no programmers involved, no crazy maths.
SECONDARY NORMAL MAP
Back in Substance Designer, we will create a normal map directly from the threshold map. We will do this with the Height to Normal node (in this image the blue channel is removed but we don’t need to do it for this example):
Using this normal map will give us nice borders during the animation but will make the center of it to be “generic” and won’t have the nice details we got from Blender.
So, my way of thinking here was, instead of jumping to the shader itself I started playing around in Photoshop being always conscious of what we can do in real time. Up to this point we have:
- Primary normal map from Blender: this gives us a nice static result
- Secondary normal map from Designer: this gives us nice borders during the animation
- Threshold map: this acts as a mask and gives us the animation via shader
Basically what we are aiming for is to get the primary normal map in the center and the secondary normal map on the borders. So what I thought is, I will mask the normal maps based on the threshold map, inverting one of them. Then I will expose variables in the shader to control the brightness/contrast of the threshold map so that we can expand/shrink the borders. Here are 2 examples (pay attention to the differences between the 2 threshold masks):
- Thinner border:
- Wider border:
FINAL RESULT IN ENGINE
In our shader, we have to mix both normal maps in the same way as Photoshop. So I ended up adding some maths to the mask used in the blend/lerp function (brightness and contrast):
- WITH procedural borders:
- This is what we had without the “procedural” borders:
In this example case my final touch was adding UV Distortion to all above so that we had an extra animation on it:
This will be it, the rest would be using this technique with other textures and compose the effect with different emitters. Of course, you have to be careful with how many textures you add and all, that’s why I used this technique with a generic normal map plus generic threshold map to use it in other cases in which I didn’t/couldn’t add a specific texture. Here is again the video with the result in the game: