Hi there, my name is Collin Harris! I am a 3d environment artist currently attending Ringling College of Art and Design. Earlier this year I had the opportunity to experiment with the idea of creating a completely modular, procedurally built environment. My goal was to implement procedural elements without sacrificing my design and aesthetic targets. With this in mind, I thought I would share a few of the various lessons that I learned over the duration of my project.
#1: A PERFECT ENVIRONMENT IS, IN THEORY, IMPERFECT.
In the land of random numbers, variation is king. If you are already going to be placing your objects based on randomly generated numbers, do yourself a favor and add some variation to your spawning variables. Giving your objects imperfection is the best way to sell an environment and mitigate the biggest pitfall of procedural generation: perfection. Programming in deviation is the easiest way to begin really directing your objects in the visual direction that you want your environment to take. Failure to do this puts your environment at risk of feeling less like a lived in space, and more like a museum exhibit (as seen below).
#2: Think about the relationships between objects.
When you begin to think about creating your scene, think about how each object is going to interact together. Even if you are not going to be modeling a lot of unique objects, there is still a huge abundance of variation that can be expressed through each object and its relationship with its surrounding objects. Something as basic as a coffee cup could be used to characterize an entire scene. Is it broken? Is it from a souvenir shop in an interesting location? Maybe it’s not even being used as a coffee cup and it’s being used to hold up a screen or being used as an ashtray. Once you begin to look at objects as narrative building blocks instead of one off props, you’re going to make your life easier and hopefully make your environments much better.
#3: Break down your environment’s construction early.
Generating an environment from scratch is complicated. But if you go through the process of breaking down your environment beforehand, you will get a much better picture of what needs to be made, and how to make it all fit together. Think about all of the elements that an environment needs, from the construction of the walls and floors, down to the signage around the room or the props on the floor. If you go through these processes early in your development, when it comes time to start assembling everything in your blueprint, you’ll be able to focus more on how to fit everything together to better fit your story, and less on how to make your environment just “feel right”.
#4: The LOGIC
So, now that I’ve given you some high level tips, let’s look at specifically how I put together the procedurally generated tiles in my environment. All of the basic logic I implemented sits in a single blueprint tile, and is controlled by a single data struct. All arcade cabinets, walls, props, lighting, and miscellaneous objects are spawned and placed based on a random seed dictated in the blueprint class.
All of the spawn values are stored in a struct which allows local manipulations to any generated asset. I ended up making two variants of my tile, one that propagates during runtime, and one that propagates during construction. The tile that propagates during runtime is more costly, but allows for more flexibility during gameplay. The construction tile propagates all its props at execution, and because of that has no effect on frame rate. Below you can see the tiles updating at runtime!
Below is a portion of the construction graph used for the procedural tile. Most of the graph on the left is generating all the values needed for generating each tile, while most of the graph on the right contains the logic for each element of the tile. (ie: not spawning posters over windows, or turning off lights if there are no fixtures)
As I began working on this environment, I came across a problem. In order to output the level of texture variation I wanted in my scene, I would have to spend an inordinate amount of time creating and assigning a torrent of material instances. The solution to this problem, was the Object Position node. I set up a function that takes the object position of whatever mesh the material is assigned, and uses that value to randomly select a 1:1 UV from a tilesheet. With the function made, I was able to add a huge amount of variance to my assets with a single material.
Above you can see the object variance function at work as I move around each object. On the left you can see the screen texture and color of my arcade machine randomly generate. The static image in the middle is the tilesheet texture I used to randomly generate the screen variance in the arcade. On the right you can see the poster diffuse and edge wear change as it moves. With these two materials I was able to populate my entire environment with just a handful of materials.
Finished Environment Shots!
Hopefully this helped you put together your own procedurally generated environment! If you have any questions you can catch me on Twitter @grossbloy
Thanks for reading <3