home about gallery publications education links contact

..::school of fish ("crowd control")::..

Throughout the past few years we’ve seen crowd simulations in several features, documentaries and TV series. Here we are going to create our own crowd simulation and environment to simulate an underwater school of fish, but how can we go about creating such an effect?

We could go down the particle route, however getting a smooth motion when there is so much particle to particle interaction can result in an erratic motion, intersection problems and the crowd members turning on a hairpin without some serious fixing. Therefore we are going to use 3ds max’s Crowd system which comes with Character Studio (native in 3ds max since version 7). Using the model provided we will set up the initial delegate’s characteristics – how it turns, acceleration, maximum speed, banking properties (etc) before duplicating it multiple times. Next we will add characteristics to our delegates – what happens when a certain event occurs, when they come into contact with another object or what their main goal is, before performing the initial test simulation and adding additional delegates as necessary. Next we will introduce the provided fish model which has basic animation states that Crowd will reference depending on the individual delegate’s motion – this is set up in the motion Synthesis which uses block controllers to point to the desired animation frame range of the animated source object for that specific behaviour. Finally, we will set up the scene’s lighting and environment effects before adding in a camera.

Depending on the amount of delegates we decide to introduce to this tutorial, it may take some time to simulate, so if you decide to simulate the flock with hundreds and hundreds of fish you might need to run the simulation overnight, or go and do something else – wash the car, rotate your tyres or do a spot of embroidery. Be aware that for each simulation you perform it takes a little bit longer to calculate, so if you are going to perform a heavy one, then it is advisable to save the scene, reset (or restart) 3ds max and then run the simulation.

