Billboard grid pixelshader for Instant Animated Grass

updated the shaders



now it works… but only from one direction…

and i don't know whats the correct eyeVector

Wow, have to try it. The screenshots in the paper are looking amazing! I will right now download the textures. :slight_smile:



Too bad I don't have a real 3D card in this PC… so there won't be a shader effect. Have to try it as soon as I'm at home.

and again an update… now it looks ok if viewed from top, but at low angles it gets strange…









Thanks for the videos! Looks good as a starting point, maybe even better if the grid structure isn't so obvious. The videos are a bit sharper , the 2nd of your pics look kinda weird. Anyway, I'm trying to set it up on my machine, but it is not correct yet. Do I have to override a location to load the shaders?

I changed the following line to load the shaders:



terrainShader = DisplaySystem.getDisplaySystem().getRenderer().createGLSLShaderObjectsState();
if (terrainShader.isSupported()) {
   terrainShader.load("texture/fullshader.vert", "texture/fullshader.frag");
   terrainShader.setUniform("grassblades", 1);
   terrainShader.setUniform("ground", 0);
   terrainShader.setUniform("windnoise", 2);
   terrainShader.setEnabled(true);
   quad.setRenderState(ts);
   quad.setRenderState(terrainShader);
   quad.copyTextureCoords(0, 0, 1);
   quad.copyTextureCoords(0, 0, 2);
   quad.updateRenderState();
} else {
   System.out.println("Error: GLSLShader not supported.");
}



But now I get this exception:


Exception in game loop
org.lwjgl.opengl.OpenGLException: Invalid operation (1282)
   at org.lwjgl.opengl.Util.checkGLError(Util.java:56)
   at org.lwjgl.opengl.Display.swapBuffers(Display.java:555)
   at org.lwjgl.opengl.Display.update(Display.java:571)
   at com.jme.renderer.lwjgl.LWJGLRenderer.displayBackBuffer(Unknown Source)
   at com.jme.app.BaseGame.start(Unknown Source)
   at RealisticSky.main(RealisticSky.java:528)

I saw this thread and your attempts to create an implementation of MrCoders (with some small contribution from me) jME port of the "Instant Grass" shader. Since I have our original source, I thought you might want to take a look at it :slight_smile:



The test class:

public class TestInstantGrass extends SimpleGame {
   private static final Logger logger = Logger.getLogger(TestInstantGrass.class.getName());
   private GLSLShaderObjectsState so;
   private Quad quad;

   private Vector3f tmpVec = new Vector3f();
   private Matrix4f modelMatrix = new Matrix4f();
   private Matrix4f tmpMatrix = new Matrix4f();

   public static void main(String[] args) {
      TestInstantGrass app = new TestInstantGrass();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
      app.start();
   }
   
