Animated grass using vertex shader

Hey.

I’ve begun implementing animated grass, based on:

http://http.developer.nvidia.com/GPUGems/gpugems_ch07.html.



Here’s a first look at it. Sorry for the bad quality, i’m not befriended with CamStudio yet.







http://www.youtube.com/watch?v=JTbQUA7kU9k



Still some tweaking to do with the noise, vertex colors should be based on underlying texture, etc.



Source:





MovingGrass.j3md:

[java]MaterialDef Grass2 {



MaterialParameters {

Texture2D Texture

Texture2D Noise

Color Color

Float Time

Float WindStrength

Vector2 WindDirection

Boolean Use_VertexColor

Vector3 CamPos

}



Technique {

LightMode SinglePass

VertexShader GLSL100: MatDefs/Misc/MovingGrass.vert

FragmentShader GLSL100: MatDefs/Misc/MovingGrass.frag



WorldParameters {

WorldViewProjectionMatrix

WorldViewMatrix

}



Defines {

TEXTURE : Texture

VERTEX_COLOR : Use_VertexColor

}



}



Technique FixedFunc {

}



}[/java]



MovingGrass.frag:

[java]#ifdef TEXTURE

uniform sampler2D m_Texture;

varying vec2 texCoord;

#endif



varying vec4 color;



void main(void)

{

#ifdef TEXTURE

vec4 texVal = texture2D(m_Texture, texCoord);

gl_FragColor = texVal * color;

#else

gl_FragColor = color;

#endif

}[/java]



MovingGrass.vert:

[java]

uniform mat4 g_WorldViewProjectionMatrix;

uniform mat4 g_WorldViewMatrix;

uniform vec4 g_LightColor;



uniform float m_Time;

uniform float m_WindStrength;

uniform vec2 m_WindDirection;

uniform vec3 m_ObjectCenter;

uniform vec3 m_CamPos;



uniform sampler2D m_Texture;

uniform sampler2D m_Noise;

uniform vec4 m_Color;



attribute vec2 inTexCoord;



attribute vec3 inPosition;



uniform float moveFactor = 0.06; // Play around with this



varying vec2 texCoord;

varying vec4 color;



#ifdef VERTEX_COLOR

attribute vec4 inColor;

#endif



void main()

{

vec3 displacedVertex;

displacedVertex = inPosition;

texCoord = inTexCoord;



float len = length( displacedVertex );



float noiseCoord = m_Time;



int totalTime = int(m_Time);

if (totalTime > 4096) totalTime -= 4096;



int pixelY = int(totalTime/64);

int pixelX = totalTime/ - pixelY;

float noiseFactor = texture2D(m_Noise, vec2( pixelX10, pixelY10 ) ).r;

// get pixel from noise map based on time. use to create additional variation



vec3 wvPosition = (g_WorldViewProjectionMatrix * vec4(displacedVertex, 1.0)).xyz;



if(inPosition.y>=0.1)

{

displacedVertex.x += moveFactor * sin(m_Time * texture2D(m_Noise, wvPosition.xz50).r + len) + (m_WindStrength * noiseFactor * m_WindDirection.x)/10;

displacedVertex.z += moveFactor * cos(m_Time * texture2D(m_Noise, wvPosition.zx
50).r + len) + (m_WindStrength * noiseFactor * m_WindDirection.y)/10;

}



gl_Position = g_WorldViewProjectionMatrix * vec4(displacedVertex, 1.0);





#ifdef VERTEX_COLOR

color = m_Color * inColor;

color.rgb *= g_LightColor.rgb;

#else

color = m_Color;

color.rgb = g_LightColor.rgb;

#endif



}

[/java]



As well as a test. It’s not a good one however, since it requires some additional assets:

[java]

import com.jme3.app.SimpleApplication;

import com.jme3.material.Material;

import com.jme3.material.RenderState.BlendMode;

import com.jme3.material.RenderState.FaceCullMode;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector2f;

import com.jme3.math.Vector3f;

import com.jme3.renderer.queue.RenderQueue.Bucket;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Quad;

import com.jme3.texture.Texture;

import everwar.objects.GrassMesh;

import java.util.Random;

import jme3tools.optimize.GeometryBatchFactory;



