How to Grass ? Reborn the Forest**!

Hi everyone ! I’m here to discuss about a relatively new matter and not over hoped subject ! A grass system for JMonkey. I’m warning you this’ll be a really long subject so if you want to leave it’s now :smiley:


1°) Introduction

A grass system might seem easy to implement or easy to see what you want to achieve, “I just want grass in my game that’s it”. But it isn’t that simple, sadly… There is so many approach that you can’t even choose what you really want to do, and in the mess of this, each on of this technique as an impact on the grass looking and the performance. But there is a time when you have to choose and I’ll talk about what I personally choose. But you are really invited to express yourself on this. How to improve ? What do you think of this choice ? What would you do if you where me ? Or anything that could help :wink:.

:rainbow: First a screenshot :rainbow:

As you can see there is many triangles in this scene (maybe a bit too much) but the overhaul looking isn’t that bad. Right now the performance is still miserable and the number of test variable and comment or even misconception is huge but I’m planning to rewrite it all from scratch with what I learn on the first test and make something that could be used by most of you guys.

2°) The planting

Like a gardener you need to know how to plant your plants, if you want them to looks good together, and that’s exactly what we are going to talk. The first thing to notice is that if you are a good gardener you’ll plants all your seed aligned horizontally and vertically like that.

But since we try to reproduce the randomness of a grassy area we need it to be randomly translated. We need to do something like that

float tempX = x + (float) FastMath.rand.nextGaussian() / 4;
tempX = FastMath.clamp(tempX, locX, locX + patchSize * 2);
float tempZ = z + (float) FastMath.rand.nextGaussian() / 4;
tempZ = FastMath.clamp(tempZ, locZ, locZ + patchSize * 2);

But we need it to not get translated too much or else it’ll be out of the terrain space that’s why the value is clamped. And every grass quad need to be on the terrain so we’ll look for the terrain height at the given point

        heightAt = terrain.getHeight(new Vector2f(locX+tempX, locZ+tempZ));
        //This method doesn't give me a really accurate results but with a perpendicular ray
        //cast above the terrain some value a totally wrong and messing up everything

So we have everything planted really well, but there is one thing you might want to change during the planting process. The fact that you need a density for this grass of yours. The density is simply done by augmenting or decreasing the float value of the planting loop. It allow the grass to be planted with a given density on the terrain.

    for (float x = locX; x < (locX + patchSize * 2); x += grassDensity) {
        for (float z = locZ; z < (locZ + patchSize * 2); z += grassDensity) {
            //Future work
            //grassDensity = 1-alphaMap[x][y];
            plant();
        }
    }

##2.1°) Which seeds to use ?

Once you have determined where you want to plant your grass, you need to know what kind of seeds you’ll use for this particular position. by that, I mean that you need to create something to hold this information. You give the position information to a GrassFactory and it’ll give you a GrassBlade object with all the informations you need to create the grass mesh.

public class GrassBlade{
    //The position of the grass
    public Vector3f position;
    //The size (we'll need it to translate the quad vertex to face the camera position)
    public float size;
    //The index of the texture atla to use (not implemented yet)
    public int index;
    //A contrast value which is given by a perlin noise array holded by the grass factory
    public float color;
    
    public GrassBlade(Vector3f pos, float color, float s, int index){
        this.position = pos;
        this.color = color;
        this.size = s;
        this.index = index;
    }
}

The GrassFactory is a Singleton with an init function called at the beginning of the grass process.

##2.2°) Always look at the sun

In my mind a grass system must be a static shadowed object. I mean the performance issue with a real time grass object with shadow casting can be a really big problem. So the next step to this system will be static shadowing, by casting a ray in the given physic space to see if each blade of grass is hidden by something directly from the sun, and if it is, just make it darker.

Like so :

This is just a first approach on this problem it isn’t implemented yet.

##2.3°) Density on hills

One of the other problem I encounter during the planting process is that on hills we need a higher grass density to hide the terrain. Let me show you what I mean :

