diff --git a/engine/src/core-data/Common/MatDefs/Light/Lighting.frag b/engine/src/core-data/Common/MatDefs/Light/Lighting.frag index 78002358c..2fb50f985 100644 --- a/engine/src/core-data/Common/MatDefs/Light/Lighting.frag +++ b/engine/src/core-data/Common/MatDefs/Light/Lighting.frag @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/Parallax.glsllib" #import "Common/ShaderLib/Optics.glsllib" #define ATTENUATION //#define HQ_ATTENUATION @@ -30,7 +31,10 @@ varying vec3 SpecularSum; #endif #ifdef PARALLAXMAP - uniform sampler2D m_ParallaxMap; + uniform sampler2D m_ParallaxMap; +#endif +#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + uniform float m_ParallaxHeight; #endif #ifdef LIGHTMAP @@ -38,7 +42,7 @@ varying vec3 SpecularSum; #endif #ifdef NORMALMAP - uniform sampler2D m_NormalMap; + uniform sampler2D m_NormalMap; #else varying vec3 vNormal; #endif @@ -132,20 +136,27 @@ vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec3 w void main(){ vec2 newTexCoord; - #if (defined(PARALLAXMAP) || defined(NORMALMAP_PARALLAX)) && !defined(VERTEX_LIGHTING) - float h; - #ifdef PARALLAXMAP - h = texture2D(m_ParallaxMap, texCoord).r; + #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + + #ifdef STEEP_PARALLAX + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + #else + //parallax map is a texture + newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + #endif #else - h = texture2D(m_NormalMap, texCoord).a; + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + #else + //parallax map is a texture + newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + #endif #endif - float heightScale = 0.05; - float heightBias = heightScale * -0.5; - vec3 normView = normalize(vViewDir); - h = (h * heightScale + heightBias) * normView.z; - newTexCoord = texCoord + (h * normView.xy); #else - newTexCoord = texCoord; + newTexCoord = texCoord; #endif #ifdef DIFFUSEMAP diff --git a/engine/src/core-data/Common/MatDefs/Light/Lighting.j3md b/engine/src/core-data/Common/MatDefs/Light/Lighting.j3md index d6fda8927..95042b7c1 100644 --- a/engine/src/core-data/Common/MatDefs/Light/Lighting.j3md +++ b/engine/src/core-data/Common/MatDefs/Light/Lighting.j3md @@ -61,6 +61,15 @@ MaterialDef Phong Lighting { // Parallax/height map Texture2D ParallaxMap + //Set to true is parallax map is stored in the alpha channel of the normal map + Boolean PackedNormalParallax + + //Sets the relief height for parallax mapping + Float ParallaxHeight : 0.05 + + //Set to true to activate Steep Parallax mapping + Boolean SteepParallax + // Texture that specifies alpha values Texture2D AlphaMap @@ -124,6 +133,8 @@ MaterialDef Phong Lighting { NORMALMAP : NormalMap SPECULARMAP : SpecularMap PARALLAXMAP : ParallaxMap + NORMALMAP_PARALLAX : PackedNormalParallax + STEEP_PARALLAX : SteepParallax ALPHAMAP : AlphaMap COLORRAMP : ColorRamp LIGHTMAP : LightMap diff --git a/engine/src/core-data/Common/MatDefs/Water/Textures/gradient_map.jpg b/engine/src/core-data/Common/MatDefs/Water/Textures/gradient_map.jpg deleted file mode 100644 index d114d5f97..000000000 Binary files a/engine/src/core-data/Common/MatDefs/Water/Textures/gradient_map.jpg and /dev/null differ diff --git a/engine/src/core-data/Common/ShaderLib/Parallax.glsllib b/engine/src/core-data/Common/ShaderLib/Parallax.glsllib new file mode 100644 index 000000000..2eacbde44 --- /dev/null +++ b/engine/src/core-data/Common/ShaderLib/Parallax.glsllib @@ -0,0 +1,78 @@ +#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + vec2 steepParallaxOffset(sampler2D parallaxMap, vec3 vViewDir,vec2 texCoord,float parallaxScale){ + vec2 vParallaxDirection = normalize( vViewDir.xy ); + + // The length of this vector determines the furthest amount of displacement: (Ati's comment) + float fLength = length( vViewDir ); + float fParallaxLength = sqrt( fLength * fLength - vViewDir.z * vViewDir.z ) / vViewDir.z; + + // Compute the actual reverse parallax displacement vector: (Ati's comment) + vec2 vParallaxOffsetTS = vParallaxDirection * fParallaxLength; + + // Need to scale the amount of displacement to account for different height ranges + // in height maps. This is controlled by an artist-editable parameter: (Ati's comment) + parallaxScale *=0.3; + vParallaxOffsetTS *= parallaxScale; + + vec3 eyeDir = normalize(vViewDir).xyz; + + float nMinSamples = 6; + float nMaxSamples = 1000 * parallaxScale; + float nNumSamples = mix( nMinSamples, nMaxSamples, 1.0 - eyeDir.z ); //In reference shader: int nNumSamples = (int)(lerp( nMinSamples, nMaxSamples, dot( eyeDirWS, N ) )); + float fStepSize = 1.0 / nNumSamples; + float fCurrHeight = 0.0; + float fPrevHeight = 1.0; + float fNextHeight = 0.0; + float nStepIndex = 0; + vec2 vTexOffsetPerStep = fStepSize * vParallaxOffsetTS; + vec2 vTexCurrentOffset = texCoord; + float fCurrentBound = 1.0; + float fParallaxAmount = 0.0; + + while ( nStepIndex < nNumSamples && fCurrHeight <= fCurrentBound ) { + vTexCurrentOffset -= vTexOffsetPerStep; + fPrevHeight = fCurrHeight; + + + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + fCurrHeight = texture2DLod( parallaxMap, vTexCurrentOffset,1.0).a; + #else + //parallax map is a texture + fCurrHeight = texture2DLod( parallaxMap, vTexCurrentOffset,1.0).r; + #endif + + fCurrentBound -= fStepSize; + nStepIndex+=1.0; + } + vec2 pt1 = vec2( fCurrentBound, fCurrHeight ); + vec2 pt2 = vec2( fCurrentBound + fStepSize, fPrevHeight ); + + float fDelta2 = pt2.x - pt2.y; + float fDelta1 = pt1.x - pt1.y; + + float fDenominator = fDelta2 - fDelta1; + + fParallaxAmount = (pt1.x * fDelta2 - pt2.x * fDelta1 ) / fDenominator; + + vec2 vParallaxOffset = vParallaxOffsetTS * (1.0 - fParallaxAmount ); + return texCoord - vParallaxOffset; + } + + vec2 classicParallaxOffset(sampler2D parallaxMap, vec3 vViewDir,vec2 texCoord,float parallaxScale){ + float h; + h = texture2D(parallaxMap, texCoord).a; + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + h = texture2D(parallaxMap, texCoord).a; + #else + //parallax map is a texture + h = texture2D(parallaxMap, texCoord).r; + #endif + float heightScale = parallaxScale; + float heightBias = heightScale* -0.6; + vec3 normView = normalize(vViewDir); + h = (h * heightScale + heightBias) * normView.z; + return texCoord + (h * normView.xy); + } +#endif \ No newline at end of file diff --git a/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall2.j3m b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall2.j3m new file mode 100644 index 000000000..7db3ab893 --- /dev/null +++ b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall2.j3m @@ -0,0 +1,8 @@ +Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { + MaterialParameters { + Shininess: 2.0 + DiffuseMap : Textures/Terrain/BrickWall/BrickWall.jpg + NormalMap : Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds + PackedNormalParallax: true + } +} \ No newline at end of file diff --git a/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal.jpg b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal.jpg index 80ce82efb..249d553c5 100644 Binary files a/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal.jpg and b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal.jpg differ diff --git a/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds new file mode 100644 index 000000000..fd93003dd Binary files /dev/null and b/engine/src/test-data/Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds differ diff --git a/engine/src/test/jme3test/material/TestParallax.java b/engine/src/test/jme3test/material/TestParallax.java new file mode 100644 index 000000000..2f5cad5ef --- /dev/null +++ b/engine/src/test/jme3test/material/TestParallax.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2009-2010 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 jme3test.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +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.post.FilterPostProcessor; +import com.jme3.post.filters.FXAAFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; + +public class TestParallax extends SimpleApplication { + + private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + + public static void main(String[] args) { + TestParallax app = new TestParallax(); + app.start(); + } + + public void setupSkyBox() { + rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false)); + } + DirectionalLight dl; + + public void setupLighting() { + + dl = new DirectionalLight(); + dl.setDirection(lightDir); + dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1)); + rootNode.addLight(dl); + } + Material mat; + + public void setupFloor() { + mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall2.j3m"); + mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); + mat.setFloat("Shininess", 0); + + Node floorGeom = (Node) assetManager.loadAsset("Models/WaterTest/WaterTest.mesh.xml"); + Geometry g = ((Geometry) floorGeom.getChild(0)); + g.getMesh().scaleTextureCoordinates(new Vector2f(10, 10)); + TangentBinormalGenerator.generate(floorGeom); + floorGeom.setLocalTranslation(0, 22, 0); + floorGeom.setLocalScale(100); + + floorGeom.setMaterial(mat); + rootNode.attachChild(floorGeom); + } + + public void setupSignpost() { + Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); + Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + TangentBinormalGenerator.generate(signpost); + signpost.setMaterial(mat); + signpost.rotate(0, FastMath.HALF_PI, 0); + signpost.setLocalTranslation(12, 23.5f, 30); + signpost.setLocalScale(4); + signpost.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(signpost); + } + + @Override + public void simpleInitApp() { + cam.setLocation(new Vector3f(-15.445636f, 30.162927f, 60.252777f)); + cam.setRotation(new Quaternion(0.05173137f, 0.92363626f, -0.13454558f, 0.35513034f)); + + flyCam.setMoveSpeed(30); + + + setupLighting(); + setupSkyBox(); + setupFloor(); + setupSignpost(); + + inputManager.addListener(new AnalogListener() { + + public void onAnalog(String name, float value, float tpf) { + if ("heightUP".equals(name)) { + parallaxHeigh += 0.0001; + mat.setFloat("ParallaxHeight", parallaxHeigh); + } + if ("heightDown".equals(name)) { + parallaxHeigh -= 0.0001; + parallaxHeigh = Math.max(parallaxHeigh, 0); + mat.setFloat("ParallaxHeight", parallaxHeigh); + } + + } + }, "heightUP", "heightDown"); + inputManager.addMapping("heightUP", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addMapping("heightDown", new KeyTrigger(KeyInput.KEY_K)); + + inputManager.addListener(new ActionListener() { + + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed && "toggleSteep".equals(name)) { + steep = !steep; + mat.setBoolean("SteepParallax", steep); + } + } + }, "toggleSteep"); + inputManager.addMapping("toggleSteep", new KeyTrigger(KeyInput.KEY_SPACE)); + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + FXAAFilter fxaa = new FXAAFilter(); + fxaa.setReduceMul(0.08f); + fpp.addFilter(fxaa); + viewPort.addProcessor(fpp); + } + float parallaxHeigh = 0.05f; + float time = 0; + boolean steep = false; + + @Override + public void simpleUpdate(float tpf) { +// time+=tpf; +// lightDir.set(FastMath.sin(time), -1, FastMath.cos(time)); +// bsr.setDirection(lightDir); +// dl.setDirection(lightDir); + } +}