public class TestMovingGrass extends SimpleApplication {



public static void main(String[] args){

TestMovingGrass app = new TestMovingGrass();

app.start();

}



private float elapsedTime = 0;



private Material grassShader;



private Geometry grassGeom;



private Node allGrass = new Node(“all grass”);



private Vector2f windDirection = new Vector2f();

private float windStrength;



private Random random = new Random();



private Geometry ground;



public void simpleInitApp() {

this.getFlyByCamera().setMoveSpeed(15);

ground = new Geometry(“ground”, new Quad(10, 10) );

ground.setMaterial( new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”) );

ground.getMaterial().setColor(“Color”, ColorRGBA.Green.mult(ColorRGBA.DarkGray));

//Texture t = assetManager.loadTexture(“Textures/Terrain/grass.dds”);

//t.setWrap(Texture.WrapMode.Repeat);

//ground.getMaterial().setTexture(“ColorMap”, t );



ground.setLocalTranslation(0, 0, 10);

ground.rotate(-90
FastMath.DEG_TO_RAD, 0, 0);

rootNode.attachChild(ground);



windDirection.x = random.nextFloat();

windDirection.y = random.nextFloat();

windDirection.normalize();



grassGeom = new Geometry(“grass”, new GrassMesh() );



grassShader = new Material(assetManager, “MatDefs/Misc/MovingGrass.j3md”);

Texture grass = assetManager.loadTexture(“Textures/World/Vegetation/Grass/grassStencil2.png”);

grass.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat);

grassShader.setTexture(“Texture”, grass);

grassShader.setTexture(“Noise”, assetManager.loadTexture(“Textures/Noise/noise1.jpg”));



// set wind direction

grassShader.setVector2(“WindDirection”, windDirection);

windStrength = 0.8f;

grassShader.setFloat(“WindStrength”, windStrength);



grassShader.setTransparent(true);

grassShader.getAdditionalRenderState().setAlphaTest(true);

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

grassShader.getAdditionalRenderState().setAlphaFallOff(0.5f);

grassShader.setColor(“Color”, new ColorRGBA(0.13f, 0.23f, 0.13f, 1f));

grassShader.setFloat(“Time”, 0);



grassShader.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);

grassGeom.setQueueBucket(Bucket.Transparent);

grassGeom.setMaterial(grassShader);

grassGeom.center();



for (int y = 0; y < 10; y++){

for (int x = 0; x < 10; x++){

Geometry grassInstance = grassGeom.clone();



grassInstance.setLocalTranslation(x + (float)(Math.random()*1f), 0, y + (float)(Math.random()1f));

grassInstance.scale (0.4f, 0.4f + random.nextFloat()
.2f, 0.4f);

allGrass.attachChild(grassInstance);

}

}



Spatial grassNode = GeometryBatchFactory.optimize(allGrass);

grassNode.setQueueBucket(Bucket.Transparent);

grassNode.setMaterial(grassShader);

grassNode.updateModelBound();



rootNode.attachChild(grassNode);



cam.setLocation(new Vector3f(8.378951f, 5.4324f, 8.795956f));

cam.setRotation(new Quaternion(-0.083419204f, 0.90370524f, -0.20599906f, -0.36595422f));



}



@Override

public void simpleUpdate(float tpf){

elapsedTime += 0.01;

grassShader.setFloat(“Time”, elapsedTime);

}

[/java]



Notes:

For this you need a grass stencil texture, and a noise map texture (i generated one using “difference coulds” in gimp).

If you don’t mind some more regularity you can strip it out of the shader.

You most likely need to modify the “moveFactor” float in the vertex shader depending on the scale of your meshes.

Batching is optional, but increases performance significantly.

Wind direction doesn’t really work like wind should, but makes the grass move fairly natural.

8 Likes

Niiiice!!

good Work!

Yey, awesome :smiley:



Is it complete?

Whoa it moves! Great work! Its based on the nvidia (or was it ati?) article? Like you have grass textures “cris-crossing” the map that are moved afaic see?

Yes, it’s based on those articles + some other snippets.

I hope to post a better video + a functional shader soon.

Updated video with a higher res one (800x600). I’ve added some noise functions to the grass which causes some additional chaos to the way it moves, based on both time and position.

Also replaced the grass texture with a better one. Now i want to add some color variation too.

Going to look a bit more at this topic:

http://hub.jmonkeyengine.org/groups/graphics/forum/topic/high-speed-grass-rendering-using-glsl-shader?topic_page=3&num=15

Nice job :slight_smile: looks good so far!

Great job looks :wink:



I just wonder, how hard would it bo/is it possible to add two uniforms ? one vec3 with the position of your player + one float for radius, so that if you have a moving player you move that vec with him and it pushes the grass a little down/out of it’s way?

