Q: Textures and Blending

A pair of misc OpenGL questions:

  1. how to “fade out” a textured quad with a given alpha value ?

  2. blending to a clear (0x000000) PBuffer leads to wrong results according to Porter&Duff principles:

I have two white circles. The upper (100% opaque) is painted before the lower (50% opaque).

OpenGL:

With OpenGL the circles look already blended to a black transparent background. Even more the lower circle alters the alpha of the upper circle. That’s wrong.

Java2D:

With Java2D (AlphaComposite.SrcOver) the circles are painted in the right way.

Whats’ wrong ?

Tinker with the alpha values over time. Easiest is to change the vertex colours (glColor4f if using immediate mode) and remember to use pure white if you don’t want to tint your textures.

[quote]2) blending to a clear (0x000000) PBuffer leads to wrong results according to Porter&Duff principles:
[/quote]
Porter & Duff?

For the blending, whats the lower circle drawn as? Is it a pure white circle that you’ve changed the alpha value only, or have you changed the colour at all?

I suspect that you’re getting different behaviour because Java2D is using a different blend mode. OpenGL defaults to a weighted blend - glBlendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA) - whereas Java2D looks like an additive blend - glBlendFunc(SRC_ALPHA, ONE). Try changing that before drawing.

Ah, I see. If I read correctly then the Java2D SrcOver sounds like it should be the same as OpenGL’s default. I’d guess you’re setting the vertex colours (or similar) to a darker colour without realising it.

I tried to simplify the problem by using triangles instead of textured quads. The result is the same:


package JOGL;
 
 
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import net.java.games.jogl.*;
 
 
 
public class PBDemo extends JFrame implements GLEventListener {
  private static int        maxPbufferWidth = 720;
  private static int        maxPbufferHeight = 576;
  private GLDrawable        pbuffer;
  private BufferedImage     pbImage;
  private DataBufferInt     dbInt;
  
  private JLabel            label;
  private ImageIcon         image;
  private JScrollPane       scrollPane;
  
  public PBDemo() {
    setTitle( "Pbuffer Demo" );
    setSize( 640, 480 );
    
    // Create a temporary window to create a GLCanvas
    // that is never displayed on screen.
    Window tempWindow = new Window( this );
    tempWindow.setSize( 0, 0 );
    GLCanvas tempCanvas = GLDrawableFactory.getFactory().createGLCanvas( new GLCapabilities() );
    tempWindow.add( tempCanvas );
    tempWindow.setVisible( true );
    
    // Create a GLCapabilities object for the pbuffer
    GLCapabilities pbCaps = new GLCapabilities();
    pbCaps.setAlphaBits( 8 );
    pbCaps.setDoubleBuffered( false );
    pbuffer = tempCanvas.createOffscreenDrawable( pbCaps, maxPbufferWidth, maxPbufferHeight );
    // PBuffers are lazily created so the tempCanvas needs to run
    // through its display method once.
    tempCanvas.display();
      
    // Create the BufferedImage that will hold the pixel data
    // from the pbuffer.
    pbImage = new BufferedImage( maxPbufferWidth, maxPbufferHeight, BufferedImage.TYPE_INT_ARGB );
    // dbByte is the array that the pbuffer pixel data is stored in.
    dbInt = (DataBufferInt)pbImage.getRaster().getDataBuffer();
    pbuffer.addGLEventListener( this );
    pbuffer.display();
    
    // Display the pbuffer in a JScrollPane
    image = new ImageIcon( pbImage );
    label = new JLabel( image );
    scrollPane = new JScrollPane( label );
    getContentPane().add( scrollPane );
    
    
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        dispose();
        System.exit(0);
      }
    });
    setVisible( true );
  }
  
  public void displayChanged(GLDrawable gLDrawable, boolean modeChanged, boolean deviceChanged) {
  }
  
  public void init( GLDrawable drawable ) {
    GL gl = drawable.getGL();    
    gl.glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
    gl.glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
    System.out.println("init");
    reshape( drawable, 0, 0, maxPbufferWidth, maxPbufferHeight );
  }
  
  public void display( GLDrawable drawable ) {
    GL gl = drawable.getGL();
    System.out.println("display");
    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
    gl.glLoadIdentity();                                                      // Reset The View
    gl.glTranslatef(0.0f,0.0f,-5.0f);
    
    gl.glEnable(GL.GL_BLEND);
    gl.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE_MINUS_SRC_ALPHA);
    
    gl.glColor4f(0f,0f,0f,1f);
    gl.glBegin( GL.GL_TRIANGLES );
      gl.glVertex3f( 0.0f, 0.0f, 0.0f );
      gl.glVertex3f( 1.0f, 0.0f, 0.0f );
      gl.glVertex3f( 0.0f, 1.0f, 0.0f );
    gl.glEnd();
    
    gl.glTranslatef(.5f,0.0f,0.0f);
    
    gl.glColor4f(0f,0f,0f,.5f);
    gl.glBegin( GL.GL_TRIANGLES );
      gl.glVertex3f( 0.0f, 0.0f, 0.0f );
      gl.glVertex3f( 1.0f, 0.0f, 0.0f );
      gl.glVertex3f( 0.0f, 1.0f, 0.0f );
    gl.glEnd();
    
    gl.glLoadIdentity();                                                      // Reset The View

        
    // Because opengl's origin is the lower left corner and java's origin
    // is the upper right corner that image will be flipped upside down.
    gl.glReadBuffer( GL.GL_FRONT );
    gl.glReadPixels( 0, 0, maxPbufferWidth, maxPbufferHeight, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, dbInt.getData() );
    gl.glFlush();
  }
  
  public void reshape( GLDrawable drawable, int x, int y, int width, int height ) {
    GL gl = drawable.getGL();
    GLU glu = drawable.getGLU();
    
    if (height <= 0) // avoid a divide by zero error!
        height = 1;
    final float h = (float)width / (float)height;
    gl.glViewport(0, 0, width, height);
    gl.glMatrixMode(GL.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluPerspective(45.0f, h, 1.0, 20.0);
    gl.glMatrixMode(GL.GL_MODELVIEW);
    gl.glLoadIdentity();
  }
  
  public static void main( String[] args ) {
    PBDemo demo = new PBDemo();
  }
}

The second triangle is 50% opaque and should not appear over the black 100% opaque triangle.

opengl:

java2d:

Thats… odd.

I tried that myself (with a quick conversion to just render direct into a LWJGL framebuffer) and it works as expected (ie the Java2d one). Nothing looks wrong from the code.

So I guess that leaves some oddity in either:

  • pbuffers
  • your drivers
  • Jogl
    My first guess would be some pbuffer irregularity, try it into a framebuffer and see if that helps.

Incidentally, why are you asking for help with Jogl code in the LWJGL forum? ???

The output is correct. The blend function is incorrect for the result you’re aiming for. Looking at the spec for AlphaComposite.SRC_OVER and the OpenGL red book, the blend function you want is glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA).

This is GL_ONE, GL_ONE_MINUS_SRC_ALPHA.

Orangy, can you post your code ? And yes, I’ve used Jogl (shame on me ;)) just to compare my LWJGL results…

Maybe that’s a driver issue. Don’t know… I’m with a NVidia Fx5700 (71.89, XP). Maybe someone can try the above src on ATI ?

Here we are:


            if (rule == AlphaComposite.SRC_OVER)
            {
                  // the best AlphaComposite.SRC_OVER combination
                  EXTBlendFuncSeparate.glBlendFuncSeparateEXT
                  (
                              GL11.GL_SRC_ALPHA,
                              GL11.GL_ONE_MINUS_SRC_ALPHA,
                              GL11.GL_ONE,
                              GL11.GL_ONE_MINUS_SRC_ALPHA
                  );
            }

Always remember to clear the Pbuffer with:


GL11.glClearColor(1f, 1f, 1f, 0f);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

and you get:

which is 1:1 the same with the Java2D counterpart.

Cheers!

… and finally I have the first stable implementation of SGL, my OpenGL accelerated java2d wrapper, robust enough to animate Castalia projects at least 200% faster, including pixels readback to bufferedimage. (goes up to 10-20 times better with other sample projects).

Java2D

LWJGL (SGL)

My special thanks fly to the LWJGL team!