How to create a Magnetic Vector Field in Houdini - Tutorial

A few days ago I was chatting with a fellow colleague (Jon Parker, great Houdini Artist) about magnetic fields. So I decided to create a little OTL to generate a vector field starting from a pole cloud distribution and share the workflow, since it's really simple to implement and the results are great. I am not sure (actually I highly doubt) that the following implementation is physically correct in any way, I just wanted to have a way to quickly create a magnetic-looking vector field.

I generally use Wrangle nodes pretty much for everything since it's very quick to create, initialize attributes and modify their value with just a few lines of code. Feel free to use VOP nodes if you prefer a node-based approach.

For what concerns Volumes, I generally prefer to use VDB (when I am not forced to use Houdini Volumes). VDB have an optimized memory structure that allow a very fast access to voxels data, plus they are sparse so they have a lower memory foot print compared to Houdini Volumes. They are slightly more tricky to use, but the reward is huge in terms of speed and memory.

Let's start simple. Two points with a "f@pole" attribute that can be either +1 or -1. 


For mere visualization purpose I created a little setup where a sphere is copied (copy SOP) to each point ...


.. the sphere is RED if the pole is negative, and GREEN if the pole is positive (using a copy SOP and the stamp expression function).


Using a wrangle node and the stamp function in one of the parameters I can select the color of each sphere based on f@pole attribute.


EDIT : I was completely unaware of chv ( ... ) vex command, which allows to read directly a vector parameter from the UI  (thank you anonymous poster !!). Which means that the lines above can be written, much more elegantly, this way :
vector negcol = chv ("negativecol");
vector poscol = chv ("positivecol");

... your setup should now look more or less like this.


It's now time to create the Volume that will contain our vector field.
Let's create a VDB node, make sure to give the field a name (I used "magneticfield"), and set it to be a vector field.

This will create an empty VDB volume. By default a VDB volume is dimensionless. Why ? Cause VDB is a sparse volume, meaning that it exists only where we want to. Consequently we need to "activate" the VDB in the regions of space where we need it, before actually 'filling' it. 

In order to do that, we use "VDB_activate" SOP which allows to use geometry to activate the region. 

In this case I am using a bounding box, with some padding, generated directly by my point cloud.
This is what the node graph should look like roughly.
NOTE: don't forget to enable "Pack Geometry before Copying" in the Copy SOP, under Stamp section (later, when we'll use thousands of points, this option will make a huge difference).


And the viewport (note the empty VDB defined by the bounding box surrounding the poles):


There's one more step we should follow before starting to write our Volume Wrangle node. At the moment, we wouldn't be able to visualize the content of the Vector VDB cause of the nature of a vector field. For this purpose we can use the Volume Trail node. This node will sample the vector field in the points specified by the First Input , and draw curves following the vector volume connected in the Second Input.

I used the previously created bounding box to generate a Fog Volume, and scatter points within it. These will be the sample points used by the Trail SOP.
And this is what the graph should look like. Note that the sample points go in the input 1 of the Volume Trail SOP.
Now, we can't see any trail cause the vector field is empty. Let's test if it works filling the volume with a constant vector.

Your viewport should now look like this. 
Horizontal lines, matching the horizontal vector {1.0 , 0.0 , 0.0 }.


Now we have a good environment that will allow us to visualize and debug our code.

Let's now discuss how to calculate the magnetic field resulting by the 2 poles , for each voxel in the VDB grid.

Let's calculate the magnetic field generated by the poles p1 and p2 at the position v@P (the dark grey voxel visible in the picture above).
That Voxel will feel the negative influence of p1 (meaning , will be repelled from p1) and the positive influence of p2 (meaning, will be attracted towards p2). If we calculate the vector distance between the voxel and each pole position, of course we obtain a vector that is very small when the voxel is close to the point, and very big when it's far. We want the opposite. That's why, in our calculation we use the inverse of the distance from each pole, as a multiplier of the (normalized) distance vector. This way, the closer we are to the pole, the stronger is the attraction or repulsion as you can see from the function plot below.


