GL_SAMPLE_ALPHA_TO_COVERAGE?

I am looking at the OpenGL Wiki entry on alpha blending here:

http://www.opengl.org/wiki/index.php/Alpha_Blending

The GL_SAMPLE_ALPHA_TO_COVERAGE section is not written yet. Can anybody give me some information on what direction that was going and how it relates to solving the blending problems discussed in the article? I’m having trouble finding any more info on the internet, there’s lots of info about multisampling but I can’t make it relate to that article and I’m having trouble learning how “coverage” works – plus, ironically, the first Google result for “GL_SAMPLE_ALPHA_TO_COVERAGE example” is that very wiki page.

Mostly I’m just looking for some good Google keywords.

Thanks,
Jason

This is a demo that uses it and describes briefly how it works:
http://www.humus.ca/index.php?page=3D&ID=61

As far as I know, you can use it for an order-independent transparency, see this sample: http://www.codesampler.com/oglsrc/oglsrc_14.htm#ogl_multisample_transparency
However there is a bug discussed here: http://www.codesampler.com/phpBB2/viewtopic.php?p=4743#4743

It is also good for the antialiasing of alpha-tested edges, see AlphaToCoverage demo here: http://ati.amd.com/developer/SDK/Samples_Documents.html

EDIT: sqrt[-1] was faster :stuck_out_tongue:

Thanks for your replies; I am interested more in order-independent transparency.

Well, I’m not using Windows so I couldn’t get the example to compile as-is, but I worked through it and there’s not much being done (it looks like all it does is enable multisampling and GL_SAMPLE_TO_ALPHA_COVERAGE and then somehow magic happens).

I made a new test program that just does this. On Linux, so with GLX I request:

GLX_RGBA,
GLX_DOUBLEBUFFER,
GLX_ALPHA_SIZE, 1,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
GLX_DEPTH_SIZE, 1,
GLX_SAMPLE_BUFFERS, 1,
GLX_SAMPLES, 4,

It succeeds and gives me the visual I asked for. (I verified that multisampling does indeed work with a different test program.) Then I set up GL like so:

  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_COLOR_MATERIAL);
  glEnable(GL_MULTISAMPLE_ARB);
  glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);
  glClearColor(0.0, 0.0, 0.0, 1.0);

GL_BLEND is not enabled, and GL_DEPTH_TEST is enabled – it also appears to be this way in that example. Also the clear color alpha component is 1.0.

And each time I render a frame:

  glColor4f(1.0, 1.0, 0.0, 0.25);
  RenderSurfaceModel();

Where RenderSurfaceModel just draws a bunch of triangles.

When I do this, no magic happens. Instead, I see the object as normal with no transparency at all but, depending on the alpha amount, it’s color is multiplied by it’s alpha component (setting blend function has no effect) and it’s also dithered with the background showing through (background shows through 1 pixel of every 2x2 pixel block).

Am I missing something obvious? I’m not really sure I quite understand what’s going on with the sample in the first place. I think I see what it is trying to do – “hack” multi-sampling into “blending” transparent fragments together, but I am not seeing anything special in the example that gets around the normal depth-buffer issues.

Thanks,
Jason

I ported the sample to GLX and it worked, but it only seems to work with textures with transparency, and not with transparent colors. With textures off, just using solid colors, it does that dithering thing.

So, I tried drawing a bunch of triangles using a texture with a partially transparent alpha component, and it did not work like the example. It did blend the color with the background color, but you could not see other triangles through it.

I’m really boggled. I’m looking at this example, I’ve been staring at it for an hour. It does very little, but I can’t figure out why you can still see the far faces of that cube, from every angle, even though GL_DEPTH_TEST is enabled…? What am I missing?

Thanks,
Jason

That is kind of what you should expect. What A2C does is so-called “screen door transparency”, taking multisamples into account. Instead of blending with the background, which would require you to render in back-to-front order, a number of samples proportional to the alpha value are drawn opaque while the rest is not drawn at all.

Because 4x multisampling would give you only 3 transparency levels, the coverage mask is actually dithered over 2x2 pixel blocks in hardware, so you get 15 transparency levels but visible dithering as tradeoff.

You probably get better results with an object with varying alpha taken from a texture.

