Shader-based Grass (or Fur)

I’ve been trying to figure out a good grass solution, and I stumbled upon this fur tutorial on xbdev.net. The examples are all in HLSL DirectX shaders, so I spent a few days figuring out the differences and converting them to GLSL and jME 3. Here’s the code and instructions:

The way I implemented it required a slightly modified Geometry subclass, a slightly modified RenderManager, and a Main class that implements Application (not SimpleApplication), because you have to use the modified RenderManager instead of the original one (you could just skip extending Application and rewrite it entirely, but extending Application makes it a bit simpler). It also requires a jME j3md file and its associated .frag and .vert files.

LayerRenderManager.java:

[java]import com.jme3.renderer.RenderManager;

import com.jme3.renderer.Renderer;

import com.jme3.scene.Geometry;

import com.jme3.texture.Texture;

import java.util.ArrayList;

/**

*

  • @author Brian Babcock

    */

    public class LayerRenderManager extends RenderManager {

    public LayerRenderManager(Renderer r) {

    super®;

    }

    @Override

    public void renderGeometry(Geometry g) {

    if (g instanceof GrassGeometry) {

    GrassGeometry gg = (GrassGeometry) g;

    //Render the ground layer first

    g.getMaterial().setTexture("DiffuseMap", gg.groundTex);

    g.getMaterial().setFloat("Layer", 0);

    g.getMaterial().setFloat("Scale", gg.groundTexScale);

    g.getMaterial().setFloat("Length", 0);

    super.renderGeometry(g);

    //Render the grass layers

    ArrayList<Texture> layerMaps = gg.layerMaps;

    g.getMaterial().setFloat("Scale", gg.grassScale);

    //Depending on how many different layer maps were generated,

    //we move on to the next layer map after rendering the right

    //number of layers

    int curDiffLayer = 0;

    int layerChangeInterval = Main.NUM_LAYERS / Main.NUM_DIFFERENT_LAYERS;

    //Main layer render loop

    for (int i = 1; i < gg.numLayers; i++) {

    float layer = i / (float) gg.numLayers;

    //Move to the next layer map if necessary

    if (i % layerChangeInterval == 0) {

    g.getMaterial().setTexture("DiffuseMap", layerMaps.get(curDiffLayer));

    curDiffLayer++;

    }

    //Set the layer and current length

    g.getMaterial().setFloat("Layer", layer);

    g.getMaterial().setFloat("Length", 0.2f * layer);

    //And finally, pass to the superclass to render the layer

    super.renderGeometry(g);

    }

    } else {

    super.renderGeometry(g);

    }

    }

    }[/java]

    The above LayerRenderManager will run its special layer rendering code if the Geometry is an instance of GrassGeometry, and do regular rendering otherwise.

    GrassGeometry.java

    [java]

    import com.jme3.scene.Geometry;

    import com.jme3.scene.Mesh;

    import com.jme3.texture.Texture;

    import java.util.ArrayList;

    /**
  • Subclasses Geometry to add a little extra data about the layers.

    *
  • @author Brian Babcock

    /

    public class GrassGeometry extends Geometry {

    public ArrayList<Texture> layerMaps;

    public int numLayers;

    public Texture groundTex;

    public float groundTexScale;

    public float grassScale;

    public GrassGeometry(String name, Mesh mesh) {

    super(name, mesh);

    layerMaps = null;

    groundTex = null;

    numLayers = 1;

    }

    }

    [/java]

    Nothing too complicated for GrassGeometry, just a few extra variables to hold the extra information required for the layering. Yes, I know, public variables are evil, but I’m trying to make a simple example here. And let’s be honest, there’s really no difference between a public variable and a setVariable() method unless you’re checking for concurrent modification in the set method.

    Main.java

    [java]



    private void initCamera() {

    renderManager = new LayerRenderManager(renderer);

    //Remy - 09/14/2010 setted the timer in the renderManager

    renderManager.setTimer(timer);

    viewPort = renderManager.createMainView(“Default”, cam);

    viewPort.setClearFlags(true, true, true);

    // Create a new cam for the gui

    Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());

    guiViewPort = renderManager.createPostView(“Gui Default”, guiCam);

    guiViewPort.setClearFlags(false, false, false);

    }

    @Override

    public void initialize() {

    super.initialize();

    initCamera();

    mainCam = cam;

    guiNode.setQueueBucket(Bucket.Gui);

    guiNode.setCullHint(CullHint.Never);

    loadFPSText();

    loadStatsView();

    viewPort.attachScene(rootNode);

    guiViewPort.attachScene(guiNode);

    if (inputManager != null) {

    flyCam = new FlyByCamera(cam);

    flyCam.setMoveSpeed(1f);

    flyCam.registerWithInput(inputManager);

    if (context.getType() == com.jme3.system.JmeContext.Type.Display) {

    inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));

    }

    inputManager.addMapping(INPUT_MAPPING_CAMERA_POS, new KeyTrigger(KeyInput.KEY_C));

    inputManager.addMapping(INPUT_MAPPING_MEMORY, new KeyTrigger(KeyInput.KEY_M));

    inputManager.addMapping(“Increase layers”, new KeyTrigger(KeyInput.KEY_O));

    inputManager.addMapping(“Decrease layers”, new KeyTrigger(KeyInput.KEY_L));

    inputManager.addListener(actionListener, INPUT_MAPPING_EXIT,

    INPUT_MAPPING_CAMERA_POS, INPUT_MAPPING_MEMORY, “Increase layers”, “Decrease layers”);

    }

    // call user code

    simpleInitApp();

    }

    public void simpleInitApp() {

    cam.setLocation(cam.getLocation().add(new Vector3f(0, 0, 20)));

    Sphere b = new Sphere(16, 16, 10);

    geom = new GrassGeometry(“Quad”, b);

    //Rotate the quad so it’s facing up

    geom.rotate(new Quaternion().fromAngleAxis(FastMath.PI
    3/2, new Vector3f(1,0,0)));

    Material mat = new Material(assetManager, “MatDefs/Grassy.j3md”);

    geom.groundTex = assetManager.loadTexture(“Textures/Terrain/splat/dirt.jpg”);

    geom.groundTex.setWrap(Texture.WrapMode.Repeat);

    geom.groundTexScale = 64f;

    //Scale value makes each grass blade thinner

    geom.grassScale = 32f;

    //mat.setTexture(“NormalMap”, assetManager.loadTexture(“Textures/grass_normal.png”));

    geom.setMaterial(mat);

    geom.numLayers = NUM_LAYERS;

    //Set blend mode to Alpha to support each layer’s transparency

    mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

    //We need a seed to initialize each layer from.

    //this way, although each layer has LESS points,

    //the remaining points are still in the same position.

    long seed = FastMath.rand.nextLong();

    Random rand = new Random(seed);

    //Load 60 different alpha maps into the material, one for each layer.

    //Each one has the same pixel pattern, but each layer has a few less.

    //This way, the grass gradually thins as it gets to the top.

    for (int i = 0; i < NUM_DIFFERENT_LAYERS; i++) {

    //Set up the image

    Image diffuseMap = new Image();

    diffuseMap.setFormat(Image.Format.ABGR8);

    diffuseMap.setHeight(TEX_SIZE);

    diffuseMap.setWidth(TEX_SIZE);

    //Image objects store their pixel data in a ByteBuffer.

    //The ByteBuffer is simply four bytes per pixel,

    //In the order specified above (Alpha (transparency),

    //then Blue, then Green, then Red), one byte each.

    ByteBuffer data = ByteBuffer.allocateDirect(TEX_SIZETEX_SIZE4);

    //Thin the density as it approaches the top layer

    //The bottom layer will have 1000, the top layer 100.

    float density = i/(float)NUM_DIFFERENT_LAYERS;

    //density = FastMath.pow(density, 3);

    int numGrass = (int)(2000-((1800*density)+200));

    System.out.println("Grass blades in layer " + i + ": " + numGrass); //DEBUG

    //Reinitialize the random generator

    rand.setSeed(seed);

    //Generate the points

    for (int j = 0; j < numGrass; j++) {

    int curPoint = rand.nextInt(40000)*4;

    data.put(curPoint, (byte)255); //Alpha value

    data.put(curPoint+1, (byte)(rand.nextInt(39) + 21)); //Blue value

    data.put(curPoint+2, (byte)(rand.nextInt(100) + 85)); //Green value

    data.put(curPoint+3, (byte)(rand.nextInt(40) + 28)); //Red value

    }

    diffuseMap.setData(data);

    Texture2D texToAdd = new Texture2D(diffuseMap);

    texToAdd.setWrap(Texture.WrapMode.Repeat);

    diffuseMaps.add(texToAdd);

    }

    //Attach the alpha maps to the geometry so we can access them in RenderManager

    geom.layerMaps = diffuseMaps;

    geom.setQueueBucket(Bucket.Transparent);

    flyCam.setMoveSpeed(10f);

    rootNode.attachChild(geom);

    rootNode.setCullHint(CullHint.Never);

    }

    …[/java]

    The Main class is more complex than that (because it needs to contain most of the code from SimpleApplication as well), but those are the three important methods. the Initialize() method lets the superclass (Application) initialize as usual, and then calls its own initCamera() (which is almost a direct copy of Application’s initCamera() except that it uses LayerRenderManager) method to reinitialize the renderManager and viewPorts. In the next post, I’ll talk about the shader files.
7 Likes

The shaders are fairly simple, partly because I haven’t implemented lighting yet.



Grassy.j3md:

MaterialDef Grassy {

MaterialParameters {
Color Color : Color
Float Layer
Float Length
Texture2D DiffuseMap
Float Scale
}

Technique {
VertexShader GLSL100: MatDefs/Grassy.vert
FragmentShader GLSL100: MatDefs/Grassy.frag

WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
}

Defines {
LAYER : Layer
LENGTH : Length
COLOR : Color
DIFFUSEMAP : DiffuseMap
SCALE : Scale
}
}

Technique PreNormalPass {

VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert
FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag

WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
}

RenderState {

}

}

}

Pretty simple, nothing too crazy.

Grassy.frag:
uniform vec4 m_Color;
varying vec3 normal;
varying vec4 texCoord;
uniform sampler2D m_DiffuseMap;
uniform float m_Scale;

void main(){

vec4 GrassColour = texture2D( m_DiffuseMap, texCoord * m_Scale); //Set the color according to the color map

//

//Basic Directional Lighting
//vec4 ambient = vec4(0.3, 0.3, 0.3, 0.0);
//ambient = ambient * FinalColour;
//vec4 diffuse = FinalColour;
//FinalColour = ambient + diffuse * dot(vec4(0.8,0.8,1,0), normal);

//

gl_FragColor = GrassColour;
//gl_FragColor = FinalColour;
}

Even simpler, because I haven't worked on any sort of lighting or anything yet, except for a copied block of generic ambient lighting that doesn't work very well, that I converted from the xbdev.net example.

Grassy.vert:
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
attribute vec3 inPosition;
attribute vec3 inNormal;
attribute vec4 inTexCoord;
uniform float m_Layer;
uniform float m_Length;
varying vec3 normal;
varying vec4 texCoord;

void main(){

vec3 P = inPosition + (inNormal * m_Length);

//Set up the normals so that lighting is accurate
normal = normalize((vec4(inNormal, 1.0))*g_WorldViewMatrix);

//Bend the grass
vec3 vGravity = ((vec4(0,-0.1,0,0))*g_WorldViewMatrix).xyz;
float k = pow(m_Layer, 2); // The higher the exponent is here, the closer to the tip the grass will start curving
P = P + vGravity*k;

texCoord = inTexCoord;

gl_Position = g_WorldViewProjectionMatrix * vec4(P, 1.0);
}

This, along with the texture swapping that happens in LayerRenderManager, is what really creates the grass. The position of the vertex is increased by an amount relative to what layer is being rendered, and there's a small snippet of code in there that will bend the top of each grass blade in the Y direction.

Here’s a link to the files themselves. Just create a new jME3 project, and copy them in. Note that I use the nightly build, and I last ran the code in this link on May 24th, 2011.



GrassTest.zip (Rapidshare, sorry)



EDIT: Forgot to mention the usual disclaimer of “I hold no responsibility if your computer explodes when you run this or for what you do with this code etc. etc. …”. That in mind, feel free to use the code however you wish.

Cool!!! Any screenshots?

canis85 said:
Here's a link to the files themselves. Just create a new jME3 project, and copy them in. Note that I use the nightly build, and I last ran the code in this link on May 24th, 2011.
GrassTest.zip (Rapidshare, sorry)

EDIT: Forgot to mention the usual disclaimer of "I hold no responsibility if your computer explodes when you run this or for what you do with this code etc. etc. ...". That in mind, feel free to use the code however you wish.

Forgot to mention, make sure you either change the ground texture or add the jme3-test-data library to the project you create, or it'll crash telling you it can't find the dirt.jpg texture.
It looks like a pink ball with green hair right now, but I'm working on it ;). And it probably looks better on a terrain rather than a sphere.
glaucomardano said:
Cool!!! Any screenshots?


Here's a screenshot of the program I linked in my last post:
1 Like

Very, very cool :)!!! The grass move?

glaucomardano said:
Very, very cool :)!!! The grass move?

Not yet, but I'd like to implement something like that. I suppose I could add some code that keeps track of and adjusts the grass bend amount based on the alpha value in the texture map, and multiply it by a float representing the current windiness or something. The LayerRenderManager could keep track of the bend modifier, and fluctuate it back and forth to make the grass 'wave in the breeze'

Very cool man, you rock!!! Is it possible to use b/w mask texture for density of particles?

mifth said:
Very cool man, you rock!!! Is it possible to use b/w mask texture for density of particles?


Thanks :)

And yes, you could make custom texture masks. You just need to comment out the code that generates random ones and add your custom ones to the diffuseMaps array.

[java]...
Texture2D texToAdd = new Texture2D(/*your image*/);
texToAdd.setWrap(Texture.WrapMode.Repeat);
diffuseMaps.add(texToAdd);
...[/java]

