Houdini 21.0 Heightfields and terrains

Scattering: Removing points

Get full control over scatter points.

On this page

The HeightField Scatter SOP creates points on the entire surface according to your settings. And sometimes, there are points and places where you don’t need them. Of course, you can create masks to remove points, but in some cases it’s not so easy to create an appropriate mask. A good example is the area outside a camera frustum. There might also be occasions where you want the scatter points to fade out towards the terrain’s borders.

This page is part two of a four-pages guide to heightfield scattering:

For the some examples, VEX scripting is a convenient way to remove unwanted points.

Tip

If you're interested in VEX scripting for heightfields in general, please also take a look at the VEX scripts for heightfields page.

Masking

Note

There’s already a description on the Scattering: SOP page. This chapter is for your convenience so you don’t have to switch between different pages.

Masking with scatter points on heightfields works in the exact same way as masking in general. To be precise: masks don’t delete points, but instead you define areas where points will appear. Here’s a very basic setup for a scatter mask, assuming that you've already created a HeightField SOP.

  1. Lay down a HeightField Mask Noise SOP (or any other mask node) and connect its first input to the output of the heightfield.

  2. Add a HeightField Scatter SOP. Connect its first input with the output of the heightfield. Then, link the second input with with output of the noise mask.

  3. Turn on the scatter node’s blue Display/Render flag to see the result.

With the mask node’s default values you will notice a blank spot, while the rest of the terrain is covered with points. A closer look reveals areas of varying density. You can influence point density directly with the noise node’s Amplitude parameter. With

  • values, smaller than 1, you’ll enlarge the blank areas

  • a value of 2, for example, you’ll get a very dense structure.

Now, change Element Size. This parameter controls the size of the noise pattern. Smaller values create more noise and also more detail. The image below uses an Amplitude of 1.5 and an Element Size of 200. The result is a realistic point distribution.

Note

You can also apply all the scripts, presented below, to a masked heightfield to further decrease the number of scatter points and create interesting effects.

Scatter Method

The HeightField Scatter node provides a Scatter Method dropdown menu. The first three entries also support a Mask Layer to control the number of points. The default choice is By Coverage using Mask Layer and you can see the result in the image directly above. When you change Coverage, you can remove or add points.

By Density using Mask Layer works in the same way as the “by coverage” method, but here you have a Density parameter.

Finally, there’s Total Point Count using Mask Layer. Here you set a fixed number of points. Your mask can cover the entire terrain or just a small area of 10%: the number of points will always be the same.

Preparing the nodes

Since we're dealing with points here, you’ll apply the scripts through a Point Wrangle SOP instead of a HeightField Wrangle SOP. The steps to create the appropriate nodes and add the scripts are always the same.

  1. Add a Point Wrangle SOP and connect its input with the output of the last upstream node.

  2. Use copy ⌃ Ctrl + C and paste ⌃ Ctrl + V to add the code to the VEXpression field.

  3. Click the button to create the custom parameters.

Border points

The first script removes points from a terrain’s borders within a customizable area. You can also choose, whether you want to create a soft or a hard transition. The points aren’t deleted here, but stored inside a separate group. Just in case you need them again somewhere else. The script

  • adds custom parameters to define the scatter area’s limits in X and Y direction

  • adds a random offset to quickly create a smooth transition towards the edges

  • compares a point’s position against the borders of the scatter area

  • removes the points within the seam.

Here is the VEX code. As always, this is just one method to write such a script.

float limit_x = chf("limit_x");
float limit_z = chf("limit_z");
float offset = chf("offset");
float seam = fit01(random(@ptnum), -offset, 0);
float border_x = limit_x + seam;
float border_z = limit_z + seam;

if (@P.x > border_x || @P.x < -border_x || 
    @P.z > border_z || @P.z < -border_z) {
        removepoint(geoself(), @ptnum);
}

To apply the above code and delete the border points, do the following.

  1. For Limit X and Limit Z enter, for example, 480. This creates a frame with a width of 20 m around a standard heightfield of 1000 m x 1000 m.

  2. Set Offset to 100 to get a smooth transition of scatter points towards the terrain’s borders.

Camera frustum

A very common method for saving resources is to delete points outside a camera frustum. The concept is basically the same as in the Border points example above. The main difference is that you need a special vector that transforms a position to the normal device coordinates (“NDC”) for a camera. Houdini’s VEX has a built-in function to perform this transformation automatically: toNDC. You can also use this function with a light source instead of a camera. The script

  • takes a custom camera to calculate the transformation

  • lets you define a seam to create a safety buffer around the visible area

  • compares a point’s position against the X and Z components of the NDC vector to identify points outside the frustum

  • deletes the points directly.

The good thing with this script is that it works with animated cameras, because the per-point attributes will be maintained. This avoids that scales or the instance objects themselves change with each new frame.

The problem, however, is that the entire heightfield is removed at a certain position. When you open the Geometry Spreadsheet pane’s Points mode, you’ll see that the first two entries have several attributes with a value of 0. These two entries belong to the heightfield.

To avoid that the script deletes the heightfield by accident, go to the scatter node’s Relaxation section. There, turn off Keep Incoming Terrain. Later you can use a Merge SOP to bring points and terrain together again.

