High-Performance Non-Reflective Water

The WaterRenderPass slows down my game too much, I think, so here's a little something I threw together with the help of MrCoder and a few other of the jME forum-goers.



This code reflects the changes suggested below (as of July 25th, 2008 at 1:03 PM)



TestTranslucentWater.java:


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package testtranslucentwater;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.RawHeightMap;

/**
 *
 * @author Tyler
 */
public class TestTranslucentWater extends SimpleGame
{
    private Quad waterQuad; //The quad (which will behave like a plane) that we will consider our water.
    private TextureState waterTex; //The TextureState for applying the textures.
    private Texture waterTexture1; //The first water texture
    private Texture waterTexture2; //The second water texture
   
    public static void main(String args[])
    {
        TestTranslucentWater app = new TestTranslucentWater(); //Usual startup methods...
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
   
    @Override
    protected void simpleInitGame()
    {
        createTerrain(); //Set up our terrain and put our camera above-ground.
        createLighting(); //Make our lighting better so we can see.
       
        /* BEGIN WATER IMPLEMENTATION */
        waterQuad = new Quad("water quad", 10240, 10240); //Sets up our quad. We'll have it be the size of 100 tiles, to make it look infinite.
        waterTex = display.getRenderer().createTextureState(); //Initialize our TextureState
        flipQuad(waterQuad); //Quads start facing up, we need to flip this.
       
        //These will assign our textures....
        waterTexture1 = TextureManager.loadTexture(TestTranslucentWater.class.getClassLoader().getResource("water1.jpg"), Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR);
        waterTexture2 = TextureManager.loadTexture(TestTranslucentWater.class.getClassLoader().getResource("water2.jpg"), Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR);
        waterTexture1.setWrap(Texture.WM_WRAP_S_WRAP_T);
       
        //Scale our textures to make them look better, along with wrapping so they don't "fall off" the quad.
        waterTexture1.setScale(new Vector3f(100f, 100f, 1f));
        waterTexture1.setWrap(Texture.WM_WRAP_S_WRAP_T);
        waterTexture2.setScale(new Vector3f(100f, 100f, 1f));
        waterTexture2.setWrap(Texture.WM_WRAP_S_WRAP_T);
       
        //Assign our Textures to our TextureState
        waterTex.setTexture(waterTexture1, 0);
        waterTex.setTexture(waterTexture2, 1);
       
        //Make our quad transparent and assign the TextureState
        waterQuad.setRenderState(makeAlphaForTransparency());
        waterQuad.setRenderState(waterTex);
       
        //We need to make sure the quad is rendered after the land. QUEUE TRANSPARENT!
        waterQuad.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
        waterQuad.setLightCombineMode(0);
       
        //Set up our position (i.e. the height of the water at 10)
        waterQuad.setModelBound(new BoundingBox());
        waterQuad.setLocalTranslation(new Vector3f(0,10, 0));
       
        //The usual
        waterQuad.updateModelBound();
        waterQuad.updateRenderState();
       
        //Avoids NPE's when we do our first update!
        waterTexture1.setTranslation(new Vector3f(0,0,0));
        waterTexture2.setTranslation(new Vector3f(0,0,0));
       
        rootNode.attachChild(waterQuad);
    }
   
    protected void simpleUpdate()
    {
        updateWater();
    }
   
    private void updateWater()
    {
        //Prepares our Vector3f
        Vector3f tex1, tex2;
        tex1 = waterTexture1.getTranslation();
        tex2 = waterTexture2.getTranslation();
       
        //These are to keep the float from getting too big. They offer the possibility of a jump, but nobody should notice.
        if(tex1.getX() > 1000)
            waterTexture1.setTranslation(new Vector3f(0,0,0));
        if(tex2.getZ() > 1000)
            waterTexture2.setTranslation(new Vector3f(0,0,0));
       
        //Moves our water, nice and slowly.
        tex1.x += .04 * tpf;
        tex2.y += .09 * tpf;
       
        //The usual.
        waterQuad.updateRenderState();
        waterQuad.updateGeometricState(tpf, true);
    }
   
    private AlphaState makeAlphaForTransparency()
    {
        AlphaState as1 = display.getRenderer().createAlphaState();
        as1.setBlendEnabled(true);
        as1.setSrcFunction(4);
        as1.setTestEnabled(true);
        as1.setTestFunction(4);
        as1.setDstFunction(2);

        return as1;
    }
   
    private void flipQuad(Spatial s)
    {
        Quaternion rotQuad = new Quaternion();
        rotQuad = rotQuad.fromAngleNormalAxis(FastMath.PI * .5f, Vector3f.UNIT_X);
        s.setLocalRotation(rotQuad);
    }
   
    private void createTerrain()
    {
        Vector3f terrainScale = new Vector3f(8f, .001f, 8f);
        RawHeightMap map = new RawHeightMap(TestTranslucentWater.class.getClassLoader().getResource("0.raw"), 129, RawHeightMap.FORMAT_16BITLE, true);
        TerrainPage page = new TerrainPage("mypage", 33, map.getSize(), terrainScale, map.getHeightMap(), false);
        page.setDetailTexture(1,1);
        TextureState ts = display.getRenderer().createTextureState();
        Texture t0 = TextureManager.loadTexture(TestTranslucentWater.class.getClassLoader().getResource("grass.jpg"), Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR);
        t0.setWrap(Texture.WM_WRAP_S_WRAP_T);
        t0.setApply(Texture.AM_MODULATE);
        t0.setScale(new Vector3f(100.0f, 100.0f, 1.0f));
        ts.setTexture(t0);
        page.setRenderState(ts);
        rootNode.attachChild(page);
       
        cam.setLocation(new Vector3f(0f, page.getHeightFromWorld(new Vector3f(0, 0, 0)) + 5f, 0));
    }
   
    private void createLighting()
    {
        lightState.detachAll();
        DirectionalLight light = new DirectionalLight();
        light.setDirection(new Vector3f(.5f, -.75f, 0f));
        light.setAmbient(ColorRGBA.white);
        light.setDiffuse(ColorRGBA.white);
        light.setEnabled(true);
        lightState.attach(light);
    }
}



That's the source for slow-moving texture-based water (transparent, too!).

Here is a ZIP'd archive of the NetBeans project folder, along with a folder called "rsrc" that contains the textures I used (found them at ShareCG) in the test. If you open the NetBeans project folder, don't bother using the rsrc folder, NetBeans will know where to look. The rsrc folder is for all of you *cringes* Eclipse users.

Wow, exactly what I'm looking for!  :slight_smile:



Great thanks for this.

maybe I will use it too, my graphic card has some problems with the jme standart water

where is the screenshot  ? :slight_smile:

yes, a screenshot plz :slight_smile:

so this is just an animated textured quad right? or am i missing something?

btw, you should just add to the textures translation vectors instead of creating new ones every update.

You may also think about multiplying the movement by the FPS so that the water is a consistent speed no matter how the update runs

SCREENSHOT


neakor said:

so this is just an animated textured quad right? or am i missing something?

Way to make it seem like I didn't do anything. :( But pretty much, yes.

MrCoder said:

btw, you should just add to the textures translation vectors instead of creating new ones every update.

I may be misunderstanding you, but I think I am already doing that...

waterTexture2.setTranslation(tex2.add(new Vector3f(0, 0, .0009f)));


I am adding to the vectors here, tex2 is created beforehand and is the vector3f for waterTexture2's local translation. Is that what you mean?
Trussell said:

SCREENSHOT

neakor said:

so this is just an animated textured quad right? or am i missing something?

Way to make it seem like I didn't do anything. :( But pretty much, yes.


hah thats not what i meant  :D

i just wanted to make sure thats the method u r using instead of some fancy glsl thing. but great work! thx  ;)