Enlarge Screenshot Open the fish_school_start.max scene included in the resources zip file below. Accept any unit change if prompted. Here we have a basic scene with the fish that we are going to use with some initial animation states already applied – select the “mackerel anim source” object, grab hold of the Timebar and scrub it from side to side over the (preset) 300 frames.
Enlarge Screenshot If we go across to the Modifier tab, we will notice that the animation has been generated using several modifiers. Starting from the bottom (the basic fish model), we have a Volume Select modifier which drives a soft selection so that the strength of any modifiers we apply afterwards is reduced along the length of the model.
Enlarge Screenshot The first animation state set up is the swimming motion, after an initial “cruise” period (no animation assigned). This is important as we will assign this stationary period to the system’s animation later on. After an initial swim cycle generated by animating the Wave modifier’s Phase, there are two additional turning cycles applied which you can introduce to the system if you wish later on.
Enlarge Screenshot To get the school to travel around the scene, we are going to create an object that the fish will attempt to follow. This object’s motion will be smooth yet random, so we will generate this by using a Noise position controller and setting its parameters accordingly. In the Top Viewport, create a Geosphere with a Radius of 100 and label it “School Follow”.
Enlarge Screenshot With the Geosphere object still selected, right click in the Viewport and select Curve Editor in the Quad menu. Next we will replace the existing position controller of this object with a random noise generator controller so that the object moves around the scene smoothly. In the Curve Editor, select the Position controller, right-click and select Assign Controller. Select Noise Position from the list and click OK.
Enlarge Screenshot The Geosphere object’s position is now driven by the Noise controller, so it has been immediately relocated. Scrub through the animation, noting that the object’s motion is erratic, therefore we need to smooth it out. Set the X, Y and Z Strength to 2000 for a larger range of motion, amend the Frequency to 0.01 to increase the wavelength and turn off Fractal Noise so the motion is smooth.
Enlarge Screenshot The crowd system calculates the simulation using non-renderable helper objects called Delegates. Our renderable objects, namely the fish, are linked to these objects later on. Here we will set up our single initial delegate, and assign properties which will tell it how to move, turn (etc). In the Top Viewport, create a Delegate helper object with a Width of 17, Depth of 25 and Height of 17. Label it Delegate_Fish01
Enlarge Screenshot Turn off Constrain to XY Plane so it can move in all axes. In the Delegate’s Motion Parameters rollout, set the Average Speed to 20 for a fast darting motion. Set the Decel Weight to 0.1 (turn angle) so it doesn’t slow down as much when turning. Set the Incline and Decline Angle settings to 10 so we have a slight deceleration when the delegate is turning to travel up or down.
Enlarge Screenshot In the Turning group, set the Max Turn Velocity to 60 which is how many degrees the delegate can turn in a single frame. As we are dealing with fish, which can turn pretty sharply, this value is set high. Set the Max Turn Accel to 10 so we can have a sharp turning acceleration. Set the Max Incline and Max Decline to 30 as the delegate (fish) cannot turn vertically or downwards as easily as it can sideways.
Enlarge Screenshot In the Banking group, set to Max Bank value to 10 so that the fish do not start tilting when turning. Looking at reference material online, we notice that most fish tend to stay virtually vertical unless turning at exceptionally high speeds. Therefore we want our fish to stay upright as much as possible, therefore reducing this value so it is more subtle.
Enlarge Screenshot Amend the Max Bank Velocity setting to 10 which, as with the Max Turn Velocity, indicates how many degrees that the banking will change per frame, so we get a banking change that is sudden when turning at high speeds, which these types of fish tend to do, to keep formation and avoid predators.
Enlarge Screenshot Now that we have the initial properties of our fish delegates established, we will distribute a small number of them in the scene to test out the simulation using an object as the distribution method. In the Top Viewport, create a Geosphere at 0,0,0 with a Radius of 500 and label it “Delegate Distribution”. Add a Noise modifier and set its X, Y and Z Strength settings to 500.
Enlarge Screenshot Before we start setting up the behavioural properties, we will first scatter a small number of delegates over the distribution object. In the Top Viewport, create a Crowd helper object. Click its Scatter button and, in the resulting pop-up panel, add the Delegate_Fish01 object to its Object to Clone field. Set the number of objects to 49 and turn off Clone Controllers so any animation isn’t also cloned. Click Generate Clones.
Enlarge Screenshot In the Position tab in the panel, select On Surface and add the Delegate Distribution object to its Grid/Box/Sphere/Surface/Shape field. Ensuring no scattered objects intersect, enter a Spacing value of 1.5 and click on Generate Locations. In Rotation tab set the Sideways and Up/Down Deviation values to 180 to get the scattered delegates to orient themselves at random so they turn and head to the target object. Click on Generate Orientations and OK.
Next we will add the main behaviour sets for our crowd system. We need three main behavioural events – Seek Target: to find the moving Geosphere we have set up using the Noise controller, Seek School: to find the main mass of the school and Avoid School: to prevent delegates hitting each other. In addition to this, we will introduce an additional behaviour which is driven by a Wind Space Warp to get the fish to be buffeted around by ocean swell! We are currently working with a reduced number of delegates so that our simulation calculation times are reduced, as having more delegates in the simulation at this stage would not be suitable for any test we perform.
Enlarge Screenshot Click on the New button and select Seek Behavior. Edit the name to “Seek Target”. In the new Seek Behavior rollout, click on the None button and select the School Follow object. Set the Method to Force so the attraction to the target is constant. Add another Seek behaviour. Rename it Seek School, select Multiple Selection button and select all of the Delegate_Fish helpers. Select Average of Targets to find the central average point of all listed delegates.
Enlarge Screenshot Add an Avoid Behaviour and rename it Avoid School. Click the Multiple Selection button and select all of the Delegate_Fish helper objects. Set to Look Ahead value to 150 so that the delegates look further forward to try to avoid objects instead of having to stop and wait. Set the Detour Angle to 90 so that the delegate will not turn around to try to find a way around and the Brake Pressure to 0.5 so the delegate will more likely try to find another route.
Enlarge Screenshot In the Repel Group, set the Strength to 0.5 to increase the repulsion force, which forces delegates at close proximity to avoid one-another. Set the Radius and Falloff to 10, so that even though we have a large radius size, the repulsion strength is stronger towards to the delegate than at its outer boundary.
Enlarge Screenshot Add a Wind Space Warp to the scene in the Top Viewport. Set its Strength to 0 so that direction is not affected, reduce Turbulence to 0.5 and set the Scale to 0.001 for a large turbulent waveform. Finally, add a Space Warp Behaviour to the Crowd helper and add the Wind Space warp to its Space Warp Behaviour rollout.
Enlarge Screenshot Now we have our behaviours set up, we now need to assign them to the delegates and to weight them as necessary. Click on the Behaviour Assignments button and, in the resulting panel, create a New Team and add all delegates to it. Label the team “Fish School”. This makes managing multiple delegates with the same properties easier to assign behaviours.
Enlarge Screenshot Once the team has been created, it has been added to the Teams list. Select the Fish School team and, in the Behaviours list, select all four behaviours and click on the New Assignment button to add them to the Behaviour Assignments list for weighting. We want the Wind Space Warp to affect the delegates overall, so leave the weighting at 1.
Enlarge Screenshot Next in order of preference is finding the target, so set the weighing for this at 0.75 and finally the Seek School weighing to 0.5. Obviously if the school was avoiding a predator then the weighting would be different, with Seek School having a higher weighting. Click on OK to accept these values. Next we need to run the simulation, but first we need to tweak a setting or two beforehand.
Enlarge Screenshot In the Solve rollout, amend the End Solve setting to the length of our scene’s animation, which is 300 frames. If we are performing multiple test simulations, it is always wise to Delete Keys before Solve, so enable this. As we won’t need every single keyframe, amend the Positions and Rotations Save Every Nth Key settings to 2, save the scene and then click Solve, which should take a few minutes to calculate.
Enlarge Screenshot Now we can go back and add more delegates, but for this it is advisable to remove all existing delegates apart from Delegate_Fish01 and clone another 199 as before, re-scatter, position, orientate, assign into behaviours and teams before assigning to behaviours. This might sound like wasted time, but, like with particles, we ALWAYS test simulations with reduced numbers. With the new delegates set up, run the simulation again. This might take a little while.
Enlarge Screenshot With the new delegates created and new simulation ran with the full 200 delegates, we can now assign the model to the delegates. For this, we will go back into scatter to create the clones before orienting and linking them to the delegates using Object/Delegate Associations which binds the relevant model to its delegate so that animation properties can be derived from them later on.
Enlarge Screenshot Hide the Delegate Distribution and School Follow objects as we no longer need them. In Crowd, select Scatter and select the Object to Clone as the “Mackerel Anim Source” object. Set the number of copies to 200 as we still need to use the original object. Click the Generate Clones button.
Enlarge Screenshot As the fish are too big, we need to scale them down to about 2% of their original size. Go to the Scale tab and enter the value of 0.02 (with 1.0 being 100%) in the X, Y and Z Scale fields and click on Generate Scales. You will notice that the original fish model has also been scaled down – this is normal. Click OK to exit this panel. Don’t worry that the fish have not been linked with the delegates as we will sort this out next.
Enlarge Screenshot We now need to assign a mackerel object with its corresponding delegate. Click on the Object/Delegate Associations button. In the panel, click on the Objects list Add button and add in objects “Mackerel Anim Source01” to #200, NOT the main Mackerel Anim Source object as we need this for animation reference later on.
Enlarge Screenshot Add all of the Delegates to the Delegates list. Turn off Align Scale as we don’t want the objects to inherit the delegate’s scale (which is at 100% else we will end up with a lot of large fish!), click on Align Objects with Delegates to reposition them, and Link Objects to Delegates to bind the corresponding model to the delegate (as illustrated in the Objects and Delegates list). Click OK.
Enlarge Screenshot Scrub through the animation to ensure that all 200 non-animated fish are linked to a delegate and that the one remaining source fish is animated, yet un-linked in the middle of the scene. With our fish bound to the delegates, we now need to reference parts of the source object’s animation for each of the animated fish so they behave accordingly when accelerating, cruising (etc).
Enlarge Screenshot In Crowd, click on the New button in the Global Clip Controllers rollout. Select the Mackerel Anim Source object and click OK to add it to the clip controllers list. Highlight this entry and click Edit to set up its parameters. Click on the New button in the from Global Object group and rename it to Accelerate. Set the Start setting to 5 and End to 15 which is the Swim section of the source object.
Enlarge Screenshot To simulate a constant speed, we will add in another animation state. Click on the New button as before and rename the entry as Constant. Set the Start setting to 0 and End setting to 4, which is the non-animated section of the source animation sequence, so the fish simply glides through the water using its existing momentum.
Enlarge Screenshot In addition to this, we will add in a random flick of the tail to keep momentum. Click on the New button again and label the new entry as Cruise. Set the Start setting to 5 and End to 15 as in the Accelerate motion clip. Next we need to set up parameters which will call on these animation states when a certain motion event occurs.
Enlarge Screenshot Click on the State tab and click on the New State button. Rename the new Synthesis State to Accelerate and click on the Add Clip button to select the Accelerate Motion Clip it will reference. As we want the animation to blend to the central position of the swim cycle, we will set the Animation Start Percent setting to 50 so that it is ½ way through the cycle.
Enlarge Screenshot Click on the Edit State button to bring up an additional panel. Click on the Accelerate tab and enable Use Acceleration. Click on unique and both Increasing icons to monitor for increasing values. Enable Scale Playback Speed and set to Percentage to 300, which will play the source swim cycle 300 times faster to suggest rapid acceleration. Click the Exit button to close the panel.
Enlarge Screenshot Next we need to setup Constant & Cruise, which will be called at random (due to equal weighting) through a single Synthesis State. Click on New State and edit the name to Constant & Cruise. Set the Animation Start Percent to 50 as before. Click on Add Clip and select both Constant & Cruise clips. Click OK to accept the selection.
Enlarge Screenshot Click on the Edit Properties button, and in the resulting properties panel enable Use Speed in the Speed tab. Enable Scale Playback Speed and set the Base Speed to 3, which tells the system that the original animation cycle speed should be played at the delegate motion speed of 3, and should scale the animation cycle speed up and down accordingly if the motion speed is not at this Base Speed value. Click Exit.
Enlarge Screenshot The last stage of the crowd setup is to tell the system which objects should be affected by the synthesis and how we blend the animation states together (duration). Master Motion Clips & Synthesis Blend Parameters. Click on the Synthesis tab and click on the New Master Motion Clip button, and add all Mackerel Anim Source objects from 1 to 200 (this might take a little time).
Enlarge Screenshot As we need the change in animation state to be quite fast, we only need a short Blend setting from one clip to the next. Therefore, go through the From Clip and To Clip lists for every combination and set each Blend Start setting to 2. Finally, click on the Synthesize All button to apply the synthesis to all objects. Output an animation preview from the Perspective Viewport to see the result.
  Lighting the Environment
