Large-Scale Roblox Terrain: The ultimate guide

Large-scale Landscapes on Roblox

Updated on 4/21/2020 with improvements to the workflow and quality-of-life changes that lead to better results.

Updated (again) on 7/3/2020 with a method to automatically paint materials to an imported mesh based on slope and altitude (rough first version, improvements can be made in this area). Also updated the accompanying intro thumbnails to reflect the improvements that have been made.



Some example landscapes that can be achieved with this workflow.


REQUIRED TOOLS:

  • Quadspinner Gaea (Separate program; you just need the free version)
  • Blender (Separate program)
  • .OBJ Importer (A Studio plugin)
  • Automated part to terrain conversion script (There’s one provided in this tutorial)

The problem

Making believable terrain on a large scale is a daunting task. If done by hand, it is a tedious process. Roblox-provided tools such as terrain sculpting and procedural generation are helpful, but oftentimes lacking. While the sculpting tools are perfectly adequate for experiences with smaller worlds, it’s an insurmountable task to model a massive open-world landscape alone using Roblox’s default terrain toolset.

Luckily, Roblox has provided us with tools to make this easier. Importing heightmaps has made it much faster and easier to prototype a good base for big landscapes. This feature presents its own set of issues, however.

A heightmap is good for conveying information of basic landscapes. Change in elevation, whether steep or gradual, is relatively easy to achieve. However, due to the nature of a 2D representation of a 3D space, it’s not possible to generate features such as overhang cliffs or even cave systems with just a basic heightmap. For the sake of avoiding confusion, I won’t go into detail on the specifics of generating such features procedurally.

My intentions with this tutorial are to ensure accessibility. This means that no prior programming knowledge is required; anybody who can follow a set of instructions can achieve this terrain. One of the biggest advantages to this process is that you don’t need a big team to make impressive terrain.


Part 1: Quadspinner Gaea

In my search for alternative tools for terrain generation, I found a program called Quadspinner Gaea. Its free version allows for a map resolution of 1k without a watermark, which is more than enough resolution for generating terrain on Roblox.

In this tutorial, I will teach you how to use Quadspinner Gaea’s features to generate your terrain. Since it’s a node-based experience, it’s relatively easy to pick up and understand quickly.

Before we begin…

PLEASE NOTE: Gaea can eat up a lot of system resources. Bearing that in mind, here are my recommended specs:

  • At least 8 GB of RAM (though I strongly recommend 16 GB, as Studio will eat up a lot of RAM during the import process)

  • A reasonable GPU (at least 1 GB of VRAM as reported by the official website)
    (Here’s a useful metric: For slower tasks, Gaea uses < 10% of my 1080 ti, so you probably don’t need anything super powerful here.)

  • I’m using an i5-8600k, but anything equivalent to a Ryzen 5 1400 should do.

I haven’t been able to test on lower-end systems, so I can’t vouch for a smooth experience on lesser hardware. It’s definitely worth trying, though. Feel free to report your usage experience in the replies!

Update on the specs situation:

https://devforum.roblox.com/t/large-scale-natural-terrain-the-ultimate-guide/405672/6?u=vexture
As it turns out, lower-end hardware can actually manage this! Thanks, @outofbearspace.

The official website’s listed minimum requirements are very high, but you don’t need that for our purposes. For super-high terrain resolutions (well over 1k resolution) using professional (paid) features, you’d need beefier specs.

The program’s installation is self-explanatory, so I’ll skip that bit.
When you first open the program, you are greeted with this screen:

Each mode allows for different ways to edit terrain. I personally use Create a terrain (Graph), and that is the mode I will be covering in this tutorial. I feel that it is the most powerful mode, but feel free to experiment with the other options.

Once you start a new Gaea project, you will be greeted with the Graph workflow. You will notice 4 vital components: The viewport (middle top), the node editor (middle bottom), the toolbox (left), and the properties menu (right).

The starting shape of your landscape is called a Primitive. Primitives act as the base for your terrain, so all nodes connected to your primitive will act on it. You always start with a Mountain, a Displace node, and an Erosion node. Their functions are self-explanatory; displace warps the terrain a bit, while Erosion etches rivers into your landscape.

The Toolbox

