Im not sure about the wider Roblox community, but due to the lack of grid based modeling tools, I have found it near impossible to get my terrain to neatly line up with my builds, which is why I love the heightmap importer. Now, I can make maps in blender using the tools available to me there and import the result into studio. In this tutorial, I am going to be showing you how to take a mesh and bake its height values onto a texture that you can upload to roblox and use as a heightmap. I apologize in advance as it going to be rather long.
Note: This tutorial assumes a basic level of knowledge with blender. There are plenty of good tutorials with how to use blender, so I will not be going over the generic functions.
Note: Blender and roblox use different coordinate systems. I will be referring to X/Z as your horizontal axis, with Y being your vertical.
Warning: As a heightmap has no way of generating caves, I have not tested this workflow with meshes that have them. Use at your own risk.
Now with that preamble out of the way, here is my map that I will be importing
1.1: Size of your map in studs: The X,Y,and Z values of the size in voxels (4x4x4 studs) of the map that you are going to want to generate in studio. My example map is going to be 952x952 voxels in the X,Z, and 25 voxels tall.
1.2: Detail level: How accurate of a replication you desire (as in how many pixels per voxel). If you are using CloneTrooper1019’s terrain importer (Custom Terrain Importer - Roblox) it shouldn’t matter too much, but if you are not, having a high detail level can mean having to split your image up and do multiple imports if you have a larger map. I suggest 1x (1x1 pixel per voxel as a minimum), with 2x (2x2 pixels per voxel) being “ideal”, and 4x (4x4 pixels per voxel) being as high as you can go and still notice any sort of difference. In my example I went for a 1x
1.3: Blender Scale: The value that determines how many blender units are worth a voxel in the X,Z axis. In my example have set my grid to be divided up by 4 instead of the standard 10 to better emulate my preferred style of building, and thus set my scale to 1 blender unit is equal to 4 voxels.
1.4: Y scale: The value that determines how many blender units are worth a voxel in the Y axis. Note, a heightmap supports 256 levels of height, so if you need super accurate Y levels for alignment, its best to make your blender scale a factor/multiple of 256. Also note that you can not export anything with a height less then one voxel, so if your scale is 256, anything with a Y (blender Z) value under 4 won’t be rendered. To make building easier I have gone with the same scaling system as in my X,Z.
1.5: Image Size. The size of the image we will be baking our heightmap onto. To make unwrapping a bit more reliable, and to make optimal use of robloxes image size, I am going to suggest making the image a square. To calculate the side length of the image, it should be slightly longer then the length in voxels of the longest side of your map times the desired detail level. In my case I need it to be a bit longer then 952, so I am going to go with 1024 as it’s a standard size for a texture.
2.1: Build/import your map so that your lowest vertices has a y value of zero and your highest vertices are lower then 256 times your Y Scale. You will also want to center your map as best as possible.
2.2: Make a square consisting of 4 lines around your map at y=0. This box needs to be scaled in accordance with the size of your image. The math for this is (image size)/2(Because I am building in the center of the Cartesian plane)/(X/Z ratio). In my case this works out to 1024/2/4=128, so I will need a vertex at (128,128), (128,-128), (-128,-128), and (-128,128).
2.3: Connect the resulting square with your mesh, and fill in any holes. My map now looks like this.
3.1: Go into the UV editing mode
3.2: Generate a new image using your image size. To do this, in the bottom left click on image->new, give it a name, and set the Width and Height to your image size value.
3.3: Unwrap your image by selecting your map, go into edit mode, select everything (a) go into orthographic mode (numpad 5) and top view (numpad 7). Then unwrap (u) and click on “Project from View (Bounds)” this should generate a UV map that is scaled exactly according to your map. If it does not, you may have an issue with how your map is layed out.
4.1 In the side bar go into context and set your renderer to cycles
4.2 Create a new material by selecting your map, going into edit mode (tab), selecting everything (a) and clicking the + symbol to add a new material
4.3: Go into shader editor and build out the following shader. Of note, the orange box is an image texture, and should be set to the image you generated in 3.2, and its color space needs to be set to non color, else you get some wonky scaling issues. The divide node is just a math node set to divide and should be set to slightly higher then your highest Y value on the map. In this case my highest value is 6, so I will be going with 8. Remember what you set this to, you will need it later.
If you do it correctly, your end result should look something like
5.1: To bake your texture, go back into the context tab and click bake.
5.2: Go back into UV view, and you should see your heightmap being generated. Once it is done click on image, save as, and save it to your computer.
6.1: If you are not using clone troopers plugin you may have to split your image into several smaller images an upload them to roblox first. Else select the height map you generated and set the X and Z values to your image size divided by your detail level. Your Y value will be your Y scale multiplied by the Y value you generated in 4.3. Once these values are set, you should get a scaled replica of your mesh in studio.
Bonus: Making color map. It is a very similar process to making a heightmap, so I may as well describe it here.
B.1: First you will need to make a bunch more materials, one for every roblox material you intend to use. Each material should have its color set to one of these values as provided by longlongchien
Asphalt - RGB[115, 123, 107] - #737b6b
Basalt - RGB[ 30, 30, 37] - #1e1e25
Brick - RGB[138, 86, 62] - #8a563e
Cobblestone - RGB[132, 123, 90] - #847b5a
Concrete - RGB[127, 102, 63] - #7f663f
CrackedLava - RGB[232, 156, 74] - #e89c4a
Glacier - RGB[101, 176, 234] - #65b0ea
Grass - RGB[106, 127, 63] - #6a7f3f
Ground - RGB[102, 92, 59] - #665c3b
Ice - RGB[129, 194, 224] - #81c2e0
LeafyGrass - RGB[115, 132, 74] - #73844a
Limestone - RGB[206, 173, 148] - #cead94
Mud - RGB[ 58, 46, 36] - #3a2e24
Pavement - RGB[148, 148, 140] - #94948c
Rock - RGB[102, 108, 111] - #666c6f
Salt - RGB[198, 189, 181] - #c6bdb5
Sand - RGB[143, 126, 95] - #8f7e5f
Sandstone - RGB[137, 90, 71] - #895a47
Slate - RGB[ 63, 127, 107] - #3f7f6b
Snow - RGB[195, 199, 218] - #c3c7da
WoodPlanks - RGB[139, 109, 79] - #8b6d4f
Water - RGB[ 12, 84, 92] - #0c545c
B.2: Inside each material, put a image texture identical to the one we made in 4.3, except for color space should now be set to SRGB
B.3: Once you have assigned the new materials as you have seen fit, Bake it as described in 5.1 and save it as described in 5.2
And now this monstrosity of a tutorial is complete. If you have any ideas on how to improve it or require clarification, please leave a comment.