Making a Landscape Generator for Maya in Python

Anse Vandevelde explains how the idea for the landscape generator emerged, talks about the process of studying Maya's nodes, writing the script in Python, and testing it out.

Introduction

My name is Anse Vandevelde. I am a 19-year-old 2nd year VFX student at Digital Arts and Entertainment in Kortrijk, Belgium.  

During the past year, I learned more about the technical side of the industry. I was really excited about this part. I had done some programming which I really enjoyed. I love setting my mind to work and solving all the problems that come my way throughout a project. This was definitely necessary when I got into proceduralism in Houdini and scripting in Python for Maya. I loved working on these projects and figuring out ways to get everything to work, time could really fly by when I was busy working.  

Working on the Landscape Generator

When the opportunity came to make our own script in Maya for our last assignment, I wanted to do something procedural. The projects in Houdini inspired me a lot and the software had limitless possibilities. I always tried to incorporate some code to make parts less breakable while keeping the project as extensive as possible. I love this combination, so I really wanted to try and develop something in this area.

I didn’t know immediately what I wanted to make for the script, so I looked around on the Internet for some inspiration. I came across some projects that made me think about the noise nodes in Maya. I had used these before on another assignment. Thinking about this sparked the idea to make a landscape generator.

Starting this project, I first needed to see how I would set up a base for a landscape with the noises and how it could be possible to make several variations that looked realistic. I started playing around with some noises and combinations of noises layered onto each other.

When I had a structure in mind (more or less), I wrote down a small scheme of how I would build it up, separating the base and top of the landscape and writing all the nodes that I might be able to use as well. This structure was very useful throughout the whole project to keep things organized in my mind but also it was useful when working in PyCharm because creating nodes in Hypershade and connecting everything takes up a lot of code. This was also helpful during the creation of the UI and when separating everything.

With the scheme written out, I jumped into Hypershade in Maya and tried to recreate the setup. When this gave me a good base as a result, I switched to PyCharm to start programming this base setup. However, first I needed to do some research on several commands to make the different nodes and link them to each other, like how to assign a material to a mesh, etc. I hadn’t used most of these before, so this was an important part to figure out. With this information, I recreated the base scheme I had written out and tested everything. This of course changed a little throughout the whole process but the base stayed more or less the same.

With all this done, I had a general idea of the script and I already had some extra features in mind. So I made a small blockout of how I imagined how the UI would look. Doing this before coding makes it easier to see which layouts should be parented to which and again makes the overall organization easier. 

The Art of Writing Scripts

As I mentioned, the script uses different noises. In the end, I used a combination of Maya and Arnold noise nodes, like the aiCellNoise and fractal. The aiCellNoise in particular was really one of my favorites because it has more options thanks to the different patterns. Because I was using both Arnold and Maya nodes, I couldn’t add any of the extra functionalities to my UI or the viewport like visualizing the displacement in the viewport or the preview of a material in my UI so you could already see what you were doing before rendering — that was not possible.

I blended the different noises with layered texture nodes. With these, you have a lot of options for how you want them to layer the given inputs. You can use the blend modes and the alpha slider to change the look. Changing the blend mode from “over” to “subtract” would already give a whole different result. Sometimes it looked too extreme and unnatural but tweaking some other attributes could solve this. At the end of the chain of nodes, I put it into a displacement node and then into the shader.

Early on I also noticed that in order to, for instance, change the top without it affecting anything else, I would need to isolate that piece. For this, I used the Remap HSV nodes. I would use the value ramp to do this. I would put this result in a layered texture node together with another noise that I wanted to add to, for instance, the top. Putting the blend mode on multiply would then isolate the part I needed. Though the ramp could have multiple functions. I could edit how many dark spots I wanted, or how smooth the gradient from white to black for the base was, or even make some sort of valleys by making a tooth-like shape.

With all this figured out and with the scheme, I continued to work. So now I had all the necessary nodes, but I didn’t have a UI where I could change the attributes yet, so that was the next step. I started playing with all the settings and seeing which ones seemed most useful and would really make some interesting changes to the environment.