The amazing thing about this program is its sheer versatility. The best way to understand what each node does is to experiment with them yourself, but behaviors are based on the category they’re in. Here’s a short description of what’s in your toolbox, from top to bottom:

  • Primitives: The starting shape of your landscape. They act as the base for your terrain, so all nodes connected to it will act on it.

  • Adjustments: These nodes are like post-processing effects you would apply on images. They are applied indiscriminately to the structure of your landscape. Should you decide to use them, they should probably be the last element on your node tree.

  • Filters: A bit more useful than Adjustments, Filters are a bit smarter. They exhibit predictable behavior, but rather than degrading the structure of your terrain, they enhance it by changing the shape of your primitive.

  • Erosion: Super important! These add depth and detail to your initial primitive. Rivers, landslides, and much more can be easily achieved with these modifiers.

  • Data Maps and Color: I’m combining these two because they work pretty much the same. Data maps and Color are better suited for simulation of things like vegetation growth, ground texture, and more. While I haven’t actually used these in practice yet, you could export these maps as images and place vegetation or textures based on these maps alone, which is super useful in theory.

  • Selectors: The only node from this category I use is Height, though less frequently now due to a step I will introduce later in this tutorial. Height generates a heightmap, which can be imported into ROBLOX through the built-in terrain editor. I will cover generating heightmaps in a separate tutorial; for the best results, I recommend using the methods I’m about to go over.

tl;dr: It’s actually pretty self-explanatory, so trial and error will teach you everything you need to know about your toolbox.

Properties

These are pretty easy to figure out, so just mess around with the sliders. Click on the node you want to edit and its properties will show up on the right, just like Roblox Studio. Use the Scale slider on your primitive to cram your chunk of terrain with more features for bigger maps, or fewer for more enclosed areas.

Interacting with the node editor

It’s so simple!
GaeaTutorial1

Exporting your terrain from Gaea

Here’s where the process starts to fork a bit. As I mentioned above, I will be covering heightmaps at a later date. The advantage that heightmaps have over what I’m about to show you is that they are significantly faster to import into ROBLOX as voxel terrain.
For now, here’s how to export:
On the top-left corner, click on Gaea. Then, click on Entire Terrain.

https://gyazo.com/35f528040f01d36517092e52d63bc445

A recent update to Gaea has changed the export process slightly, so I’ve edited this section for accuracy and completeness:

Due to a recent update to Quadspinner Gaea, you cannot export your terrain as a .obj from any particular node. In order to export your terrain as a .obj file, you must add a Mesher node at the end of your graph.

EDIT: There have been improvements to heightmaps on Roblox! It is much faster to export your terrain as a heightmap rather than going the mesh route, as you no longer have to wait for content moderation to upload your image and it now supports higher resolutions. The only drawback is that the auto material script shown below will NOT work with imported heightmaps. For material painting in this case, export a color map from Gaea that adheres to Roblox’s colormap material set.

Click on Start Build, choose where you want to save your file, and let it run! Leave the file extension as .tor in the Save As menu.

When complete, Gaea will open a folder with your saved terrain object


Part 2: The Import Process

Now that you’ve made your shiny new terrain, you want to import it to Studio, right? Well, luckily that process is relatively easy! Since we’re not using heightmaps (we’re using the .obj file itself), the aim is to get that file into Studio and convert it to voxel terrain. We do this with NexusAvenger’s .OBJ Importer.

For those of you with weaker PCs, I apologize in advance for the lag you’re about to endure… It’s worth it, though!

This incredible plugin takes vertex data from your .obj file and converts it into parts! This means that you can surpass Roblox’s polygon limit for .OBJ files and it has the added benefit of being convertible to voxel terrain using some simple code! That’s what makes this whole process viable. If we had to use massive amounts of part terrain, this wouldn’t work for a real gameplay setting!

Before we do that, we still want to reduce the poly count, as otherwise we’ll be importing millions of polygons into our world! We definitely don’t want that.

Using Blender to optimize terrain for import*

*Due to some updates I’ve made to this post, I actually find it better to skip the Blender step entirely. If your PC can handle it, I recommend ignoring this step entirely.
Blender optimization (if your PC cannot handle the following import process)

You’ll need to download Blender for this next step. It’s a free and powerful 3D modeling software (but of course you already knew that)!