Although, right now, that would make a whole bunch of black grass/fur, as I haven't separated the density mask from the texture yet. A quick shader edit would solve that, though.

Made a quick hack to apply it to a terrain. FPS isn’t great, but that’s because the ENTIRE terrain is covered with the same amount of grass. I’ll have to make some sort of distance check to see if it should apply.



Also, the lines between TerrainPatches are very noticeable… might not be noticeable once the grass that’s close to you is the only grass that’s rendered, maybe.



Dirt texture still looks pink because there still isn’t any lighting code…



Looking good, keep it up! This makes me want to get back into shaders, although all my shader experience lies in DirectX/HLSL…

very nice work!

Looks incredible, but couldn’t this been achieved with a SceenProcessor too ?

Awesome work, very in fact.

MAN YOU ARE REALLY COOL!!! You are making a vital tool for our community!



Another one desire: is it possible to visualize only camera view particles and particles with some of z depth and other will be culled? I mean:







Uploaded with ImageShack.us

First of all, thanks for all the kudos! I’m glad to finally give something back to the jME community. I’ve monkeyed around with jME since jME2 a couple of years ago, but only recently have I actually started trying to make a complete game.



Secondly, I’ve got an idea for particle culling, I’ll try it and see how it goes:



The particles are actually extensions of the geometry being rendered. If you read the article I linked to in my first post from xbdev.net, you’ll see that essentially the grass “particles” are just layers (or “shells”, as the author calls them). Alpha (transparency) values are super important, because each shell is a sheet that’s rendered according to a mask texture.



