Depth Check for Projective Texture Rendering (Proper Render Order)

This is a fix for @survivor 's Projective Texture Renderer. You can find the original information about his work in this thread:



LINK



You can get the needed files, example usage and such there. Just replace TextureProjectorRenderer.java with this, and the projected texture will be rendered in the proper order depth-wise.



TextureProjectorRenderer.java

[java]

/*

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

    */

    package cGameClient.Renderers;



    import com.jme3.material.Material;

    import com.jme3.renderer.queue.RenderQueue;

    import com.jme3.asset.AssetManager;

    import com.jme3.bounding.BoundingSphere;

    import com.jme3.collision.CollisionResults;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.Vector3f;

    import com.jme3.post.SceneProcessor;

    import com.jme3.renderer.RenderManager;

    import com.jme3.renderer.ViewPort;

    import com.jme3.renderer.queue.GeometryList;

    import com.jme3.renderer.queue.OpaqueComparator;

    import com.jme3.scene.Geometry;

    import com.jme3.texture.FrameBuffer;

    import java.util.ArrayList;

    import java.util.List;



    /**
  • A SceneProcessor that renders TextureProjectors, which means it projects
  • textures on scene geometry.

    *
  • @author survivor

    */

    public class TextureProjectorRenderer implements SceneProcessor

    {

    private RenderManager renderManager;

    private ViewPort viewPort;

    private Material textureMat, newMat;

    private ArrayList<TextureProjector> textureProjectors;



    public TextureProjectorRenderer(AssetManager assetManager)

    {

    newMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

    newMat.setColor("Color", new ColorRGBA(0,0,0,1));

    textureMat = new Material(assetManager, "MatDefs/Misc/ProjectiveTextureMapping.j3md");

    textureProjectors = new ArrayList<TextureProjector>();

    renderManager = null;

    viewPort = null;

    }



    /**
  • @return A list of TextureProjectors rendered by this instance.

    */

    public List<TextureProjector> getTextureProjectors()

    {

    return textureProjectors;

    }



    /**
  • Initializes this instance.
  • @see SceneProcessor

    */

    @Override

    public void initialize(RenderManager rm, ViewPort vp)

    {

    renderManager = rm;

    viewPort = vp;

    }



    /**
  • @return true, if this instance is initialized, false otherwise.
  • @see SceneProcessor

    */

    @Override

    public boolean isInitialized()

    {

    return viewPort != null;

    }



    /**
  • Called before the a frame is rendered.
  • @see SceneProcessor

    */

    @Override

    public void preFrame(float tpf) {



    }



    /**
  • Called before the render queue is flushed.
  • @see SceneProcessor

    */

    @Override

    public void postQueue(RenderQueue rq)

    {

    }



    /**
  • Renders each TextureProjector with its corresponding material parameters.
  • Called after a frame has been rendered.
  • @see SceneProcessor

    */

    @Override

    public void postFrame(FrameBuffer out)

    {

    renderManager.getRenderer().setFrameBuffer(out);

    for (TextureProjector textureProjector : textureProjectors)

    {

    float fadeOutDistance = textureProjector.getFallOffDistance();

    textureMat.setTexture("ProjectiveMap", textureProjector.getProjectiveTexture());

    textureMat.setMatrix4("ProjectorViewProjectionMatrix", textureProjector.getProjectorViewProjectionMatrix());



    if (textureProjector.isParallelProjection())

    {

    textureMat.clearParam("ProjectorLocation");

    textureMat.setVector3("ProjectorDirection", textureProjector.getProjectorDirection());

    }

    else

    {

    textureMat.clearParam("ProjectorDirection");

    textureMat.setVector3("ProjectorLocation", textureProjector.getProjectorLocation());

    }



    if (fadeOutDistance != Float.MAX_VALUE)

    {

    textureMat.setFloat("FallOffDistance", textureProjector.getFallOffDistance());

    textureMat.setFloat("FallOffPower", textureProjector.getFallOffPower());

    }

    else

    {

    textureMat.clearParam("FallOffDistance");

    textureMat.clearParam("FallOffPower");

    }



    renderManager.setForcedMaterial(textureMat);



    GeometryList targetGeometryList = textureProjector.getTargetGeometryList();

    if (targetGeometryList != null)

    {

    renderManager.renderGeometryList(targetGeometryList);

    }

    else

    {

    renderManager.renderViewPortRaw(viewPort);

    }



    renderManager.setForcedMaterial(null);





    GeometryList reverseTargetGeometryList = textureProjector.getReverseTargetGeometryList();



    if (reverseTargetGeometryList!= null) {

    renderManager.setForcedMaterial(newMat);

    renderManager.renderGeometryList(reverseTargetGeometryList);

    renderManager.setForcedMaterial(null);

    }

    }

    }



    /**
  • Cleans up this instance.
  • @see SceneProcessor

    */

    @Override

    public void cleanup()

    {

    }



    /**
  • Called if the shape of the ViewPort changed.
  • @see SceneProcessor

    */

    @Override

    public void reshape(ViewPort vp, int w, int h)

    {

    }

    }

    [/java]
