Remember the last episode of Game of Thrones? By the last, I meant not the 6th episode which made no sense and nearly destroyed the story of the whole show, but 3rd episode – The Long Night. Surely, most of us were absolutely hyped for the Long night episode. For me, I was mostly looking for a battle between a dead dragon and two other dragons also how the Dothraki army would fight against the dead.
The Dothraki was so fearsome and terrorized everyone throughout 8 seasons. They were living legends and were so famous that someone tells stories about them to scare people. Also, their army was the biggest ever Dothraki army. Then the dead came and massacred them with a blink of an eye. (Talk about a disappointment!)
Anyhow, there was one shot that was appealing to me. The shot was, you know, a witch came to the army and ignite a fire on their blade. At that moment, I thought it would be painful to create such fire effects for a whole army. There are hundreds of them and each of them potentially moves slightly, because they were sitting on a horse, and, of course, it is fire simulation. How would you create and simulate hundreds of fire blade efficiently? I decided to give a try. Here is my process from a TD view.
Firstly, I created a fire simulation on Maya Fluid. That hero fire fx would be sent to a lighting and rendering artist while I figure out how to simulate hundreds of them. Obviously, I would not want to manually work on them and would assume a fire emitter object and fluid fx will change when feedback comes. With those assumptions, I started coding in Python.
The first issue is creating clones of hero fx. Here I assumed that I would have animated objects from layout and animation artists that could be emitters. That could be anything – mesh, curve, and shape etc. Because of this, I abstracted a type of the object and wrote a function to create a clone of hero fx for each animated objects.
def duplicate_fluids(fluid, obj): """ Create fluid containers :param fluid: a source fluid name :param obj: an emitter object type """ # Duplicate a fluild container for obj in cmds.ls(type=obj): cmds.duplicate(fluid) cmds.delete(fluid)
Then those fluid containers need to be transformed and linked to emitters. MatchTransform method and a translation were used for this task. Once each fluid containers moved to a position I wanted, an emitter created and linked to it.
def create_fluid_on_emitters(emit_objects, fluids, obj, y_offset, rate=100, her=1, der=1): """ Create fluid containers on an emitter and move container. Also, set some attributes of an emitter :param emit_objects: objects emit from :param fluids: fluid containers :param obj: emit object type :param y_offset: move y of a container :param rate: emitter rate :param her: heatEmissionRate :param der: densityEmissionRate """ for i in range(len(cmds.ls(type=obj))): # Moving up a fluid container cmds.matchTransform(fluids[i], emit_objects[i]) cmds.select(fluids[i]) cmds.move(y_offset, y=True) # Create an emitter and connect it to fluid container emitter = cmds.fluidEmitter(emit_objects[i], type='surface', rate=rate, her=her, der=der) cmds.connectDynamic(fluids[i], em=emitter[i])
Now I am ready to cache them. However, I still need to select them. Instead of selecting them from a maya outline, I wrote a function to accomplish it. The function simply checks all ‘transform’ nodes whether their name has ‘fluid’ and a substring from a start is shorter than a given number. Since there are so many other unnecessary nodes with ‘fluid’ in it, I needed to find nodes like ‘fluid1’, ‘fluid12’, and ‘fluid3433’.
def select_fluid_containers(count): """ Select all fluid containers :param count: a number of digits (fluidXXX) :return: fluid containers """ # Select fuild containers return cmds.ls([f for f in cmds.ls(type='transform') if (('fluid' in f) and (len(f) <= len('fluid') + count))])
With those functions, I can create any number of fire fx and cache them without touching anything. Lastly, on the original shot, there was a slight offset for fire ignition. With a camera pan, it did add suspension to the screen. To assist this, I wrote a function to change a start frame of fluid fx. Now I simply need to use the function in a loop to set a slight offset. It changes start frame of all fluid containers one by one, 5 by 5, or any number in a loop.
def set_animation_start(fluid, start): """ Set startFrame of a given fuild :param fluid: fluid container :param start: startFrame """ cmds.select(fluid) cmds.setAttr(cmds.listRelatives() + '.startFrame', start)
Finally, I can package those functions in a python script, add some animation or write a guide creating an offset animation, and send it along with hero fx to an animation team.
In the future, I could have a different fire fluid fx to choose from. For this case, I will send a list of fire fx to the ‘duplicate_fluids’ function and randomly select from them for each animated objects. And, offset may need other work such as adding a bit randomness. That will be adding a random number on the start frame when using the ‘set_animation_start’ function. Furthermore, when feedback comes or hero fire fx is changed, I could pass a dictionary with necessary attributes to the functions.
Thanks to Python and Maya. Creating hundreds of fire turned out to be not so hard.