Trees1 example

This example adds billboard trees to the "terrain3" example.

Lcc map

The first step to add such trees is to provide a tree density map, so that trees will be instantiated only where this density is not null. For this we define a tile cache to store the tiles of this density map (we call it a "land cover classification" map, or LCC map, because it contains in general more than one density - although here we use only one):

    <tileCache name="groundLccGpu" scheduler="defaultScheduler">
        <gpuTileStorage tileSize="100" nTiles="512"
            internalformat="R8" format="RED" type="UNSIGNED_BYTE" min="LINEAR" mag="LINEAR"/>

Here we specify that our LCC tiles are 100x100 pixels (including borders), and have 1 channel per pixel. We can then define an ortho producer to produce these LCC tiles. Here we want a fractal noise, modified based on the local terrain elevation and slope, in order to avoid planting trees in the oceans or in cliffs. For this we use an ortho producer (to produce the raw noise) with two layers (one to exclude ocean areas, the other to exclude cliffs - note that we said earlier that an ortho producer cannot have layers: in fact this is possible in practice, but generally does not give meaningful results. Here is an exception that proves the rule):

    <orthoProducer name="groundLccGpu1" cache="groundLccGpu" maxLevel="7"
        upsampleProg="upsampleLccShader;" cnoise="127.5,0,0,0"
        <textureLayer name="slopeFilter1" producer="groundNormals1"
            renderProg="slopeFilter;" tileSamplerName="fragmentNormalSampler"/>
        <textureLayer name="elevationFilter1" producer="groundElevations1"
            renderProg="elevationFilter;" tileSamplerName="elevationSampler"/>

The raw ortho producer is defined in a similar way as in the "terrain3" example, except that we use a slightly different upsample and add shader. The first layer modifies the raw densities generated by this producer, based on the normal tiles produced by the "groundNormals1" producer, and used via the "fragmentNormalSampler" uniform in the "slopeFilter" shader. This shader computes a color and an alpha mask, based on these normals, which is then blended in the raw density map (here no blending mode is specified in the layer definition, so the default mode is used - ADD with SRC_ALPHA and ONE_MINUS_SRC_ALPHA):

uniform samplerTile fragmentNormalSampler;

in vec2 st;
layout(location=0) out vec4 data;

