Silhouette rendering of obstructed objects

This isn’t really plugin worthy, but thought I would post it for anyone who have a need for it:

It’s a simple post filter that renders a specified GeomtryList with a forced material and then does a depth check to blend back into the scene any portions of the geometries that are obstructed by other objects.

Here is a screen shot of what it does:

The code:

GhostImage.java
[java]
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.OpaqueComparator;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture2D;

/**
*

  • @author t0neg0d
    */
    public class GhostImage extends Filter {
    RenderManager rm;
    AssetManager am;
    ViewPort vp;
    GeometryList geoms;
    Pass ghosts;
    Material ghostMat;
    FrameBuffer ghostBuffer;

    public GhostImage(ViewPort vp) {
    super(“GhostImage”);
    OpaqueComparator comp = new OpaqueComparator();
    comp.setCamera(vp.getCamera());
    geoms = new GeometryList(comp);
    }

    @Override
    protected void postQueue(RenderQueue renderQueue) {
    if (geoms.size() > 0) {
    Renderer r = rm.getRenderer();
    r.setFrameBuffer(ghostBuffer);
    rm.getRenderer().clearBuffers(true, true, true);
    rm.setForcedMaterial(ghostMat);
    rm.renderGeometryList(geoms);
    rm.setForcedMaterial(null);
    r.setFrameBuffer(vp.getOutputFrameBuffer());
    }
    }

    @Override
    protected boolean isRequiresDepthTexture() {
    return true;
    }

    @Override
    protected boolean isRequiresSceneTexture() {
    return true;
    }

    @Override
    protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
    this.rm = renderManager;
    this.am = manager;
    this.vp = vp;

     ghostBuffer = new FrameBuffer(w,h,1);
     
     Texture2D ghostImage = new Texture2D(w,h,1,Format.ABGR8);
     Texture2D ghostDepth = new Texture2D(w,h,1,Format.Depth);
     
     ghostBuffer.setColorTexture(ghostImage);
     ghostBuffer.setDepthTexture(ghostDepth);
     
     ghostMat = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
     ghostMat.setColor("Color", ColorRGBA.Blue);
     
     material = new Material(manager, "MatDefs/GhostImage.j3md");
     material.setTexture("GhostDepth", ghostDepth);
     material.setTexture("GhostImage", ghostImage);
    

    }

    @Override
    protected Material getMaterial() {
    return this.material;
    }

    public void addToGeometryList(GeometryList geoms) {
    for (int g = 0; g < geoms.size(); g++) {
    boolean add = true;
    for (int g2 = 0; g2 < this.geoms.size(); g2++) {
    if (this.geoms.get(g2) == geoms.get(g)) {
    add = false;
    break;
    }
    }
    if (add)
    this.geoms.add(geoms.get(g));
    }
    }

    public GeometryList getGeometryList() {
    return this.geoms;
    }
    }
    [/java]

GhostImage.j3md
[java]
MaterialDef GhostImage {
MaterialParameters {
Int NumSamples
Int NumSamplesDepth
Texture2D DepthTexture
Texture2D Texture
Texture2D GhostImage
Texture2D GhostDepth
}

Technique {
	VertexShader GLSL100:   Common/MatDefs/Post/Post.vert
	FragmentShader GLSL100: Shaders/GhostImage.frag
	
	WorldParameters {
		WorldViewProjectionMatrix
	}
}

}
[/java]

GhostImage.frag
[java]
uniform sampler2D m_Texture;
uniform sampler2D m_DepthTexture;
uniform sampler2D m_GhostImage;
uniform sampler2D m_GhostDepth;
varying vec2 texCoord;

void main() {
float sceneDepth = texture2D(m_DepthTexture,texCoord).r;
float ghostDepth = texture2D(m_GhostDepth,texCoord).r;

vec4 scene = texture2D(m_Texture,texCoord);

if (ghostDepth &lt;= sceneDepth || ghostDepth == 1.0)
	gl_FragColor = scene;
else
	gl_FragColor = mix(scene,texture2D(m_GhostImage,texCoord),0.25);

}
[/java]

Simple usage:
[java]
// Create filter instance
ghosts = new GhostImage(viewPort);

// Add to FilterPostProcessor

// Create GeometryList, add Geometries to it, set the filter’s current Geometry list
GeometryList ghostGeoms = new GeometryList(new OpaqueComparator());
ghostGeoms.add( someGeom );
ghostGeoms.add( someOtherGeom );

ghosts.addToGeometryList(ghostGeoms);
[/java]

You can swap out the GeometryList at any point during a frame update.

End of Line

5 Likes

Eh… quick note about this. I stated adding the geometries as if calling addToGeometryList would overwrite the existing list. This is not the case. It appends any Geometry in the list that the filter doesn’t already have in it’s local list.

To clear the list of current Geometries, use:

[java]
ghosts.getGeometryList().clear();
[/java]

The use addToGeometryList to send the new list.

Don’t you need to call sort on GeometryList somewhere? In other case, they will stay unsorted, kind of defeating idea of passing opaque comparator there.

@abies said: Don't you need to call sort on GeometryList somewhere? In other case, they will stay unsorted, kind of defeating idea of passing opaque comparator there.

It’s sort of a moot point seeing as they are all rendered with a single forced material. It really is a silhouette. The depth buffer and complete shape are all that really matter in this case.

Now, if someone decides to take this and change what is being used to render the list with… then, you might!

EDIT: Ah… yeah… the opaque comparator… Kinda forces you to pass in one as a param of the GeometryList. I didn’t try passing in null… maybe that is doable?

EDIT 2: Actually, I never bothered to take a look at renderGeometryList… it may very well force a sort against the comparator you associate with the geom list.

And… survey says…

renderGeometryList renders the geometries in the order they are added if it has not been sorted. So, the answer is YES! If you decide to swap out the material that this filter uses for rendering the list, you will need to sort it.

Sorting is kind of unnecessary unless you need a certain order for blending. If you have a lot of overlapping objects then there may also be a performance reason… otherwise, probably not something to worry about.

@pspeed said: Sorting is kind of unnecessary unless you need a certain order for blending. If you have a lot of overlapping objects then there may also be a performance reason... otherwise, probably not something to worry about.

Just for my own clarification, each geometry is rendered in whatever order they are in the list, but they are drawn to the final frame according to depth?

EDIT: Erm… if they are not sorted, that is

They are drawn in whatever order they are in that list. If depth test and depth write are on then each fragment is tested against the existing depth at that pixel.

1 Like