Thanks for the explanation, it looks like I discovered something similar at the same time you posted.

I have the example working, and it does use a texture. I tried creating a solid colored, transparent texture to use when drawing triangles, instead of vertex colors, but to no avail (see above post). I can’t figure out why the example works, esp. with depth testing enabled, but my own code does not.

Sorry, but I don’t quite understand what’s wrong from your description.

One thing you have to be aware of is that the dithering is deterministic. If you draw the same triangle twice using the same alpha values but different colors, both will cover the same samples and only one of them and the background will be visible.

I guess I described what I saw, poorly. I’m also getting ahead of myself. First, I don’t understand how the example works. Take a look at the sample Eosie linked to above:

http://www.codesampler.com/oglsrc/oglsrc_14.htm#ogl_multisample_transparency

If you are using X+GLX here is a replacement ogl_multisample_transparency.cpp:


//------------------------------------------------------------------------------
//           Name: ogl_multisample_transparency.cpp
//         Author: Kevin Harris
//  Last Modified: 12/13/07
//    Description: This sample demonstrates how to use multi-sample transparency 
//                 to skip the typically necessary step of sorting all 
//                 transparent geometry back to front. See the related sample
//                 "ogl_alpha_blending_texture" for the more typical setup which
//                 does require sorting.
//
//   Control Keys: b - Toggle blending via multi-sample transparency
//                 Up Arrow - Move the test cube closer
//                 Down Arrow - Move the test cube away
//
// NOTE: This sample will not work correctly unless the driver has been set to
//       use Antialiasing or Multisample Transparency. I can't tell you how to 
//       do this since every driver control panel does it a little differently.
//       For my particular nVIDIA card, I need to set the Antialias level to 4X 
//       to get the sample to work correctly.
//
// --------------------------
// This file is not the original code by the above author. This has been ported
// to use X+GLX (Jason Cipriani).
//------------------------------------------------------------------------------

#include <X11/Xlib.h>
#include <GL/glx.h>
#include <GL/glu.h>
#include <stdio.h>
#include <stdlib.h>
#include "tga.h"

//-----------------------------------------------------------------------------
// GLOBALS
//-----------------------------------------------------------------------------
Display   *g_XDisp;
int        g_XScreen;
Window     g_XWin;
GLXContext g_GLX;

GLuint g_textureID = -1;

bool g_bBlending = true;

float g_fDistance = -4.5f;
float g_fSpinX    = 0.0f;
float g_fSpinY    = 0.0f;

struct POINT { int x, y; };

struct Vertex
{
    float tu, tv;
    float x, y, z;
};

Vertex g_cubeVertices[] =
{
    { 0.0f,0.0f, -1.0f,-1.0f, 1.0f },
    { 1.0f,0.0f,  1.0f,-1.0f, 1.0f },
    { 1.0f,1.0f,  1.0f, 1.0f, 1.0f },
    { 0.0f,1.0f, -1.0f, 1.0f, 1.0f },
   
    { 1.0f,0.0f, -1.0f,-1.0f,-1.0f },
    { 1.0f,1.0f, -1.0f, 1.0f,-1.0f },
    { 0.0f,1.0f,  1.0f, 1.0f,-1.0f },
    { 0.0f,0.0f,  1.0f,-1.0f,-1.0f },
   
    { 0.0f,1.0f, -1.0f, 1.0f,-1.0f },
    { 0.0f,0.0f, -1.0f, 1.0f, 1.0f },
    { 1.0f,0.0f,  1.0f, 1.0f, 1.0f },
    { 1.0f,1.0f,  1.0f, 1.0f,-1.0f },
   
    { 1.0f,1.0f, -1.0f,-1.0f,-1.0f },
    { 0.0f,1.0f,  1.0f,-1.0f,-1.0f },
    { 0.0f,0.0f,  1.0f,-1.0f, 1.0f },
    { 1.0f,0.0f, -1.0f,-1.0f, 1.0f },
   
    { 1.0f,0.0f,  1.0f,-1.0f,-1.0f },
    { 1.0f,1.0f,  1.0f, 1.0f,-1.0f },
    { 0.0f,1.0f,  1.0f, 1.0f, 1.0f },
    { 0.0f,0.0f,  1.0f,-1.0f, 1.0f },
   
    { 0.0f,0.0f, -1.0f,-1.0f,-1.0f },
    { 1.0f,0.0f, -1.0f,-1.0f, 1.0f },
    { 1.0f,1.0f, -1.0f, 1.0f, 1.0f },
    { 0.0f,1.0f, -1.0f, 1.0f,-1.0f }
};


