Projective Texture Mapping

Hi H,



that’s a cool feature and there’s no reason why this should not be possible. Go ahead and implement it! I’m excited about seeing this in action.



Regards

Cool code! I want to try using this now! :smiley:

Hi @survivor,



I’m working on the projective animated texture mapping. I’ve done fine so far :slight_smile: but I’m having a issue with the texture :cry:. I don’t know how to get rid of the sections of the texture that are not to be shown. The following video shows the issue:



http://www.youtube.com/watch?v=gfQYVJlaEF4



Help will be greatly appreciate!! :D

ProjectiveAnimatedTextureMapping.vert
[java]
attribute vec3 inPosition;
attribute vec3 inNormal;

uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldMatrix;
uniform mat4 m_ProjectorViewProjectionMatrix;
uniform float m_Time;

#ifdef IS_PARALLEL_PROJECTION
uniform vec3 m_ProjectorDirection;
#else
uniform vec3 m_ProjectorLocation;
#endif

// simple sprite
uniform int m_numTilesU;
uniform int m_numTilesV;
uniform int m_Speed;

varying vec4 projCoord;
varying float cosAngle;
// simple sprite
varying vec2 texCoordAni;


const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0);

void main()
{
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0);

projCoord = biasMat * m_ProjectorViewProjectionMatrix * worldPos;

#ifdef IS_PARALLEL_PROJECTION
cosAngle = dot(inNormal, -m_ProjectorDirection);
#else
cosAngle = dot(inNormal, normalize(m_ProjectorLocation - inPosition));
#endif

int selectedTile = 1;
selectedTile = int(mod(m_Time * m_Speed, m_numTilesU));

projCoord.x = (1.0 - ((projCoord.x + mod((selectedTile), (m_numTilesU))) / m_numTilesU));
projCoord.y = ((projCoord.y - (selectedTile / m_numTilesU)) / m_numTilesV);

}
[/java]

ProjectiveAnimatedTextureMapping.frag (it is the same as ProjectiveTextureMapping.frag)
[java]
varying vec4 projCoord;
varying float cosAngle;

uniform sampler2D m_ProjectiveMap;

#ifdef FALL_OFF
uniform float m_FallOffDistance;
uniform float m_FallOffPower;
#endif

const float SOFTNESS = 0.1;
const float SOFTNESS_INV = 1.0 / SOFTNESS;

void main()
{
gl_FragColor = vec4(0.0);

if (projCoord.w > 0.0)
{
if (cosAngle > 0.0)
{
vec4 projColor = texture2DProj(m_ProjectiveMap, projCoord);

if (cosAngle < SOFTNESS)
{
projColor.a *= cosAngle * SOFTNESS_INV;
}

#ifdef FALL_OFF
if (projCoord.w > m_FallOffDistance)
{
float maxDist = m_FallOffDistance + 1.0;
projColor.a *= clamp(pow(maxDist - projCoord.w, m_FallOffPower), 0.0, 1.0);
}
#endif

gl_FragColor = projColor;
}
}
}
[/java]

ProjectiveAnimatedTextureMapping.j3md
[java]
MaterialDef Projective Texture Mapping {

MaterialParameters {
Texture2D ProjectiveMap
Matrix4 ProjectorViewProjectionMatrix
Vector3 ProjectorLocation
Vector3 ProjectorDirection
Float FallOffDistance
Float FallOffPower

// Number of Tiles U and V
Int numTilesU
Int numTilesV
Int Speed
Float Time
}

Technique {
VertexShader GLSL100: Common/MatDefs/Misc/ProjectiveAnimatedTextureMapping.vert
FragmentShader GLSL100: Common/MatDefs/Misc/ProjectiveAnimatedTextureMapping.frag

WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
}

RenderState {
Blend Alpha
}

Defines {
IS_PARALLEL_PROJECTION : ProjectorDirection
FALL_OFF : FallOffDistance
}
}
}
[/java]

AnimatedTextureProjectorRenderer.java
[java]
package com.jme3.post;

import java.util.ArrayList;
import java.util.List;

import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.system.Timer;
import com.jme3.texture.FrameBuffer;

