top of page

Senior Thesis - "Embers" - Archive Part 3

Updated: Jan 2, 2022

Week 12 and 13:

Here's the report on my progress over last week and couple days! I was traveling for the week in California so I'm putting these two weeks together. I worked on rendering the beginning of the first shot; refining the particulate and the smoke; and adding more details to the FX and some of the compositing set up.

Looking at the renders I have a texture fix on the kettle that broke because of the slight modeling adjustments to the kettle. The kettle looks much better than last week and its an easy fix on the texture. (currently fixed)

The camera based particulate needs to move quicker and I want to kill some of the pieces that awkwardly fly in front of the camera because are too close. The smoke needs more disturbance detail and is slightly too viscous. I may speed up the smoke a bit more as well.

Overall, I am very happy with the progression of the renders.

Compositing Effects:

Composite with light wrap, glow, blur and grading applied to embers and kettle.

I setup the cryptomatte in my file for use with Renderman. Something to note that's different from other render engines is that it is written as a separate file, not a separate layer. To use this with render multiple passes, I do have to set up a cryptomatte shader for every pass to be fed into the sample filter of each Renderman ROP.

I have chosen to have it identify objects based on material since nearly every piece of wood, FX, etc. has a different material and requires no other attribute set up on my part.

Left: current Nuke node tree Right: sample cryptomatte for kettle and embers


I also got to finally continue on the check for triggering the ashes. It's a really simple check to write and it will prevent pieces from stretching in the simulation like they used to.

I moved the SOP solver that accumulated the trigger color outside of the DOP net, then promoted that attribute to a primitive so I could more easily work on a per-primitive, per-piece basis. I'll promote it back to point at the end. That was then fed into a connected piece for loop where I have two wrangles.

make trigger group

// make array of all the primtives in the piece
// add primitives to a trigger group if the color is over a certain threshold

int myprims[] = array(@primnum);
i@group_trigger = 0;