It doesn’t have to be that difficult, i guess. You’d pass the players world translation, and compare it with the vertex’s and move it if < than a certain distance. Not a prio for my project, but i’ve already over shot my needs :slight_smile:

Were you able to get CamStudio recording video with something more than 5-6 frames per second ? It is terribly choppy for me.

Well I prefer fraps , it’s not really high quality either but it’s a acceptable solution for windows at least.

Regarding recording software, how about Screenr?

For gaming recording software, you can check this:

http://www.anewmorning.com/2009/01/06/5-free-alternatives-to-fraps/



Also fraps is actually a very good software, you can record 1080p video from games at 60 fps and the video recorded is lossless (meaning no quality loss), but it does take a lot of space. The only issue with fraps is that the free version has a watermark and a limit of 30 second recording.

would be great to see this, so I would not have to do it too, as it is on my todo too…

Is there any sourcecode available to play with this grass?

Sorry for the delay. I meant to add some documentation, see if it was possible to optimize etc.

Anyway, here’s the basic stuff:



MovingGrass.j3md:

MaterialDef Grass2 {



MaterialParameters {

Texture2D Texture

Texture2D Noise

Color Color

Float Time

Float WindStrength

Vector2 WindDirection

Boolean Use_VertexColor

Vector3 CamPos

}



Technique {

LightMode SinglePass

VertexShader GLSL100: MatDefs/Misc/MovingGrass.vert

FragmentShader GLSL100: MatDefs/Misc/MovingGrass.frag



WorldParameters {

WorldViewProjectionMatrix

WorldViewMatrix

}



Defines {

TEXTURE : Texture

VERTEX_COLOR : Use_VertexColor

}



}



Technique FixedFunc {

}



}



MovingGrass.frag:

#ifdef TEXTURE

uniform sampler2D m_Texture;

varying vec2 texCoord;

#endif



varying vec4 color;



void main(void)

{

#ifdef TEXTURE

vec4 texVal = texture2D(m_Texture, texCoord);

gl_FragColor = texVal * color;

#else

gl_FragColor = color;

#endif

}



MovingGrass.vert:



uniform mat4 g_WorldViewProjectionMatrix;

uniform mat4 g_WorldViewMatrix;

uniform vec4 g_LightColor;



uniform float m_Time;

uniform float m_WindStrength;

uniform vec2 m_WindDirection;

uniform vec3 m_ObjectCenter;

uniform vec3 m_CamPos;



uniform sampler2D m_Texture;

uniform sampler2D m_Noise;

uniform vec4 m_Color;



attribute vec2 inTexCoord;



attribute vec3 inPosition;



uniform float moveFactor = 0.06; // Play around with this



varying vec2 texCoord;

varying vec4 color;



#ifdef VERTEX_COLOR

attribute vec4 inColor;

#endif



void main()

{

vec3 displacedVertex;

displacedVertex = inPosition;

texCoord = inTexCoord;



float len = length( displacedVertex );



float noiseCoord = m_Time;



int totalTime = int(m_Time);

if (totalTime > 4096) totalTime -= 4096;



int pixelY = int(totalTime/64);

int pixelX = totalTime/ - pixelY;

float noiseFactor = texture2D(m_Noise, vec2( pixelX10, pixelY10 ) ).r;

// get pixel from noise map based on time. use to create additional variation



vec3 wvPosition = (g_WorldViewProjectionMatrix * vec4(displacedVertex, 1.0)).xyz;



if(inPosition.y>=0.1)

{

displacedVertex.x += moveFactor * sin(m_Time * texture2D(m_Noise, wvPosition.xz50).r + len) + (m_WindStrength * noiseFactor * m_WindDirection.x)/10;

displacedVertex.z += moveFactor * cos(m_Time * texture2D(m_Noise, wvPosition.zx
50).r + len) + (m_WindStrength * noiseFactor * m_WindDirection.y)/10;

}



gl_Position = g_WorldViewProjectionMatrix * vec4(displacedVertex, 1.0);





#ifdef VERTEX_COLOR

color = m_Color * inColor;

color.rgb *= g_LightColor.rgb;

#else

color = m_Color;

color.rgb = g_LightColor.rgb;

#endif



}





As well as a test. It’s not a good one however, since it requires some additional assets:



import com.jme3.app.SimpleApplication;

import com.jme3.material.Material;

import com.jme3.material.RenderState.BlendMode;

import com.jme3.material.RenderState.FaceCullMode;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector2f;

import com.jme3.math.Vector3f;

import com.jme3.renderer.queue.RenderQueue.Bucket;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Quad;