Since this isn’t a Blender tutorial, I’ll keep this section brief. When you install and open Blender, you’ll be prompted to create a new file. Just click on General.

To import your OBJ file to Blender, go to File > Import > Wavefront (.obj) and navigate to where you saved your terrain.

https://gyazo.com/f09003f750a735393ad8eb9344c75c8b

Click on the blue wrench icon on the right, click Add Modifier, and add the Decimate modifier. We’re using this to reduce the poly count significantly.

image

Now, just reduce the ratio until the “Face count” is at around 200,000 (Try 50,000 (minimum ratio of 0.1) if you’re getting a weird banding effect in your terrain). This is low poly enough to reasonably import into Roblox as parts while still retaining enough detail for voxel conversion to be viable.

Click on Apply, and then export your optimized .obj by going to File > Export > Wavefront (.obj)

The reason I am now advising against Blender optimization is due to the new conversion method. If you don’t have enough triangles, once you convert your terrain, there will be more holes present in the final product.

Importing to Studio

If your PC isn’t already crying, this is where things might get tough. (You might want to try reducing your poly count further if this next step is impossible for you to complete.)

Install the OBJ importer plugin. In a new blank place, go ahead and open that plugin. You can delete the Baseplate.

This plugin works by reading vertex data pasted into a script and generates all of the faces as wedges.

Right-click on your optimized .obj file and navigate to Open With > Notepad.

Copy all the contents of this notepad file into a script. I named mine Vertex Data, but it doesn’t matter what you call it.


Each line represents a vertex in your terrain model.

Set your scale to whatever size you want your map to be. I personally went with 4,000. The size will not impact part count, as that’s predetermined by your polygon count.

You will see two options under Triangle Type: Part and Terrain. While at first glance Terrain might be the better option, make sure you are using Part.

If you don’t use Part, your final import will look awful, like this:

Now with the script selected, click Continue until it begins to parse the faces.
https://gyazo.com/fe70323814eb910e1cf44fb6891c4f38

One this process is complete, hit continue until the faces start to render. This will take quite some time, so grab a cup of coffee or something.

https://i.gyazo.com/27eb415c3c1d8a26b05e193270aa71c7.mp4

(Yes, this takes a while.)

The hardest part is behind us! There’s just one more step.


Part 3: Converting your parts into terrain

With the recent addition of :FillWedge(), I have updated this post with new code that gives far better results. I’ve also added new functionality, including automatic material painting. It significantly improves the initial import results even before any by-hand adjustments are made.
Note: Run this code in Studio’s command bar, you shouldn’t run Test Mode or you might not be able to save your terrain.

local rate = .00001
local RunService = game:GetService("RunService")
local whitelist = workspace.Mesh:GetChildren()
local thickness = 25
local sea_level = .125 --From 0 to 1, where 0 is the lowest elevation of the map and 1 is the highest (gets denormalized in terms of the relative elevations of your map automatically)
local regionIncrement = 1024

local function TimerWait(duration) --Framerate-dependent function that waits at the theoretically lowest possible step, Heartbeat:Wait(), until some time dt has passed.
	local start = tick()
	repeat RunService.Heartbeat:Wait() until tick() - start >= duration
	return true
end