As you can see the terrain is visible on hills there is a solution during the shader process to hide this, but it cost really a lot in terms of performances.

The method is explained here : http://www2.disney.co.uk/cms_res/blackrockstudio/pdf/Foliage_Rendering_in_Pure.pdf

So maybe casting a ray to see if there is any non-hidden part of the terrain, or maybe just increase the density on hills might be able to solve the problem. I still don’t know what to do to fix this issue :sunflower:

##3°) Organize your garden

So now we have our seeds planted at a defined location, and we have everything we need to make our grassy terrain dream come true. But at this point we have over a millions of polygons to render, and your might have something like 1 fps or even less. We need to do something about this ! One of the possible solutions is to split your grass object into several smaller part, and each one of them will contain the planting informations we need. What’s cool about this idea is that you can edit your terrain or grass in real time because you only need to recreate the modified part, and do not recreate the whole creation process. The impact in terms of overall performance is great because all the faraway chunk of grass will not be rendered or even checked.

##3.1°) How to ?

We want to put it on the JMonkey terrain system, so we’ll create a grid that fit the size of the terrain. Since TerrainQuad use a Grid system too. We just have to use the same number used to create the Terrain (or maybe less for the patchSize) but anyway it’s really simple. We create a GrassArea a Node that will hold the whole grass system. And split it in 4 GrassPatch at each corner. In the GrassPatch if the size of the patch is equal to the size of the final patch size then we stop the splitting process and create a GrassGeom to contain all the GrassBlade mentionned before.

##3.2°) How to control the generating process

The key for this system is that we need to control the way it show and generate grass blade. All this system work with the Camera. And is implemented so that each frame we’ll check in which area the camera is and look the for GrassPatch with the correct distance. It’s a recursive loop with a distance check every time we want to go further in the GrassArea tree system.

But since we only want the grass in front of us, we need to do compute the drawing distance with the camera direction like so

With this really simple technique we can improve the grass distance view by 50% without any cost in terms of performances ! In terms of recursive loop and distance the process is shown below

We just explore the grass area tree, and check for distance until we found the grass geom object with the correct distance in the correct patch to draw them. If a grass geom is at the right distance he return his GrassBlade list to draw to the GrassControl.

##3.3°) Finally ! Drawing the Grass !

The grass area control now have all the GrassBlade to draw but still, we don’t have anything relevant. And we have one step until we can do this ! Sorting the grass :frowning:. Since all the grass blade you’ll draw are transparent and the z-buffer is defined when the mesh is created, we have to sort all the grass blade depending on the distance between the camera and the quad. To do so we have a comparator that’ll sort all of them.

    grassComp = new Comparator<GrassBlade>() {
        public int compare(GrassBlade o1, GrassBlade o2) {
            float dist1 = compareRealDistance(cam.getLocation(), o1.position);
            float dist2 = compareRealDistance(cam.getLocation(), o2.position);
            if (dist1 > dist2) {
                return -1;
            }
            if (dist1 == dist2) {
                return 0;
            }
            return 1;
        }
    };  
   Collections.sort(grassList, grassComp);

The final step ! At least for the JMonkey part :wink:. We need to draw this mesh. This work is done by the GrassArea itself.

public void createMesh(List<GrassBlade> grassBlades)

We need to create 4 essential buffer and one more for the Billboarding and even for every fancy stuff we can imagine.

            FloatBuffer pb = BufferUtils.createVector3Buffer(triCount * 4);
            FloatBuffer nb = BufferUtils.createVector3Buffer(triCount * 4);
            FloatBuffer tb1 = BufferUtils.createVector2Buffer(triCount * 4);
            FloatBuffer tb2 = BufferUtils.createVector2Buffer(triCount * 4);
            IntBuffer ib = BufferUtils.createIntBuffer(triCount * 6);

            int i = 0;
            for (GrassBlade grassB : grassBlades) {
                Vector3f p1 = grassB.position;
                float size = grassB.size;
                float color = grassB.color;

//The Grass quad vertex position
/*p1.x-size/2,p1.y+size,0------------p1.x+size/2,p1.y+size,0
|                                                         |
|                                                         |
|                                                         |
p1.x-size/2,p1.y,0------------p1.x+size/2,p1.y,0 */
                pb.put(p1.x - size / 2).put(p1.y + 0).put(p1.z + 0);
                pb.put(p1.x + size / 2).put(p1.y + 0).put(p1.z + 0);
                pb.put(p1.x - size / 2).put(p1.y + size).put(p1.z + 0);
                pb.put(p1.x + size / 2).put(p1.y + size).put(p1.z + 0);

                tb1.put(0).put(0);
                tb1.put(1).put(0);
                tb1.put(0).put(1);
                tb1.put(1).put(1);
                //color is the perlin noise contrast information and size the size of the grass blade as well as the vertically aligned vertices
                tb2.put(color).put(size);
                tb2.put(color).put(-size);
                tb2.put(color).put(size);
                tb2.put(color).put(-size);
                //The normal of the grass blade
                nb.put(0).put(0).put(1);
                nb.put(0).put(0).put(1);
                nb.put(0).put(0).put(1);
                nb.put(0).put(0).put(1);
                //The index buffer (in which we need to link those vertex)
                ib.put(2 + i * 4).put(0 + i * 4).put(1 + i * 4).put(1 + i * 4).put(3 + i * 4).put(2 + i * 4);
                i++;
            }

            mesh.setBuffer(Type.Position, 3, pb);
            mesh.setBuffer(Type.Normal, 3, nb);
            mesh.setBuffer(Type.TexCoord, 2, tb1);
            mesh.setBuffer(Type.TexCoord2, 2, tb2);
            mesh.setBuffer(Type.Index, 1, ib);
            grassGeom.updateModelBound();

And after that you have your GrassMesh !!!

##4°) Be a japanese GARDENER

And now the final step ! :rainbow: SHADER :rainbow:. First,all of what I was talking about earlier in this subject can be done with a single Geometry shader, but since JMe doesn’t fully support GeometryShader we’ll not talk about this further more and get right into the subject of vertex shader and fragment shader !

In the vertex shader we need to billboard all the quad mesh. What it means is that we need them to face the camera to have something realistic and pretty :sunflower:. There is 2 method for this subject. The first one is to face the camera direction ! And the second one is to face the camera position. I choose the second one because in the first one you can see the quad moving along has you move your eyes in the scenes which can be pretty disturbing.

##4.1°) Look at me GRASS ! Look at me !

So what’s going to happen in the vertex shader ? First we’ll illustrate the scene, remember the mesh creation process ? We created all the quad blade aligned along the X axis ! And so at the beginning of our shader process we have something like that

What we need to do is translate both vertically aligned vertices along the camera position and grass center vector. Well since an image is more explicit that anything else

For each vertices you have something like that

        vec3 toAdd = vec3(0,0,0);
        cameraOffset = normalize(cameraOffset);
        float angle = atan(cameraOffset.z, cameraOffset.x); 
        toAdd.z += -cos(angle) * abs(texCoord2.y);
        toAdd.x += sin(angle) * abs(texCoord2.y);
        toAdd.x += abs(texCoord2.y);
        modelSpacePos.xyz += toAdd/2;
        //And then we project
        projPosition = projectionMatrix * viewMatrix * modelSpacePos;

We also need to calculate the diffuse light… Since I’m really not a shader guru I’ll not explain my lightning technique since it’s just a bunch of things I find on the Internet… But I’ll do it at some point.

And also we need to give our fragment shader the perlin noise information

colorToMix = texCoord2.x;

##4.2°) Green is the grass ! Grass is the Green

This the last part… (Finally)
We need to give our grass some good looking and the fragment shader is designed to do this ! We’ll manipulate the color of the grass by first giving it a texture and put on the table a bunch of other things.

