Skip to content

Commit

Permalink
Stencil buffer fix (#2325)
Browse files Browse the repository at this point in the history
* Add mask/reference to hash function

* Add mask/reference to copyMergedTo

* Add mask/reference to set(RenderState state)

* Add TestStencilOutline.java

* Removed not used imports

* Fixed spacing
  • Loading branch information
zzuegg authored Oct 28, 2024
1 parent deef748 commit b34fed9
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 0 deletions.
18 changes: 18 additions & 0 deletions jme3-core/src/main/java/com/jme3/material/RenderState.java
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,10 @@ public int contentHashCode() {
hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0);
hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0);
hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0);
hash = 79 * hash + (this.frontStencilMask);
hash = 79 * hash + (this.frontStencilReference);
hash = 79 * hash + (this.backStencilMask);
hash = 79 * hash + (this.backStencilReference);
hash = 79 * hash + Float.floatToIntBits(this.lineWidth);

hash = 79 * hash + this.sfactorRGB.hashCode();
Expand Down Expand Up @@ -1623,6 +1627,11 @@ public RenderState copyMergedTo(RenderState additionalState, RenderState state)

state.frontStencilFunction = additionalState.frontStencilFunction;
state.backStencilFunction = additionalState.backStencilFunction;

state.frontStencilMask = additionalState.frontStencilMask;
state.frontStencilReference = additionalState.frontStencilMask;
state.backStencilMask = additionalState.backStencilMask;
state.backStencilReference = additionalState.backStencilMask;
} else {
state.stencilTest = stencilTest;

Expand All @@ -1636,6 +1645,11 @@ public RenderState copyMergedTo(RenderState additionalState, RenderState state)

state.frontStencilFunction = frontStencilFunction;
state.backStencilFunction = backStencilFunction;

state.frontStencilMask = frontStencilMask;
state.frontStencilReference = frontStencilMask;
state.backStencilMask = backStencilMask;
state.backStencilReference = backStencilMask;
}
if (additionalState.applyLineWidth) {
state.lineWidth = additionalState.lineWidth;
Expand Down Expand Up @@ -1665,6 +1679,10 @@ public void set(RenderState state) {
backStencilDepthPassOperation = state.backStencilDepthPassOperation;
frontStencilFunction = state.frontStencilFunction;
backStencilFunction = state.backStencilFunction;
frontStencilMask = state.frontStencilMask;
frontStencilReference = state.frontStencilReference;
backStencilMask = state.backStencilMask;
backStencilReference = state.backStencilReference;
blendEquationAlpha = state.blendEquationAlpha;
blendEquation = state.blendEquation;
depthFunc = state.depthFunc;
Expand Down
151 changes: 151 additions & 0 deletions jme3-examples/src/main/java/jme3test/stencil/TestStencilOutline.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package jme3test.stencil;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.OpaqueComparator;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.texture.Image;

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

public class TestStencilOutline extends SimpleApplication {

class OutlineComparator extends OpaqueComparator {
@Override
public int compare(Geometry o1, Geometry o2) {
boolean ol1 = o1.getUserData("Outline") != null;
boolean ol2 = o2.getUserData("Outline") != null;
if (ol1 == ol2) {
return super.compare(o1, o2);
} else {
if (ol1) {
return 1;
} else {
return -1;
}
}
}
}

class OutlineControl extends AbstractControl {
private Material outlineMaterial;

public OutlineControl(AssetManager assetManager, ColorRGBA colorRGBA) {
outlineMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
outlineMaterial.setColor("Color", colorRGBA);
outlineMaterial.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Front);
outlineMaterial.getAdditionalRenderState().setDepthFunc(RenderState.TestFunction.Always);
outlineMaterial.getAdditionalRenderState().setStencil(true,
RenderState.StencilOperation.Keep, //front triangle fails stencil test
RenderState.StencilOperation.Keep, //front triangle fails depth test
RenderState.StencilOperation.Keep, //front triangle passes depth test
RenderState.StencilOperation.Keep, //back triangle fails stencil test
RenderState.StencilOperation.Keep, //back triangle fails depth test
RenderState.StencilOperation.Keep, //back triangle passes depth test
RenderState.TestFunction.NotEqual, //front triangle stencil test function
RenderState.TestFunction.NotEqual); //back triangle stencil test function
outlineMaterial.getAdditionalRenderState().setFrontStencilReference(1);
outlineMaterial.getAdditionalRenderState().setBackStencilReference(1);
outlineMaterial.getAdditionalRenderState().setFrontStencilMask(0xFF);
outlineMaterial.getAdditionalRenderState().setBackStencilMask(0xFF);
}

@Override
protected void controlUpdate(float v) {

}

@Override
protected void controlRender(RenderManager renderManager, ViewPort viewPort) {
if (spatial instanceof Geometry) {
Geometry geometry= (Geometry) spatial;
Geometry clone = geometry.clone();
clone.scale(1.1f);
clone.setUserData("Outline", true);
clone.setMaterial(outlineMaterial);
clone.updateGeometricState();
viewPort.getQueue().addToQueue(clone, RenderQueue.Bucket.Opaque);
}
}
}

@Override
public void update() {
super.update();
}

@Override
public void simpleInitApp() {
flyCam.setMoveSpeed(24);
flyCam.setZoomSpeed(-5);
viewPort.getQueue().setGeometryComparator(RenderQueue.Bucket.Opaque, new OutlineComparator());
viewPort.setClearFlags(true,true,true);
Material boxMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
boxMat.getAdditionalRenderState().setStencil(true,
RenderState.StencilOperation.Keep, //front triangle fails stencil test
RenderState.StencilOperation.Replace, //front triangle fails depth test
RenderState.StencilOperation.Replace, //front triangle passes depth test
RenderState.StencilOperation.Keep, //back triangle fails stencil test
RenderState.StencilOperation.Replace, //back triangle fails depth test
RenderState.StencilOperation.Replace, //back triangle passes depth test
RenderState.TestFunction.Always, //front triangle stencil test function
RenderState.TestFunction.Always); //back triangle stencil test function
boxMat.getAdditionalRenderState().setFrontStencilReference(1);
boxMat.getAdditionalRenderState().setBackStencilReference(1);
boxMat.getAdditionalRenderState().setFrontStencilMask(0xFF);
boxMat.getAdditionalRenderState().setBackStencilMask(0xFF);
boxMat.setTexture("ColorMap", assetManager.loadTexture("Common/Textures/MissingTexture.png"));

Material floorMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
floorMat.setTexture("ColorMap", assetManager.loadTexture("Common/Textures/MissingTexture.png"));

Geometry floor = new Geometry("Floor", new Box(10f, 0, 10f));
floor.setMaterial(floorMat);
rootNode.attachChild(floor);

Geometry box1 = new Geometry("Box1", new Box(0.5f, 0.5f, 0.5f));
box1.setLocalTranslation(3, 1.5f, 0);
box1.setLocalScale(4);
box1.addControl(new OutlineControl(assetManager, ColorRGBA.Blue));
box1.setMaterial(boxMat);
Geometry box2 = new Geometry("Box2", new Box(0.5f, 0.5f, 0.5f));
box2.setLocalTranslation(-3, 1.5f, 0);
box2.setLocalScale(3);
box2.addControl(new OutlineControl(assetManager,ColorRGBA.Red));
box2.setMaterial(boxMat);

rootNode.attachChild(box1);
rootNode.attachChild(box2);

//This is to make sure a depth stencil format is used in the TestChooser app.
FilterPostProcessor postProcessor=new FilterPostProcessor(assetManager);
postProcessor.setFrameBufferDepthFormat(Image.Format.Depth24Stencil8);
viewPort.addProcessor(postProcessor);
postProcessor.addFilter(new BloomFilter());
}


public static void main(String[] args) {
Logger.getLogger("").setLevel(Level.FINEST);
TestStencilOutline app = new TestStencilOutline();
AppSettings settings = new AppSettings(true);
settings.setGraphicsDebug(true);
settings.setDepthBits(24);
settings.setStencilBits(8);
app.setSettings(settings);
app.setShowSettings(false);
app.start();
}
}

0 comments on commit b34fed9

Please sign in to comment.