top of page

PROCEDURAL ENVIRONMENT AND ASSET OPTIMIZATION for Games Using Houdini (Chapter 1)

Writer's picture: Pavel ZosimPavel Zosim

Introduction

In this article, I will walk through the process of creating a procedural environment and optimizing assets for game development using Houdini. The focus will be on modeling, texturing, and preparing assets efficiently while maintaining optimal performance for a game engine such as Unity.


Project Scope

The goal of this project is to model and texture the provided assets: a modular building, and a terrain with a road. Each asset has specific low texel density, polygon count, and material usage. The final deliverables will include:

  • Splat map mask for terrain

  • Wrapped Tile Atlas shader for one texture with possibility to break tilable ground frame

  • Procedural modular building to minimize performance impact


Generation of the Procedural Terrain and Road

This Is The Terrain Reference That We Will Replicate Using A Low-Poly 3D Mesh.
This Is The Terrain Reference That We Will Replicate Using A Low-Poly 3D Mesh.

 

1.HEIGHFIELD GENERATION

Generate terrain and draw the road using curves and Labs Road Generator, Expand2D, Sweep or your custom solution. At this stage, it is crucial to store masks of the zones and terrain types, as this will help with remeshing and the wrapped tile atlas.

Generating Masks
Generating Masks
 

2.ROAD ALIGNMENT

Since the road passes through mountainous terrain, and the generated mountain surfaces (heightfield) are high-density volumes, they have unevenness. Projecting a low-poly road mesh onto this volume can distort the geometry and add unnecessary slopes to the road surface. To fix this, at this stage, I used several steps. The first one is the projection of geometry points onto the volume.

Projection of the Geometry to Volume
Projection of the Geometry to Volume


2nd Step: Define and Group Edges by Max Y Point Position Value in the Row




3rd Step: Transfer to the Lowest Point Y Position in the Row the Position of the Highest Point

Align Points by Maximum Y Position in the Row
Align Points by Maximum Y Position in the Row


In 4th Step adjust the terrain heightfield based on the newly repositioned road. Since the road has been aligned in the previous steps, it’s essential to smooth the transitions and slopes along the road path to avoid harsh geometry changes.


Adjust Heightfield Based On The Road New Point Positions
Adjust Heightfield Based On The Road New Point Positions
 

3.CONVERT HEIGHFIELD AND STORE LAYERS TO ATTRIBUTE

Once we’ve stored the terrain layers to a primitive attribute and completed the necessary road alignment and smoothing, we can now convert the heightfield volume into a dense mesh. This step is particularly important when we need to perform texture baking for normal maps, ambient occlusion (AO), or curvature maps.

Converting Heightfield to The Dense Mesh
Converting Heightfield to The Dense Mesh
 

4.BAKING SPLAT MAP MASK TEXTURE

In this step, we will assign different colored zones to the terrain, which will be used in the Splat Map mask baking process. This will allow us to specify areas with different terrain types (e.g., roads, grass, dirt) for later use in texture blending within the game engine or rendering software.

Adding Different Terrain Zones as Colors to the Color Channels (RGB/RGBA)
Adding Different Terrain Zones as Colors to the Color Channels (RGB/RGBA)
 

5.REMESHING

The remeshing process is divided into several substeps to ensure optimal polycount reduction without losing essential geometry, especially along mesh boundaries. In the first substep, we focus on reducing the polycount individually for each terrain zone. This approach is necessary because each zone may have different surface area, and using uniform remesh settings across the entire mesh can result in improper interpretations, causing the loss of crucial geometry — particularly at the edges.

Remeshing Process - 1st Iteration: Mesh Looks Acceptable. :3
Remeshing Process - 1st Iteration: Mesh Looks Acceptable. :3

This part of the process was the most interesting and challenging for me. The goal was to seamlessly weld all terrain points to the road while ensuring that the road mesh itself remained unchanged.


At this stage, I grouped all borders of each terrain zone to prepare for the next welding and division process. This step was essential to ensure that different terrain areas could be seamlessly connected while maintaining their distinct characteristics.

Computer-generated grid with blue curved lines and small numbers. Grayscale background with diagonal lines. A technical or design interface.
Grouping Zone Points, Edges and Primitives

The process of combining meshes with different polycounts was designed to be fully procedural, allowing for efficient and automated blending between different terrain zones and road without changing road geometry.


How it works: Points that touch the road at the terrain zone borders are added to a group, and this group is then promoted to edges and primitives. Next, the outer contour is subtracted from these groups—this is necessary to determine which points need to be welded with the road mesh points later.

Knowing which points and edges form the boundary between the terrain and the road mesh, we can add a central point to these "contact edges" and then draw a bisector from this vertex, effectively triangulating the polygon.

The newly created point is then added to a separate group to be used in the welding process with the nearest edge point of the road mesh based on distance. This entire process runs inside a For-Each Loop with Feedback, iterating until no new groups are formed.

I love Houdini for solving these processes, even the iteration number might be defined based on checking the mesh for non-connected polygons.  Now Geometry are ready for the game. Total tris: 3111
I love Houdini for solving these processes, even the iteration number might be defined based on checking the mesh for non-connected polygons. Now Geometry are ready for the game. Total tris: 3111
 

6.AUTO UV's

I made two UV channels. The first UV is for using a Wrapped Tile Atlas—depending on the number of terrain types, you might divide the UV into sections. In my case, there are three sections, which means each tile is equal to x0.5. The second UV channel is used for splat map masking and light baking.

 

7.PREPARING ATLASES AND OTHER TEXTURES

I bake the color information of the splat map into a texture using the second UV. I also created an atlas containing two different grass textures and two different rocky textures. This type of texture atlas helps reduce draw calls and break the repetitiveness of the terrain texture. Let's get jump in to the Unity!


 

8.DEVELOPING SHADER AND SUBGRAPHS FOR USING ATLASES FOR WRAPPED TILED ATLAS TEXTURES

The Splat Map setup is pretty straightforward. By splitting the color channels, we can easily mask terrain zones.

Each color channel that represents terrain zones needs to be multiplied with the wrapped tiled subgraph described below.
Each color channel that represents terrain zones needs to be multiplied with the wrapped tiled subgraph described below.

Let's remap our terrain texture atlas, which will help scale it on the geometry and feed it into the tiling node using the first UV. The next step is the Fraction function, which returns the fractional part of the input value. For example, if the input value is 3.75, the Fraction function will return 0.75 that means it works in the borders of the one UV tile in our case x0.5. This can be useful for creating repeating or cyclical effects. In our case, this is used to work within the tile UV, which we define in the Flipbook node. In our case, this value is a Vector2 Tiling, and we then connect it to the input of our texture atlas.


Now we can use a single texture, scale it, set the offset, and all of this will be within the bounds of the tile UV and the single texture. Additionally, we can mix it with the neighboring texture using a noise mask multiplier, which will help break the repetitiveness of the terrain.

Before
Before
After. Same Tile Size.  1 Texture = 4 Textures :3
After. Same Tile Size. 1 Texture = 4 Textures :3

In the next chapter, I'll demonstrate how to create a procedural, optimized modular building. Stay in touch!


 

Thanks for watching!

Follow my work:

コメント


bottom of page