In this, I’ll use two texture, the first one is a standard ColorMap with all the texture color information and the second an alpha mask based on a perlin noise texture that we’ll use to create a dissolving effect for the grass. First let’s load those two textures

vec4 base_pixel = texture2D(texture,texCoord);
vec4 mask_pixel = texture2D(dissolveTexture,texCoord);

And then we need to mix this texture with a diffuseLighting, and interpolate those value depending on the given points vertical position. Don’t really know what it does but that give me a great effect :smiley:

 finalDiffuse += vec3(3,3,3);
finalDiffuse = vec3(mix(minGrass, finalDiffuse.rgb, texCoord.y));
finalDiffuse = vec3(mix(finalDiffuse.rgb, maxGrass, texCoord.y));

base_pixel.rgb = base_pixel.rgb * finalDiffuse;

Then we’ll use the perlin noise information to give some variety to the grass contrast

base_pixel.rgb = base_pixel.rgb * colorToMix;

Finally we’ll make the texture dissolve in the faraway, to make the transition between the grass and the terrain really smooth

//camDist is the camera distance between the current grass and grass dist is the desired grass distance value
float mMult = 1.0 - pow(min(camDist / grassDist, 1.0), 10.0);
base_pixel.a = base_pixel.a*mMult;
base_pixel.a = base_pixel.a-min(((mask_pixel.r*((camDist/(grassDist))))),(camDist/grassDist));
if(base_pixel.a < 0.01){
    discard;
}

##5°) Do you grass now ?

Are you still here ? If you read all of that really good job, because English isn’t really my cup of tea. I really hope this can help people like me to understand the purpose of a grass system. Don’t forget to criticise and express yourself on this subject.

People that helped me : @pspeed, @nehon

Sources :

https://en.wikibooks.org/wiki/GLSL_Programming/Unity/Billboards
http://hub.jmonkeyengine.org/t/how-to-go-with-billboarding-geometries-inside-a-vertex-shader
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter01.html

http://www2.disney.co.uk/cms_res/blackrockstudio/pdf/Foliage_Rendering_in_Pure.pdf

Thanks, Stomrage

28 Likes

Thanks for sharing this :wink:

Amazing system and amazing explanation!!!

wow that’s so super helpful, now will always ask you before looking into google :smiley:
so many thing i dont know, will try to implement grass myself :smile: just curious, how long did it took to make it?

Thanks guys ! If I have to do something for the community, I just want to be sure it can be understandable by everyone and that’s why I did this topic.

@eraslt It’s a pleasure to share the knowledge I acquired with other peoples :smiley:. In fact, It doesn’t took me that long to make it (in terms of programming). But putting on paper everything I said in this topic was a pain in the a**, really. If I have one thing to give you before implementing your own grass system look at pspeed project it’s really a golden mine : https://code.google.com/p/simsilica-tools/source/browse/trunk/IsoSurface/src/main/java/com/simsilica/iso/plot/GrassZone.java

The final step is now to understand what features peoples wants for a system like this. And how it can be integrated with JMonkey.

//The creation process right now
grassArea = new GrassArea(TerrainQuad terrain, int GrassGeomSize, int grassDist);
grassAreaControl = new GrassAreaControl(camera);
GrassArea.addControl(grassAreaControl);

The class diagram look like this (this is a really quick overview)

Now I need to find how to implements layer, density map, etc… (The intuitive way to do it)

5 Likes

maybe reading from texture would be easiest way?

Cool stuff. Also nice doing it in Controls. Maybe you can even make it work easily with the SDK if you add some empty constructors and good read/write support. (Otherwise adding a special “Add Control” wrapper is easily done as well)

2 Likes

@eraslt

Yeah I’m going to use a texture for this purpose. But what I mean by that is that I need to define the creation process of my GrassArea to have something customisable and intuitive.

@normen

This is what I’m planning to do with my next version. I already did that with some custom Node in my editor but nothing of this scale. So except me take some time to do it.

