Parallax mapping and directional light

Hy there,



it seems that the parallax-mapped wall does not 'react' with a directional light.



I extended the TestFragmentProgramState class a bit so the light-states can be switched with KEY_1.

There is also a cube as a reference besides it, where the light-switches seem to work correctly.



If you zoom in and out after switching to DirectionalLight you can see another strange effect (The last two PointLights still light the parallaxed texture when zooming out, when zooming in I don't know exaclty what I see … a single light, but somehow at the wrong position).



The current LightState-Type LSType, which containing two types (TwoCirclingLights / OneDirectionalLight) is logged as an info. 

   

Either my switching is not correctly implemented, or the parallax mapped wall does not behave correctly with DirectionalLight.





My questions are therefore:


  • Is it my state-switching implementation (see toggleLightState()) which causes that problem ?
  • Is DirectionalLight "supported" ?



    Thank you







/*
 * Copyright (c) 2003-2009 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jmetest.renderer.state;

import java.nio.FloatBuffer;
import java.util.logging.Logger;

import com.jme.app.SimpleGame;
import com.jme.image.Texture;
import com.jme.light.PointLight;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Spatial;
import com.jme.scene.TexCoords;
import com.jme.scene.shape.Quad;
import com.jme.scene.shape.Box;
import com.jme.scene.state.*;
import com.jme.util.TextureManager;
import com.jme.util.geom.BufferUtils;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.bounding.BoundingBox;


/**
 * Demonstrates the use of the GL_ARB_fragment_program extension in jME. Uses a parallax
 * mapping technique outlined in the paper "Parallax Mapping with Offset Limiting:
 * A PerPixel Approximation of Uneven Surfaces".
 *
 * @author Eric Woroshow
 * @version $Id: TestFragmentProgramState.java 4130 2009-03-19 20:04:51Z blaine.dev $
 * @modified by maxx_981 for testing a light-state switch ... which fails.
 * -> it seems that the parallax-mapped wall does not react on the directional light. The light-states can be
 *    switched with KEY_1. If you zoom in and out after switching to DirectionalLight you can see another strange effect.
 *
 *    Either my switching is not correctly implemented, or the parallax mapped wall does
 *    not behave correctly with DirectionalLight. Is it supposed to ?
 *
 */
public class TestFragmentProgramStateSwitchLightState extends SimpleGame
{
    private static final Logger logger = Logger.getLogger(TestFragmentProgramStateSwitchLightState.class.getName());

    private final static String BRICK_TEX = "jmetest/data/images/rockwall2.jpg";
    private final static String BRICK_HEIGHT = "jmetest/data/images/rockwall_height2.jpg";
    private final static String BRICK_NRML = "jmetest/data/images/rockwall_normal2.jpg";
    private final static String BRICK_VP = "jmetest/data/images/bump_parallax.vp";
    private final static String BRICK_FP = "jmetest/data/images/bump_parallax.fp";


    protected LightState[] lightStates = new LightState[LSType.values().length]; //holds the lightStates
    private LSType currentLightState = LSType.OneDirectionalLight; // the currently active LightState, we start with two circling lights...

    private long lastSwitchTime = 0;
    private long minSwitchTimeMilllis = 1000;

    /**
     * The LightState Type
     */
    public static enum LSType
    {
        TwoCirclingLights,
        OneDirectionalLight;

        private LightState lightState;

        public void setLightState(LightState lightState)
        {
            this.lightState = lightState;
        }

        public LightState getLightState()
        {
            return lightState;
        }

        public LSType getNextLightStateType()
        {
            if (this.ordinal() + 1 >= LSType.values().length)
            {
                return values()[0];
            }
            else
            {
                return values()[this.ordinal() + 1];
            }
        }
    }


    /**
     * Light positioning
     */
    private float angle0 = 0.0f, angle1 = 0.0f;

    /**
     * Entry point for the test.
     *
     * @param args command line arguments; unused
     */
    public static void main(String[] args)
    {
        TestFragmentProgramStateSwitchLightState app = new TestFragmentProgramStateSwitchLightState();
        app.setConfigShowMode(ConfigShowMode.AlwaysShow);
        app.start();
    }

    private void initLightStates()
    {
       {
            // initialize directional light
            DirectionalLight dr = new DirectionalLight();
            dr.setAmbient(new ColorRGBA(0.8f, 0.8f, 0.8f, 1));
            dr.setDiffuse(new ColorRGBA(1f, 1f, 1f, 1));
            dr.setEnabled(true);
            dr.setDirection(new Vector3f(-1, -1, -1));

            LightState lightState = display.getRenderer().createLightState();
            lightState.detachAll();
            lightState.setEnabled(true);
            lightState.attach(dr);
            lightState.setGlobalAmbient(new ColorRGBA(0, 0, 0, 1));

            LSType.OneDirectionalLight.setLightState(lightState);
        }

        {
            // initialize moving lights
            //Set up two lights in the scene
            PointLight light0 = new PointLight();
            light0.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
            light0.setLocation(new Vector3f(2f, 4f, 1f));
            light0.setEnabled(true);

            PointLight light1 = new PointLight();
            light1.setDiffuse(new ColorRGBA(1.0f, 0.5f, 0.0f, 1.0f));
            light1.setLocation(new Vector3f(2f, 2f, 1f));
            light1.setEnabled(true);


            LightState lightState = display.getRenderer().createLightState();

            lightState.detachAll();
            lightState.setEnabled(true);
            lightState.attach(light0);
            lightState.attach(light1);
            lightState.setGlobalAmbient(new ColorRGBA(0, 0, 0, 1));

            LSType.TwoCirclingLights.setLightState(lightState);
        }

        toggleLightState();
    }

    private void toggleLightState()
    {
        // disable current state
        currentLightState.getLightState().setEnabled(false);
        // get the new state
        currentLightState = currentLightState.getNextLightStateType();
        // enable the state
        currentLightState.getLightState().setEnabled(true);
        // set the state on the root node
        rootNode.setRenderState(currentLightState.getLightState());
        // update the render state
        rootNode.updateRenderState();

        logger.info("Switching to Light State : " + currentLightState);
    }

    protected void simpleInitGame()
    {
        //Set up cull state
        CullState cs = display.getRenderer().createCullState();
        cs.setCullFace(CullState.Face.Back);
        cs.setEnabled(true);

        //Basic brick texture
        TextureState brick = display.getRenderer().createTextureState();

        Texture tex = TextureManager.loadTexture(TestFragmentProgramStateSwitchLightState.class.getClassLoader().getResource(BRICK_TEX), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
        tex.setWrap(Texture.WrapMode.Repeat);

        //Height map of the brick wall      
        Texture height = TextureManager.loadTexture(TestFragmentProgramStateSwitchLightState.class.getClassLoader().getResource(BRICK_HEIGHT), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
        height.setWrap(Texture.WrapMode.Repeat);

        //Normal map of the brick wall
        Texture normal = TextureManager.loadTexture(TestFragmentProgramStateSwitchLightState.class.getClassLoader().getResource(BRICK_NRML), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
        normal.setWrap(Texture.WrapMode.Repeat);

        brick.setTexture(tex, 0);
        brick.setTexture(normal, 1);
        brick.setTexture(height, 2);

        brick.setEnabled(true);

        VertexProgramState vert = display.getRenderer().createVertexProgramState();
        FragmentProgramState frag = display.getRenderer().createFragmentProgramState();
        //Ensure the extensions are supported, else exit immediately
        if (!vert.isSupported() || !frag.isSupported())
        {
            logger.severe("Your graphics card does not support vertex or fragment programs, and thus cannot run this test.");
            quit();
        }

        //Load vertex program
        vert.load(TestFragmentProgramStateSwitchLightState.class.getClassLoader().getResource(BRICK_VP));
        vert.setEnabled(true);

        //Load fragment program      
        frag.load(TestFragmentProgramStateSwitchLightState.class.getClassLoader().getResource(BRICK_FP));
        frag.setEnabled(true);

        Quad q = new Quad("wall", 10f, 10f);

        //Set up textures
        q.setRenderState(brick);

        FloatBuffer tex1 = BufferUtils.createVector2Buffer(4);
        for (int x = 0; x < 4; x++)
        {
            tex1.put(1.0f).put(0.0f);
        }
        q.setTextureCoords(new TexCoords(tex1), 1);

        FloatBuffer tex2 = BufferUtils.createVector2Buffer(4);
        for (int x = 0; x < 4; x++)
        {
            tex2.put(0.0f).put(1.0f);
        }
        q.setTextureCoords(new TexCoords(tex2), 2);

        //Set up ARB programs
        q.setRenderState(vert);
        q.setRenderState(frag);

        q.setRenderState(cs);

        // initialize all the light-states
        initLightStates();

        // create a reference box to see the effect of the light
        createReferenceBox();

        initKeyBindings();

        rootNode.attachChild(q);
        rootNode.setCullHint(Spatial.CullHint.Never);
    }

    public void createReferenceBox()
    {
        // I create a box and attach it too my lightnode.  This lets me see where my light is
        Box b = new Box("Reference", new Vector3f(10f, 0f, 0f), 3f, 3f, 3f);
        // Give the box bounds
        b.setModelBound(new BoundingBox());
        b.setRandomColors();
        b.updateModelBound();

        rootNode.attachChild(b);
    }


    private void initKeyBindings()
    {
        KeyBindingManager manager = KeyBindingManager.getKeyBindingManager();
        manager.set("toggleLight", KeyInput.KEY_1);
    }

    protected void simpleUpdate()
    {
        KeyBindingManager manager = KeyBindingManager.getKeyBindingManager();
        long curTime = System.currentTimeMillis();
        if (manager.isValidCommand("toggleLight") && curTime - lastSwitchTime > minSwitchTimeMilllis)
        {
            toggleLightState();
            lastSwitchTime = curTime;
        }

        switch (currentLightState)
        {
            case OneDirectionalLight:
            {
                //Nothing to do  ...
                break;
            }

            case TwoCirclingLights:
            {
                angle0 += 2 * tpf;
                angle1 += 4 * tpf;

                ((PointLight) currentLightState.getLightState().get(0)).setLocation(new Vector3f(2.0f * FastMath.cos(angle0), 2.0f * FastMath.sin(angle0), 1.5f));
                ((PointLight) currentLightState.getLightState().get(1)).setLocation(new Vector3f(2.0f * FastMath.cos(angle1), 2.0f * FastMath.sin(angle1), 1.5f));
                break;
            }
        }
    }
}

- Is DirectionalLight "supported" ?

This is probably the problem. The parallax shader included with jME2 is hardcoded to use 2 point lights- anything that's different might not work.  :|
The main issue is that jME2 was not designed for heavy shader use, you must manage your own shaders to make them useful. If you would like to take advantage of bump/parallax mapping, consider using jME3 which was built from bottom with shaders in mind.  :)

Hmm, I see.



What is the status of jme3 ? As far as I can see there is no ogre-stuff in there, right ?

Or is jme3 more of an extension right now ?



For my project I need the ogre-xml importer and mesh-animation capabilities, also I need physics.

Is this all possible yet with jme3 ?



... you must manage your own shaders to make them useful.


What exactly do you mean with managing them myself/creating my own shaders ?
Do you mean writing more of those programs (vertex/fragment) and enable/disable them accordingly ?
maxx_981 said:

For my project I need the ogre-xml importer and mesh-animation capabilities, also I need physics.
Is this all possible yet with jme3 ?

It is not possible yet, but it definitely will be. If you're up for it, you can port the ogre-xml importer to jME3.
If you need these features now, you will have to give up on shaders since jME2 is not really made to use them  :|
Of course if your scene always has 1 or 2 point lights then the parallax shader should work fine in most cases.

maxx_981 said:


... you must manage your own shaders to make them useful.

What exactly do you mean with managing them myself/creating my own shaders ?
Do you mean writing more of those programs (vertex/fragment) and enable/disable them accordingly ?

Shaders make many things possible, but also much more difficult. In jME3 for example, there's an entire framework dedicated to shaders. You need to generate them, make sure they fit all the requirements imposed by the application, etc. It's not an easy task to fully integrate shaders into your game.

It is not possible yet, but it definitely will be. If you're up for it, you can port the ogre-xml importer to jME3.


Not a bad idea ;) But I think as a beginner in graphics I will stick to working examples for now :)


Of course if your scene always has 1 or 2 point lights then the parallax shader should work fine in most cases.


Huh, I don't know ... seems a bit limiting :)

I will wait for jme3, or at least the branch you are maintaining, to get more evolved. Seems that I have to get into shaders then extensively.

Till then I'll use my Ogre dot3Bump-Mapping loader implementation.

I just noticed that my dot3Bump-mapped object is not really good reacting on diffuse PointLights, for example a light with: light.setDiffuse(new ColorRGBA(1.0f, 0.5f, 0.0f, 1.0f)).

Has that a reason ?

It just seems that the PointLights do not have enough 'power' to enlight the dot3Bump-mapped surface. I can see some light reflecting, but really, really weak. So the object nearly stays black. A directional light seems to work fine, also a PointLight with diffuse settings set to (1 1 1 1). 

When I used the same object and the parallax-mapping (the ARB static coded one), also the diffuse PointLights really enlightened the object.

Thank you for the answers so far ...