Approach to creating 'map-aware' grass

I am trying to figure out a way to create grass that ‘knows where it sits’ on my globe. I need this awareness at shader level to make the grass respond to people and animals walking in it and more stuff that happens on the map.

I have an extra Buffer that holds map data. I currently use that for the watershader, but I could share it with any other mesh.

I have seen quite a few examples of grass for JME, it seems that most of them instance or batch a lot of individual grass billboards or meshes. But in what way could I tell each of them where they live on the map?

I think if I could give each ‘instance’ just one or two floats as ‘custom data’ I would have a way to map them to a globe-point.

Anyone any thoughts on this?

A complicating factor is that my terrain is a globe, and the points of this globe are not arranged as a grid. The globe is an Isosphere.

Any ideas?

If this is your only problem you could use TexCoord2 to pass those floats to the shader.
If you place individual grass quads you can actually use the position buffer as world position. If you can use geometry shaders you can emit the quad from a point mesh, else you have to create a mesh out of quads

As for grass i would not use billboarding but a fixed angle.

As for reacting to other objects, i would try to pass those as uniform parameters and see how it turns out. not an easy task however.

Thats what i used here:

Point sprites look interesting, but how make just a single quad wave? I could not see it on the video, but did you find a way to bend the grass, or is it just moving the tops?

I think in the video it is just moving tops. if you want bending you can offset the texture coordinates. something like:

vec2 newTexCoords = vec2(texCoords.x+maxOffset*texCoords.y,texCoords.y)

that should bend the grass and not tilt it. I would have to play around with it

1 Like

If you do not go for billbording pack the angle and the position in a vec4. since it is the favorite datalayout of a gpu i suspect the added angle does come for free.

Here the thing is that I already need to give every grass part an angle since my terrain is a globe:

i fail to see the second angle. grass tends to grow up. independently of the terrain slope. in your case up is normalize(position)

1 Like

In cases like this, I would be really tempted to use fur techniques for grass.

I played with this a bit a couple of years ago:
https://twitter.com/Simsilica/status/1436651787293970432

That was just a super quick prototype with an existing noise texture. Lots of tweaking could be done. I even animated the grass for wind.

No extra draw calls, I think I just added one buffer to make the mesh instanced that had the distance of each of the layers and then used that to project the vertexes out along the normals.

If you already have a map texture for your whole world then that approach would be very tempting to play with because you essentially have the data already.

1 Like

The Fur approach looks interesting. Trying to find some examples of shaders is hard though. If have some code to share I would be very interested.

The examples I found did not look like the way JME or ThreeJS shaders look, making it hard to see what is happening inside.

Probably the best way is to create a test program to check it out. I started playing with a blue cube style app just to mess with it.

Test geometry setup looks like this:

        Box box = new Box(1, 1, 1);
        FloatBuffer xb = BufferUtils.createFloatBuffer(0, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f);
        VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.InstanceData);
        vb.setInstanceSpan(1);
        vb.setupData(VertexBuffer.Usage.Stream, 1, VertexBuffer.Format.Float, xb);
        box.setBuffer(vb);
        
        Geometry geom = new Geometry("testBox", box);        
        Material mat = new Material(assetManager, "MatDefs/YourMaterial.j3md");
        mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
        geom.setMaterial(mat);
        geom.setQueueBucket(Bucket.Transparent);
        rootNode.attachChild(geom);

Copy Lighting.* to YourMaterial.* and change the stuff inside to point to your .frag and .vert.

Then in the .vert make sure to add:

attribute float inInstanceData;

And in the main() area:

modelSpacePos.xyz += modelSpaceNorm.xyz * (inInstanceData * 0.2);

If you use a texture with transparency then you should see multiple layers at 0, 0.1, 0.2, 0.3, etc…

That’s the basic setup and then you can work on incorporating techniques from some of the online resources…

I personally used voronoi texture and passed the inInstanceData depth value to the frag shader so that it could discard fragments where the texture value was less than the depth value. So each layer was smaller.

I think you can find better fur textures online maybe… I didn’t really look as I was just spending some time playing with the principles and not really looking for a finished product yet.

Edit: some voronoi textures here: (It’s a gallery)

Each channel is a separate ‘seed’ basically. Sample one channel to compare to depth. That’s what I’m doing in the image above.

1 Like

Tnx… I will go do some experimenting and share what I may find. This is fun!

The first step was not too hard. I’ve got a lot of grass:

When the camera is moving it looks quite convincing already.

It is, however, not receiving shadows and there are a lot of stupid black artifacts.

3 Likes

To make the shader play nice with shadows, I think you need to fork the shadow pass stuff.

Not sure about black artifacts.

The shader is a fork of the lighting shader, where I only added some stuff to lighting.vert and lighting.frag

So everything from lighting to shadows is still there.

Light.j3md has separate shaders for rendering shadows:

…that’s if you mean “shadows cast by other objects”. If you mean “shadows because this is not pointing at the light” then yeah, that should have been fine as is.

Without knowing what you changed about the shaders, I can’t really say.

I did not change PostShadow.vert, and I should have. It did not know about instancing. So I forked it and added some stuff for layering:

// FUR
attribute float inInstanceData;
attribute vec3 inNormal;

[…]

void main(){
   vec4 modelSpacePos = vec4(inPosition, 1.0);
   // FUR
   modelSpacePos.xyz += inNormal.xyz * (inInstanceData * 0.4);

I now have shadows again, but all shadows are black now instead of transparent.
image
(I also think the other shadows are too dark here, so I turned shadow intensity real low)

I figured the black shadows were due to the many layers. To tackle the black shadows I forked PostShadow.frag as well and added this line in the end to skip all but the first layer:

if (grassAmount<.5 || layerDepth>0) discard;

grassAmount is one of the values of the world-data-buffer. It can be raised and lowered. I’ll share the full hacked shader bundle with comments later on.

Since the fur-approach to grass was impacting performance quite a bit, I also tried instanced grass:

I must say, it is much faster and I think it looks better in this case.

I now have 200K instances on screen, with hardly any impact. Did some testing with one million and no real performance hit there either. But i like this density better.

The grass patches consist of three textured panels in a star shape. The panels are not just quads but are splitted in vertical direction. This makes their moving in the wind more realistic. The patches also have a textured ground quad so they are better visible from above.

Every patch knows where it sits on the map, using a lookup texture they will be informed if anything needs them to bend out of the way. Coding that response will be the next step.

6 Likes

I dont know how it looks in motion but on the images it looks also better

This is the video: Unga Munga: Waving Grass - YouTube

2 Likes

Looks super nice.