public class AnimatedTextureProjectorRenderer implements SceneProcessor {

private RenderManager renderManager;
private ViewPort viewPort;
private final Material textureMat;
private final ArrayList<TextureProjector> textureProjectors;
private final Timer timer;

public AnimatedTextureProjectorRenderer(final AssetManager assetManager, final Timer timer, final int numTilesU,
final int numTilesV, final int speed) {
this(assetManager, timer, numTilesU, numTilesV, speed, null);
}

public AnimatedTextureProjectorRenderer(final AssetManager assetManager, final Timer timer, final int numTilesU,
final int numTilesV, final int speed, final ColorRGBA color) {
this.textureMat = new Material(assetManager, "Common/MatDefs/Misc/ProjectiveAnimatedTextureMapping.j3md");
this.textureMat.setInt("numTilesU", numTilesU);
this.textureMat.setInt("numTilesV", numTilesV);
this.textureMat.setInt("Speed", speed);
this.textureProjectors = new ArrayList<TextureProjector>();
this.renderManager = null;
this.viewPort = null;
this.timer = timer;
setPolyOffset(-0.1f, -0.1f);
}

public List<TextureProjector> getTextureProjectors() {
return this.textureProjectors;
}

public final void setPolyOffset(final float factor, final float units) {
this.textureMat.getAdditionalRenderState().setPolyOffset(factor, units);
}

@Override
public void initialize(final RenderManager rm, final ViewPort vp) {
this.renderManager = rm;
this.viewPort = vp;
}

@Override
public boolean isInitialized() {
return this.viewPort != null;
}

@Override
public void preFrame(final float tpf) {
}

@Override
public void postQueue(final RenderQueue rq) {
}

@Override
public void postFrame(final FrameBuffer out) {
this.renderManager.setForcedMaterial(this.textureMat);
this.renderManager.getRenderer().setFrameBuffer(out); // ToDo: check if needed

for (final TextureProjector textureProjector : this.textureProjectors) {
final float fallOffDistance = textureProjector.getFallOffDistance();
this.textureMat.setTexture("ProjectiveMap", textureProjector.getProjectiveTexture());
this.textureMat.setMatrix4("ProjectorViewProjectionMatrix",
textureProjector.getProjectorViewProjectionMatrix());

this.textureMat.setFloat("Time", this.timer.getTimeInSeconds());

if (textureProjector.isParallelProjection()) {
this.textureMat.clearParam("ProjectorLocation");
this.textureMat.setVector3("ProjectorDirection", textureProjector.getProjectorDirection());
} else {
this.textureMat.clearParam("ProjectorDirection");
this.textureMat.setVector3("ProjectorLocation", textureProjector.getProjectorLocation());
}

if (fallOffDistance != Float.MAX_VALUE) {
this.textureMat.setFloat("FallOffDistance", textureProjector.getFallOffDistance());
this.textureMat.setFloat("FallOffPower", textureProjector.getFallOffPower());
} else {
this.textureMat.clearParam("FallOffDistance");
this.textureMat.clearParam("FallOffPower");
}

final GeometryList targetGeometryList = textureProjector.getTargetGeometryList();
if (targetGeometryList != null) {
this.renderManager.renderGeometryList(targetGeometryList);
} else {
this.renderManager.renderViewPortRaw(this.viewPort);
}
}

this.renderManager.setForcedMaterial(null);
}

@Override
public void cleanup() {
}

@Override
public void reshape(final ViewPort vp, final int w, final int h) {
}
}
[/java]

Test Case: TestProjectiveTextureMapping.java
[java]
package jme3test.post;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.post.AnimatedTextureProjectorRenderer;
import com.jme3.post.SimpleTextureProjector;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.OpaqueComparator;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.WireFrustum;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;

