When I started working on the material in Unreal Engine 3 I had the following goals in mind:
When I started working on the material in Unreal Engine 3 I had the following goals in mind:
Fulfilling these core requirements was simple enough after some experimentation and I ended up spending most of the time on optimizing and making the controlling parameters easy to handle.
There is an option for factoring in object position when it comes to time sensitive functions, like panners. It’s just (ObjPos.X + ObjPos.Y + ObjPos.X + Time) which makes the same material instance look different at different places.
To keep the node network manageable I relied heavily on custom material functions. Min(), Max(), Sign() and similar easy to make nodes helped a lot. I also made several “Relay” nodes (RelayScalar, RelayVector2, etc) which don’t do anything only guide links and prevent the crossing connection lines making a mess. (Like the stock “PassThrough” node but that only handles vector 3.)
I batched related scalar parameters into a vector one to decrease clutter in the material instance editor. For example in the vector 4 param called “FireTilingAndSpeed” R and G controls tiling while B and A adjusts speed.
The fire texture is grayscale which has two main advantage: it saves texture memory and the different operations in the material don’t distort the original colors.
The fire is colorized as the very last step, by a color gradient. This way the color adjustment is more intuitive and entirely decoupled from the rest of the fire parameters.
The opacity mask is a distance field which allows flexible coverage and hardness adjustments. Flames don’t fade out in the classic sense (which looks LDR-ugly) but shrink (coverage decreases).
The fire texture is factored in to opacity to add detail to the very low resolution masks.
Since I wanted fine control over material complexity I made sure that the node network is modular. The rule is simple: every single static switch parameter makes the material more expensive and each one is disabled by default.
Vertex noise is implemented in a way that the top of the fire is displaced more than its base. I also made sure that the surface is never pushed inward to prevent it sinking into the burning object the fire envelops.
Some fires on the video have swaying enabled but the effect was toned down. The following is an extreme example where the material is on a simple quad.
The material has another version, to be used in particle systems. The core mechanics are the same but the features which make no sense on a sprite (like vertex displacement or fresnel fade) were removed. Certain parameters can be controlled through vertex color and dynamic parameters.
In its current form the fire material produces acceptable results but there are a few things which need further research, including support for moving bases, DX11 tessellation and more complex, swirly distortions.
With the material done I needed a context for it so I started making assets in Luxology modo.
The high poly candle is sculpted, multi-resolution Pixar SDS while the holder is a more basic SDS model, together weighing 225K polys. The “low” poly mesh is 9K polygons as polycount was of no concern in this case.
Occlusion (accessibility) layers were used to darken crevasses and brighten edges. Diffuse, normal and SSS maps were baked.
I needed no mesh for the candle flame as it was a particle system (with two particles) so I moved on the next asset, the stone pillar which was meant to provide a base for both the candle and the torch.
Same deal as before, sculpted pSDS (655K), occlusion layers for dirt and edge highlights, 2K polys for the in-game mesh. I used a high resolution stone texture (4Kx4K) as a base, which I stitched together from several photos taken in a quarry nearby.
I used instances of the base mesh arranged in a 3×3 grid to check proper tiling. They seamlessly connect not just in an even grid but also when a row is offset by half a tile which breaks up repetition a bit. (Thinking back I should have made the blocks in a way that they also match when one is rotated 90 degrees.)
The final piece of the showcase was the medieval house. It was not suited for a high poly baking workflow so I went with the more conventional method: single low poly mesh with a texture atlas on it.
The third image show the convexity/concavity based layers which were baked onto the second (lightmap) UVs. It’s mixed onto the diffuse texture in the unreal material, with an overlay-like blending mode.
The next step was importing all the assets and setting up the material instances. At the end of that not particularly exciting procedure I had all the building blocks I needed to make the scenes.
The first one had the candle mesh, it’s flame particle system and a few lights.
A bright point light has a big radius and casts dynamic shadows while another acts as a filler to relieve the deep shadows on the candle holder. A dim spot light, embedded in the wall, lightens up the ground.
Since there was a disconnect between the flickering flame and the stationary pointlights I made a RandomMoverActor to carry the lights around. (They are those griffon heads.) The lights also have a light function which vary their brightness.
The volumetric smoke is a FogVolumeSphericalDensityInfo using a custom material:
It’s based on the mix of 3 textures projected on each world axis, with some billowing, panning, tiling and contrast controls. It has the same flicker logic as the light function used by the lights so the two material instances will be in sync if they have the same random seed parameter.
The projection stretch artifacts are clearly visible on the video but they are not that bad with a busy background.
With all the setpieces in order the last thing I needed was the camera control. I used Gavit’s input remapping which made it easy to link camera location and rotation to the same properties of the Hydra controller, right in the editor. After playing around with the smoothing and sensitivity adjustments I ended up with a handheld virtual camera.