3 Likes

The current process

    //8 is size of the GrassHolder
    //75 is the grass view distance
    GrassArea grassArea = new GrassArea(terrain, 8, assetManager,75);        
    try {
        grassArea.setColorTexture(assetManager.loadTexture("Textures/tile_1.png"));
        grassArea.setDissolveTexture(assetManager.loadTexture("Textures/noise.png"));
        grassArea.addDensityMap(assetManager.loadTexture("Textures/noise.png"));
        grassArea.addDensityMap(assetManager.loadTexture("Textures/noise_2.png"));
        //the first 2 number are here to set the texture position in the atlas
        //The third parameter is the density for this layer
        //The last 2 number are the min and max grass size for this layer
        grassArea.addLayer(0f,0.5f,2f, ColorChannel.RED_CHANNEL, DensityMap.DENSITY_MAP_1 ,2f,3f);
        grassArea.addLayer(0.5f,0.5f,0.5f, ColorChannel.BLUE_CHANNEL, DensityMap.DENSITY_MAP_2 ,2f,3f);
        //Generate the whole Geometry (not sure if this is the correct way to this)
        grassArea.generate();
    } catch (Exception ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
    }
    GrassAreaControl grassAreaControl = new GrassAreaControl(cam);
    grassArea.addControl(grassAreaControl);
    rootNode.attachChild(grassArea);

To make it completely clean I’d suggest moving the whole code out of a Geometry into a Control, really. Terrain sadly isn’t a very good example on how these things should be done - generally its not needed to extend Node, Geometry or Spatial ever - all their properties, including collision etc. can be changed from the outside, making everything more portable and modal. But I don’t want to distract you from getting an actual implementation going first so do what you see fit.

1 Like

…collision cannot be changed outside of extension… at least not in the collidesWith() sense.

Ah, I thought we made the handlers replaceable when we talked about it like years ago or so ^^

Nope. It’s non-trivial to do in a not-performance-killing and backwards-compatible way, I guess.

Well, I’ll make my first implementation like @normen said and we’ll discuss what’s the best to do after that. I got the GrassArea working as well as the read and write serialisation (which was really a pain to make).

public void loadGrassArea(){
    grassArea = (GrassArea) assetManager.loadModel("Models/MyModel.j3o");
    grassArea.getControl(GrassAreaControl.class).setCamera(cam);
    rootNode.attachChild(grassArea);        
}

public void createGrassArea(){
    grassArea = new GrassArea(terrain, 8, assetManager, 75);
    try {
        grassArea.setColorTexture(assetManager.loadTexture("Textures/tile_1.png"));
        grassArea.setDissolveTexture(assetManager.loadTexture("Textures/noise.png"));
        grassArea.addDensityMap(assetManager.loadTexture("Textures/noise.png"));
        grassArea.addDensityMap(assetManager.loadTexture("Textures/noise_2.png"));
        grassArea.addLayer(0f, 0.5f, 2f, ColorChannel.RED_CHANNEL, DensityMap.DENSITY_MAP_1, 2f, 3f);
        grassArea.addLayer(0.5f, 0.5f, 0.5f, ColorChannel.BLUE_CHANNEL, DensityMap.DENSITY_MAP_2, 2f, 3f);
        grassArea.generate();
    } catch (Exception ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
    }
    GrassAreaControl grassAreaControl = new GrassAreaControl(cam);
    grassArea.addControl(grassAreaControl);
    rootNode.attachChild(grassArea);        
}

Now I’ll write some JavaDoc, and make some little change into variable and function access modifiers. I’ll also try to fix some bug and throws more exception as well as trying some improvements.

6 Likes

Looks good. :smile:

Also a good example of picking a ground texture that matches your grass geometry. :slight_smile:

2 Likes

Finally :smiley:

6 Likes

looks really good

1 Like

Wow, nice. This could really make it to core, what do the others say?

Can you please add a file or some information about what license your code is about distribution?

Awesome