1 Like

Thanks! Could you please explain what your changes are fixing (test case)? I’ve got a bit stress at work at the moment (deadline), but after that, I’ll apply your fix. I found another bug and I’ll fix that, too then.

@survivor The fix was very simple. The renderer was writting into it’s own framebuffer for each camera (viewport), I just added a line to set the frameBuffer of the renderManager to main output frameBuffer and it takes into account the depth buffer to render in the proper order. Thanks to @nehon for explaining how the depth buffer works.



Would you mind if a take a crack at writing the shader for rendering all cameras in a single pass? This particular piece of code you wrote is invaluable. I hope it is added to the core engine.



I’m particularly interested in trying this out with the texture deformation shader I’m working on. I’m betting you could produce some really cool effects this way.

I’ve already implemented a one pass renderer. Take a look at the files with “1pass” in their name in project ProjectiveTextureMapping. It renders 8 textures per pass which is the number of texture units a legacy GPU has. One could auto detect the number of texture units to increase this number, but 8 is already a nice boost.



@mifth: Perhaps it’s time to move it to the contribution depot.

3 Likes

Nice… it’s been a while since I grabbed the original files. Now that you mentioned it, I remember that being there. Again, this is a really nice piece of work! Great job.

@survivor , strange, I did not get your Notification. Possibly bacause of “mifth + :”. This is the great honor for me! I want to add you as a developer to the shader project http://code.google.com/p/jme-glsl-shaders/ . The project uses Mercurial(like svn). You will need TortoiseHG, or just a Mercurial Client. If you want of course. Just send me your gmail by private message and i’ll add you.



@t0neg0d , you are already there as a developer. You can commit to the project.



The project uses nightly builds, because i use blender 2.62 models (it’s supported by JME only a week ago).

@survivor said:
@mifth: Perhaps it's time to move it to the contribution depot.

Or this is getting into the core (if you don't mind of course). The projective texture feature has been asked very often, so i guess it's time.

Thanks to both of you.

Do as you like. I’m happy to contribute.



My future plans for this are:

  • Parallax mapped decals on parallax mapped geometry (like here)
  • Multiple decals / projectors use different areas of the same texture to allow more decals per pass

@nehon , make it man!

@survivor said:
Thanks! Could you please explain what your changes are fixing (test case)? I've got a bit stress at work at the moment (deadline), but after that, I'll apply your fix. I found another bug and I'll fix that, too then.


I just realized I never answered your question... I read it wrong the first time.

The way it worked before is it rendered the projected texture in front of everything else... which unfortunately made it's usefulness limited in most cases. The fix renders the projected texture in the order it should be... example: I project a texture onto the ground... there are trees between the camera and where the texture is being projected. Before, the textuire showed up infront of the trees (and everything else for that matter). The fix just makes sure it takes into account the depth buffer and only draws the texture where it should be showing.

Oh... you'll want to add the same line of code to your single pass renderer as well.

I think it is:

[java]
renderManager.getRenderer().setFrameBuffer(out); // the variable out is the main buffer passed into postFrame
[/java]

Ah, I see. I guess the problem only occurs if there’s another SceneProcessor (FilterPostProcessor for example) in the chain. That’s why my test cases work and I didn’t notice that I did something wrong. Thanks a lot! I’ll apply your fix and do some more bugfixes and cleanup on tuesday. I just have to survive the monday at work. :smiley:



btw: Now I know one get’s an update for every edit. I’m also someone who corrects his posts a lot. That means I must have annoyed a lot of people. My mail client did “tadaa” all the time while you edited your post. :smiley: