Friday, April 22, 2016

Cigarette Smoke - Part 1

Before you start reading, be aware there is a Part 2, and you'll find it here :
LINK

But don't read it before reading Part 1. So pretend I didn't say anything.

Now for real.

I don't think there is a single person on earth who didn't spent at least 10 minutes of their life contemplating in awe the beautiful shapes drawn in mid air by an incense stick or cigarette smoke. If I had to put together the time I spent observing smoke till this day I'm sure it wouldn't be less than a week.

I found this beautiful reference video on Vimeo.
Let's watch it together for a little while.

Cigarette Smoke Reference

Many years ago I tried to create this fx in Maya. I recall the attempt, but I can't remember the outcome. 

Let's try with Houdini. 

The first solution that crossed my mind was the same I would apply to generate ink in water: lots and lots of points advected by the velocity field generated by a Pyro/Smoke sim.
The only difference would simply be the distribution of the points source.
Let's try this approach first.

Here I created an low-res smoke sim with a little bit of turbulence.



Let's cache this sim (only "vel" and "density" volume fields are really required for this tutorial).

OPTION 1 : Advect (a lot of) Points

Now let's advect a points along the velocity field generated by this sim.

In the pic below, in the right branch, import your smoke sim, convert it to VDB and make sure to use a VDB Vector Merge to merge vel.x, vel.y, vel.z in a single VDB vector field "vel".
Connect this branch to the right input of the Solver SOP.

In the left branch I just imported the same geometry I used to emit the smoke, made it really small and scattered points (make sure to randomize the Scatter seed per frame). This will be the points emitter.
Connect this branch to the left input of the Solver SOP.


Note 1 :
The reason cause I made the points emitter very small is because I want to keep the advected points as close as possible for as long as possible during the simulation, to mimic the behavior of this kind of smoke. The velocity field will be in charge of moving those points, so the closer the points, the higher the chances that they will live in contiguous velocity voxels, or maybe even in the same voxel, and consequently move together, at least for that specific time step !

Note 2:
After the scatter node I added a Transform node , and added an expression to animate the scale using trigonometric functions (see pic below for details, but feel free to experiment with different values of course). Why ? Well, if you notice in the reference video at the beginning of this article, the width of the stream of smoke taking off from the source changes a lot. This is important to add variety to the evolution of the smoke, and to mimic nature as best as we can of course !



The content of the Solver is the usual feedback loop with an injection of new points every frame.
If we assume that the first frame of the range is 1, at frame 1 the switch node will let pass the first set of scattered points.
At frame 2 the switch node will let pass the previous state, merged with the second set of scattered points. And so on for all the frames after 2.


Note :
You don't have to use a Solver for this setup. You can achieve the same result using a POP Network and a POP Points Advect. I prefer to use a Solver cause of my masochistic tendency of creating everything from scratch as an exercise. 
And because VDB Points Advect is much faster than POP Points Advect.


This is the result scattering 100 points per frame.
Let's try with 1000 points per frame.


Better , but still there are sparse, disconnected points.
Furthermore, I don't like the stepped pattern in the lower part.


Let's "smear" the initial position of the source points along "vel":

Add a Point Wrangle SOP (red node below) and connect the input points to the input 1, and the volumes to the input 2.


In the Point Wrangle node enter this vex code:
vector vel=volumesamplev(@OpInput2,"vel",v@P);
v@P+=float(random(i@ptnum*3.432))*normalize(vel)*0.025;
I used 0.025 in my sim cause it was working for me. If you still see 'steps' in the lower part of the sim, feel free to change this value to whatever fits your sim.

If you play the sim now the lower part should look roughly like this.


I guess this solves the stepping issue...


...but we still got the sparse / disconnected points in the upper part of the sim.

Let's try to reduce the points sparse-ness using the Gradient Vector Field generated by the density field . A quick way that helped me to understand the Gradient vector is the following : the Gradient vector field is for a density scalar field what normals are for a surface. In other words think of the Gradient as the normal vector for the density field. What we want to do is a sort of 'peak' SOP in Volume-land. We want to "shrink" the points along a normal vector (which for volumes is called Gradient), just a little bit, every time step.

