Alpha with No Culling, front and back

Have built a test case.



Making a simple revolving snowflake that is made from two quads crossed together.



As the snowflake revolves you will notice that on one side of the quad it completely culls the second quad, but on the second side of the quad it blends the transparency.



How do you make both sides of the quad transparent and behave the same.









import javax.imageio.ImageIO;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;

public class TestAlpha extends SimpleGame {

    private Quad quad;
    private Node snowFlake;
    private Quaternion rotQuat = new Quaternion();
    private Quaternion rotQuatSnowFlake = new Quaternion();

    private float angle = 0;
    private float angleSnowFlake = 0;

    private Vector3f axis = new Vector3f(0, 1, 0);
    private Vector3f axisSnowFlake = new Vector3f(0, 1, 0);

    public static void main(String[] args) {
        TestAlpha app = new TestAlpha();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }

    /**
     * Rotates the dome
     */
    protected void simpleUpdate() {
        if (tpf < 1) {
            angle = angle + (tpf * 1);
            if (angle > 360) {
                angle = 0;
            }
            angleSnowFlake = angleSnowFlake + (tpf*1.1f);
            if (angleSnowFlake > 360) {
                angle = 0;
            }
           
        }
        rotQuat.fromAngleAxis(angle, axis);       
        quad.setLocalRotation(rotQuat);
       
        rotQuatSnowFlake.fromAngleAxis(angleSnowFlake, axisSnowFlake);
        snowFlake.setLocalRotation(rotQuatSnowFlake);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.jme.app.SimpleGame#initGame()
     */
    protected void simpleInitGame() {
        display.setTitle("jME - Quad");

        addRevolvingGlassPane();
        quad.updateGeometricState(0,false);
        quad.updateRenderState();
        rootNode.attachChild(quad);
       
        addRevolvingSnowflake();
        snowFlake.updateGeometricState(0,false);
        snowFlake.updateRenderState();
        rootNode.attachChild(snowFlake);
       
       
    }
   
    private void addRevolvingGlassPane() {
       quad = new Quad("quad", 20, 20);
        quad.setLocalTranslation(new Vector3f(0, 0, -40));
        quad.setModelBound(new BoundingBox());
        quad.updateModelBound();
        
        TextureState ts = getTextureState("/jmetest/data/texture/flare1.png");       
        quad.setRenderState(ts);
       
       AlphaState as = getAlpha();       
        quad.setRenderState(as);
       
        quad.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
    }
   
    private void addRevolvingSnowflake() {
       
       snowFlake = new Node("Snowflake");
       
       Quad q1 = new Quad("q1", 25,25);      
      q1.setModelBound(new BoundingBox());
      q1.updateModelBound();
      Quad q2 = new Quad("q2", 25,25);      
      q2.setModelBound(new BoundingBox());
      q2.updateModelBound();
      
      TextureState ts = getTextureState("/jmetest/data/texture/flare1.png");
      
      q1.setRenderState(ts);
      q2.setRenderState(ts);

       q1.setRenderState( getAlpha());
       q1.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
       q2.setRenderState(getAlpha());
       q2.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);

       q1.getLocalTranslation().y = -(25 /2f);     
       q2.getLocalTranslation().y = -(25 /2f);
      
       CullState cullstate = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
      cullstate.setCullMode(CullState.CS_NONE);
      q1.setRenderState(cullstate);
      q2.setRenderState(cullstate);
      
      Quaternion q = q2.getLocalRotation();      
      q = q1.getLocalRotation();
      q.fromAngles(new float[] {   FastMath.DEG_TO_RAD  * 90f, FastMath.DEG_TO_RAD  * 90f, FastMath.DEG_TO_RAD  * 90f});
   
      snowFlake.attachChild(q1);
      snowFlake.attachChild(q2);      
      q = snowFlake.getLocalRotation();
      q.fromAngleAxis(FastMath.DEG_TO_RAD  * 180, q.getRotationColumn(2));      
      
    }
       
       
    private AlphaState getAlpha() {
       AlphaState as = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState();
        as.setBlendEnabled(true);
        as.setSrcFunction(AlphaState.SB_SRC_ALPHA);
        as.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA );
        as.setTestEnabled(true);
        as.setTestFunction(AlphaState.TF_GREATER);
        as.setReference( 0.6f );
        as.setEnabled(true);
        return as;
    }
   
    private TextureState getTextureState(String image) {
       TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
      
      Texture t = TextureManager.loadTexture(getImage(image), Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR, false);
      t.setApply(Texture.AM_ADD);
      t.setCombineFuncRGB(Texture.ACF_ADD_SIGNED);
      t.setCombineSrc0RGB(Texture.ACS_TEXTURE);
      t.setCombineOp0RGB(Texture.ACO_SRC_COLOR);
      t.setCombineSrc1RGB(Texture.ACS_PREVIOUS);
      t.setCombineOp1RGB(Texture.ACO_SRC_COLOR);
      t.setCombineScaleRGB(1.0f);
       ts.setTexture(t);   
       return ts;
    }
   
   
    private BufferedImage getImage(String pathAndName) {
       BufferedImage image = null;
      try {
         image = ImageIO.read(TestAlpha.class.getResource(pathAndName));
      } catch(Exception e) {
         // cop out just make one
         image = new BufferedImage(4,4,1);
      }
      return image;
    }
   
}


Did you try?


cullstate.setEnabled(true)


like you did it in the Alphastate

the rest looks ok

Enabled the cullstate but it still isnt working.



Give it a quick run and you will see the weirdness

Try not adding it to the TransparentQueue.

okies, a few pics to show issue



Ignore the second snow flake ( top left, its about the snowflake consisting of two quads )



This pic you can see the snowflake of quad 2 through quad 1





This pic the snowflake of quad 1 blocks quad 2







How can this be fixed that both quad1 and quad2 are always transparent in the same way front and back

Maybe if you cull the back faces and create 4 quads instead of just 2 and rotate the second set of quads by 180 on the Y-axis.



You might also try creating 4 quads (joined at the center, not crossing at the center) and adjust the texture coordinates accordingly.

basixs said:

Maybe if you cull the back faces and create 4 quads instead of just 2 and rotate the second set of quads by 180 on the Y-axis.

You might also try creating 4 quads (joined at the center, not crossing at the center) and adjust the texture coordinates accordingly.


Thanks basics, going to reluctantly resort to this workaround of stitching the quads back to back. Its going to itch like crazy until I can understand why the back is treated differently. Normals maybe and light ?

The problem is that transparency works by blending the transparent object with the scene behind it.  Because you are drawing one of the quads, then the other, only the second quad will be "transparent" in the sense that you'll be able to see the first through the second.  You can't see the second through the first because it's already been drawn.  You can kind of get the effect you are after by disabling depth writing on your snowflakes, but you'll run into a problem with the blending not looking quite right.  The best solution would be to use 4 quads and draw half your snowflake on each as mentioned above.



Here's your code refactored to do that (and ported to 2.0 since that is what I have at hand… sorry.)


import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.image.Texture.ApplyMode;
import com.jme.image.Texture.CombinerFunctionRGB;
import com.jme.image.Texture.CombinerOperandRGB;
import com.jme.image.Texture.CombinerScale;
import com.jme.image.Texture.CombinerSource;
import com.jme.image.Texture.MagnificationFilter;
import com.jme.image.Texture.MinificationFilter;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.BlendState.DestinationFunction;
import com.jme.scene.state.BlendState.SourceFunction;
import com.jme.scene.state.BlendState.TestFunction;
import com.jme.scene.state.CullState.Face;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;

public class TestAlpha extends SimpleGame {

    private Quad quad;
    private Node snowFlake;
    private Quaternion rotQuat = new Quaternion();
    private Quaternion rotQuatSnowFlake = new Quaternion();

    private float angle = 0;
    private float angleSnowFlake = 0;

    private Vector3f axis = new Vector3f(0, 1, 0);
    private Vector3f axisSnowFlake = new Vector3f(0, 1, 0);

    public static void main(String[] args) {
        TestAlpha app = new TestAlpha();
        app.setConfigShowMode(ConfigShowMode.AlwaysShow);
        app.start();
    }

    /**
     * Rotates the dome
     */
    protected void simpleUpdate() {
        if (tpf < 1) {
            angle = angle + (tpf * 1);
            if (angle > 360) {
                angle = 0;
            }
            angleSnowFlake = angleSnowFlake + (tpf*1.1f);
            if (angleSnowFlake > 360) {
                angle = 0;
            }
           
        }
        rotQuat.fromAngleAxis(angle, axis);       
        quad.setLocalRotation(rotQuat);
       
        rotQuatSnowFlake.fromAngleAxis(angleSnowFlake, axisSnowFlake);
        snowFlake.setLocalRotation(rotQuatSnowFlake);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.jme.app.SimpleGame#initGame()
     */
    protected void simpleInitGame() {
        display.setTitle("jME - Quad");

        addRevolvingGlassPane();
        quad.updateGeometricState(0,false);
        quad.updateRenderState();
        rootNode.attachChild(quad);
       
        addRevolvingSnowflake();
        snowFlake.updateGeometricState(0,false);
        snowFlake.updateRenderState();
        rootNode.attachChild(snowFlake);
       
       
    }
   
    private void addRevolvingGlassPane() {
        quad = new Quad("quad", 20, 20);
        quad.setLocalTranslation(new Vector3f(0, 0, -40));
        quad.setModelBound(new BoundingBox());
        quad.updateModelBound();
        
        TextureState ts = getTextureState("/jmetest/data/texture/flare1.png");       
        quad.setRenderState(ts);
       
        BlendState as = getBlendState();        
        quad.setRenderState(as);
       
        quad.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
    }
   
    float[] halfA = {0, 1, 0, 0, .5f, 0, .5f, 1};
    float[] halfB = {.5f, 1, .5f, 0, 1, 0, 1, 1};
    private void addRevolvingSnowflake() {
       
        snowFlake = new Node("Snowflake");
        snowFlake.getLocalTranslation().y = -(25 /2f);   
       
        Quad q1 = new Quad("q1", 12.5f,25);
        q1.getLocalTranslation().z+=6.25f;
        q1.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
        q1.setModelBound(new BoundingBox());
        q1.updateModelBound();
        q1.getTextureCoords(0).coords.rewind();
        q1.getTextureCoords(0).coords.put(halfA);
        Quad q2 = new Quad("q2", 12.5f,25);       
        q2.getLocalTranslation().z-=6.25f;
        q2.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
        q2.setModelBound(new BoundingBox());
        q2.getTextureCoords(0).coords.rewind();
        q2.getTextureCoords(0).coords.put(halfB);
        q2.updateModelBound();
        Quad q3 = new Quad("q1", 12.5f,25);       
        q3.getLocalTranslation().x-=6.25f;
        q3.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
        q3.setModelBound(new BoundingBox());
        q3.getTextureCoords(0).coords.rewind();
        q3.getTextureCoords(0).coords.put(halfA);
        q3.updateModelBound();
        Quad q4 = new Quad("q2", 12.5f,25);       
        q4.getLocalTranslation().x+=6.25f;
        q4.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
        q4.setModelBound(new BoundingBox());
        q4.getTextureCoords(0).coords.rewind();
        q4.getTextureCoords(0).coords.put(halfB);
        q4.updateModelBound();
       
        TextureState ts = getTextureState("/jmetest/data/texture/flare1.png");
        snowFlake.setRenderState(ts);
        snowFlake.setRenderState( getBlendState());
       
        CullState cullstate = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
        cullstate.setCullFace(Face.None);
        snowFlake.setRenderState(cullstate);
       
        Quaternion q = q2.getLocalRotation();      
        q = q1.getLocalRotation();
        q.fromAngles(new float[] {  FastMath.DEG_TO_RAD  * 90f, FastMath.DEG_TO_RAD  * 90f, FastMath.DEG_TO_RAD  * 90f});
        q = q2.getLocalRotation();
        q.fromAngles(new float[] {  FastMath.DEG_TO_RAD  * 90f, FastMath.DEG_TO_RAD  * 90f, FastMath.DEG_TO_RAD  * 90f});
   
        snowFlake.attachChild(q1);
        snowFlake.attachChild(q2);
        snowFlake.attachChild(q3);
        snowFlake.attachChild(q4);
        q = snowFlake.getLocalRotation();
        q.fromAngleAxis(FastMath.DEG_TO_RAD  * 180, q.getRotationColumn(2));       
       
    }
       
       
    private BlendState getBlendState() {
        BlendState as = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
        as.setBlendEnabled(true);
        as.setSourceFunction(SourceFunction.SourceAlpha);
        as.setDestinationFunction(DestinationFunction.OneMinusSourceAlpha );
        as.setTestEnabled(true);
        as.setTestFunction(TestFunction.GreaterThan);
        as.setReference( 0.6f );
        as.setEnabled(true);
        return as;
    }
   
    private TextureState getTextureState(String image) {
        TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
       
        Texture t = TextureManager.loadTexture(getImage(image), MinificationFilter.Trilinear, MagnificationFilter.Bilinear, false);
        t.setApply(ApplyMode.Add);
        t.setCombineFuncRGB(CombinerFunctionRGB.AddSigned);
        t.setCombineSrc0RGB(CombinerSource.CurrentTexture);
        t.setCombineOp0RGB(CombinerOperandRGB.SourceColor);
        t.setCombineSrc1RGB(CombinerSource.Previous);
        t.setCombineOp1RGB(CombinerOperandRGB.SourceColor);
        t.setCombineScaleRGB(CombinerScale.One);
        ts.setTexture(t);  
        return ts;
    }
   
   
    private BufferedImage getImage(String pathAndName) {
        BufferedImage image = null;
        try {
            image = ImageIO.read(TestAlpha.class.getResource(pathAndName));
        } catch(Exception e) {
            // cop out just make one
            image = new BufferedImage(4,4,1);
        }
        return image;
    }
   
}

Thats great. Thankyou.



Makes sense with the drawing order