d is red (no good, cause it gets bigger and bigger with the distance)
1/d is green (good cause it big close to the pole, and smaller and smaller far from the pole).

This was the trickiest part. The rest is pretty simple.
All we need to do is to store in each voxel the sum of all the normalized distance from each pole position, multiplied by the pole attribute ( to make sure the contributing vector is repelling or attracting depending on the pole ) and multiplied again by the inverse of the distance, as explained before.  

The pseudo code is the following:
  • For each Voxel position v@P
    • initialize a vector called VectorField = { 0 , 0 , 0 }
    • iterate through all the points (pi) in a certain radius from v@P
      • import the pole attribute of the point, pole
      • find the vector d between pi and v@P
      • find the normalized (nd) and magnitude (md) of vector d
      • add nd , multiplied by pole, and by the inverse of the distance md to VectorField
    • the magnetic field v@magneticfield in the voxel v@P is set to VectorField
Let's create a Volume Wrangle, and connect the Input 1 to the VDB volume , and the input 2 to the merge node containing the 2 points with the "pole" attribute.



Converting the pseudo code in VEX is probably easier than writing the pseudo code itself.


Now this is what the view port should look like:


Now we can replace the boring 2 poles setup with something more attractive.
How about ... simulating the magnetic field on the surface of the sun ? 

This picture I downloaded from Google Image is a good reference.

In order to recreate that look, all we need to do is scatter points on a sphere (about 2k), randomly assign f@pole to -1 or 1 and feed it into the simple setup we just created.


I find this pretty cool ! :)
Ok, I guess that's it.
If you like this tutorial, or found a better way to achieve the same result, please don't hesitate to comment.
Thank you for reading !

Since you read the whole article you definitely deserve the hip file.

It's better to use the inverse of the square of the distance, instead of the inverse of the distance. This will definitely give better results and less interference in the voxels far from poles.
Thanks to Jon Parker for this suggestion.
[ EDITED in 2015 ]


ADVECT PARTICLES ALONG THE CURVES

Many people asked me this question:
Great job with the lines, but ... what now ?? For instance, how do I advect particles along those lines ?
Ok this is what I would do.

First off I assume you are creating some kind of Sun Flare FX, so I assume you've got your sun surface from which you're generating your magnetic arcs.
I'll write here broad strokes, without really going into details for now and see if that works:

  1. find a way to create a direction vector on the lines (one way could be using a PolyFrame SOP). Let's call this v@linedir , and find a way to orient this path so that it goes in the dir you want (like from the positive to the negative or the other way around).
  2. emit particles from the surface of the sun in some POP network
  3. using a POP Wrangle open a point cloud (pcopen / pcfind) on the curves, sample v@linedir attribute (using pcfilter or pcimport in a for loop ...) to return an average direction (lets call it magv) and add it to the particles velocity (v@v += magv).
  4. optionally use the distance from the sun to modulate magv length using a ramp.

[ EDITED in 2019 ]