The lighting in the scene seems quite straight-forward, however there are several factors that need to be taken into consideration. Firstly, the main key light (the sun) casts volumetric rays (covered later on) through the water which our school of fish interact with them. Secondly, the broken surface generates multiple specular highlights, so we have a large specular lighting array above the school. Finally, we have additional lights shaded and positioned to illustrate scattered ambient lighting from the water’s particular matter. In the final scene in the resources zip file, these lights don’t have shadows enabled as default to save render time, but they can be turned on for a better effect.
Enlarge Screenshot In the Top Viewport, create a Free Direct light at 0,0,0 and label it Volumetric Rays. Move it vertically upwards 10000 units in the Front Viewport. Enable Shadow Maps so that the fish cast shadow rays within the volumetrics. To control the volumetric ray falloff, enable Use Far Attenuation and set the End value to 12000 so we get a large falloff.
Enlarge Screenshot In the Directional Parameters rollout, set the Hotspot/Beam to 865 and the Falloff/Field to 5000 so that the light fades out between these two values (with extra control in the Projector map). Expand the Advanced Effects rollout and instance the “Rays Projector” Mix map from the Material Editor to its Projector slot. As we have a large area to cover, we need a relatively large Shadow Map size…
Enlarge Screenshot …Expand the Shadow Map Params rollout and set the Bias to 0.01 to bring the shadows in tight to the objects that cast them. Set the Size to 1024 and Sample Range to 16 to blur the shadow slightly. Expand the Atmospheres & Effects rollout and click on the Add button to add a Volume Light effect. Select this entry in the list and click on Setup. Enable Exponential.
Enlarge Screenshot Next we will create a large Specular Lighting rig to simulate the broken light from the waters surface. Create a Target Direct light at 0,10000,0 in the Top Viewport and drag the light’s target to the center. Instance this light at 90 degrees (at 10000,0,0) and another two times to create four instanced lights pointing inwards. Select all four lights (not the targets) and instance-rotate them 45 degrees to create an array of 8 lights (illustrated).
Enlarge Screenshot Select all eight lights (not the targets) and reposition them vertically in the Left Viewport. Instance the lights again to create another ring and reposition as illustrated. Enable Shadow Maps, set the Multplier to 0.25 and light colour to RGB 184,198,208. Enable Overshoot and set the Falloff/Field to 2000. Set the Shadow Map Bias to 0.01, Size to 256 to keep render times down, and Sample Range to 8 to blur the shadows.
Enlarge Screenshot Next we need to simulate subtle ambient light emitting from the deep waters. Create a ring of instanced shadow casting lights as before, this time underneath the school as illustrated, labelling the initial light Deep Water Illumination01. Set the Multiplier to 0.25 and colour to RGB 58,77,89. Set the Falloff / Field to 2000, and turn on Overshoot. Copy the light colour to the Environment Background colour as illustrated.
Enlarge Screenshot This time turn off Specular in the light’s Advanced Effects rollout as we don’t want additional specular highlights on the underside of the fish; we just want some additional tinted diffused lighting. Expand the Shadow Map Params rollout and set the shadow map settings as in the other lighting array (if the settings aren’t already inserted).
  Volumetric Lighting