import com.jme3.texture.Texture;

import everwar.objects.GrassMesh;

import java.util.Random;

import jme3tools.optimize.GeometryBatchFactory;



public class TestMovingGrass extends SimpleApplication {



public static void main(String[] args){

TestMovingGrass app = new TestMovingGrass();

app.start();

}



private float elapsedTime = 0;



private Material grassShader;



private Geometry grassGeom;



private Node allGrass = new Node(“all grass”);



private Vector2f windDirection = new Vector2f();

private float windStrength;



private Random random = new Random();



private Geometry ground;



public void simpleInitApp() {

this.getFlyByCamera().setMoveSpeed(15);

ground = new Geometry(“ground”, new Quad(10, 10) );

ground.setMaterial( new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”) );

ground.getMaterial().setColor(“Color”, ColorRGBA.Green.mult(ColorRGBA.DarkGray));

//Texture t = assetManager.loadTexture(“Textures/Terrain/grass.dds”);

//t.setWrap(Texture.WrapMode.Repeat);

//ground.getMaterial().setTexture(“ColorMap”, t );



ground.setLocalTranslation(0, 0, 10);

ground.rotate(-90
FastMath.DEG_TO_RAD, 0, 0);

rootNode.attachChild(ground);



windDirection.x = random.nextFloat();

windDirection.y = random.nextFloat();

windDirection.normalize();



grassGeom = new Geometry(“grass”, new GrassMesh() );



grassShader = new Material(assetManager, “MatDefs/Misc/MovingGrass.j3md”);

Texture grass = assetManager.loadTexture(“Textures/World/Vegetation/Grass/grassStencil2.png”);

grass.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat);

grassShader.setTexture(“Texture”, grass);

grassShader.setTexture(“Noise”, assetManager.loadTexture(“Textures/Noise/noise1.jpg”));



// set wind direction

grassShader.setVector2(“WindDirection”, windDirection);

windStrength = 0.8f;

grassShader.setFloat(“WindStrength”, windStrength);



grassShader.setTransparent(true);

grassShader.getAdditionalRenderState().setAlphaTest(true);

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

grassShader.getAdditionalRenderState().setAlphaFallOff(0.5f);

grassShader.setColor(“Color”, new ColorRGBA(0.13f, 0.23f, 0.13f, 1f));

grassShader.setFloat(“Time”, 0);



grassShader.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);

grassGeom.setQueueBucket(Bucket.Transparent);

grassGeom.setMaterial(grassShader);

grassGeom.center();



for (int y = 0; y < 10; y++){

for (int x = 0; x < 10; x++){

Geometry grassInstance = grassGeom.clone();



grassInstance.setLocalTranslation(x + (float)(Math.random()*1f), 0, y + (float)(Math.random()1f));

grassInstance.scale (0.4f, 0.4f + random.nextFloat()
.2f, 0.4f);

allGrass.attachChild(grassInstance);

}

}



Spatial grassNode = GeometryBatchFactory.optimize(allGrass);

grassNode.setQueueBucket(Bucket.Transparent);

grassNode.setMaterial(grassShader);

grassNode.updateModelBound();



rootNode.attachChild(grassNode);



cam.setLocation(new Vector3f(8.378951f, 5.4324f, 8.795956f));

cam.setRotation(new Quaternion(-0.083419204f, 0.90370524f, -0.20599906f, -0.36595422f));



}



@Override

public void simpleUpdate(float tpf){

elapsedTime += 0.01;

grassShader.setFloat(“Time”, elapsedTime);

}





Notes:

For this you need a grass stencil texture, and a noise map texture (i generated one using “difference coulds” in gimp).

If you don’t mind some more regularity you can strip it out of the shader.

You most likely need to modify the “moveFactor” float in the vertex shader depending on the scale of your meshes.

Batching is optional, but increases performance significantly.

Wind direction doesn’t really work like wind should, but makes the grass move fairly natural.

I couldn’t get this to work anymore. I will try to fix it up and repackage it along with the proper textures so others can use this. It is extremely useful for almost any game and I can’t find any other grass shaders for JME right now.

1 Like

I wish we had a proper plugin repo! Here is my fixed up version:

I fixed a few bugs in the .j3md and in the .vert that were causing it to crash. Also fixed the color variable so you can properly control the color. I am sure rickard’s code worked before but something must have changed with the engine since then.

Screenie: https://raw.githubusercontent.com/admazzola/animated-grass-shader-jme3/master/src/grass/grasstest.png

4 Likes