   protected void simpleInitGame() {
      display.setTitle("Instant grass");
      display.getRenderer().setBackgroundColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f));

      cam.setFrustumPerspective(50.0f, (float) display.getWidth() / (float) display.getHeight(), 0.01f, 100f);
      cam.setLocation(new Vector3f(0, 0, 1));
      cam.update();

      CameraNode camNode = new CameraNode("Camera Node", cam);
      camNode.setLocalTranslation(new Vector3f(0.0f, 1.0f, 1.0f));
      camNode.lookAt(new Vector3f(0.0f, 0.0f, 0.0f), new Vector3f(0.0f, 1.0f, 0.0f));
      camNode.updateWorldData(0);
      input = new NodeHandler(camNode, 2, 1);
      rootNode.attachChild(camNode);

      Quad brick = createBrickQuad();
      rootNode.attachChild(brick);

      rootNode.updateRenderState();
   }

   protected void simpleUpdate() {
      modelMatrix.loadIdentity();
      quad.getWorldRotation().toRotationMatrix(tmpMatrix);
      modelMatrix.multLocal(tmpMatrix);
      modelMatrix.m00 *= quad.getWorldScale().x;
      modelMatrix.m11 *= quad.getWorldScale().y;
      modelMatrix.m22 *= quad.getWorldScale().z;
      modelMatrix.setTranslation(quad.getWorldTranslation());

      tmpVec.set(cam.getLocation());
      modelMatrix.invertLocal();
      modelMatrix.mult(tmpVec, tmpVec);
      
      float time = timer.getTimeInSeconds();

      so.clearUniforms();
      so.setUniform("RT", 0);
      so.setUniform("ground", 1);
      so.setUniform("windnoise", 2);
      so.setUniform("time", time);
      so.setUniform("cameraPos", tmpVec.x, tmpVec.y, tmpVec.z);
      so.setUniform("tangent", 1.0f, 0.0f, 0.0f);
      so.setUniform("binormal", 0.0f, 1.0f, 0.0f);
      
      so.apply();
   }

   private Quad createBrickQuad() {
      so = display.getRenderer()
            .createGLSLShaderObjectsState();

      // Check is GLSL is supported on current hardware
      if (!so.isSupported()) {
         logger.log(java.util.logging.Level.SEVERE, "Your graphics card does not support GLSL programs, and thus cannot run this test.");
         quit();
      }

      so.load(TestInstantGrass.class.getClassLoader().getResource("shaders/instantgrass.vert"),
            TestInstantGrass.class.getClassLoader().getResource("shaders/instantgrass.frag"));
      so.setEnabled(true);

      // Generate quad
      quad = new Quad("glslQuad", 1f, 1f);
      quad.setRenderState(so);
      
      Matrix3f mtRot = new Matrix3f();
      mtRot.fromAngleAxis(-90.0f, new Vector3f(1, 0, 0));      
      quad.setLocalRotation(mtRot);

      TextureState ts = display.getRenderer().createTextureState();
      Texture t0 = TextureManager.loadTexture(
            TestInstantGrass.class.getClassLoader().getResource(
                  "textures/m_grassblades.tga"),
            Texture.MM_LINEAR,
            Texture.FM_LINEAR, 1.0f, false);
      t0.setWrap(Texture.WM_WRAP_S_WRAP_T);
      ts.setTexture(t0, 0);

      t0 = TextureManager.loadTexture(
            TestInstantGrass.class.getClassLoader().getResource(
                  "textures/m_grass_ground.tga"),
            Texture.MM_LINEAR_LINEAR,
            Texture.FM_LINEAR, 1.0f, false);
      t0.setWrap(Texture.WM_WRAP_S_WRAP_T);
      ts.setTexture(t0, 1);
      
      t0 = TextureManager.loadTexture(
            TestInstantGrass.class.getClassLoader().getResource(
                  "textures/windnoise.tga"),
            Texture.MM_LINEAR_LINEAR,
            Texture.FM_LINEAR, 1.0f, false);
      t0.setWrap(Texture.WM_WRAP_S_WRAP_T);
      ts.setTexture(t0, 2);

      quad.setRenderState(ts);

      return quad;
   }
}



The vertex shader:

uniform vec3 cameraPos;
uniform vec3 tangent;
uniform vec3 binormal;

uniform float time;

varying vec3 viewTangetSpace;

void main(void)
{
   gl_Position = ftransform();
   gl_TexCoord[0] = gl_MultiTexCoord0;
   gl_TexCoord[2].xy = vec2((gl_MultiTexCoord2.x+time*0.2)/2.0,(gl_MultiTexCoord2.y+time*0.2)/2.0); // offset second texture coordinate
   

    // Calculate the vector coming from the vertex to the camera
   vec3 viewDir = -(cameraPos - gl_Vertex.xyz);

   // Compute tangent space for the view direction
   viewTangetSpace.x = dot(viewDir, tangent);
   viewTangetSpace.y = dot(viewDir, binormal);
   viewTangetSpace.z = dot(viewDir, gl_Normal);
}



Pixel shader (a bit messy):

uniform sampler2D RT;
uniform sampler2D ground;
uniform sampler2D windnoise;

varying vec3 viewTangetSpace;

#define MAX_RAYDEPTH 5 //Number of iterations.

#define PLANE_NUM 32.0 //Number of grass slice grid planes per unit in tangent space.

#define PLANE_NUM_INV (1.0/PLANE_NUM)
#define PLANE_NUM_INV_DIV2 (PLANE_NUM_INV/2.0)

#define GRASS_SLICE_NUM 8.0 // Number of grass slices in texture grassblades.

#define GRASS_SLICE_NUM_INV (1.0/GRASS_SLICE_NUM)
#define GRASS_SLICE_NUM_INV_DIV2 (GRASS_SLICE_NUM_INV/2.0)

#define GRASSDEPTH GRASS_SLICE_NUM_INV //Depth set to inverse of number of grass slices so no stretching occurs.

#define TC1_TO_TC2_RATIO 8.0 //Ratio of texture coordinate set 1 to texture coordinate set 2, used for the animation lookup.

#define PREMULT (GRASS_SLICE_NUM_INV*PLANE_NUM) //Saves a multiply in the shader.