foreach(int primnum; myprims){
    if(@Cd.x > ch("threshold")){
        @group_trigger = 1;

trigger all if met threshold

// if the percentage of triggered items is greater than the threshold,
// trigger all pieces in the primitive

float primcount = nprimitives(0);
float count = nprimitivesgroup(0, 'trigger');

float percent = count/primcount;

if (percent > ch("threshold")){
    @Cd = {1,1,1};

left: new trigger right: old trigger

There's a significant difference between the two results as well when it comes to movement, not just the stretching behavior.

With the old trigger, there could be one last point that's just hanging on, waiting to meet the threshold to become active and so there would be pieces swinging and stretching off of the wood logs.

The new trigger allows that if the majority of the threshold is met then trigger the whole piece. This also allows for small pieces to become active earlier and larger pieces to become active much later. I can reduce the trigger threshold to still get some deformation in the pieces, but it is much more realistic and controlled.

Shader Progress:

Burning wood/ashy wood shaders:

Last week I talked a little bit about how I've decided to go about creating shaders on the logs when they have been burned or are burning. Between the two classes this week I worked on making more variations and applying that to my render geometry.

I also thought of a more simple shader after seeing this reference:

On the inside wood there's less of a displaced texture and more just a change of color. I could set this up based upon an accumulation of the burn attribute.

Task list for the upcoming weekend:

- Refine camera-based particulate per-shot

- Complete smoke and fire cache

- Refine steam simulations

- Continue on ashes simulation if time allows

Week 14:

It's the week before my last midterms! It's a bit of stressful one. I'm trying to finalize all the effects. I've been hard focused on the fire this past weekend and running it many times to get it right. I'm mostly happy with it, but it unfortunately still needs some more adjusting.

I've been tangling with the "flare-up" portion of the sequence and the shapes of the flames for that. I'm almost happy with the timing of the flare-up, I think I need to make the flare much shorter, but the shape of the flames are still too long. They appear longer in the viewport flipbooks than they do in the render, but still need adjusting. I need the shape of the flames to be much more chopped up. They are way too smooth right now.

Key: disturbance!

I have revisited a resource that someone made to show the results of different pyro settings.

I definitely needed to add some disturbance in there to help shape the flames more. I also adjusted viscosity, cooling rate, turbulence settings and the temperature attribute being fed in.


password: embers

Version 11 is the iteration I had the end of the weekend. It was crazy looking and the flame shapes were not appealing mainly because of the velocities being fed into the simulation. I adjust those to be shorter in length and biased towards the positive y direction. Version 16 is what I had at the end of yesterday where I'm not happy with the flame shapes.

The last short clip of the video (accidentally mislabelled) shows good improvements with the disturbance. I'm currently running another iteration with more disturbance to see the result.

Changes Going Forward:

Since this morning I have been rethinking how I want to do the flare-up. The audio I've chosen has these wonderful "piano beats" that musically serve well to time the flare. Instead of one large big flare, I want to do multiple flares that hit on the beats and affect only parts of the fire so it appears more natural and controlled.

Right now my VEX for scaling temperature over a frame range scales the attribute all over the source. My plan now is to set up a ripple of sorts using pcfind functions, distance functions and point scattering. It's a simple set up that I think will be beneficial to honing a more artistic look. attributes will be scaled based on the "ripple".

That same ripple then can be fed into the particulate simulations to scale density emission and the forces in the custom advection.

Week 15:

Hey guys its a big week! It's the last midterm of my college career and that means FX needs to be wrapping up this week so I can focus on look development and rendering.

End of last week I mentioned I made some big changes to the fire. Here is the new set up for creating the attribute flare.

I have six flares so I scattered six points on the same geometry as the source points and each one receives a flare_id, a dropframe and an endframe.

int range = chi("end_emit") - chi("start_frame") - chi("end_padding");
i@distrib = range / npoints(0);

i@dropframe = ((@flare_id + 1) * @distrib) + chi("start_frame");
i@endframe = @dropframe + @distrib;

From those things I can calculate and even distribution of flares to start with. I can match the frame range to original range I had and I added padding at the end so no flare received a dropframe too close to the end of the sim.

I delete every point except for the active one.

if(@Frame < @dropframe || @Frame >= @endframe) removepoint(0, i@ptnum);

That point then receives a pscale and a color.

From there is where it gets slightly complex. In a wrangle I have the source geometry as the first input and the active point as the second.

// calculate size of r based on where in the distribution the current frame is
// and how many frames (clamped by distrib) the flare should take
@r = 1 - ((point(1, "endframe", 0) - @Frame) / clamp(chi("speed_by_frame"), 1, point(1, "distrib", 0)));

// get distance from active point on source mesh
vector a = point(0, "P", i@ptnum);
vector b = point(1, "P", 0);
f@dist = fit(clamp(distance(a, b), 0, 1), 0, 1, 1, 0);

// distance threshold should be set between 0 and 1
// refine area affected by flare
if(@dist < ch("distance_threshold")){
    @dist = 0.0;

//store og dist in case
@og_dist = @dist;
@dist = fit(pow(@dist, ch("power_scale")), 0, 1, 0, 2);

int pts [ ] = pcfind_radius (1, "P", "pscale", rscale, @P, @r, chi("maxpts") );
int pts2 [ ] = pcfind_radius (1, "P", "pscale", rscale, @P, @r + width, chi("maxpts") );

if ( len( pts ) == 0 && len ( pts2 ) > 0)
    //vector color = point (1, "Cd", pts2 [0]);
    vector color = @dist;
    @Cd = color;
    @flare_scale = @dist;

@flare_scale_viz = @dist;

Here I am procedurally animating the spread of the attribute flare using pcfind and distance functions with that active point. I have refined that scaling value by settings a distance threshold to further clamp the gradient created from distance from the active point.

I also use a power function to concentrate the dist value and a fit to redistribute the scaling value within the affected area. The dist value is used to visualize and feeds the flare_scale attribute to be used later.


With the density of points, the scaling and resolution of simulation I did much earlier in the project, this method was very successful. I will shift my camera angles to better see the flares.

Matching speeds of all effects:

Something that has been challenging with project is matching the speeds of all the effects. Sometimes the levels of microsolvers changed the "speed" of the simulation because of scaling of the effect the solver had on the pyro result. As well as, I had to scale my simulations to achieve the voxel resolution I wanted without going to a crazy small number like 0.002. I had every simulation running at 0.1. It was reasonable for cache size, solve time and produced a result that rendered well.

Here is a comparison I did with the fire, smoke, and both the lid and spout steam. I felt the steam was off so I did a quick retime of 1.5x and 2x the original speed before I re-simulated.

I decided to move with something closer to 2x the original speed. The steam is more viscous than the smoke but I don't that difference to be so significant that it's questionable whether or not the speed is cohesive.

Particulate Recache with Adjustments:

I was decently happy with the last run of the particulate, but still didn't have that natural quality that I've been going for with this project. Initially after recaching the smoke I ran the particulate with the new velocities. I wasn't happy with the result.

First, I wanted to make changes with the emission based on temperature rather than random scattering of points. I created a simple emission density attribute with VEX. I also ended up changing the source velocities from the smoke to the flames.

vector center = point(1, "P", 0);
vector P = v@P;
f@dist = length(P - center);

@temp_density *= 1 - @dist;
@temp_density *= ch("scale");

I also added a third particulate simulation. This one is solely for the emission of the particulate that is caused by the flare from the flames. This has allowed for more control. Instead of feeding the smoke velocities like I had originally with the regular emission particulate, I fed in the flame velocities and adjusted those values to give me the result I wanted.

I ended up making the second force the advection is lerped to based on the age {0,0,0} because I applied a pop wind in the post-solve where each of the floats in the wind vector are decided by CHOPS added with an initial value to get it going in the right direction.

The result I'm happier with but still needs tweaks. I'll be making that top priority in my mentor meeting this week.

Burn Maps Setup:

This is a pretty simple setup to do. I wanted to accumulate from the the flame attribute of the fire volume. A burn attribute is generated at the source to define where "detonation" of the flames will occur. This attribute is very controllable for spreading fire (similar to legacy fuel, but is more predictable). This is merged with flame and a divergence fields.

I plugged in the clean geometry and the flame field into a sop solver where inside I have a wrangle that is accumulating and clamping a volume sample of the flame field.

@Cd = set(0,0,0);

float flame = volumesample(1, "flame", v@P);

f@burn += flame;
@Cd = set(clamp(@burn,0,1), 0, 0);

After the SOP solver, I have an attribute blur to soften the mask. I then write the @Cd.r to a float attribute called @burn_mask. I then copied that mask to the render geometry.

Midterm Checkpoint Flipbook:

I recut pretty the entire back half of the sequence. I shortened the portion with the slow motion effects to have the sudden time warp to be more sudden and not be over used. I trimmed about 2-3 seconds off the end as well. I also decided to remove the huge sweeping camera moves which will also benefit me in reducing the amount of environment to make.

In terms of FX, I am finished except for minor tweaks to the particulate and the ashes. This is fine because that doesn't affect my ability to move onto environment and look development because the cache time each time is under 5 minutes.

Now with all the big cache out of the way, I am able to run renders at night instead of cache. I can get started on rendering the volumes locally. I was going to use a third party service, but I am running about 2TB in FX cache which is not something I can facilitate at a reasonable cost.

75% of the way there in the timeline! Time to wrap up soon and be solely rendering.

bottom of page