local function MinMaxAvg(data)
	local min, max, average = data[1], data[#data], 0
	for _, element in pairs(data) do
		if element < min then
			min = element
		end
		if element > max then
			max = element
		end
		average = average + element
	end
	average = average / #data
	
	return min, max, average
end

local function Normalize(Min, Max, Val)
	local Normal = (Val - Min) / (Max - Min)
	return Normal
end

local function Denormalize(Min, Max, Val)
	local Denormal = (Val * (Max - Min)) + Min
	return Denormal
end

local function ConvertTerrain()
	local data = {}
		
	for _, part in pairs(whitelist) do
		table.insert(data, part.Position.Y)
		if tick() % 5 > 4.5 then
			TimerWait(rate)
		end
	end
	
	local min, max, average = MinMaxAvg(data)
	local orient, extents = workspace.Mesh:GetBoundingBox()
	local MeshCenter, MeshSize = workspace.Mesh:GetModelCFrame().p, workspace.Mesh:GetExtentsSize()
	local LowerBound = Vector3.new(MeshCenter.X - MeshSize.X/2, MeshCenter.Y - MeshSize.Y/2, MeshCenter.Z - MeshSize.Z/2)
	local UpperBound = Vector3.new(MeshCenter.X + MeshSize.X/2, Denormalize(min, max, sea_level), MeshCenter.Z + MeshSize.Z/2)
	
	
	--Enable for automatic sea level filling based on sea_level variable (VERY slow, recommend using Roblox's sea level method instead. My version of this functionality isn't complete.
	--(It's also not guaranteed that it will be aligned with your map. That's why it's off by default.)
	--[[for x = LowerBound.X, UpperBound.X, regionIncrement do
		for z = LowerBound.Z, UpperBound.Z, regionIncrement do
			if tick() % 5 > 4.5 then
				TimerWait(rate)
			end
			for y = LowerBound.Y, UpperBound.Y, regionIncrement do
				local Lower = Vector3.new(x, y, z)
				local Upper = Vector3.new(x + regionIncrement, y + regionIncrement, z + regionIncrement)
				local WaterRegion = Region3.new(Lower, Upper)
				workspace.Terrain:FillRegion(WaterRegion:ExpandToGrid(4), 4, Enum.Material.Water)
			end
		end
	end]]--
	
	for _, part in pairs(whitelist) do
		local Material
		local cf, pos, size = part.CFrame, part.CFrame.p, Vector3.new(thickness, part.Size.Y + 15, part.Size.Z + 15)
		if math.abs(90 - math.abs(part.Orientation.Z)) > 35 or math.abs(0 - math.abs(part.Orientation.X)) > 35 then
			Material = Enum.Material.Rock
		else
			Material = Enum.Material.Grass
		end
		
		if pos.Y < Denormalize(min, max, sea_level) + 6 then
			if Material == Enum.Material.Grass then
				Material = Enum.Material.Sand
			elseif Material == Enum.Material.Rock then
				Material = Enum.Material.Slate
			end
		end
	
		
		workspace.Terrain:FillWedge(cf, size, Material)
		
		part:Destroy()
		if tick() % 5 > 4.5 then
			TimerWait(rate)
		end
	end
end

ConvertTerrain()

This code has been updated with a method to automatically paint materials to the terrain based on
slope and altitude.

WARNING: Back up your Studio file before you do this, as crashing is possible. It WILL take time to convert all of those triangles to terrain, but probably not as long as it took to render the triangles themselves.

The end result:

Depending on the primitive you used in Gaea, your landscapes will look vastly different. The end result shown here is using the Mountain primitive with erosion and displacement nodes.


Conclusion

These methods produce consistently impressive results and make large-scale Roblox terrain more convenient and approachable, especially if you don’t want to go down the procedural generation route.

I have significantly updated this post to improve the quality of the results; thanks to Roblox updates, this got a bit easier.

This method is incredibly useful but it requires a beefy PC to generate, import, and convert the terrain without crashing. I do wish there was a faster, more performant way to do terrain (i.e. deformable planes, custom materials. I think that would go far to improve the terrain experience. Mesh deformation is on the way, but as far as I can tell, that’s only for animated bone deformation :frowning:)

I hope this proved useful. If anything confused you, I encourage you to ask me questions!

If you skipped everything for the tl;dr: Read the tutorial. :slight_smile:

By the way, I plan on writing more tutorials and I take suggestions! DM me if there’s something you want to know about, and I might write something up.

465 Likes

Did you use any lighting plugins to make it look more realistic?

9 Likes

Nope, that’s vanilla Roblox! The aim of this tutorial is purely for terrain generation. At a later time, I will cover other topics such as touching up your terrain (the sculpting practices I use, materials, trees, etc.) and the specific lighting properties I use. Your mileage may vary, as lighting settings don’t work across every kind of game!

22 Likes

Can you please include the website you can download Quadspinner Gaea?

4 Likes

Initially, I did have the link in, but unfortunately I decided to remove it since it’s against the devforum rules to include offsite links for most websites. That’s why the only link you see throughout this tutorial is to the plugin. Thankfully, a quick Google search for the program will yield the result you want on the top of the search page. :wink:

12 Likes

That’s great to hear! I wanted to play it safe and make sure that people were at least aware of potentially bad performance before they started. I’ll go ahead and update the post to reflect that lower-end hardware actually can do this.

6 Likes

You helped me create a monster… Takes 1-4 minutes to load into game.

i7-4770k 4 cores @ 3.9ghz
32gb ddr3 2444mhz
gtx 980… and still takes 1 minutes+ to load

12 Likes

Beware of scale! At that point, it’s not your system specs, but Roblox’s servers that are the bottleneck. Since Roblox aims to release 64-bit servers soon™ (hopefully this month!) this could be improved. I also recommend enabling world streaming if you haven’t already.

10 Likes

I haven’t done much research into world streaming? Where would that be. I’m a newly returning dev. A lot of this is new to me.

5 Likes

For the concern over off-site links, afaik that policy only applies to the Bulletin Board due to the nature of it being shared on the main website assets. I did double check before posting this, and it is the only category with such a rule.

3 Likes

Adding on to this, removing any terrain that won’t be visible to the player will benefit performance greatly. It’s tedious work though it’ll be worth it if you’re looking to optimize performance

6 Likes

Like someone already said, Roblox is the bottleneck. Streaming will help the client greatly, however the server still has to handle that sheer amount of terrain, which is why it takes so long to load.

When Roblox spins up a game, it has to download world data for the game. A map of this scale will slow that process down a lot.


Id recommend your own streaming solution. Not only will this drastically improve load times, but also decrease server memory usage.

I’d recommend having a module script with that vertex data in that you got from Blender. Then you can do the same process you did to turn that into terrain (vertex data —> triangle terrain —> smooth terrain) automatically and split it into chunks as the player moves.

Make that run on the client and you should be good to go! Obviously there will be other optimisations needed but that’s the gist of it.

8 Likes

I agree fully. Though this will be a sharp learning curve for me. I wouldn’t know where to start. I’m still pretty novice when it comes to scripting. Though I agree. I will need to do my own rendering system likely.

6 Likes

This is an awesome tutorial! I can’t wait to start using this more for projects.
I do have one question. Would increasing the resolution make a difference even after decimating the terrain in blender?

5 Likes

I don’t think so. By decimating in Blender, you’re essentially reducing the resolution of your model.

7 Likes

Yeah, I figured that would probably be it but was hopeful it’d still somehow make it better. It still looks pretty good, and the recent addition of grass definitely helps out with appearance. Thanks for the quick response!

5 Likes

Saiier, a friend of mine has just reported that if you decimate to 50k (but no less), it fixes the banding effects that some people have been experiencing. I will update the tutorial to reflect that.

Ukendio, thanks! I’m not sure what I’ll be covering next yet, but it will probably be something related to terrain. (Read below)

The reason I don’t like heightmaps is two-fold: Roblox has you manually scale the X, Y, and Z sizes. While this gives you a finer degree of control, as a consequence it’s much harder to get an “aspect ratio” of sorts that doesn’t look stretched horizontally or vertically.

I did mention in the tutorial something about cliff faces and cave systems, which may be what I cover next. You can’t get those types of features with heightmaps. I don’t think Gaea can actually do those, so my next tutorial will likely be an in-depth exploration on procedural generation of terrain. Things will be more complex and I have to do all sorts of research myself before I feel confident in teaching it, so it might be a ways off. The upside of procedural generation is that you can generate all kinds of biomes, directly modify terrain materials to look more natural, and have direct control over all of the features of your map.

6 Likes

Fantastic tutorial using a whole new way and with great results. Thank you!

2 Likes

There have been significant updates to Quadspinner Gaea and I have made improvements to the conversion script since the creation of this tutorial.

Something to note as well: If you want to lower the face count of your terrain mesh, you can do so using the Mesher node. You can directly change the vertex count of your mesh, so there’s no longer a need to export to Blender and use the Decimate modifier (which could cause significant artifacts to the mesh).

It’s possible to just export a heightmap and a color map from this program, but keep in mind that you have to wait for Roblox moderation to approve those assets in a published game before you can import your terrain. You also have to go through and customize the color map’s color output so it matches up with what Roblox listens for (this is a tedious process; if your computer has the horsepower, it’s best in my opinion to just import a semi high-quality mesh into roblox and use the suggested conversion script).

6 Likes

I’ve done everything and the parts converted to terrain but I’m not quite sure how i am supposed to save it while in test mode?

2 Likes