it looks nice.

Trussell said:

I may be misunderstanding you, but I think I am already doing that...

waterTexture2.setTranslation(tex2.add(new Vector3f(0, 0,.0009f)));


I am adding to the vectors here, tex2 is created beforehand and is the vector3f for waterTexture2's local translation. Is that what you mean?


but every update cycle two new Vector3f are created, thats unnecessary work for the garbage collector.

you could do:

 tex2.z += 0.09 *tpf;


tex2 is always the same reference to the Textures Vector3f.
multiply by tpf so that the water always moves with the same speed, regardless of the framerate.

Dang core-dump! I don't type fast enough.  XD

me neither s



the add operation creates a new vec3 as well so he's creating four vec3's every frame… you could also make use of addLocal…

Thank you guys for all of the suggestions! I have made the modifications and have edited both the download and the code on the first post.



By the way, Core-Dump, it's creepy that I had made the multiply by TPF changes on my local machine but not posted it on the forum, and we used the exact same numbers  :smiley:

After all of this time and research I can't even make it work in Fyrestone! Just shows a big white quad and messes with my FengGUI… :frowning:

This is probably the first time you change texture coordinates in your project, and now FengGUI inherits them and the Menu looks messy right ?



Before drawing FengGUI, you need to reset any Texture coordinat changes and Texture Scale.



As a workaround you can try something like that, thats the render method of a FengGUI GameState:

Note that it is horribly performance wise, its just a workaround.

    @Override
    public void render(float tpf) {
        // set a default TextureState, tis is needed to not let FengGUI inherit
        // wrong Texture coordinates and stuff.
        Texture defTex = TextureState.getDefaultTexture().createSimpleClone();
        defTex.setScale(new Vector3f(1, 1, 1));
        TextureState defaultTextureState = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
        defaultTextureState.setTexture(defTex);
        defaultTextureState.apply();
 
        // render the GUI
        disp.display();
    }




edit:
i've updated the FengGUI example in the wiki with a better example:
http://www.jmonkeyengine.com/wiki/doku.php?id=fenggui_jme_appl_using_standardgame

The default texturestate is created in the FengGUI constructor, and just applied before rendering FengGUI.
But there seems to be still a problem when using more than 1 Texture unit, which i couldn't fix yet.

Very very good Core-Dump. Thank you very much, your fix worked like a charm.

I've deleted the old project, and now I can't even get the Quad to show up! My character starts at 1000,1000 in World coordinates, so he should see this Quad.


waterPass = new RenderPass();
       
        waterNode = new Node("water");
        waterQuad = new Quad("water", 10240, 10240);
       
        waterQuad.setLocalTranslation(new Vector3f(0, 10, 0));
       
        Quaternion rotQuad = new Quaternion();
        rotQuad = rotQuad.fromAngleNormalAxis(FastMath.PI * .5f, Vector3f.UNIT_X);
        waterQuad.setLocalRotation(rotQuad);
       
        waterQuad.updateRenderState();
        waterQuad.updateGeometricState(tpf, true);
       
        waterNode.attachChild(waterQuad);
       
        waterPass.add(waterNode);
       
        pManager.add(waterPass);



Yet for some reason the Quad doesn't ever appear.

EDIT: Fixed it, apparently you have to put it before the terrain in the Passes and also set it's render queue to transparent.

By the way, just a note for anybody using this method. If you're looking for the "twinkling" that I was looking for, you may want to consider rendering two quads. I find having two quads shows the textures gliding over each other much better, making the desired twinkle effect.

now i have some ingame shots with the water,  the water works fine.

it fits to my lowpoly models  :D  , it reminds me of HalfLige(1) water gg





http://img185.imageshack.us/my.php?image=testwaterny2.jpg



now I will have to implement swimming XD