So, the more layers you have, the more realistic it looks, but the more expensive it is to render because you’re rendering that tri completely for every layer.



That in mind, a function (maybe where the regular camera culling occurs?) at the right location could do a simple distance check from the camera, and set the number of layers for each triangle according to its distance from the camera, just before it gets rendered. So for tris a few units away from the camera, you get the full complement of layers, and the realism that goes along with it. For more distant tris, you get less and less layers as it goes further from the camera. For tris starting a certain distance away, you either don’t get any layers (if your ground texture meshes well with the grass), or maybe you only get one layer.



My goal is to eventually merge this with TerrainLighting.j3md, which is what my current game uses (albeit somewhat modified to support additional splat textures). I’d like to be able to simply assign the grass as one of the splat textures, with the color value being the length of the grass and the ground texture being whatever other splat texture is at that location.

1 Like
Setekh said:
Looks incredible, but couldn't this been achieved with a SceenProcessor too ?
Awesome work, very in fact.


I'm not familiar with ScreenProcessors, honestly. I haven't looked at the code behind any of them yet to see how they work.

Thanks for answering. I think you work is very important for JME. Let us know about any news or progress. :slight_smile:

I’ve been trying to make the grass wave back and forth a little, and I have an implementation that I feel should work, but I’m having issues with some of the uniforms.