public class TestProjectiveTextureMapping extends SimpleApplication {
private ProjectorData pd2;
private AnimatedTextureProjectorRenderer aptr;

public static void main(final String[] args) {
final TestProjectiveTextureMapping app = new TestProjectiveTextureMapping();
app.start();
Logger.getLogger("").setLevel(Level.SEVERE);
}

@Override
public void simpleInitApp() {
setPauseOnLostFocus(false);
this.flyCam.setMoveSpeed(3f);

final Material mat = new Material(this.assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setColor("Diffuse", ColorRGBA.LightGray);
mat.setColor("Ambient", ColorRGBA.LightGray);
mat.setBoolean("UseMaterialColors", true);

final Box box1 = new Box(Vector3f.ZERO.clone(), 10f, 0.1f, 10f);
final Geometry geom1 = new Geometry("Box1", box1);
geom1.setMaterial(mat);
this.rootNode.attachChild(geom1);

final Material mat2 = new Material(this.assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat2.setColor("Diffuse", ColorRGBA.Orange);
mat.setColor("Ambient", ColorRGBA.LightGray);
mat2.setBoolean("UseMaterialColors", true);

final Sphere sphere1 = new Sphere(32, 32, 0.5f);
final Geometry geom2 = new Geometry("Sphere1", sphere1);
geom2.setMaterial(mat2);
this.rootNode.attachChild(geom2);

DirectionalLight dl = new DirectionalLight();
dl.setDirection(new Vector3f(-0.1f, -0.7f, 1).normalizeLocal());
dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f));
this.rootNode.addLight(dl);

// skylight
dl = new DirectionalLight();
dl.setDirection(new Vector3f(-0.6f, -1, -0.6f).normalizeLocal());
dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f));
this.rootNode.addLight(dl);

// white ambient light
dl = new DirectionalLight();
dl.setDirection(new Vector3f(1, -0.5f, -0.1f).normalizeLocal());
dl.setColor(new ColorRGBA(0.80f, 0.70f, 0.80f, 1.0f));
this.rootNode.addLight(dl);

// real ambient light
final AmbientLight al = new AmbientLight();
al.setColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f));
this.rootNode.addLight(al);

final float ar = (float) this.settings.getWidth() / (float) this.settings.getHeight();
this.cam.setFrustumPerspective(45, ar, 0.1f, 1000.0f);
this.cam.setLocation(new Vector3f(-1, 3, -1));
this.cam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y.clone());

final Texture2D texture2 = (Texture2D) this.assetManager.loadTexture("Textures/simbolo1 animado.png");

this.pd2 = new ProjectorData();
initProjectorData(this.pd2, new Vector3f(1f, 2.1f, 2f), texture2);

final GeometryList gl = new GeometryList(new OpaqueComparator());
gl.add(geom1);
this.pd2.projector.setTargetGeometryList(gl);
this.pd2.projector.getProjectorCamera().setParallelProjection(true);
this.pd2.projector.getProjectorCamera().setFrustumPerspective(90f, 1f, 1f, 5f);

this.aptr = new AnimatedTextureProjectorRenderer(this.assetManager, this.timer, 6, 1, 20);
this.aptr.getTextureProjectors().add(this.pd2.projector);

Logger.getLogger("").severe(
"NUM_PROJECTORS: " + this.aptr.getTextureProjectors().size() + ", NUM_PASSES: "
+ this.aptr.getTextureProjectors().size());

this.viewPort.addProcessor(this.aptr);
}

private void initProjectorData(final ProjectorData pd, final Vector3f location, final Texture2D texture) {
texture.setMinFilter(Texture.MinFilter.Trilinear);
texture.setMagFilter(Texture.MagFilter.Bilinear);
texture.setAnisotropicFilter(16);
// texture.setWrap(Texture.WrapMode.EdgeClamp);
texture.setWrap(Texture.WrapMode.BorderClamp);

final int textureWidth = texture.getImage().getWidth();
final int textureHeight = texture.getImage().getHeight();
final float textureAspectRatio = (float) textureWidth / (float) textureHeight;

pd.projector = new SimpleTextureProjector(texture);
final Camera projectorCamera = pd.projector.getProjectorCamera();
projectorCamera.setLocation(location);
projectorCamera.lookAt(Vector3f.ZERO.clone(), Vector3f.UNIT_X.clone());
projectorCamera.setFrustumPerspective(45, textureAspectRatio, 1f, 5f);
projectorCamera.setParallelProjection(false);

pd.frustumPoints = new Vector3f[8];
for (int i = 0; i < 8; i++) {
pd.frustumPoints = new Vector3f();
}

pd.frustum = new WireFrustum(pd.frustumPoints);
final Geometry frustumMdl = new Geometry("f", pd.frustum);
frustumMdl.setCullHint(Spatial.CullHint.Never);
frustumMdl.setShadowMode(ShadowMode.Off);
frustumMdl.setMaterial(new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
frustumMdl.getMaterial().setColor("Color", ColorRGBA.White);
this.rootNode.attachChild(frustumMdl);
}

@Override
public void simpleUpdate(final float tpf) {
final float s = FastMath.sin(this.timer.getTimeInSeconds() * 0.8f - FastMath.PI)
* FastMath.sin(this.timer.getTimeInSeconds() * 0.5f - FastMath.PI);
final float t = FastMath.cos(this.timer.getTimeInSeconds() * 0.6f - FastMath.PI)
* FastMath.sin(this.timer.getTimeInSeconds() * 0.3f - FastMath.PI);

this.pd2.projector.getProjectorCamera().lookAtDirection(Vector3f.UNIT_Y.negate(), Vector3f.UNIT_X.clone());
this.pd2.projector.getProjectorCamera().setLocation(new Vector3f(t * 2f, 2.1f, s * 2f));

this.pd2.projector.updateFrustumPoints(this.pd2.frustumPoints);
this.pd2.frustum.update(this.pd2.frustumPoints);
}

private class ProjectorData {
SimpleTextureProjector projector;
WireFrustum frustum;
Vector3f[] frustumPoints;
}
}
[/java]

