[Solved] Specular lighting in TestNormalmap

hey guys.

I applied the shaders from TestNormalmap to my own model and noticed that the specular "spot" ("reflection") is not correct at all.

So I changed the TestNormalmap and used a jme.scene.Sphere for an earth Geometry and applied a diffuse, a normal and a specular map to it. The problem is actually already there. I changed the Testcase like this:

package jmetest.renderer.loader;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import jmetest.renderer.TestMipMaps;

import com.jme.app.SimpleGame;
import com.jme.image.Image;
import com.jme.image.Texture;
import com.jme.input.FirstPersonHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.SharedMesh;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.Spatial.LightCombineMode;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.shape.Torus;
import com.jme.scene.state.GLSLShaderObjectsState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.resource.ResourceLocatorTool;
import com.jme.util.resource.SimpleResourceLocator;
import com.jmex.model.collada.ColladaImporter;

 * TestNormalmap
public class TestNormalmap extends SimpleGame {
    private static final Logger logger = Logger.getLogger(TestNormalmap.class.getName());

    private Vector3f lightDir = new Vector3f();
    private GLSLShaderObjectsState so;
    private String currentShaderStr = "jmetest/data/images/normalmap";

    private Sphere lightSphere;

    public static void main(String[] args) {
        TestNormalmap app = new TestNormalmap();

    protected void simpleUpdate() {
        if (KeyBindingManager.getKeyBindingManager().isValidCommand("reloadShader", false)) {

        float spinValX = FastMath.sin(timer.getTimeInSeconds() * 2.0f);
        float spinValY = FastMath.cos(timer.getTimeInSeconds() * 2.0f);
        lightDir.set(spinValX, spinValY, -1.0f).normalizeLocal();

    public void reloadShader() {
        GLSLShaderObjectsState testShader = DisplaySystem.getDisplaySystem().getRenderer()
        try {
            testShader.load(TestColladaLoading.class.getClassLoader().getResource(currentShaderStr + ".vert"),
                    TestColladaLoading.class.getClassLoader().getResource(currentShaderStr + ".frag"));
        } catch (JmeException e) {
            logger.log(Level.WARNING, "Failed to reload shader", e);

        so.load(TestColladaLoading.class.getClassLoader().getResource(currentShaderStr + ".vert"),
                TestColladaLoading.class.getClassLoader().getResource(currentShaderStr + ".frag"));

        so.setUniform("baseMap", 0);
        so.setUniform("normalMap", 1);
        so.setUniform("specularMap", 2);

        logger.info("Shader reloaded...");

    protected void simpleInitGame() {
        KeyBindingManager.getKeyBindingManager().set("reloadShader", KeyInput.KEY_F);

        // Our model is Z up so orient the camera properly.
        cam.setAxes(new Vector3f(-1, 0, 0), new Vector3f(0, 0, 1), new Vector3f(0, 1, 0));
        cam.setLocation(new Vector3f(0, -100, 0));

        // Create a directional light
        DirectionalLight dr = new DirectionalLight();
        dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        dr.setAmbient(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f));
        dr.setSpecular(new ColorRGBA(0.7f, 0.7f, 0.7f, 1.0f));


        so = display.getRenderer().createGLSLShaderObjectsState();

        // Check is GLSL is supported on current hardware.
        if (!GLSLShaderObjectsState.isSupported()) {
            logger.severe("Your graphics card does not support GLSL programs, and thus cannot run this test.");


        TextureState ts = display.getRenderer().createTextureState();

        // Base texture
        Texture baseMap = TextureManager.loadTexture(TestNormalmap.class
                Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
        ts.setTexture(baseMap, 0);

        // Normal map
        Texture normalMap = TextureManager.loadTexture(TestNormalmap.class
                Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear,
                Image.Format.GuessNoCompression, 0.0f, true);
        ts.setTexture(normalMap, 1);

        // Specular map
        Texture specMap = TextureManager.loadTexture(TestNormalmap.class
                Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
        ts.setTexture(specMap, 2);
        try {
            ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, new SimpleResourceLocator(
        } catch (URISyntaxException e1) {
            logger.warning("Unable to add texture directory to RLT: " + e1.toString());

        // this stream points to the model itself.
        InputStream modelStream = TestColladaLoading.class.getClassLoader().getResourceAsStream(

        if (modelStream == null) {
            logger.info("Unable to find file, did you include jme-test.jar in classpath?");
        Sphere model = new Sphere("earth", new Vector3f(0, 0, 0), 100, 100, 20.0f, true);
//        Torus model = new Torus("earth", 50, 50, 5, 10);
        lightSphere = new Sphere("earth", new Vector3f(0, 0, 0), 10, 10, 1.0f, true);

        // Test materialstate (should be set through the import anyway)
        MaterialState ms = display.getRenderer().createMaterialState();
        ms.setAmbient(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        ms.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        ms.setSpecular(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));

        // Set all states on model



        rootNode.updateGeometricState(0, true);

        input = new FirstPersonHandler(cam, 80, 1);

And the unwanted result looks like seen in the image attached with my earth textures. As you can see I added a sphere for where the DirectionalLight comes from and you can notice that the specular spot is somewhere it simply cannot be. (Actually it is being drawn (or sucked in) to that point and then it comes out of it again). Start the test above with the original textures of this test, you will know what i mean.

also the shader has a bug (hardcoded texture scaling in the shader), heres the fix:

Index: src/jmetest/data/images/normalmap.vert
--- src/jmetest/data/images/normalmap.vert   (revision 4706)
+++ src/jmetest/data/images/normalmap.vert   (working copy)
@@ -8,7 +8,7 @@
    /* Transform vertices and pass on texture coordinates */
    gl_Position = ftransform();
-   gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0 * vec4(4.0, 4.0, 1.0, 1.0);
+   gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
    /* Transform vertex into viewspace */
    vec4 vertexViewSpace = gl_ModelViewMatrix * gl_Vertex;

I have compared the shader to several other resources  (including the shader pendant in jme3) and the specular color computation seems completely sound to me. I also tried it with reflect() and the result is the same. Furthermore, I tried this with oder Geometries (like Torus) and they all have a spot where the specular seems to get sucked in to.

The post above is with directional lights. With point lights and a sphere with normal normals  :wink: look weird too:

The specular highlight is just not correct for a perfect sphere. It should be round.

I'm with you dhdd.  Shininess/specularity, in jME 2 at least, models reality poorly and the settings and effects are non-intuitive IMO.  I'm glad you're digging into it.  I'm afraid to because I cant afford to get sucked into a work hole for a month.

I found the solution, I am whipping up a whole new testcase with new shaders and I’ll post it in the Contribution Depot.

EDIT: The main reason is that the Tangents are not passed to the shader correctly. The testcase TestNormalmap imports a collada sphere that should have coded the Tangents in the ColorBuffer, but it seems as if it doesnt work. I created a shader for which you pass the tangents as attributes.

another EDIT: done  :smiley: http://www.jmonkeyengine.com/forum/index.php?topic=12334.0