//-----------------------------------------------------------------------------
// PROTOTYPES
//-----------------------------------------------------------------------------
void loadTexture();
void init();
void render();
void shutDown();


//-----------------------------------------------------------------------------
// Name: main()
// Desc: The application's entry point
//-----------------------------------------------------------------------------
int main (int argc, char **argv) {

  XSetWindowAttributes attrs;
  XEvent event;
  XVisualInfo *vi;
  POINT ptLastMousePosit;
  POINT ptCurrentMousePosit;
  bool bMousing;

  g_XDisp = XOpenDisplay(NULL);
  g_XScreen = DefaultScreen(g_XDisp);

  g_XWin = XCreateWindow(g_XDisp, XRootWindow(g_XDisp, g_XScreen), 0, 0, 640, 
			 480, 0, DefaultDepth(g_XDisp, g_XScreen), InputOutput, 
			 DefaultVisual(g_XDisp, g_XScreen), 0, &attrs);

  XSelectInput(g_XDisp, g_XWin, ExposureMask | ButtonPressMask | ButtonReleaseMask | Button1MotionMask | KeyPressMask);
  XMapWindow(g_XDisp, g_XWin);

  init();

  do {
    if (XPending(g_XDisp) > 0) {
      XNextEvent(g_XDisp, &event);
      if (event.type == KeyPress) {
	int n = XKeycodeToKeysym(g_XDisp, event.xkey.keycode, (event.xkey.state & ShiftMask) ? 1 : 0);
	if (n == 65307) // escape
	  break;
	else if (n == 'b' || n == 'B')
	  g_bBlending = !g_bBlending;
	else if (n == 65362) // up
	  g_fDistance += 0.1f;
	else if (n == 65364) // down
	  g_fDistance -= 0.1f;
      } else if (event.type == ButtonPress && event.xbutton.button == Button1) {
	ptLastMousePosit.x = ptCurrentMousePosit.x = event.xbutton.x;
	ptLastMousePosit.y = ptCurrentMousePosit.y = event.xbutton.y;
	bMousing = true;
      } else if (event.type == ButtonRelease && event.xbutton.button == Button1) {
	bMousing = false;
      } else if (event.type == MotionNotify) {
	ptCurrentMousePosit.x = event.xmotion.x;
	ptCurrentMousePosit.y = event.xmotion.y;
	if( bMousing ) {
	  g_fSpinX -= (ptCurrentMousePosit.x - ptLastMousePosit.x);
	  g_fSpinY -= (ptCurrentMousePosit.y - ptLastMousePosit.y);
	}
	ptLastMousePosit.x = ptCurrentMousePosit.x;
	ptLastMousePosit.y = ptCurrentMousePosit.y;
      }
    } else
      render();
  } while (1);

  shutDown();

  return 0;

}


//-----------------------------------------------------------------------------
// Name: loadTexture()
// Desc: 
//-----------------------------------------------------------------------------
void loadTexture () {

  tgaImageFile tgaImage;
  tgaImage.load( "radiation_box.tga" );
  
  glGenTextures( 1, &g_textureID );
  
  glBindTexture( GL_TEXTURE_2D, g_textureID );
  
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  
  glTexImage2D( GL_TEXTURE_2D, 0, tgaImage.m_texFormat, 
		tgaImage.m_nImageWidth, tgaImage.m_nImageHeight, 
		0, tgaImage.m_texFormat, GL_UNSIGNED_BYTE, 
		tgaImage.m_nImageData );

}

