Hey everyone, and welcome to another episode of Quick Compounds.
In today’s episode, I’m going to show you how to generate a set of points
using either a Maya mesh or a Bifrost volume.
These kinds of point clouds make great, lightweight visualization tools
for more complex models or effects,
or act as great starting points for procedural scene layout or hair/fur.
As usual, I’ll start with a simple primitive like a sphere.
Then create a Bifrost graph and middle-drag it in.
Now, for a polygon object, this process is super easy.
All I’m going to do is generate a bunch of random locations on its surface,
then sample their positions.
This process will sound familiar to those of you who did my previous Transfer CPV tutorial.
Once I have an array of point positions, I’ll just construct points on them.
So, first I’ll need to generate my random locations on the sphere,
which I can do with a generate_sample_locations node.
Then, I’ll sample their locations using the sample_property node.
Recall that in my previous tutorial I sampled colorSet data.
This time however, I’m going to sample “point_position” data
since I need know where to put my points.
And of course, location data requires a default value of float3, for their X, Y, Z values.
This gives me an array of random positions on my sphere surface.
To generate points in those spots, I can just feed them into a construct_points node.
And then I’ll just output the result.
At first nothing seems to have happened,
but that’s because my generate_sample_locations node starts at 0.
Let me just bump that up to 1000.
And now the points appear as I expected!
Well, not exactly as I expected.
They’re a little small for my liking, so let me insert a “set_point_shape” node in here as well.
This lets me set all kinds of visual options like shape and size.
And for even more pizzazz,
I can daisy-chain in my transfer_CPV compound from the previous tutorial
and then paint some CPV data onto the original sphere!
So, this was easy enough to do for a polygon. What about a Bifrost volume?
The easiest way is to just change the generate_sample_locations node
to Volume mode.
Then, instead of using point positions, I can use voxel positions instead –
3D pixels used in the volume generation process.
See? Easy.
But this doesn’t teach us much.
We can learn a lot more by building a custom solution from scratch.
So let’s do that, while also making our version even more powerful.
This time, instead of generating a random assortment of points on the volume surface,
I’m going to generate a point at each voxel in order to get a nice clean grid look.
I’ll start by creating an update_voxel_position node and feeding my volume into it.
Think of this node as taking a “snapshot” of the volume’s voxels at a moment in time,
that updates on each frame.
Next, I’ll use a get_geo_property node to grab the voxel positions out of this snapshot.
This generates an array of points, so I’ll need to make an appropriate default value.
Then I’ll use that array to construct my points.
Whoa, okay that’s not quite what I expected.
The reason for all this noise is actually a logic problem on my part.
You see, when creating a volume technically the entire scene has voxels in it.
But as you can see if I play the scene,
the voxels are much denser where the simulation is.
This density is what I’ve failed to take into account.
Really, I should only be constructing points when the voxel fog density is sufficiently thick
to indicate smoke.
So first I’ll create a second get_geo_property node
to grab the voxel_fog_density at part of my volume.
Density is just an array of float values.
I’ll just rename these two get nodes to make it clear what each is doing.
Now for the tricky part.
I need to look at each voxel fog density index, one-by-one,
and check if it’s thicker than some threshold amount.
If it’s not, I want to throw its respective point position away.
So first I’ll create a “greater” node,
which checks to see if the first value is greater than the second and spits out true or false.
So I’ll wire in my voxel densities and then check if they’re greater than, let’s say, 0.1.
Now, you may be wondering
“wait a second. You’re inputting an entire array of values
and comparing them to just one number”.
Luckily, the greater node is smart enough to compare each value in the array
individually to 0.1, then spits out an array of trues and falses for each comparison.
In particular, I want to find every instance of “false”
because that means the respective fog density is too thin.
So I’ll use a find_all_in_array.
I’ll input my “truth” array, and check for falses, indicated by this unchecked box.
This outputs an array of only the indexes that are false.
Then I can use a simple remove_from_array
to take those indexes out of my voxel_position array.
So to review, I’m checking every voxel_fog_density to see if it’s thick enough
to consider putting a point on.
Then I’m stripping out every voxel that’s NOT thick enough.
And that’s it! Now this array should only contain point positions
whose voxel_fog_density is thick enough to constitute a volume.
So let’s try plugging that into the construct_points node.
Beautiful!
Now I can work on making my compound more robust.
First off, right now I’m only filtering points based on whether they’re density
is thick enough. But why stop there?
I’m going to add a “less” node and specify an upper bound as well. Let’s say, 0.5.
With two sets of data, I’ll need to use an And node put them together.
You may notice that Bifrost now pauses every time I make changes.
This is because of the Aero simulation, which as to re-calculate every time I make a change.
I can go to Edit > Pause Graph Execution, or just hit Ctrl + . to mitigate that temporarily.
And of course, rather than hard coding this lower and upper bound,
I’ll just wire them to inputs to let the user specify what values they want.
Now compared to before, this generates a shell of points,
because the interior is of the sim is too thick.
At this point, I’m left with two very different solutions
depending on whether I’m dealing with a mesh or volume.
Let me first bundle each of these up into their own compounds just to make it easier to read.
I’ll call them “generate_points_on_volume” and “generate_points_on_mesh”
Now, it’d be a pain to have to switch between these manually,
so instead I’m going to create something to detect one or the other automatically.
To do this, I’ll need to use an if node.
This node receives a Boolean condition, then executes one path or the other
depending on whether that condition is true or false.
In this case, I’m going to check if the object is a mesh,
and if so, run it through my generate_points_on_mesh compound.
So to check if it’s a mesh, I can use an “is_a” node and plug it in here.
Now I need something to compare it to.
I’ll use a “create_object_from_schema”, which creates an object based on an input file.
In this case, I’m going to use a generic mesh schema,
which can be found here on a normal Bifrost install.
There’s also schemas for things like points and other constructs as well.
Now, if the comparison is true, I want to plug my generate compound into it.
You’ll notice there's an error.
That’s because the inputs and outputs were disconnected
when I made the compound earlier.
I’ll need to go inside and reconnect those.
So, that handles the case where it’s a mesh. But what about when it’s a volume?
Well, I’ll just do the whole thing again with another if node connected to another is_a node.
This time, I’ll compare it to a create_empty_volume node
to check if it’s a volume.
So now, my graph checks to see if the object is a mesh.
If it’s not, then it checks to see if it’s a volume.
If it’s still not, then it won’t do anything.
If it’s either of those two things, it’ll run the appropriate point generation compound.
And now I can group all these into a new compound called
"generate_points_from_object".
Great, now the last thing to do is wire up all my useful options to the top level.
I’ve already got my min and max fog density.
To that I’ll add a bunch of options to control the points shape and size.
As well as some options to control the number of points generated
when I’m dealing with a mesh.
Finally, I’m also going to output the point positions themselves.
This might be useful if someone wants to just find the positions without actually drawing them
(for example, if they wanted to use them for generating random trees on terrain).
By the way, if you find your wires turning into spaghetti like this,
you can change to an alternate style to make them easier to read.
Now for some tidying up. First I’ll rename my second output to point_positions.
First I'll rename my second output to "Point Positions".
Then I’ll rearrange my input options a bit and organize them into three distinct groups.
The first will be “Point Properties” for options that change how the points are displayed.
The second will be “Volume Properties” for the options specific to dealing with volumes.
And as you probably guessed, the last will be “Mesh Properties”
for options dealing with a mesh.
Now we’re just about done,
but if you want to test what you’ve learned in this tutorial, I’ll leave you with this challenge:
Re-introduce the ability to generate random points on a volume surface,
just like I showed you back at the 3min mark of this video…
…then use an IF node to allow the user to toggle between
random or grid when working with a volume.