I’ve got eight uniforms that I pass into the shader that contain information for the grass rendering. Four of those I added for the grass wave code, each of which is simply a float that contains a ‘bend percentage’ for each of four ‘bend groups’. This is so that not every blade of grass is bent the same amount, making the grass wave effect more realistic.



Anyway, each of the four bend values is modified every update based on the tpf value. The problem is, every time I update the bend values, I get:



May 25, 2011 3:33:20 PM com.jme3.renderer.lwjgl.LwjglRenderer updateUniformLocation

INFO: Uniform m_GrassTexture is not declared in shader.

May 25, 2011 3:33:20 PM com.jme3.renderer.lwjgl.LwjglRenderer updateUniformLocation

INFO: Uniform m_TexScale is not declared in shader.

May 25, 2011 3:33:20 PM com.jme3.renderer.lwjgl.LwjglRenderer updateUniformLocation

INFO: Uniform m_GrassTexture is not declared in shader.

May 25, 2011 3:33:20 PM com.jme3.renderer.lwjgl.LwjglRenderer updateUniformLocation

INFO: Uniform m_TexScale is not declared in shader.

May 25, 2011 3:33:20 PM com.jme3.renderer.lwjgl.LwjglRenderer updateUniformLocation

INFO: Uniform m_BendFactor0 is not declared in shader.