void main() {
    vec2 lNormal = textureTile(fragmentNormalSampler, st).xy * 2.0 - 1.0;
    float z = sqrt(1.0 - dot(lNormal, lNormal));
    data = vec4(0.0, 0.0, 0.0, 1.0 - smoothstep(0.85, 0.9, z));

Here the output color is black (i.e., density 0) and the alpha mask is one when the slope is high. The result, after blending, is that the tree density is set to 0 in areas where the slope is high. The second layer is similar, and sets the tree density to zero when the altitude is less than 4:

uniform samplerTile elevationSampler;

in vec2 st;
layout(location=0) out vec4 data;

void main() {
    float z = textureTile(elevationSampler, st).z;
    data = vec4(0.0, 0.0, 0.0, 1.0 - smoothstep(4.0, 5.0, z));

Trees specification

Once we have a tree density map, the next step is to specify how we want to instantiate trees based on this map. This is done as follows:

    <plants name="trees"
        minLevel="5" maxLevel="7" maxDistance="5000.0" tileCacheSize="400"
        minDensity="8500" maxDensity="8500" patternCount="10">

This specifies that trees will be instantiated for terrain quads whose level in the quadtree is between 5 and 7, that no trees will be visible at a distance more than 5000, and that for each terrain quad at level 7, 8500 trees will be instantiated (4 times more for quads at level 6, and 16 times more at level 5 - the maximum number of instantiated trees will be 400x8500=3400000). For each quad at level 7 a random point pattern will be used to instantiate the trees, chosen at random amongst 10 precomputed patterns.

The "selectProg" program is responsible to remove some points from these patterns, in order to generate the desired tree density (based on the LCC map produced above). It is also responsible to generate some attributes for these points, based on random seeds associated with each point, and optionally on external maps (color, elevation, normal, etc). For instance a very important attribute is the tree altitude, which can be generated by sampling an elevation tile (see below).

Finally the "renderProg" program will be responsible to generate camera facing quads from these points, and to texture and shade them (it is also possible to specify a "shadowProg" in order to render the trees in cascaded shadow maps, in order to simulate the shadows of trees, but we do not use it here).

The "selectProg" shader receives as input a point pattern, where each point has xy coordinates in the [0,1] range, and a z coordinate encoding four random bytes. It may discard some points, and must output for the remaining points at most two vec3 values. In this example the "selectTreeShader" outputs a xyz position on the terrain for each point, in a first vec3, as well as the terrain normal, a random color, a random size, and a random value, packed in a second vec3:

in vec3 pt[];
out vec3 pos;
out vec3 params;

void main()
    if (textureTile(lccSampler, pt[0].xy).r > 0.25) {
        float z = textureTile(elevationSampler, pt[0].xy).z - 0.35;
        vec2 n = textureTile(fragmentNormalSampler, pt[0].xy).xy;
        pos = vec3(pt[0].xy * tileOffset.z + tileOffset.xy, z);
        vec4 seeds = unpackUnorm4x8(floatBitsToUint(pt[0].z));
        vec3 color = vec3(1.0) + (seeds.rgb - vec3(0.5)) * vec3(0.45, 0.45, 0.75);
        float size = mix(0.8, 1.2, seeds.w) * 0.5;
        float seed = dot(seeds, vec4(0.25));
        params = vec3(uintBitsToFloat(packUnorm2x16(n)),
                    uintBitsToFloat(packUnorm4x8(vec4(color, size))),
                    uintBitsToFloat(packUnorm4x8(vec4(seed, 0.0, 0.0, 0.0))));

This code discards the input point pt if the tree density at this point, given by lccSampler, is less than 0.25. For the remaining points, the terrain height and normal are read from the elevationSampler and fragmentNormalSampler tile samplers, the random bytes are extracted from the z coordinate of pt, and the tree position and attributes are computed and output in "pos" and "params". Note that, in order for these outputs to be stored in a VBO for use during rendering, you must enable transform feedback for them:

    <module name="selectTreeShader" version="400" source="selectTreeShader.glsl"
        feedback="interleaved" varyings="pos,params"/>

During rendering the "renderTreeShader.glsl" reads these points, discards those outside the view frustum based on the "clip" uniforms set by proland::DrawPlantsTask, and generates a camera facing quad for the others (using the size attribute generated by the previous shader). It then shades these quads by using the random color, and the terrain normal stored for each tree by the previous shader.

Trees scene node

Finally, in order to generate and draw the trees, it is necessary to add a scene node for them in the scene graph:

        <node name="plants" flags="object,shadow">
            <bounds xmin="-50000" xmax="50000" ymin="-50000" ymax="50000" zmin="0" zmax="5000"/>
            <field id="terrain" value="terrainNode"/>
            <method id="draw" value="drawPlantsMethod"/>
            <method id="shadow" value="drawPlantsShadowMethod"/>

Here we specify two methods. The "shadow" method is the following:

<?xml version="1.0" ?>
<drawPlantsShadow name="terrain" plants="trees"/>

It generates the trees for the newly created terrain quads at each frame. It also renders the trees in cascaded shadow maps, if a "shadow" prog is specified in the "trees" resource. This method must be called after the terrain quadtree is updated, but before the trees are rendered. For this we add a loop in the camera method, over all objects having the "shadow" flag (which is the case of the above scene node), between the update and draw loops:

    <sequence name="cameraMethod">
        <foreach var="o" flag="dynamic" parallel="true">
            <callMethod name="$o.update"/>
        <foreach var="l" flag="shadow">
            <callMethod name="$l.shadow"/>
        <foreach var="o" flag="object" culling="true">
            <callMethod name="$o.draw"/>
        <foreach var="o" flag="overlay">
            <callMethod name="$o.draw"/>

The "draw" method is the following:

<?xml version="1.0" ?>
<drawPlants name="terrain" plants="trees"/>
both methods expects the scene node to contain a "terrain" field, specifying on which terrain scene node the trees must be instantiated. If this scene node has a tile sampler whose name is "elevation", "fnormal" or "lcc", then the corresponding GLSL uniform will be set in the "selectProg" above, so that it can use this external data to generate attributes for each tree.

Generated on Sat May 12 09:42:31 2012 for proland by  doxygen 1.6.1