simbolo1 animado.png
1 Like

Ah, now I understand what you are trying to achieve. I first thought you wanted some kind of lava animation with a seamless texture tile.



Ok, I have taken your code and assimilated it (Locutus would be proud of me :D). You can check out what I have done here (run TestProjectiveAnimatedTextureMapping.java). Basically, I’m discarding fragments with texture coordinates which are out of bounds of the current tile.



Thanks for sharing that idea. This gives great effects. I might combine all the TextureRenderers into one and do some other cleanup when I have time. A nice improvement would be not just switching but blending the tile frames.



Consider using an AccumulationBuffer when doing a lot of blending.

2 Likes

Great!! Thank you very much @survivor !!!

I’ll take a look when I go back to home after my work day ends.

Hi @survivor,

I’ve made some changes. Let me know what you think.



[patch]

Index: src/com/jme3/post/AnimatedTextureProjectorRenderer.java

===================================================================

— src/com/jme3/post/AnimatedTextureProjectorRenderer.java (revision 154)

+++ src/com/jme3/post/AnimatedTextureProjectorRenderer.java (working copy)

@@ -36,7 +36,6 @@



import com.jme3.asset.AssetManager;

import com.jme3.material.Material;

-import com.jme3.math.ColorRGBA;

import com.jme3.renderer.RenderManager;

import com.jme3.renderer.ViewPort;

import com.jme3.renderer.queue.GeometryList;

@@ -57,27 +56,12 @@

private final Material textureMat;

private final ArrayList<TextureProjector> textureProjectors;

private final Timer timer;

  • private int numTilesU;
  • private int numTilesV;
  • private int speed;



    public AnimatedTextureProjectorRenderer(final AssetManager assetManager,
  • final Timer timer, final int numTilesU, final int numTilesV, final int speed)
  • final Timer timer)

    {
  • this(assetManager, timer, numTilesU, numTilesV, speed, null);
  • }

    -
  • public AnimatedTextureProjectorRenderer(final AssetManager assetManager,
  • final Timer timer, final int numTilesU, final int numTilesV, final int speed,
  • final ColorRGBA color)
  • {
  • this.numTilesU = numTilesU;
  • this.numTilesV = numTilesV;
  • this.speed = speed;

    this.timer = timer;

    this.textureMat = new Material(assetManager, "Common/MatDefs/Misc/ProjectiveAnimatedTextureMapping.j3md");
  • this.textureMat.setInt("NumTilesU", numTilesU);
  • this.textureMat.setInt("NumTilesV", numTilesV);

    this.textureMat.setInt("SelectedTileU", 0);

    this.textureMat.setInt("SelectedTileV", 0);

    this.textureProjectors = new ArrayList<TextureProjector>();

    @@ -166,9 +150,11 @@

    textureProjector.getProjectorViewProjectionMatrix());



    //this.textureMat.setFloat("Time", this.timer.getTimeInSeconds());
  •  float now = this.timer.getTimeInSeconds() * this.speed;<br />
    
  •  int selectedTileU = (int) (now % numTilesU);<br />
    
  •  int selectedTileV = (int) (now % numTilesV);<br />
    
  •  this.textureMat.setInt(&quot;NumTilesU&quot;, textureProjector.getNumTilesU());<br />
    
  •  this.textureMat.setInt(&quot;NumTilesV&quot;, textureProjector.getNumTilesV());<br />
    
  •  float now = this.timer.getTimeInSeconds() * textureProjector.getSpeed();<br />
    
  •  int selectedTileU = (int) (now % textureProjector.getNumTilesU());<br />
    
  •  int selectedTileV = (int) (now % textureProjector.getNumTilesV());<br />
    