//-----------------------------------------------------------------------------
// Name: init()
// Desc: 
//-----------------------------------------------------------------------------
void init () {

  int attrs[] = {
    GLX_RGBA,
    GLX_RED_SIZE, 8,
    GLX_GREEN_SIZE, 8,
    GLX_BLUE_SIZE, 8,
    GLX_ALPHA_SIZE, 8,
    GLX_DEPTH_SIZE, 24,
    GLX_DOUBLEBUFFER,
    GLX_SAMPLE_BUFFERS_ARB, 1,
    GLX_SAMPLES_ARB, 4,
    None
  };

  XVisualInfo *vi;

  if (!(vi = glXChooseVisual(g_XDisp, g_XScreen, attrs))) {
    fprintf(stderr, "Could not find an acceptable pixel format!
");
    exit(1);
  }

  if (!(g_GLX = glXCreateContext(g_XDisp, vi, NULL, True))) {
    fprintf(stderr, "Could not create GLX context!
");
    exit(1);
  }

  glXMakeCurrent(g_XDisp, g_XWin, g_GLX);

  loadTexture();

  glClearColor( 0.35f, 0.53f, 0.7f, 1.0f );
  glEnable( GL_TEXTURE_2D );
  //glColor4f(1.0, 1.0, 1.0, 0.25);
  
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluPerspective( 45.0f, 640.0f / 480.0f, 0.1f, 100.0f);

}


//-----------------------------------------------------------------------------
// Name: shutDown()
// Desc: 
//-----------------------------------------------------------------------------
void shutDown () {

  glDeleteTextures(1, &g_textureID);
  glXMakeCurrent(g_XDisp, None, NULL);
  glXDestroyContext(g_XDisp, g_GLX);

}


//-----------------------------------------------------------------------------
// Name: render()
// Desc: 
//-----------------------------------------------------------------------------
void render () {

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glTranslatef( 0.0f, 0.0f, g_fDistance );
    glRotatef( -g_fSpinY, 1.0f, 0.0f, 0.0f );
    glRotatef( -g_fSpinX, 0.0f, 1.0f, 0.0f );

    if( g_bBlending == true )
	{
        glEnable( GL_DEPTH_TEST );
        glEnable( GL_MULTISAMPLE_ARB );
        glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE_ARB );

        glBindTexture( GL_TEXTURE_2D, g_textureID );
        glInterleavedArrays( GL_T2F_V3F, 0, g_cubeVertices );
        glDrawArrays( GL_QUADS, 0, 24 );
    }
    else
    {
        glDisable( GL_MULTISAMPLE_ARB );
        glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE_ARB );

        glBindTexture( GL_TEXTURE_2D, g_textureID );
        glInterleavedArrays( GL_T2F_V3F, 0, g_cubeVertices );
        glDrawArrays( GL_QUADS, 0, 24 );
    }

    glXSwapBuffers(g_XDisp, g_XWin);

}

And here is a Makefile:

BIN  = ogl_multisample_transparency
SRC  = ogl_multisample_transparency.cpp
LIBS = -lX11 -lGL -lGLU

$(BIN): $(SRC)
	g++ $(SRC) $(LIBS) -o $(BIN)

clean:
	rm -f *~ $(BIN)

When you run it you should see a box with a semi-transparent texture on each face. You should be able to see through the box to the texture on the other side. This should work correctly no matter what orientation the box is in (use the mouse to rotate). Forget everything I said about dithering, triangles, and vertex colors for now. I have a basic question before anything else:

Can you tell me why I am able to see the far face of the cube through the near face of the cube even when GL_DEPTH_TEST is enabled? Why is the far face being rendered (regardless of orientation i.e. regardless of drawing order) even when it should be failing the depth test and be obscured / overwritten by the nearest faces?

Thanks,
Jason

P.S. Here is a replacement render() function for that example that shows the difference between using A2C+multisample vs. normal GL_BLEND:

void render () {

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glTranslatef( 0.0f, 0.0f, g_fDistance );
    glRotatef( -g_fSpinY, 1.0f, 0.0f, 0.0f );
    glRotatef( -g_fSpinX, 0.0f, 1.0f, 0.0f );

    if( g_bBlending == true )
	{
        glEnable( GL_DEPTH_TEST );
        glEnable( GL_MULTISAMPLE_ARB );
        glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE_ARB );

	glDisable(GL_BLEND);

        glBindTexture( GL_TEXTURE_2D, g_textureID );
        glInterleavedArrays( GL_T2F_V3F, 0, g_cubeVertices );
        glDrawArrays( GL_QUADS, 0, 24 );
    }
    else
    {
        glDisable( GL_MULTISAMPLE_ARB );
        glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE_ARB );

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        glBindTexture( GL_TEXTURE_2D, g_textureID );
        glInterleavedArrays( GL_T2F_V3F, 0, g_cubeVertices );
        glDrawArrays( GL_QUADS, 0, 24 );
    }

    glXSwapBuffers(g_XDisp, g_XWin); // or the other one for windows sample

}