What we want to do is advect the points along the Gradient vector field, on top of advecting along "vel". Double advection !! (it'll be slightly slower, but it's worth).

Separate "vel" from the "density", apply a VDB Analysis set to "gradient", and make sure to explicitly set the name for the resulting volume to "gradient", or "grad". Then merge it back to the original branch with "vel" and "density".


Now, dive in Solver1 and add an extra VDB Advect Points , and make sure to shorten the timestep advection by at least 0.01 (feel free to play with this number).


The reason cause we need to resize the timestep for the Gradient advection is that we want to push the points towards the zone of higher density only a tiny bit for each point, and every frame. If we keep this number to 1, the points will overshoot.


This is working much better. Notice how the points tend to converge to curves and we have much less sparse points now.
This is a possible solution and if you emit enough points you can get decent results.
Personally I prefer the next solution.

OPTION 2 : Advect Lines

Apparently the problem that we have with Option 1, is the sparse points. We are trying every possible workaround to keep those points in lines. 
So .... why don't we advect lines instead of points ?

Let's try.
First off , we need to remove the expression from Scatter SOP seed. This way, we'll make sure to have a consistent point source where each point will maintain it's own id (@ptnum). 



Next, we need to assign some kind of id to the source points. This way later we can use that id to create lines using Add SOP. Since we are no longer randomizing the point scattering per frame, @ptnum will work perfectly as id.

Add a Point Wrangle to the source branch and enter this VEX code:

i@id=@ptnum;
Your network should look more or less like this ...



Good. Now the plan is to do the following every time step (in the Solver):
  • advect the points as usual (we already have this part)
  • create lines connecting all the points with the same i@id
  • resample the lines
  • smooth the lines (this is optional but highly reccomended)
  • delete the lines and keep only the points
Why do we create lines ? 
So we can resample them.

Why do we need to resample them ? 
Cause after the advection step we don't want to loose detail. So we resample the lines at fixed sized segments, and this way we are sure that every line will always be nice and smooth.

Why do we smooth ? 
To add extra niceness and smoothness (as per beautiful video reference).

Why (on earth) do we delete the lines that we just created ?
Because new points are introduced at every time step. We could extend the lines with the new points but it's easier to just delete the lines at the end of the time-step, and re-create them at the beginning of the next time step after injecting the new source points.


Let's to that.

In the Solver append the following nodes and parameters.


And immediately after the Solver plug an extra Add SOP with the same settings as he first Add SOP in the image above.


Set the number of Scattered points to 10 and run the Sim.


Finally we got rid of the sparse points and honestly this version, with only 10 lines, looks much much better than the Option 1.
Of course now, instead of having sparse points, we have sparse lines ! But somehow this works much better because the characteristic feature of this kind of smoke is curved lines in space. Which is exactly what we got.
Furthermore, sparse points are sparse in every dimension. Sparse lines at least are continuous in one dimension, the one that counts. Plus, remember that we scattered only 10 points so far.

This is the main gist of this technique and if you're interested HERE you will find the Project File.

Now, let's render this thing.
In the following iteration I worked a bit on the Render side.
  • added age and life to the points (if you use the POP Network approach age and life are created automatically)
  • scattered 1000 points (= 1000 lines)
  • created a "density" attribute mapped to the normalized age
  • Rasterized a VDB from the curves using Volume Rasterize and sampled the above mentioned density attribute into a Volume Density Attribute
  • assigned a simple Pyro shader
  • rendered in Mantra
  • added a bit of glow and bluish tint in Nuke


If you're interested in Part 2, you'll find it HERE



Thank you for reading and let me know if you come up with some idea to improve this technique.