Knowing this, I started to code. My setup from before wasn’t organized yet, it wasn’t written in any classes or functions. This I needed to tackle first. It’s best to do this early on because otherwise when you start having hundreds of lines of code, you won’t find anything anymore, it becomes more difficult to fix problems and you’ll probably have to repeat multiple lines of code. My first idea for the structure was to put everything in one class and for each part, I had a different function. At this stage of the project, this worked perfectly. It did change when I started to add more features. 

With this structure in mind, I made a basic UI with some drop-down menus for each part (the base, the details, and the top). I was still figuring out how to program a lot of things, but I knew most of them needed to be added further along. For the inner workings, I had a function for each part of the landscape, one to make the nodes and one to update the nodes when something changed in the UI.

When I was satisfied with this, I moved on to the next feature I wanted to add. This is the option to add basic colors to the base, middle, and top of the landscape. They are blended into each other for a smooth transition. For this, I used the Blend Colors node, the colors are linked to the UI and for the blender input, I used the output of the different stages of my node setup. I put this in another tab in my UI. But when I was coding everything, I noticed that my class was getting very chaotic. I was also going to add more, so at that point, I changed my whole layout again. Now each part/tab was in another class. The code itself works the same way as for the shape of the landscape, there’s a function to create nodes and one to update the nodes but now also one for each UI element. Every one of these functions then gets called separately in the main class. 

Usability of the Tool

With these procedural tools, there can be a steep learning curve to get to know the tool and all its settings. Immediately getting a desirable result can take a while. This is why I added two features: you have the ability to use a custom heightmap as a base and I made a few presets from which you can start working.

When you add the heightmap, internally you switch the base noise and its settings for this map. This enables you to work further on this map by changing details or the top. Having fewer options can help in the beginning or help you work faster. You can also easily switch back and forth between the noise node and the map. In Hypershade, they get connected and disconnected each time you switch. 

Unlinked mask

Linked mask

Setting Up New Presets

As stated above, for a tool like this, it can be very useful to have some presets. There are so many settings that it’s too difficult to remember everything. So it seemed like a good idea to add the option to save the current setup of the attributes. Everything including the ramps in the shape tab and the color settings is saved. I made 5 presets which you can load in immediately and render, you also have the ability to reset all the settings to default and restart.

When you want to save or load settings, a file dialog opens where you can choose a TXT file to open or the location to save settings. In my code, I query all the values of the UI and append them to a list which I eventually save as a TXT file using JSON. The loading of the settings works in the same way. I read the TXT file and save all those values in a list, then I go over each value and assign them to the right attributes. Then I update all the nodes again so everything is correct. While I was coding this, testing regularly was very important. It was easy to accidentally switch values or even try to access a value on the list that didn’t exist. Also querying and editing each attribute takes up a lot of lines of code, making mistakes was very easy. That’s why I also split up this part into several functions to keep it organized. This also made testing each part easier.

This method worked at first, but I noticed when I used the ramps, things could go wrong. I needed to take into account that sometimes you have more or fewer values on the list because you use more or fewer controls. This is where my favorite part came, problem-solving. I fixed it by adding a word after each part with a ramp when I save the settings. Then when I need to read a saved preset, I check when I reach a certain word on the list. The number of other attributes is set, so I can easily count how many controls each ramp has by subtracting the number of set attributes of the total until I reach the word.

Conclusion

When working specifically on procedural tools, it’s easy to get lost in all the different options you can add. But in the end, it has to stay a little compact while still extensive enough so you can have a wide range of variations. Having a time constraint helped circumvent this. I made sure to have a working base for the script before going more into detail and adding extra options/features. Though still with all the settings you can get some odd results as well. But the more you use a certain tool, the better you know which settings you can push and which ones you can’t.

Also not having worked with Python for a very long time made me have to do a lot of research at the beginning to get a base and throughout the whole process as well. I learned a lot about coding but also about how to properly research and understand the documentation. This is extremely helpful when learning things by yourself.

I loved working on this generator. I learned so much, I could set my mind to work and there was plenty of problem-solving to be done throughout the whole process. This made it so easy to work on it because of all the enthusiasm I had. I will definitely make similar projects in the future. It’s something I want to learn even more about.

Anse Vandevelde, VFX student

Interview conducted by Arti Sergeev

Join discussion

Comments 0

    You might also like

    We need your consent

    We use cookies on this website to make your browsing experience better. By using the site you agree to our use of cookies.Learn more