Use ‘b’ to toggle between the two modes. Note that with GL_BLEND, as expected, depth testing prevents some faces from being rendered. Does enabling GL_MULTISAMPLE mess with depth testing or something…?

[quote=JCipriani2]Can you tell me why I am able to see the far face of the cube through the near face of the cube even when GL_DEPTH_TEST is enabled? Why is the far face being rendered (regardless of orientation i.e. regardless of drawing order) even when it should be failing the depth test and be obscured / overwritten by the nearest faces?/quote]
it shouldnt, if it is then its a bug + should be reportd as such

(edit)
>>Note that with GL_BLEND, as expected, depth testing prevents some faces from being rendered.
blend shouldnt alter whats get rendered or not, as its a test that has no fail routin

Just out of curiosity, if you try running that demo (if possible), what do you see? Do you see a 6-sided cube with a transparent texture on each side, and you can see through to the other side of the cube no matter which way you rotate it? I.e. is the “order-independent transparency” working in that demo? If it is then you are experiencing the same bug.

I just wrote my own program to test different methods of handling transparency, and I am seeing the same thing – with GL_MULTISAMPLE + GL_SAMPLE_ALPHA_TO_COVERAGE + GL_DEPTH_TEST enabled and GL_BLEND disabled, depth testing works properly but at the same time you can still see through transparent faces to what’s behind them regardless of the order you draw things in (which is what the demo is trying to show I think). I am using an ATI Mobility Radeon X1400 with ATI Linux driver version 2.1.7412. This is what I see in that demo regardless of how I orient the box – note that GL_BLEND is disabled and GL_DEPTH_TEST is enabled but you still always see the far face through the near face (even though the color is not quite right it’s pretty close), except it’s not like the depth buffer is screwed up (far away objects aren’t drawn on top of opaque nearer ones), things look like you’d want them to look:

And here with GL_BLEND enabled and multisampling + A2C disabled, the depth buffer works as expected:

And in my own program (sorry about the warped screenshots, it was animated but the screenshot feature doesn’t work very well):

With A2C + multisampling:

With GL_BLEND:

It definitely appears that turning on multisampling somehow affects the depth buffer. It also appears that this is exactly what the demo was trying to show. Plus, it works exactly they way I want it too. Are you sure it is a bug? If it is that is really disappointing, because this is precisely the effect I want…

Jason

The depth testing is performed on a per-sample basis. Let’s assume we use 4 samples and the alpha value of the transparent parts is 0.5

e.g if you render the front face first, the transparent part of the texture will only cover 2 samples and set the depth value of those 2 samples to Z-front, the depth value of the other 2 samples remain untouched. If you render the back face next, the opaque part of the back face covers 4 samples, but two of them will fail due to the depth test so the result is 2 samples of the front face and two samples of the backface, thereby making it appear transparent.

Because of the dithering. The front face is not rendered as a solid surface, but as a surface with lots of tiny holes through which you can see the background. Hence the name “screen door transparency”.

Only a number of multisamples proportional to the alpha value are actually overwritten in the frame-/Z-buffer, the remaining samples act as sample-sized holes.

If your own screenshot-function is broken ATM, try out Fraps, it should be able to grab only single frames.

Jan.

Thanks -NiCo- and Xmas for explaining that. That clears everything up, I had this epiphany and the entire example made sense. As it turns out, while A2C for transparency is a neat trick that I’ll have to file away for later, it’s very limited in what it can do and doesn’t really produce good results except in very constrained situations – particularly I’ve noticed that alpha values must be a multiple of 1/numsamples to avoid visible dithering, and faces always obscure other faces with the same coverage value (I guess because the fragments occupy the same locations in the “screen door”).

Jan: Thanks for the Fraps tip. I’m actually using Linux right now but I’m sure there’s an alternative to the default screen shot thing. The built-in one really sucks, as you can see. :slight_smile:

Thanks for answering all these questions,
Jason