To simulate the rays of light that are cast through the water in the final scene and in the illustration we are using Volumetric Lighting. To simulate the moving surface we have a nested map tree, controlled by a Gradient Ramp map so we have more control over the light’s falloff away from camera. Inside this map are additional maps which control the animation of the rays to simulate the animated surface – two Smoke maps of different sizes and at different angles to one-another, which have their Phase values animated resulting in an effect that simulates different waves intersecting. Be aware that Volumetric Lighting has an adverse effect on rendering times, especially if the quality is cranked up and are casting shadows so use sparingly!
Enlarge Screenshot Using the Perspective Viewport, position the viewing angle so we get the best action shots of the school swimming around us. Next, hit CTRL+C to create a camera from this view. To simulate depth, add a Fog environment effect above the Volume Light effect, set its colour to that of the background colour and enable Exponential. Finally, enable Show Environment Ranges in the Camera and set the Far Range to 5000 for fog depth control.
  Final Scene Camera Animation
In the final scene in the resources zip file the camera has been animated in several ways to get a nice realistic underwater motion. Firstly, we need to create an effect to simulate the camera being buffeted around by ocean swell. This is produced by linking both components of the camera (Camera and Camera Target) to Dummy objects which are animated using subtle (unique) Position Noise controllers. Next, the camera’s target is hand-animated to follow the fish around the environment, while the camera itself is only affected by the animated Dummy object. Additional elements need to be introduced to the scene to give the impression of motion, namely particular matter (a Particle Flow system) so these particles move past the camera’s FOV as it is being animated by the Dummy object(s).
Enlarge Screenshot Our final simulation is pretty convincing, however there are a few things which could be added to take it to the next level. As some of the fish turn quickly, we will need to introduce the turning animations in the source animated object into the synthesis. Also, try going back to the delegate simulation and add in a few predators which are “herding” the fish into a tight ball or get the school to follow a mass of smaller fish in a feeding frenzy! Finally, finish off the scene with some additional debris in the water so we get a sense of travelling through the water when we animate the camera. If you feel that it takes too long to render, try reducing the quality of the volumetric lighting or disabling shadows.
Download the max file! Zip file to accompany

Initially published: 3D World magazine, Issue 70, November 2005.

Copyright © Pete Draper, November 2005. Reproduction without permission prohibited.