The objective of this brief is to create a spline-based pipe creation tool intended for use by environment artist in-engine. The idea is that the artist can create a spline and then the tool will generate a pipe mesh following it's own logic to match the spline. I have detailed the specific requirements below.
Scale in meters
Texel density of 1024x1024
Maximum total allocated texture space of 2x2048x2048
Any scripting commented to aid understanding
Tool presented in a single Unreal Engine level
Documentation explaining exposed parameter function
Add an option to draw multiple pipes based off a single spline
Add the ability to place gauges/valves etc. along the pipe
Include an advanced shader component to break repetition/tileability
Overall this brief seems very straightforward. I feel like I understand the basic logic I would use. However, I would like to challenge myself in some way with this project to develop my skills as much as possible so I think I am going to do this project in Houdini.
Have I ever used Houdini before?
No.
Therefore this is the perfect time to learn!
I knew teaching myself how an entirely new and notoriously complex software works would be a challenge, so I decided to purchase this tutorial from FastTrack who's learning resources I have used before. The tutorial consisted of about 2 hours of example project work which explained the process of creating a procedural railing tool. I wanted to make sure I understood every single node and piece of code in these videos so I spent a total of about 9 hours over 2 days focusing on getting everything right. Whenever something in my project didn't work, I tried to practise troubleshooting by going back through what I'd done without referencing the video: making sure I understood every part of the tool. By the end of the two days I had a functioning railing tool!
I understood it's nodes, its parameters and its limitations caused by the way its built, and felt confident that I could transfer what I had learned into my own project. The tutorials didn't teach me everything I needed to know but they gave me enough confidence that I could search through the documentation find my own solutions.
The most important things I gained from the tutorial were learning how to expose parameters and make the user interface somewhat navigable, how to import mesh data from engine and also the discovery of the sweep node! This node takes a 2D shape (a cylinder for example) and sweeps it along an edge (such as a curve) creating a 3D shape - like a pipe. This totally changed the way I considered creating my tool. Originally I was planning to create a modular kit of straight and corner pieces in 10 degree increments that I could then slot in along a spline based on checking normals, but with the sweep node I could just sweep along the spline to create the main body of the pipe and save myself a lot of modelling time. I understand why tech artists love Houdini now. I can see the vision.
How it feels realising all game art is just maths, and I can manipulate it all in Houdini.
(maybe not all of it, but at least enough to create a cool pipe tool.)Despite some initial bugs I quickly figured out how to draw curves in Houdini using the (you guessed it!) Draw Curve node. The end goal is obviously to have a curve imported from engine but while I was building the framework of the tool I decided to make my life easier and just give myself a curve to work with. As my pipes will be rigid I don't need to worry about beziers or anything like that I could simpky create a curve with control points on each corner for now. This is also how I want artists to draw splines in Unreal for my tool so it's something I noted down to mention in my documentation.
I used a sweep node to draw the pipe body around the spline but this left me with a very boring looking pipe. I'm imagining this is quite an industrial pipe so I want some nice bolted joints.
I'm thinking a version of something like this although with reduced detail just to keep things simple. These pipes would be background assets so minimal geometry with a nice metallic shader is my aim as far as aesthetics go. I might also have a painted pipe option for the shader if I have time. It would be easy to have the paint colour set to a parameter and then maybe have some procedural options for wear and tear... if I have time!
For the joints I used some simple shapes to generate the main bracket and the bolts. I then used a resample node on an octagon to create some nicely arranged points to copy the bolts to and merged all the pieces together at the end.
The process of calculating the locations where joints should be copied to
Showing how the pipe and joints are combined into the final mesh
At first I set up a test spline in a blueprint thinking that would be the best way to test my tool. I set up my input to be the curve type and then used my blueprinnt to draw a spline. However, weirdness soon occurred and the pipe would flatten itself in all but one plane. I found out this is due to extra scale and rotation attributes that spline points in Unreal have compared to Houdini.
I edited my tool in the session sync to remove the additional attributes as soon as the curve was imported and this solved my issue. I decided to try using a Houdini curve in engine to see how that comapred and it was much more straightforward. I don't think there are any real cost comparisons between the two but seeing as Houdini curves are designed to work with Houdini tools I opted to use that and will suggest users do the same in my documentation.
In early testing I ran into an issue where my Houdini curves couldn't be edited upon reopening the level, even with restarting the Houdini session and recooking. This is why I tried to get the Blueprint curves working by deleting the unnecessary attributes because I assumed Houdini curves simply worked this way and I needed them to be editable. It wouldn't be much use to an environment artist if they had to redraw their pipes every session.
I couldn't figure out why this was happening and then on my 5th restart it suddenly started working. I hadn't changed anything so I have attributed this to a bug. The installation of Houdini on our university machines seems to have some issues. I'm so new to learning the software that my understanding of it isn't quite at the point where I can automaticaly tell if something is a software bug, a bug in my tool, or just a mistake I have made. However to make my life easier I decided to get Houdini for myself and test my tool on my laptop. It worked every time and all the mysterious crashes I'd previously been having also stopped! I ended up working with ITMS to try and locate the issue but all we could do was isolate that it's most likely an issue with the Houdini Engine deployment. So for now I decided to continue working on my laptop in class.
I think I discovered the cause of the Houdini Engine crashes. My laptop has worse specs than the desktops in class so it's strange that it would run smoother. However our uni machines have 13th gen intel processors. If you keep up with gaming news you might remember issues coming out not to long ago about 13th and 14th gen intel processors having issues running Unreal Engine games. It seems houdini has a similar issue when caching which explains the crashes whenever I stop or restart a session. I passed on my findings and hopefully they help get the ball rolling on a fix! I'm just glad my laptop has a 12th gen processor otherwise I would not have had a workaround in the meantime.
Sources:
Unreal Engine games crashing on intel 13/14th gen processors: https://www.pcgamer.com/hardware/processors/intel-cpu-crashes-what-you-need-to-knowmicrocode-to-blame-but-fix-incoming-this-month-alongside-two-year-extended-warranty/
Houdini Engine Fatal Error: Segmentation Fault (unstable processor): https://www.sidefx.com/forum/topic/96868/?page=1#post-426126
Choosing which parameters to expose so that artists can customise their pipes came down to balancing freedom and function. If I let them customise too much I am at risk of breaking my own tool, and I want to keep things simple for the artists. I consulted my colleague and desk-neighbour Fennel to make sure that the parameter names I chose were intuitive. I will explain all of them thoroughly in my documentation but the best UI is the one you can figure out by yourself.
This is the current state of my options all of which I think are fairly simple although I might change pipe columns to something more like pipe smoothness as I think this could be misconstrued as controlling multiple pipes which is something I haven't implemented yet.
I will revise the parameters as I continue to develop the tool. I am aware they can feel a bit like an aftersight like documentation but in reality they are the main interface any user will have with your tool and quite often they will have no idea how it works behind the scenes so making this as accessible and intuitive as possible is key.
Adding to my tool to allow for multiple pipes seemed really straightforwars at first. I can just duplicate the finished pipe and translate it down/sideways! Unfortunately that didn't work and this part of the tool went through may iterations.
My first method of duplicating and translating the pipes did not work. Copying a line and moving it still causes overlap, so then I thought maybe I could scale it down but then the pipes had different radius. So then I tried copying the initial spline, translating and scaling it but there was still overlap on certain planes. So this way was out!
Trying the PolyExpand node.
The only other way I could think of to create pipes which flow together would be to extrude the original spline and create new splines based on that shape. I tried the PolyExpand node first and that gave me a very wonky result. Pipes in real life don't tend to look like this; I needed a more regular shape.
Then I tried the sweep node which I had been introduced to during the tutorial course. However instead of sweeping to create a pipe I swept a flat line to create a ribbon. Now I had multiple edges to follow and my only issue was that it was no longer a curve but a polygon. However after going back to my tutorial project I found that the add node can keep point and remove geometry (faces) which allowed me to recreate the original spline and multiple others in parallel.
Trying the Sweep node.
I used a basic switch node which when exposed as a parameter would allow users to toggle between single and multiple pipes.
The multi-pipe feature in action, with parameters to control the number of pipes and the distance beween them.
It's subtle but the pipes here are not actually nicely aligned and parallel. They're slightly wonky. This was a problem I found incredibly tricky to solve as there are so many parts at play. Somewhere in the sweep I needed to be able to fix the direction and set the alignment. However when I did that the pipes would criss-cross. I initially tried to find work arounds that involved creating custom up attributes but alas none of it worked. I needed to fix the criss-crossing problem first.
Pipes are not supposed to do this.
I traced the problem back to my sweep node.
Something strange was happening in my sweep node causeing the faces to twist. I could not figure out how to fix this and so I did something I have feared for a long time...
I posted my question the forums.
And somebody really helpful replied! My forum post can be found here.
Forum member BabaJ pointed out something obvious I should have realised - normals are calculated based on faces not points and because there is only one single face trying to change direction it is forced to twist.
Based on this revelation I came up with a very simple solution: resample the spline to add some extra geometry to support the change in direction. This fixed the issue and now my pipes remain nicely un-crossed.
After solving my criss-crossing issue I was then able to go back and fix the alignment of my original pipes without worrying about any twisted faces. Now I have perfectly parallel and nicely aligned pipes.
Testing my box/valve intersection placement system with a basic box before creating valve geometry.
One stretch goal wasn't enough so I decided to add some valves too. From the initial tutorial I followed I knew I could use a second input to interface with the first to add/remove geometry but in this case I knew I would need to do something new and calculate the specific intersection between the placed geometry and the pipe.
I decided the best way to go about this would be to add a number of set points along the pipe where a valve could be added. This would then allow an artist to drag on a regular box and set it to generate a valve at the nearest safe point.
I had a strange issue with this implementation which I again had to turn to the forums for support with. For some reason the imported boxes from engine were translated and very far away despite showing up in the right place. The solution was changing one setting in the object merge node which imports the boxes.
Once I had this solved I was able to create some geometry for the valve itself. I ended up opting to create this in 3DS Max rather than in Houdini mainly because creating wheel spokes procedurally is quite convoluted and modelling a valve in Max takes 5 minutes. Much more time efficient.
One box can be used to create multiple valves if it intersects with multiple pipes.
Valves with more polished geometry.
While zoomed in really close to my mesh examining the valves I noticed some weird artifacting on the joints. I'm fairly sure this was caused by the bool merging the base cylinder with the little bolts. Thankfully there was a really easy solution - just plug in a normal node to prompt Houdini to recalculate with the new geometry. Finally an easy solution I could figure out!
Before fixing the normals
After fixing the normals
In my initial testing I only tested the valve in one orientation. This was an oversight on my part and I needed to make sure the valves would rotate to follow the flow of the pipe.
Finding a way to do this was suprisingly covoluted because I hadn't considered it in my initial set up. However, now that I had fixed the intersection issue from before I could use the bounding geometry of the boxes to group the points inside and then use the original curve to orient along. Previously I was selecting the points then blasting away the rest of the curve which was leaving no curve to orient along. This way is much better and I'm glad I remembered to test the system along different oriented pipes.
Lesson learned: Always test multiple cases so you can make sure you didn't miss something very basic!
This is the fixed unwrap. Originally I was using a labs cylinder unwrap node which was struggling with the corners of the pipe. I discovered that the sweep node actually had an inbuilt unwrapping tool which worked a lot better.
The textures auto-unwrapped to fit the texture space, however I wanted to be able to scale my unwrap to maintain the 1024 texel density. This meant creating a simple formula to scale the circumference of the pipe relative to the roughly 1 meter width of the sheet.
I set my UVs to scale (in meters) against 2πr so that as the pipe radius increases the texel density will remain consistent. I think this is a small detail but would make a big difference in a large project.
At first I experimented with some basic worldspace aligning and while this is straightforward I know I would prefer the less costly method.
Using this painted/rusty texture I made following a tutorial revealed some clear issues with my unwrap that were not visible in in the original materials. This is something I fixed as shown above.
I made a basic rust material in substance designer and used a mask to apply it to the original metal. When using a simple flat mask the seam is very clearly visible.
This is the graph for the original plain metal material. This material allows the artist to swap between a copper and steel albedo value so that the pipes can be cohesive with multiple environment aesthetics.
This is the material after using the initial flat mask but setting it to be a world aligned texture. This allows it to disguise the seams in the original material really well.
A gif to show the shader parameters in action.
After setting up the world aligned mask I added some parameters to allow the artist to control both the intensity and spread of rust across the surface.
As this is a procedural mesh it has no baked information to draw from for rust distribution but I think these adjustable options still give the artist a good amount of control over the final outcome.
The basis of this system could also be adapted to apply to other weathering effects depending on the context such as condensation, ice, acidic corrosion and more. I think this material strikes a good balance between efficiency and aesthetic quality and definitely disguises the seam.
This project was so fun. I have loved working with Houdini; choosing to create this whole project in a software I had no experience with was a risk but it was incredibly worthwhile. Houdini is a very powerful tool that I have only scratched the surface of but now I know the fundamentals I feel more confident in experimenting further and using it in my final major project.
Now that I understand the procedural process more I would have approached this project in a similar way but with more consideration for the drawbacks of my method. I chose to model the pipes procedurally rather than using a prefabricated kit. This caused me significant limitations when it came to texturing as the UVs also needed to be created procedurally. If I had created a kit based system I could have done something like scroll along the textures or rotate the segments to reduce the effects of tileability.
I think the main areas for improvement in this project are aesthetic. There is very little variation in the pipe mesh due to the way it is created. I think adding more subtools like the valve add-ons would allow for more visual interest without being time or resource heavy. However I think as background details these pipes work well and are effective as set dressing. The purpose of procedural tools is to save artists time at the expense of having bespoke assets so in this case I think the trade off is worth it. The rust shader does allow for some minor aesthetic controls however it is still repetitive and this is clear on multi-pipe setups. I think a potential way around this would be to translate the UVs of the pipes by a random or variable vertical amount per pipe.
I met all of the essential and stretch goals of this brief which were detailed in the first post of this blog. I have created a detailed documentation pdf to explain how the tool can be used and warn of a couple known issues, the most pressing of which is that when the undo keyboard shortcut is used to remove or replace a point on the spline in Unreal it causes a duplicate static mesh to spawn. The static mesh can just be deleted so it's not a tool breaking bug but it is bizarre and I have absolutely no idea how to go about rectifying it. Perhaps if I set the tool to only re-cook when told to rather than immediately on any parameter change that would prevent this issue but I think the quality of life improvements that come with being able to instantly preview any parameter changes in real time without any extra button presses outweighs any minor bug fixes.
The environment used to demonstrate the pipe tool can be found here:
Unfinished Building, Quixel, https://www.fab.com/listings/25f2e7e5-5cca-48a5-99a3-35c38b8240ac [date accessed 07/01/2025]
To test the tool I used it in a free Quixel environment I found on the asset store. I think it has worked quite well and I could see this tool being especially useful on a project with lots of unbuilt or partially destroyed urban or industrial environments where it would save a lot of time.
Using it in a real environment definitely revealed some new quirks that weren't apparent in isolated testing such as the fact the tool always defaults to selecting the origin after adding a new point however I think this is just a quirk of how houdini splines work in engine rather than an error I need to fix.
There are some quality of life improvements I could add like more flexibility when it comes to scaling the valve. On larger pipes the joints get close together on the valve so I probably should have tied this to the joint spread multiplier rather than have it be a set size. The balance between enough parameters that a tool is functional but not overwhelming is a balance I need to reach I think currently I was a bit conservative with the number of options for the sake of simplicity.
Overall this tool functions as intended, it looks appropriate in context and it's relatively intuitive to use. I think those are the really key points that something like this needs to hit in order to be useful in a wider project and thanks to this project I now know this is the kind of work I would definitely be very happy doing in a studio.