this.textureMat.setInt("SelectedTileU", selectedTileU);

this.textureMat.setInt("SelectedTileV", selectedTileV);



Index: src/com/jme3/post/SimpleTextureProjector.java

===================================================================

— src/com/jme3/post/SimpleTextureProjector.java (revision 154)

+++ src/com/jme3/post/SimpleTextureProjector.java (working copy)

@@ -51,7 +51,10 @@

private GeometryList targetGeometryList;

private float fallOffDistance;

private float fallOffPower;

  • private HashMap parameters;
  • private HashMap<Object, Object> parameters;
  • private int numTilesU;
  • private int numTilesV;
  • private int speed;



    /**
  • Crates a new instance.

    @@ -61,15 +64,30 @@

    */

    public SimpleTextureProjector(Texture2D projectiveTextureMap)

    {
  • this(projectiveTextureMap, 1, 1, 1);
  • }

    +

    +/**
    • Crates a new instance.
    • @param projectiveTextureMap The texture to be projected.
    • @param numTilesU Number of tiles for the texture on U axis.
    • @param numTilesV Number of tiles for the texture on V axis.
    • @param speed Animation speed.
  • */
  • public SimpleTextureProjector(Texture2D projectiveTextureMap, int numTilesU, int numTilesV, int speed)
  • {

    this.projectiveTextureMap = projectiveTextureMap;

    this.projectorCamera = new Camera(
  •  projectiveTextureMap.getImage().getWidth(),<br />
    
  •  projectiveTextureMap.getImage().getHeight());<br />
    
  • projectiveTextureMap.getImage().getWidth(),
  • projectiveTextureMap.getImage().getHeight());

    this.fallOffDistance = Float.MAX_VALUE;

    this.fallOffPower = 3f;
  • this.parameters = new HashMap();
  • this.parameters = new HashMap<Object, Object>();
  • this.numTilesU = numTilesU > 1 ? numTilesU : 1;
  • this.numTilesV = numTilesV > 1 ? numTilesV : 1;
  • this.speed = speed > 1 ? speed : 1;

    }

    -

    +

    /**
  • Sets the texture to be projected.
  • @param projectiveTextureMap The texture to be projected.

    @@ -79,9 +97,27 @@

    public void setProjectiveTextureMap(Texture2D projectiveTextureMap)

    {

    this.projectiveTextureMap = projectiveTextureMap;
  • this.numTilesU = 1;
  • this.numTilesV = 1;
  • this.speed = 1;

    }



    /**
    • Sets the texture to be projected.
    • @param projectiveTextureMap The texture to be projected.
    • @param numTilesU Number of tiles for the texture on U axis.
    • @param numTilesV Number of tiles for the texture on V axis.
    • @param speed Animation speed.
  • */
  • public void setProjectiveTextureMap(Texture2D projectiveTextureMap, int numTilesU, int numTilesV, int speed)
  • {
  •  this.projectiveTextureMap = projectiveTextureMap;<br />
    
  •  this.numTilesU = numTilesU &gt; 1 ? numTilesU : 1;<br />
    
  •  this.numTilesV = numTilesV &gt; 1 ? numTilesV : 1;<br />
    
  •  this.speed = speed &gt; 1 ? speed : 1;<br />
    
  • }

    +
  • /**
  • @return The location of this TextureProjector.
  • @see TextureProjector

    */

    @@ -172,6 +208,7 @@

    /**
  • @return The distance at which the projection should start to fall off.

    */
  • @Override

    public float getFallOffDistance()

    {

    return this.fallOffDistance;

    @@ -188,6 +225,7 @@

    /**
  • @return Power at which the projection should fall off.

    */
  • @Override

    public float getFallOffPower()

    {

    return this.fallOffPower;

    @@ -197,6 +235,7 @@
  • @param key A custom key.
  • @return A custom parameter value.

    */
  • @Override

    public Object getParameter(Object key)

    {

    return this.parameters.get(key);

    @@ -232,4 +271,28 @@

    points[6].set(projectorCamera.getWorldCoordinates(new Vector2f(w, h), f));

    points[7].set(projectorCamera.getWorldCoordinates(new Vector2f(w, 0), f));

    }

    +
  • /**
    • @see com.jme3.post.TextureProjector#getNumTilesU()
  • */
  • @Override
  • public int getNumTilesU() {
  • return numTilesU;
  • }

    +
  • /**
    • @see com.jme3.post.TextureProjector#getNumTilesV()
  • */
  • @Override
  • public int getNumTilesV() {
  • return numTilesV;
  • }

    +
  • /**
    • @see com.jme3.post.TextureProjector#getSpeed()
  • */
  • @Override
  • public int getSpeed() {
  • return speed;
  • }

    }

    Index: src/com/jme3/post/TextureProjector.java

    ===================================================================

    — src/com/jme3/post/TextureProjector.java (revision 154)

    +++ src/com/jme3/post/TextureProjector.java (working copy)

    @@ -90,4 +90,19 @@
  • @return A custom parameter value.

    */

    public Object getParameter(Object key);

    +
  • /**
    • @return Number of tiles for the texture on U axis.
  • */
  • public int getNumTilesU();

    +
  • /**
    • @return Number of tiles for the texture on V axis.
  • */
  • public int getNumTilesV();

    +
  • /**
    • @return Animation speed.
  • */
  • public int getSpeed();

    }

    Index: src/jme3test/post/TestProjectiveAnimatedTextureMapping.java

    ===================================================================

    — src/jme3test/post/TestProjectiveAnimatedTextureMapping.java (revision 154)

    +++ src/jme3test/post/TestProjectiveAnimatedTextureMapping.java (working copy)

    @@ -126,7 +126,7 @@

    final Texture2D texture2 = (Texture2D) this.assetManager.loadTexture("Textures/simbolo1 animado.png");



    this.pd2 = new ProjectorData();
  • initProjectorData(this.pd2, new Vector3f(1f, 2.1f, 2f), texture2);
  • initProjectorData(this.pd2, new Vector3f(1f, 2.1f, 2f), texture2, 6, 1, 20);



    final GeometryList gl = new GeometryList(new OpaqueComparator());

    gl.add(geom1);

    @@ -134,7 +134,7 @@

    this.pd2.projector.getProjectorCamera().setParallelProjection(true);

    this.pd2.projector.getProjectorCamera().setFrustumPerspective(90f, 1f, 1f, 5f);


  • this.aptr = new AnimatedTextureProjectorRenderer(this.assetManager, this.timer, 6, 1, 20);
  • this.aptr = new AnimatedTextureProjectorRenderer(this.assetManager, this.timer);

    this.aptr.getTextureProjectors().add(this.pd2.projector);



    Logger.getLogger("").severe(

    @@ -144,7 +144,7 @@

    this.viewPort.addProcessor(this.aptr);

    }


  • private void initProjectorData(final ProjectorData pd, final Vector3f location, final Texture2D texture)
  • private void initProjectorData(final ProjectorData pd, final Vector3f location, final Texture2D texture, int numTilesU, int numTilesV, int speed)

    {

    texture.setMinFilter(Texture.MinFilter.Trilinear);

    texture.setMagFilter(Texture.MagFilter.Bilinear);

    @@ -156,7 +156,7 @@

    final int textureHeight = texture.getImage().getHeight();

    final float textureAspectRatio = (float) textureWidth / (float) textureHeight;


  • pd.projector = new SimpleTextureProjector(texture);
  • pd.projector = new SimpleTextureProjector(texture, numTilesU, numTilesV, speed);

    final Camera projectorCamera = pd.projector.getProjectorCamera();

    projectorCamera.setLocation(location);

    projectorCamera.lookAt(Vector3f.ZERO.clone(), Vector3f.UNIT_X.clone());

    [/patch]
2 Likes

Hi



I’ve made a test video showing the animated texture projector render.



http://www.youtube.com/watch?v=LFgvGHPhjjc

4 Likes

this is awesome. keep up the good work!

So I’ve been trying this out (latest code from SVN), and trying to combine it with terrain. If I try projecting onto terrain I get something like this:







It should be projecting a flat green square, but it’s only in certain areas, and seems to have messed up the shadows.



Before I sort out a simple code example to post, any ideas? Has anyone made this work before?

I won’t be able to take a closer look at this before weekend, but my guess is that that another SceneProcessor (like the shadow renderer or something with terrain) renders after the ProjectiveTextureRenderer and thus overrides it. Try to play around with the order of the SceneRenderers.

  1. Terrain (if it has one)
  2. ProjectiveTextureRenderer
  3. Shadow

Hmm ok, I had thought it could be something to do with that based on a bit of digging through the code, but couldn’t see what the terrain was doing. I’ll have another look (and post if I find something).



If you want to have a look at it, just try one of the simple terrain examples building a terrain from a heightmap (there are a few in the sdk) and add the projection code.



This seems like something many people would like to be able to do, so I’ll be sure to share the solution if I find one.

Ok, so I did some investigation, if I call:



viewPort.getProcessors().size()



once everything’s loaded, there’s just the 1 processor on the scene - the TextureProjectorRenderer. So it’s not that I think…



Also, the wireframe for the frustrum flickers white/green, so I assume some rounding error means it is getting the green projected onto it…but that’s less of an issue.



Some further info:



If I don’t add a textureprojector to the TextureProjectorRenderer, obviously none is projected but also there are no errors in the rest of the terrain. If I do, and change the terrain to use an unshaded material for simplicty it introduces errors in the terrain:







Those black bits aren’t there without the projected texture. The black bits also move if I animate the projector (like the test, swing it to and fro) I’m guessing it’s something to do with the way the terrain geometry is calculated confusing the texture projector but don’t understand enough about shaders etc. to fix this (yet…).

This project doesn’t work for me too since a few weeks. The single and multiple projected textures work but animated textures are not displayed correctly …



I’m working on … jme3.0 rc2 with nightly updates.



screenshot done only with the project code given in svn :



http://i.imgur.com/GioGR.png



http://i.imgur.com/NR5PJ.png



http://i.imgur.com/twwWD.png



it seems it only a quarter…

Works fine for me (projection code from SVN, jME nightlies).

Ok, more investigation. The projection shaders are for some reason being applied to every visible (to the user) pixel, but surely need applied only to those visible to the projecting camera. What is it that restricts where they are applied?



(i may be misunderstanding something here…)



So some further testing.



If I simplify the fragment shader, and check the color returned by texture2DProj like so:



if (projCoord.w > 0.0)

{

if (cosAngle > 0.0)

{

vec4 projColor = texture2DProj(m_ProjectiveMap, projCoord);

if (!(projColor.x == 0.0 && projColor.y == 0.0 && projColor.z == 0.0 )) {

gl_FragColor = projColor;

}

}

}




And change to parallel project it “works”:







So as the texture should just be green, what this tells me is that it’s trying to project from outside the texture for some reason. I’m not sure why yet…

Ok, yet more tests.



If I remove the checks on cosAngle in ProjectiveTextureMapping.frag, then it works fine:







Now, as I understand it, that check is just to see if the normal of that pixel is facing the camera or not, and only paint it if it’s facing. So from this I would assume for some reason the fragment normals on the terrain are wrong?



Oddly it doesn’t work when I use my own PNGs, e.g. the green one, next step to work that out… (and see if I can fix the terrain normals perhaps…assuming that is it)

2 Likes

…and I’ve got the green one working. The problem was the texture didn’t have an Alpha channel and it needs to have one.



Next step, terrain normals… :S (assuming that’s the problem).



(Edited the soluton to bold to help others skimming this thread)

2 Likes

Nice finding! I’m not an expert for terrain, but the TangentBinormalGenerator works like a charm in many cases.

The plot thickens…



if I use:



cosAngle = dot(inNormal, -m_ProjectorDirection);



in a non-parallel projection, it’s (roughly) correct. So for some reason:



cosAngle = dot(inNormal, normalize(m_ProjectorLocation - inPosition));



Isn’t calculating the angle between the normal and the vector toward the camera correctly for perspective mode. But it looks right to me. Hmmmmm.

No need to use TangentBinormalGenerator on terrain, it does that itself already.



For displaying the normals and tangents:

[java]

terrain.generateDebugTangents(assetManager.loadMaterial("Common/Materials/VertexColor.j3m"));

[/java]

(note that this will attach the geometries to the terrain, so just call it once and not repeatedly or else you will have lots of vectors)



From the picture it looks like the terrain normals are fine, there is proper shading on the dips and hills.