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.zx50).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(-90FastMath.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.
Niiiice!!
good Work!
Yey, awesome
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 looks good so far!
Great job looks
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
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.
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.zx50).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(-90FastMath.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.
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.