#define AVERAGE_COLOR vec4(0.32156,0.513725,0.0941176,1.0) //Used to fill remaining opacity, can be replaced by a texture lookup.

void main(void)
{
   vec4 color = vec4(0.0,0.0,0.0,0.0);
    vec2 plane_offset = vec2(0.0,0.0);
    vec3 rayEntry = vec3(gl_TexCoord[0].xy,0.0);
   
   
   float zOffset = 0.0;
   bool zFlag = true;
   
   
   vec2 signvec = vec2(sign(viewTangetSpace.x), sign(viewTangetSpace.y));
   vec2 plane_correct = vec2((signvec.x+1.0) * GRASS_SLICE_NUM_INV_DIV2,
                              (signvec.y+1.0) * GRASS_SLICE_NUM_INV_DIV2);
    vec2 planemod = vec2(floor(rayEntry.x*PLANE_NUM) / PLANE_NUM,
                         floor(rayEntry.y*PLANE_NUM) / PLANE_NUM);
   vec2 pre_dir_correct = vec2((signvec.x+1.0) * PLANE_NUM_INV_DIV2,
                               (signvec.y+1.0) * PLANE_NUM_INV_DIV2);

   for(int i=0; i<MAX_RAYDEPTH; i++) {
      vec2 dir_correct = vec2(signvec.x * plane_offset.x + pre_dir_correct.x,
                        signvec.y * plane_offset.y + pre_dir_correct.y);
      vec2 distance = vec2((planemod.x + dir_correct.x - rayEntry.x) / (viewTangetSpace.x),
                      (planemod.y + dir_correct.y - rayEntry.y) / (viewTangetSpace.y));

      vec3 rayHitpointX = rayEntry + viewTangetSpace * distance.x;
      vec3 rayHitpointY = rayEntry + viewTangetSpace * distance.y;

      if ((rayHitpointX.z <= -GRASSDEPTH)&& (rayHitpointY.z <= -GRASSDEPTH)) {
           float distanceZ = (-GRASSDEPTH) / viewTangetSpace.z;

           vec3 rayHitpointZ = rayEntry + viewTangetSpace * distanceZ;
         vec2 orthoLookupZ = vec2(rayHitpointZ.x, rayHitpointZ.y);

           color += (1.0-color.w) * texture2D(ground, orthoLookupZ);
           
           if(zFlag == true)
              zOffset = distanceZ; // write the distance from rayEntry to intersection
           zFlag = false; //Early exit here if faster.           
        }
        else {
         vec2 orthoLookup;
         if(distance.x <= distance.y) {
            vec4 windX = (texture2D(windnoise,gl_TexCoord[2].xy + rayHitpointX.xy / TC1_TO_TC2_RATIO) - 0.5) / 2.0;
         
            float lookupX = -(rayHitpointX.z + (planemod.x + signvec.x * plane_offset.x) * PREMULT) - plane_correct.x;
            orthoLookup = vec2(rayHitpointX.y + windX.x*(GRASSDEPTH+rayHitpointX.z), lookupX);

            plane_offset.x += PLANE_NUM_INV;
            if(zFlag == true)
               zOffset = distance.x;
         }
         else {
            vec4 windY = (texture2D(windnoise,gl_TexCoord[2].xy + rayHitpointY.xy / TC1_TO_TC2_RATIO) - 0.5) / 2.0;
         
            float lookupY = -(rayHitpointY.z + (planemod.y + signvec.y * plane_offset.y) * PREMULT) - plane_correct.y;
            orthoLookup = vec2(rayHitpointY.x + windY.y * (GRASSDEPTH + rayHitpointY.z), lookupY);

            plane_offset.y += PLANE_NUM_INV;  // increment/decrement to next grid plane on v axis
            
            if(zFlag == true)
               zOffset = distance.y;
         }

         color += (1.0-color.w)*texture2D(RT, orthoLookup);
         
         if( color.w >= 0.49 )
            zFlag = false;   //Early exit here if faster.
      }
   }

   color += (1.0 - color.w) * AVERAGE_COLOR; //Fill remaining transparency in case there is some left. Can be replaced by a texture lookup

   //color.xyz *= (vertColor.xyz); //Modulate with per vertex lightmap,as an alternative, modulate with N*L for dynamic lighting.

    //zOffset is along eye direction, transform and add to vertex position to get correct z-value.
//    vec4 positionViewProj = gl_FragCoord;
//    positionViewProj.xyz += positionViewProj.xyz * viewTangetSpace.xzy * zOffset;

    //Divide by homogenous part.
//    gl_FragDepth = positionViewProj.z / positionViewProj.w;

   gl_FragColor = color;
}



