Outliner Material (Toon Outline)

Here is a super simple outline material I made a while back, its not really finished, but it works and is a great starting point for something… what that is yet I don’t know. Being such a simple example this could be a useful learning tool for monkeys.

Material Outliner Ver 0.1 Download

It’s very basic, it renders the geometry a second time, scaled along the normals and inverted (I obviously didn’t invent this, just implemented it in my own way).

The guts of how it works :
I pulled out some of the more important bits of code to go over.

We can start off simple …

public class OutlineMaterial extends Material {

with me so far ? Good !

OutlineMaterial keeps a reference to the original material for the geometry so it cal use it later.

So all that OutlineMaterial really does is render the geom twice …

 @Override
    public void render(Geometry geometry, LightList lights, RenderManager renderManager)  {
        
        // render the original material
        if (baseMaterial != null) { 
          baseMaterial.render(geometry, lights, renderManager);
        }
        
        // render the outline 
        super.render(geometry, lights, renderManager);
    }

… straight forward so far yes ?.

Now a simple vertex shader to scale along the normals …

vec3 P = inPosition + (inNormal * (m_Thickness/10.0) );

aaaaaaaaand profit.

Caveats :

  • It will only work with smoothed faced (smooth normals) meshes, since it scales along normals A standard jme Cube (for example) would just push the faces out and not scale them up.

I probably have more to add but I have to start my Christmas shopping >=(

Cheers
James

5 Likes

[ reserved ]

@nehon has a really good outline solution, actually. Something like create a new Geometry with the mesh shared, give it a wire mode material but with wide lines and turn it inside out or something. He can probably say more.

I don’t think creating parallel geometry is any less invasive then subclassing Material. The parallel geometry has the added benefit that you can stick it in a different bucket and/or viewport.

Your solution is interesting, though.

Thanks for posting this! When I try to use this, though, I get an error saying that the “BoneMatrices” parameter is not set on the OutlineMaterial.

Here’s the error:

java.lang.IllegalArgumentException: Material parameter is not defined: BoneMatrices
	at com.jme3.material.Material.checkSetParam(Material.java:458)
	at com.jme3.material.Material.setParam(Material.java:474)
	at com.jme3.animation.SkeletonControl.controlRenderHardware(SkeletonControl.java:265)
	at com.jme3.animation.SkeletonControl.controlRender(SkeletonControl.java:299)
	at com.jme3.scene.control.AbstractControl.render(AbstractControl.java:135)
	at com.jme3.scene.Spatial.runControlRender(Spatial.java:756)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:723)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:733)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:733)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:733)
	at com.jme3.renderer.RenderManager.renderScene(RenderManager.java:712)
	at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:1086)
	at com.jme3.renderer.RenderManager.render(RenderManager.java:1145)
	at com.jme3.app.SimpleApplication.update(SimpleApplication.java:253)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
	at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:193)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:232)
	at java.lang.Thread.run(Unknown Source)

The outline solution I’m using it somehow similar, as in it needs to duplicate the geometry.
But the second geometry is not scaled, it uses an unshaded Material that is actually using the polygon offset of opengl, and the wireframe mode.

 wireMaterial.getAdditionalRenderState().setWireframe(true); //we want wireframe
 wireMaterial.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);//that's just because we add an alpha pulse to the selection later, this is not mandatory
 wireMaterial.getAdditionalRenderState().setLineWidth(2); //you can play with this param to increase the line thickness
 wireMaterial.getAdditionalRenderState().setPolyOffset(-3f,-3f); //this is trick one, offsetting the polygons
 wireMaterial.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Front); // trick 2 we hide the front faces to not see the wireframe on top of the geom

This setting gives a 1 pixel outline, increasing linewidth increase the outline width. Also the outline width is constant whether you’re near of far from the object.
It gives decent result, though you can see the trick in some cases.

As you can see the outline on the head is not just an outline of the silhouette, but that’s good enough for my need.

3 Likes

In fact, I like that effect, personally. :slight_smile:

You could try pushing poly offset really hard to remove the ‘internal edges’, or perhaps turning off depth write on the outliner so the real geom renders over the top of all the internal edges (as long as the main geom is drawn 2nd) … I offset my entire outliner mesh in the away from the screen to acheive this but I was using orthogonal rendering with large gaps between objects so could cheat this way.

My favorite method for outlines is writing the main geom to the stencil buffer, then render a wireframe version only where the stencil buffer is not written to… I cant figure out the stencil buffer in jme so I have not done it this way.