53 comments :

  1. Thank you for your sharing. very useful!

    ReplyDelete
  2. Amazing tutorial, the results are sinuous and high detail, yet I can't believe how fast it solves. Thanks for writing this!

    ReplyDelete
    Replies
    1. hey Matt, I was surprised myself ! Happy you found this interesting man.

      Delete
  3. Now that I'm done with that inky smoke project I can finally have a look at this... good stuff!

    ReplyDelete
    Replies
    1. Hey Matt ! thank you :) Yeah I ended up using this in the inky ink one ahah

      Delete
  4. Grande, davvero molto interessante ed utile. Thanks for sharing (Y)

    ReplyDelete
  5. Very nice work, while you're at it you may wanna take a look at the pop advect by filament
    http://archive.sidefx.com/docs/houdini15.0/dopparticles/filaments

    ReplyDelete
    Replies
    1. Hey Saber,
      Yes I've tested the filaments some time ago but I found difficult to art direct the shape. I should probably spend more time on it, I'm sure there is a lot of potential in that technique.

      Delete
  6. awesome tutorial! thank you very match!

    ReplyDelete
  7. Great tutorial, really neat technique. Would it be possible to elaborate a bit on the rendering process? Specifically from 'created a "density" attribute mapped to the normalized age'. Seems like a lot of the deliciousness is in that rendering treatment.

    ReplyDelete
  8. It's awesome thank very much

    ReplyDelete
  9. thank you for spending time to make these tutorials. as a new houdini learner these tutorials are really helpful.

    ReplyDelete
  10. Hey Ale! Nice setup, i'm tryng to add more vorticles using also the "filaments solver".

    ReplyDelete
    Replies
    1. Hey Leo,
      this is interesting ! Let me know how it goes and if you want I'll add a section to this tutorial.

      Delete
  11. Hey Alessandro, im a complete newbie. trying to grasp this concept to learn more about pyro within houdini. is there by any chance you plan on making a video version of this tutorial? the results are astonishingly realistic and id love to learn what youve done here! cheers :)

    ReplyDelete
    Replies
    1. Hi Nox
      I don't plan on making a video tutorial sorry. The reason is that I believe the written tutorial is easier to follow and gives more info than a video. Let me know if you've questions and I'll try to help.

      Delete
  12. How about the smoke of a good 'ole joint, Mr. Brush?

    ReplyDelete
  13. great work!!!
    but how to render with motion blur? no vel. thanks

    ReplyDelete
    Replies
    1. Great question !
      Since the lines are constantly re-sampled, there's no way to simply extract v using a Trail Sop. In order to calculate v, transfer the Pyro Velocity Field ("vel" the one you use to advect the lines) onto the advected points using a Point Wrangle with a volumesamplev vex command. You could do this in the Solver or as a post-process operation. Let me know how it works !

      Delete
  14. really cool !! :)
    thanks for sharing !!
    (need more tricks... ;))

    ReplyDelete
  15. awesome! !! very useful thx man

    ReplyDelete
  16. Hello and thanks for the great tutorial. The part 2 link 404s... is it no longer online?

    ReplyDelete
  17. Hello and thanks for the great tutorial. The part 2 link 404s... is it no longer online?

    ReplyDelete
  18. Hi, thanks so much for the tutorial. However, I am a complete newbie to Houdini and don't really grasp how to create the initial set up. Any chance on writing up a tutorial on how you created, cached and imported the initial geometry and smoke sims?

    ReplyDelete
  19. Hey Alessandro,

    I think I have the first part figured out, but I am getting "!Error Missing Velocity Grid" in vdbadvectpoints1 inside the solver. :-/

    ReplyDelete
  20. Thank You! This is awesome. I was looking for something like this, particles in houdini are not as straight forward as TP but they seem really powerful, your stuff is great for learning proposes :)

    ReplyDelete
  21. Alessandro,
    I found another solution, way faster and easer by using an inverted glow material.
    The smoke should be converted to vdb, vdb to polygons.

    ReplyDelete
    Replies
    1. hey Lam ! sorry for the late reply ! This seems really interesting.
      Can you please elaborate what you mean with an inverted glow material?

      Delete
  22. how to get just particles without smoke?I am getting both together.

    ReplyDelete
    Replies
    1. Hard to tell , try playing with the ghost options
      https://vimeo.com/113096171

      Delete
  23. Thanks for posting this blog, i am very impressed with your blog and it is very useful for me and other. Please visit at"Smoke Solution In China | Qleanwind", i hope it be prove useful for you.

    Visit Here - https://www.qleanwind.com/product/smoking-solution-smoke-cabin/

    Thanks Regards,,,

    ReplyDelete
  24. This comment has been removed by a blog administrator.

    ReplyDelete
  25. Hello! I've seen the second part and its very helpfull, but i think you forgot to attatch the hip file. Maybe its me who cant find it in the post, I have looked over the post a few times but i cant see it. I have a problem with the point's age(they have all the same age so they turn into red at the same time) so I really need the file. If you could help me putting the file or with your knowledge it would be great. Thanks!!

    ReplyDelete
    Replies
    1. Hi Pepe ! :)
      You'll find 3 big buttons in the very bottom of the Part 2 article.
      Hip File -- Nuke Script -- Environment Map File
      (I just downloaded the 3 files myself to double check and it works).
      Please let me know if you need more help on this.

      Delete
  26. superb technique...loved it..thnx for sharing

    ReplyDelete
  27. Hi ! I would like to animate it along a spline (to render it like ink in water) but the result is like a right to left movment in the viewport. Have you got an idea how can I do it ?

    ReplyDelete
    Replies
    1. I'm having troubles understanding your scenario.
      What do you mean with 'animate along a spline' ?

      Delete
  28. Hi! Fantastic tutorial thank you!
    I'm trying to make the same effect and I wonder how to add age and life attributes to the points?

    ReplyDelete
    Replies
    1. hi ! Thank you for the kind words and good question !
      Create a float 'age' and 'lifespan' attributes outside of the Solver on the geometry that you input to the solver, in a point wrangle node.

      f@age = 0; // init age to 0 for new points
      f@lifespan = 5; // let's set the lifespan to 5 seconds

      Then, inside the Solver, at the very end of the chain add another point wrangle node in which you increase @age by @TimeInc and kill particles when they get too old. Something on these lines:

      f@age += @TimeInc;
      if (f@age>f@lifespan)
      removepoint(0,@ptnum);

      I hope it helps

      Delete
    2. Thanks a lot for the detailed reply! That solves my problem perfectly!
      Just another problem I encountered: I made a vel field for the smoke sim, but the exported volume doesn't work for points advection anymore. It seems that the exported vel vectors are pointing to very different directions although the smoke does follow the curve I drew.
      Both SOP solver and DOP pop solver didn't work, changing advection type (to update force) didn't help either.
      Is there a way that I can fix this?

      Delete
    3. Can you describe more in details what you're trying to do ?
      For instance : what curve you drew ? for what ?
      When you say "changing advection type to update force" where are you changing this ?

      Delete
    4. Hi! Here's the steps I took:
      1. Drew a curve and used its tangent to make a field
      2. Applied this field in pyro for the smoke to follow
      3. Used the smoke to generate a vel and a gradient field
      And here came the problem: The particles merely follow where the smoke goes. (Please see: shorturl.at/zGRX8)
      I advected particles in DOP and tried different advect types in "pop advect by volume" node (Update velocity/ Update force/ Update position). None of them changed anything.

      Delete
    5. gotcha, thank you for explaining.
      Make sure that the source points you're using to emit the lines, match (are contained inside) the source you're using for the smoke. Meaning : if you're emitting the pyro smoke sim from the entire length of the curve, but your point source for the lines is at one end of the curve, it'll not work. Furthermore, in your pic, I see the density has a very tubular shape, i don't see any turbulence, is the white part the smoke sim coming out from pyro ?

      Delete
    6. Hi! The point emitter was exactly the one emitting smoke, and yes, the white part in my pic was smoke out from pyro.
      Just to share that I found another way around: Instead of using [gas advect field] after importing the vel into pyro, I used [gas calculate], adding the customised vel to the original smoke motion (buoyancy, turbulence, etc.), and it worked! (Plz check: shorturl.at/fhtzP)
      Well I'm not sure how it all behind works, but here's my guess of what happened: [Gas advect field] changed the smoke motion as an outer force without really altering the velocity of the smoke? Meaning, if I want to use [gas advect field] and advect the particles along the smoke, I need to find a way to get the "real" smoke velocity? Correct me if you have different insight.

      Delete