27 comments:

  1. That's really cool, thanks for sharing!

    Since you use wrangle SOPs extensively, you may want to use the chv vex function to get a vector parm directly rather than using set and getting each parameter individually - it works nicely when hitting that button which creates params automatically for you which is a real time saver! ;)

    ReplyDelete
    Replies
    1. I didn't know that ! Thank you a ton for this info !

      Delete
  2. Hey dude, I've been wondering how to do this. Absolutley amazing.

    ReplyDelete
  3. This is an amazing effect...I'm getting stuck trying to re-create it...Would it be possible to share a HIP file?

    ReplyDelete
    Replies
    1. I just attached an example hip file at the bottom of the article. I hope it helps.

      Delete
  4. have been looking for something like that for a while, thanks a bunch

    ReplyDelete
  5. It's also fun looking at that volume with the various volume visualizers. Sweeping through the volume with the Trail visualizer is somewhat hypnotizing.

    ReplyDelete
    Replies
    1. So true ! Sometimes the visualizers show stuff that it's even cooler than the fx itself. Upsetting ! ahah :)

      Delete
  6. I don't think you need to divide by the inverse square of the distance because you've already normalized the vector (which is the same as dividing by the length). My crappy pseudo-code follows:

    V is a vector that extends from a pole to a voxel

    d = length(V)
    n = normalize(V)

    n / d == V / d / d == V / (d*d)

    ReplyDelete
  7. Hey Alessandro, came across your tutorial, really good!! long times since Framestore :) I hope you still remember
    thanks
    A

    ReplyDelete
  8. That's great man ,Thank you :)

    ReplyDelete
  9. Very good tutorial ! But I am kind of stuck now, I'd like to drive particles along that vector field and I don't find how. Don't know if I have to convert the volume to be driving particles in a POP, would you point me in the right direction ?
    Thanks for your work :)

    ReplyDelete
  10. Amazing guide, thank you for this great setup. I have been now wondering how to drive particles up these lines as filaments, wider base on the surface of the sun source, and getthing thinner and more concentrated as they go up following the vector guides, do you have any pointers on how i could achieve this? Best regards

    ReplyDelete
    Replies
    1. I truly apology for not seeing this before..
      I'll write a little addendum with a few ideas on how to do that.

      Delete
  11. Im trying to figure out how to use this approach to export to fga via the ROP Vector Field from Game Tools. Any pointers?

    ReplyDelete
    Replies

    1. I truly apology for not seeing this before..
      I'll write a little addendum with a few ideas on how to do that.

      Delete
  12. thanks for sharing. "Converting the pseudo code in VEX is probably easier than writing the pseudo code itself."
    Lol, speak for yourself, I feel like I miss decades of learning before being able to write that code! I have no idea how you can figure out which command to use on each line it just blow my mind! I read the doc for each command and I just don't understand how one could guess from the doc to use the commands the way you did!

    ReplyDelete
  13. Sometime it becomes very hard to find a well written and well established bog which give you correct and useful information. However, I found this blog and got some relevant information which are really helpful for me.
    แม่เหล็ก แรง สูง

    ReplyDelete
  14. > VDB have an optimized memory structure that allow a very fast access to voxels data

    Are we comparing the access time of VDB to Houdini volumes? I'm pretty sure Houdini volumes are faster in that regard.

    > plus they are sparse so they have a lower memory foot print compared to Houdini Volumes.

    True. But the problem is that Houdini volumes are almost sparse too (constant tiles). Add a pretty good lossy compression to it and we have a way smaller memory footprint. Unfortunately not that many people know that this machinery exists and how it works and thus don't use it.

    ReplyDelete
    Replies
    1. Hi Anonymous !
      I totally agree with you. I was surprised to see how much faster the native Houdini Volumes are now. Keep in mind this article was written in 2015.
      About the lossy compression, I believe this node should do the trick, if you're ready to lose a little bit of data.
      https://www.sidefx.com/docs/houdini/nodes/sop/volumecompress.html

      Delete
  15. Hey!

    Well it was always like that. Even more, VDB was way slower to render in Mantra than a standard volume (not sure if it's still the case. On a bright side it needed a way smaller memory footprint which was a very good thing at that time. Nowdays the standard volume can be loaded on per-tile basis and the amount of RAM we can get access to is also higher).
    Yes, Volume Compress SOP is what I was referring to. It may generate 10-30 times lighter volume without significant visual quality loss.

    ReplyDelete
  16. hello mr Pepe, just found this Gold Mine of a blog, ty for your knowledge. I've been looking for this for a long time !!!!

    ReplyDelete
    Replies
    1. thank you ! I'm super happy you find it useful.

      Delete
  17. Hi, i wanted to ask - how to render a .fga vector field from this setup?

    ReplyDelete
    Replies
    1. hi !
      I am not familiar with the .fga format. Can you elaborate ?

      Delete

Featured Post

Cigarette Smoke - Part 3 - Houdini 19

Since a lot of people are asking me about this setup I thought it would be a good idea to update the setup for H19. The logic is pretty much...

Most Popular