May 25, 2011 3:33:20 PM com.jme3.renderer.lwjgl.LwjglRenderer updateUniformLocation

INFO: Uniform m_BendFactor1 is not declared in shader.

May 25, 2011 3:33:20 PM com.jme3.renderer.lwjgl.LwjglRenderer updateUniformLocation

INFO: Uniform m_BendFactor2 is not declared in shader.

May 25, 2011 3:33:20 PM com.jme3.renderer.lwjgl.LwjglRenderer updateUniformLocation

INFO: Uniform m_BendFactor3 is not declared in shader.



repeated dozens of times each frame. If I comment out the bend modifier update, it prints that once and then the scene renders fine.



The reason this is so confusing is that I have two other uniforms, m_Layer and m_Length, that I update for every shell (60 times per geometry per render), and I don’t get any error messages at all for those, even though they’re updated more often than the bend factors. Does anyone have any ideas? This project is actually my first foray into any kind of shader coding, so all I’ve got are a couple of GLSL tutorials under my belt.

yeah this message pops up not always for a good reason, you can ignore it.



you should not pass those factors each frame though you can have the time and/or tpf passed to the shader by using global uniforms (g_Time, g_tpf).

Then you can compute the factors in the vertex shader. saving a lot of bandwidth and hardware acceleration.