For this feature you should use two Attribute Wrangles, because you must read a camera path and doing this for every scatter point is not very efficient. It’s better to fetch the camera with a Detail Attribute Wrangle. For convenience reasons you’ll also create a Seam parameter here. Add it to the network before the point wrangle that removes the points!

This is the VEX code for the detail wrangle:

s@campath = chs("camera_path");
f@seam = chf("seam");

Click the Creates spare parameters for each unique call of ch() button to create the parameters.

  1. In the viewport, find a nice view of the terrain.

  2. In the upper right corner of the viewport, you can see two menus. Open the No cam menu and choose New Camera. This will create a cam1 camera object on the obj level.

  3. For Camera Path, enter /obj/cam1. This is the location of the newly created camera from step 2.

  4. Change Seam. With 0, you can delete points exactly along the outlines of the frustum. With values, greater than 0, you create a safety buffer outside the frustum.

On the point wrangle, enter the following VEX script:

vector camera_ndc = toNDC(s@campath, @P);

float border_min = 0.5 - f@seam; 
float border_max = 0.5 + f@seam;

if (camera_ndc.x + seam < 0 || camera_ndc.x - f@seam > 1 ||
    camera_ndc.y + seam < 0 || camera_ndc.y - f@seam > 1) {
        removepoint(geoself(), @ptnum);
}

In the image below, the particles outside the camera frustum are displayed in red to make the result more obvious.

Tip

You can also store the particles outside the frustum to a group instead of deleting them.

Replace removepoint(geoself(), @ptnum) with @group_outsidepoints = 1;

Camera distance

You can also remove points based on a point’s distance from a camera. Instead of drawing a mask, you can just increase distance with a slider and get rid of unwanted points. Note that you’ll get a circular or arc-shaped border. The script

  • lets you define a custom distance threshold

  • queries the camera’s position in world space

  • calculates the distance between the camera and the scatter points

  • removes the points beyond the given threshold

  • uses the custom Seam parameter to create a soft transition.

Since we're again dealing with positions, it can happen that the heightfield itself disappears. To avoid this, turn off the heightfield node’s Keep Incoming Terrain option.

As with the camera frustum you also need a detail wrangle to initialize some parameters an values. Here’s the script that also includes all relevant parameters. The 4@camera_matrix calculates a 4×4 matrix. Doing this for every scatter point would drastically slow down the process. By defining @camera_matrix as a one-time detail attribute, the distance calculation becomes really fast.

The script calculates the camera matrix with the help of the optransform function. This, more advanced approach is necessary to compensate for a camera-specific offset.

// Calculate a 4x4 transformation matrix
4@camera_matrix = optransform(chs("camera_path"));

f@dist_threshold = chf("distance_threshold");
f@seam = chf("seam");

Click the Creates spare parameters for each unique call of ch() button to create the parameters. When you create a camera from the viewport in a fresh scene, Camera Path is /obj/cam1. The Dist Threshold value for the image below is 800 and Seam was set to 200 to achieve a smooth transition.

And here’s the script for the point wrangle:

vector camera_position = set(0,0,0) * 4@camera_matrix;

float seam = fit01(random(@ptnum), f@seam, 0);
float point_distance = distance(@P, camera_position);


if (point_distance > f@dist_threshold - seam) {
    removepoint(geoself(), @ptnum);
}

In this image, the points outside Dist Threshold are displayed in orange.

Occluded points

The Camera distance script lets you delete points based on their distance to a camera. This is helpful, but not always effective. A more selective method can delete occluded points. The script sends out a ray from the camera to test for intersections with the terrain. Points that are are occluded by mountains or other geometry will be deleted.

Note

For this method you need a HeightField Noise SOP that creates mountains.

The script

  • lets you choose a camera object

  • queries the camera’s position in world space

  • calculates the camera’s direction

  • calculates a point’s distance from the camera

  • sends out rays based on the distance and direction to find occluded points

  • uses the tag attribute to prevent the heightfield itself from disappearing.

As before, you need a detail wrangle and a point wrangle. Here’s the detail code:

// Calculate a 4x4 transformation matrix
4@camera_matrix = optransform(chs("camera_path"));

Click the Creates spare parameters for each unique call of ch() button to create the parameters. In a fresh scene, the default Camera Path is /obj/cam1.

To make the frustum and distance point removal scripts work correctly, you've deleted the terrain. For this method, the heightfield is essential, because you need the “geometry” to calculate the occluded areas. This means that you must exclude the two points that define the heightfield. One method is to use the tag attribute that’s created by the scatter node. The heightfield points don’t carry this attribute and you can check if it’s empty.

vector camera_position = set(0,0,0) * 4@camera_matrix;
vector camera_direction = normalize(camera_position - @P);
float camera_distance = distance(@P, camera_position);

vector pos, uvw;

int hit_test = intersect(0, @P, camera_direction * camera_distance, pos, uvw);
if (hit_test != -1 && s@tag != "") {
    removepoint(geoself(), @ptnum);
}

if (hit_test != -1) {
    removepoint(geoself(), @ptnum);
}

Here you can see a terrain with occluded points removed.

Heightfields and terrains

Creation

Scattering

Masking

  • Masking

    Define zones of interest and detail.

  • Light masks

    Create masks from the sunlit areas on a terrain.

Natural effects

  • Erosion

    Turn mountains into dust.

  • Slump

    When mountains crumble to rocks.

  • Flow fields

    Let it flow (down the mountain).

VEX

Texturing

Shallow Water Solver