This is part 2 of the breakdown for my recent scene Half-Life 2 scanner scene (part 1 here).
This time, I’m going to focus on the Houdini web setup.
Although it took me a while to get a very subtle result in the end, it was a fun continuing learning experience, and I’m sure I’ll re-use a bunch of this stuff!
Go go Gadget webs!
I saw a bunch of really great photos of spider webs in tunnels (which you can find yourself by googling “tunnel cobwebs concrete” :)).
I figured it would be a fun time to take my tunnel into Houdini, and generate a bunch of animated hanging webby things, and bring them back into UE4.
This fun time ended up looking like a seahorse:
I will break this mess a bit 🙂
Web starting points
I import the geometry for the tunnel and rails, and scatter a bunch of points over it, setting their colour to red.
On the right hand side of the seahorse is a set of nodes for creating hanging webs, which is just some straight down line primitives, with a few attributes like noise and thickness added to them.
I’ll come back to these later:
In the top middle of the seahorse, I have a point vop apply two layers of noise to the colour attribute, and also blend the colour out aggressively below the rails, because I only wanted webs in the top half of the tunnel.
The web source points look like this:
From these points, I ray cast out back to the original geometry.
Ray casting straight out of these points would be a little boring, though, so I made another point vop that randomizes the normals a little first:
After this, I have a few nodes that delete most of the points generated from the pipe connections: they have a high vertex density, compared to every other bit of mesh, so when I first ran the thing, I had a thousand webs on the pipe connections.
I also delete really small webs, because they look lame.
We are now at seahorse upper left.
Arcy Strangs.
Not sure what I was thinking when naming this network box, but I’m rolling with it.
So anyway, the ray cast created a “dist” attribute for distance from the point to the ray hit, in the direction of the normal.
So my “copy1” node takes a line primitive, copies it onto the ray points, sets the length of the line to the “dist” attribute (my word, stamping is such a useful tool in Houdini).
Before the copy, I set the vertex red channel from black to red along the length of the line, just for convenience.
Previous up the chain, I found the longest of all the ray casts, and saved it off in a detail attribute. This is very easy to do by just using Attribute Promote, using Maximum as the Promotion Method.
So, I now define a maximum amount of “droop” I want for the webs, a bit of random droop, and then I use those values to move each point of each web down in Y a bit.
I use sample that ramp parameter up there using the web length, and then multiply that over the droop, so that each end of the web remains fastened in place.
And I don’t really care if webs intersect with the rails, because that’s just how I roll…
Fasten your seatbelts, we are entering seahorse spine.
Cross web connecty things
For each of the webs in the previous section, I create some webs bridging between them.
Here’s the network for that.
I use Connect Adjacent Pieces, using Adjacent Pieces from Points, letting the node connect just about everything up.
I use a carve node to cut the spline up, then randomly sort the primitives.
At this point, I decided that I only want two connecting pieces per named web, and I got lazy so I wrote vex for this:
string CurrentGroupName = ""; string PickedPieces[]; int PieceCount[]; int MaxPerPiece = 2; int success = 0; addprimattrib(geoself(), "toDelete", 0, "int"); for (int i = 0; i < nprimitives(geoself()); i ++) { string CurrentName = primattrib(geoself(), "name", i, success); int FindIndex = find(PickedPieces, CurrentName); if (FindIndex < 0) { push(PickedPieces, CurrentName); push(PieceCount, 1); } else { int CurrentPieceCount = PieceCount[FindIndex]; if (CurrentPieceCount >= MaxPerPiece) { setprimattrib(geoself(), "toDelete", i, 1, "set"); } else { PieceCount[FindIndex] = CurrentPieceCount + 1; } } setprimattrib(geoself(), "name", i, CurrentName); }
So that just creates an attribute on a connecting piece called “toDelete”, and you can probably guess what I do with that…
The rest of the network is the same sort of droop calculations I mentioned before.
One thing I haven’t mentioned up to this point, though, is that each web has a “Primitive ID” attribute. This is used to offset the animation on the webs in UE4, and the ID had to get transferred down the chain of webs to make sure they don’t split apart when one web meets another.
At this point, I add a bunch of extra hanging webs off these arcy webs, and here we are:
Then I dump a polywire in, and we’re pretty much good to go!
Well… Ok. There’s the entire seahorse tail section.
For some reason, Polywire didn’t want to generate UVs laid out along the web length.
I ended up using a foreach node on each web, stacking the web sections up vertically in UV space, using a vertex vop, then welding with a threshold:
Since I have the position, 0-1, along the current web, I could use that to shift the UV sections up before welding.
With that done on every web, my UVs look like this:
Which is fine.
When I import the meshes into UE4, I just let the engine pack them.
Seriously, though… These are the sorts of meshes that I really wish I could just bake lighting to vertex colours in UE4 instead of a lightmap.
It would look better, and have saved me lots and lots of pain…
And here we are, swing amount in red vertex channel, primitive offset (id) in green:
Web contact meshes
I wanted to stamp some sort of mesh / decal on the wall underneath the hanging meshes.
If you have a look back at the top of the seahorse, you might notice an OUT_WebHits node which contains all the original ray hits.
I’m not going to break this down completely, but I take the scatter points, bring in the tunnel geometry, and use the scatter points to fracture the tunnel.
I take that, copy point colour onto the mesh, and subdivide it:
Delete all the non red bits, push the mesh out along normals with some noise, polyreduce, done 🙂
I could have done much more interesting things with this, but then life is full of regrets isn’t it?
Back to UE4
So, export all that stuff out, bring it into UE4.
Fun story, first export I did was accidentally over 1 million vertices, and the mesh still rendered in less than half a millisecond on a GeForce 970.
We are living in the future, people.
Most of this material is setting up the swinging animation for the webs, using World Position Offset.
There’s two sets of parameters for everything: One for when the web is “idle”, one for when it is being affected by the Scanner being near it.
To pass the position of the scanner into the material, I have to set up a Dynamic Material Instance, so this is all handled in the web blueprint (which doesn’t do much else).
It also passes in a neutral wind direction for when the webs are idle, which I set from the forward vector of an arrow component, just to make things easy:
So now I have the scanner position, for each vertex in each web I get the distance between it, and the scanner, and use that to lerp between the idle and the “windy” settings.
All of these values are offset by the position id that I put in the green channel, so that not all of the webs are moving at exactly the same time.
Still to come…
Animation approach from Modo to blueprints, lighting rig for the scanner, all the fun stuff! 🙂