Hope this can be of any use :)
desertrunner said:

But now I get this exception:


Exception in game loop
org.lwjgl.opengl.OpenGLException: Invalid operation (1282)
   at org.lwjgl.opengl.Util.checkGLError(Util.java:56)
   at org.lwjgl.opengl.Display.swapBuffers(Display.java:555)
   at org.lwjgl.opengl.Display.update(Display.java:571)
   at com.jme.renderer.lwjgl.LWJGLRenderer.displayBackBuffer(Unknown Source)
   at com.jme.app.BaseGame.start(Unknown Source)
   at RealisticSky.main(RealisticSky.java:528)




It seems that the shader files have to be loaded with class.getResource()



thanks snylt for the sources
they are really close to mine ^^

  // Calculate the vector coming from the vertex to the camera
vec3 viewDir = -(cameraPos - gl_Vertex.xyz);

// Compute tangent space for the view direction
viewTangetSpace.x = dot(viewDir, tangent);
viewTangetSpace.y = dot(viewDir, binormal);
viewTangetSpace.z = dot(viewDir, gl_Normal);


can be changed to

viewTangetSpace = -(cameraPos - gl_Vertex.xyz);



but calculating the eyevector on the cpu means that i have to do that for every object i put the shader on
and i think this could be done with only passing the cam position to the shader
i updated my sources in the first post again ^^
phoenix1272 said:

but what should i do, if i want to apply it on a complex terrain?

ah, i see what you mean…the impl posted was only as a quick test, in a real app you would set tangent/binormal as attribute pointers with tangent info values for every vertex(the normal is allready handled per vertex)

yes, but what about the eyeVector?

i dont want to calculate it for each mesh by hand

right(i didnt work much more on this after the first port)…



i think you can just remove the cameraPos uniform and matrix calc all together and replace the vertex shader with this:



uniform vec3 tangent;
uniform vec3 binormal;

uniform float time;

varying vec3 viewTangetSpace;

void main(void)
{
   gl_Position = ftransform();
   gl_TexCoord[0] = gl_MultiTexCoord0;
   gl_TexCoord[2].xy = vec2((gl_MultiTexCoord2.x+time*0.2)/2.0,(gl_MultiTexCoord2.y+time*0.2)/2.0); // offset second texture coordinate
   
   // Calculate the vector coming from the vertex to the camera
   vec4 vertexViewSpace = gl_ModelViewMatrix * gl_Vertex;
   vec3 viewDir = vertexViewSpace.xyz;

   vec3 modelNormal = gl_NormalMatrix * gl_Normal;
   vec3 modelTangent = gl_NormalMatrix * tangent;
   vec3 modelBinormal = gl_NormalMatrix * binormal;

   // Compute tangent space for the view direction
   viewTangetSpace.x = dot(viewDir, modelTangent);
   viewTangetSpace.y = dot(viewDir, modelBinormal);
   viewTangetSpace.z = dot(viewDir, modelNormal);
}

hmm, ok thats my vertexshader ^^



seems to me that i did something wrong with the fragment shader, if i use it with yours everything is displayed correct



thanks :smiley:

hmm, ok thats my vertexshader ^^


noone claimed your shaders were wrong *s* we simply posted our working solution  ;)

one last thing:

did you get this part to work correct?


    //zOffset is along eye direction, transform and add to vertex position to get correct z-value.
//    vec4 positionViewProj = gl_FragCoord;
//    positionViewProj.xyz += positionViewProj.xyz * viewTangetSpace.xzy * zOffset;

    //Divide by homogenous part.
//    gl_FragDepth = positionViewProj.z / positionViewProj.w;

Hey all



There’s been something wrong with this web link http://www.cg.tuwien.ac.at/research/publications/2007/Habel_2007_IAG/ for a couple of days now. I’m just trying to get hold of the textures. Is anyone able to post them somewhere ?



Thanks







Mak


That is strange, I can access the site just fine… If the problem persist, I can try to host the files for some days.

For me each of the sections just reads Warning: mysql_connect(): Too many connections in /home/httpd/htdocs/research/publications/mysql.php on line 25. If you could host the files somewhere or mail them to me at makavelishae@gmail.com that would be great !



Thanks





Mak


http://neko.bio.utk.edu/~duenez/habel.tar.gz



Beware that this is a 44 MB file, so you probably don’t want it on your mailbox  :smiley:

Thank you duenez … that's wonderful, and much appreciated !







Mak