Friday, February 27, 2015

Scripting Particle Flow - Making Particles Cast Light



Creating glowing particles is easy. However, the particles should cast light to the scene, as the video above shows. Creating the light pass is not so easy. This post will suggest two methods to create such light pass using 3ds Max.

Method 1: Using Final Gather

You may already be jumping on your seat, screaming "Use FG!" to your screen. Final Gather (FG) is a method to compute indirect, or bounced, lighting. In the case of our glowing particles, the glowing material will cast indirect light to the scene (only lights cast direct lighting in CG renders). This method resulted in the video below.


This method kind of works. However, it has these weaknesses:
  • The resulting light pass flickers. On the video above, this is especially clear on the ceiling.
  • The color of light cast is difficult to control. FG tends to produce pale (desaturated) colors.
  • Render time is rather long. The light pass for the video above took around 3 hours to render.
With all these weaknesses, we should be thinking of a better way to render the light pass.

Method 2: Scripting Particle Flow

This method uses direct lighting and therefore avoid all weaknesses associated with Final Gather. The question is how do we move lights according to particle movement?

We can do so using an operator for Particle Flow called Script Operator. Here is my Particle View setup:
To add our custom script, select the Script Operator and click the "Edit Script" button. A new dialog will appear. Replace the script with the following:
on ChannelsUsed pCont do
(
     pCont.usePosition = true
)

on Init pCont do
(
    -- Keep the lights in an array.
    global LightArray = $ParticleLight* as array

    -- Default position for the lights.
    global defaultPos = getNodeByName("Help_LightDefaultPos")
)

on Proceed pCont do
(
    t = pCont.getTimeStart() as float
    if t >= 0 then
    (
        -- Move particle lights.
        pcount = pCont.NumParticles()
        for i in 1 to pcount do
        (
            pCont.particleIndex = i
            if i <= LightArray.count then
            (
                LightArray[i].position = pCont.particlePosition
            )
        )

        -- Move extra lights to default position.
        if LightArray.count > pcount then
        (
            for i in pcount+1 to LightArray.count do
            (
                LightArray[i].position = defaultPos.position
            )
        )
    )
)

on Release pCont do
(

)
This script assumes the following:
  • There are lights in the scene.
    • That is, the script does not create the lights.
    • You can create a lot of light using 3ds Max Array tool ("Tools" menu > "Array...").
  • The lights are named "ParticleLight*". For example, there should be "ParticleLight001", "ParticleLight002", and so on.
  • There is an object named "Help_LightDefaultPos" somewhere in the scene. A light will be aligned to this object if the light is not yet needed.
Here is what the script does. It starts by collecting all the lights into an array called "LightArray". Then, on each frame, it will move a light to each particle. If there are more lights than particles, the unused lights are aligned to the object called "Help_LightDefaultPos".

This method resulted in the video at the top of this post. You can download my Max file and study it.

Further Reading

This post only touches the smallest tip of particle scripting. To learn particle scripting further, have a look at Bobo's tutorials on Particle Flow scripting.