User Tools

Site Tools


Outline

Grass geometry is defined in SRT files, exactly as tree models are. This means that they are defined and edited in the SpeedTree Modeler as if they were any other tree model (often as individual “tufts” of grass), subject to the same geometry, lighting, and wind definitions as tree models. The only restriction is that they must have a single LOD and a single material. Grass models also do not use billboards for the lowest LOD since there is no LOD in the grass system.

Note: Be sure to select “Used as grass” in the Compiler application when compiling a grass model. It will help with optimization.

While we call this the “grass system”, it may be used to populate the scene with any bit of ground cover like rocks, twigs, or leaves. If the Modeler app can load it, it'll go through the pipeline and can be used as a grass model.

This page is very similar to the tree culling page and will contain a lot of the same content.


Overview

The SDK is set up for the client application to stream in those grass instances that are visible and will automatically flush those instances that are no longer visible.

The SDK organizes the world as a series of cells. As the camera moves, cells go in and out of visibility. As cellls become visible, the SDK will provide a list of these cells that need to have their populations streamed in. Hence, the SDK will perform most efficiently when the client has the tree instances already organized by cells so that the data might be passed into the SDK without further on-the-fly processing.

In the reference application, grass cells are populated using a random number generator, but any population procedure will work.


Getting Started

There are several classes and structures you'll need to get started:

  • Class CView: CView encapsulates many of the common values associated with a particular view like projection and modelview matrices and near and far clipping planes, but it also includes code for deriving values like camera azimuth and pitch, and frustum data.
Note: Unlike the tree stream/cull system, the main camera's CView class cannot be used directly. The far clipping plane is use to keep the grass's population restricted to a short distance. In the reference application, CMyApplication::StreamGrassPopulation() shows an example of deriving a grass CView object from the main CView by using CView::AdjustPerspectiveNearAndFar().
  • Class CTree: These are known as “base trees” but since grass models are SRT files, too, this class doubles as a grass base model. There is one of these objects for every SRT file loaded in a scene. It contains everything necessary to define a single grass model.
  • Structure SInstance: Each base grass model may have one or more instances, each with unique position, orientation and scale.
  • Class CCell: The forest is divided into a series of evenly-spaced cells. Cells contain SInstances.
  • Class CVisibleInstances: The central class for instance culling and streaming. It contains the functions for determining which cells are visible, instance culling, and 3D instance LOD computations.
Note: For tree instances, one CVisibleInstances objects will handle a whole forest of different 3D tree base models. For grass instances, one CVisibleInstances object is needed per base grass type. This permits individual cell size tuning based on population density as well as streamlining the cell population procedure.

These data structures are detailed here.


Streaming and Culling

As shown in CMyApplication::StreamGrassPopulation(), the general procedure followed for each frame where the camera has moved is outlined below. The whole procedure happens very quickly, even for densly populated grass.

Per frame, the general procedure is as follows:

  • Set View: Make sure the CView object has been updated with the current view. In StreamGrassPopulation(), the main camera's CView is copied and the far clip value is modified to accomodate the grass layer's much shorter draw distance (typically the camera might have a 2,000 or 3,000 ft draw distance, but the grass shouldn't render any further than a few hundred feet. CView::AdjustPerspectiveNearAndFar() is used to make the adjustment.

    Note that normally one CView object is used per view (e.g. one for the main camera, another for shadows) and they commonly persist with the world.
  • Rough Cull: Call CVisibleInstances::RoughCullCells() with the current CView. Because the SDK does not know the grass instance locations and dimensions ahead of time, it cannnot know complete extents of the cells. Spefically, it knows the width and height of the cells because they're in a grid layout, but it cannot know the height on a rolling terrain. RoughCullCells() will return a long list of cells that would be visible if the grid were composed of infinitely tall cells.

    Note that RoughCullCells() returns a bool value indicating if the rough cell list has changed from the last call (true indicates it has changed). If it hasn't changed, the grass stream/cull function can exit.
  • Set Rough Cell Extents: Provide rough cell extents. Depending on the height of the grass models in used, sometimes it's enough just to pass the extents of the terrain in the cell area. Or take the terrain extents and add the highest grass model height to the max.z component. Providing these extents quickly is key.

    Use CCell::GetExtents( ) on each of the rough culled cells to get its x/y extents.
  • Fine Cull: Call CVisibleInstances::FineCullGrassCells() with the current CView (note that this is a grass-specific fine cull call). This gives the SDK a chance to use the updated cell extents to give an exact list of those cells that are within the view frustum.
  • Get Newly-Visible Cells: Once CVisibleInstances::FineCullGrassCells() has been called, a list of newly visible cells can be retrieved from the class by calling CVisibleInstances::NewlyVisibleCells(). Newly visible cells are those cells that were not in the frustum the last time FineCullCells() was called.
  • Populate Cells With Instances: Populate newly visible cells by invoking CCell::SetGrassInstances() on each one. This function simply takes an array of CGrassInstance objects. Note that unlike the tree population function CCell::SetTreeInstances(), SetGrassInstances() does not take instance pointers, but instance objects. This is due to the fact that the grass is created at stream/cull time and not previously created and stored.
Note: The reference application uses image-based masks to control grass density, but your application is completely free to implement population in any manner.
  • * Optional * – Update Instance Lists: The SDK Render Interface library derives the CVisibleInstancesRI class from CVisibleInstances, allowing it to add rendering components to it, specificallly instancing-based rendering code for 3d trees, grass, and billboards. As such the client app can control when this derived class updates its instance list vertex buffers by calling UpdateGrassInstanceBuffers().

Some Considerations

While the list above outlines the general approach needed for efficient and accurate grass streaming and culling, there are several other important points.

Cell Size

You can pick different cell sizes per grass type and it can impact both CPU and GPU performance a great deal. Each grass model is culled in a separate call, so separate sizes are easily accomodated. In contrast to tree instances, grass instances are not individually culled. If the cell is in the frustum, then every instance in it will be rendered. This cuts down on CPU usage greatly, but can be hard on the GPU for high densities. To strike a balance, smaller cell sizes can be used. In fact, we recommend smaller cell sizes for high density grass, and larger sizes for sparse models like rocks or boulders.

By way of example, the reference application's example forest (modeled in feet) uses cell sizes of 20 for the high density grass, and up to 100 for the rocks and butterfly models.

Heap Fragmentation Control

The organization of multiple types of grass instance into an arbitrary collection of cells can easily lead to a great number of heap allocations if you're not careful. We leave it to the user to implement their own population approach that doesn't cause undue heap fragmentation if that's a requirement for your title or project.

To control the SDK's heap behavior, be sure to read about the Reserves System.