Blending

I have the following code that displays a red and a green sphere in orthographic view with the green sphere shifted a bit in X. I used the depth function always to get an idea of the shift in case it changes, no other depth function gave me a correct result. Anyway what I need o do more is to have the color of the common part with a blend of red and green that is yellow, I set an alpha =0.5 to both colors but to no avail… Any idea how this can be achieved and would it be then necessary to change the depth function?
Any solution involving fragment shaders is OK…



#include <GL/freeglut.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

double radius = 1.0;
double trans = 0.1;
int delay = 160;
int sd = 1;
int sphere;

void computeLocation(double r) {

    glMatrixMode(GL_PROJECTION);        // Set projection parameters.
    glLoadIdentity();
    glOrtho(-radius-trans , radius+trans , -radius-trans , radius+trans , -radius - trans , radius + trans);
}

void drawSphere(double r, int lats, int longs) {
    int i, j;
    for(i = 0; i <= lats; i++) {
      double lat0 = M_PI * (-0.5 + (double) (i - 1) / lats);
        double z0  = sin(lat0);
        double zr0 =  cos(lat0);

        double lat1 = M_PI * (-0.5 + (double) i / lats);
        double z1 = sin(lat1);
        double zr1 = cos(lat1);

        glBegin(GL_QUAD_STRIP);
        for(j = 0; j <= longs; j++) {
            double lng = 2 * M_PI * (double) (j - 1) / longs;
            double x = cos(lng);
            double y = sin(lng);

            glNormal3f(x * zr0, y * zr0, z0);
            glVertex3f(r*x * zr0, r*y * zr0, r*z0);
            glNormal3f(x * zr1, y * zr1, z1);
            glVertex3f(r*x * zr1, r*y * zr1, r*z1);
        }
        glEnd();
    }
}

// Initializes information for drawing within OpenGL.
void init() {
    glClearColor(1.0, 1.0, 1.0, 0.0);   // Set window color to white.

    glEnable(GL_DEPTH_TEST);            // Draw only closest surfaces
    glDepthFunc(GL_ALWAYS);
    computeLocation(radius);
}

// Draws the current image.
void draw() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear window.
    glShadeModel(GL_SMOOTH);
    if (sd==1||sd==3 || (sd==2 && sphere==1)) {
      glColor4f(1.0, 0.0, 0.0,0.5);
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      drawSphere(radius, 40, 40); // glutSolidSphere(1.0, 10, 10);
    }
    if (sd==3 || (sd==2 && sphere==2)) {
      glColor4f(0.0, 1.0, 0.0,0.5);
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glTranslated(trans, 0.0, 0.0);
      drawSphere(radius, 40, 40);
    }
    glutSwapBuffers();
}

// Arranges that the window will be redrawn roughly every 40 ms.
void idle() {
    static int lastTime = 0;                // time of last redraw
    int time = glutGet(GLUT_ELAPSED_TIME);  // current time

    if(lastTime == 0 || time >= lastTime + delay) {
        lastTime = time;
	if (sd==2)
          if (sphere==1) sphere=2;
	  else sphere=1;
        glutPostRedisplay();
    }
}

// When window becomes visible, we want the window to
// continuously repaint itself.
void visible(int vis) {
    glutIdleFunc(vis == GLUT_VISIBLE ? idle : NULL);
}

void keyboard (unsigned char k, int x, int y)
{
  switch (k) {
  case '1': sd = 1;break;
  case '2': sd = 2;sphere=1;break;
  case '3': sd = 3;
  }
}

int main(int argc, char **argv) {
  //    radius = strtod(argv[1], NULL);
    if (argc==2) {
       delay = strtod(argv[1], NULL);
    }
    else if (argc==3) {
		int t = strtod(argv[1], NULL);
		if (t > 0) delay = t;
        trans = strtod(argv[2], NULL);
    }	
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition(50, 100);    // Set up display window.
    glutInitWindowSize(600, 600);
    glutCreateWindow("Sphere");

    init();
    glutDisplayFunc(draw);
    glutVisibilityFunc(visible);
    glutKeyboardFunc(keyboard);
    glutMainLoop();
    return 0;
}


The alpha component doesn’t have any effect unless you enable blending (glEnable(GL_BLEND)) and select a blend function which uses it.

It sounds as if you want additive blending, i.e. glBlendFunc(GL_ONE,GL_ONE). Note that this doesn’t use the alpha component.

Depth tests need to be disabled if you want both spheres to be rendered in the region where they intersect (using a depth function of GL_ALWAYS is equivalent to disabling depth tests).

Thanks for the informative reply, yes indeed it works but I should add that the background color has to be black for it to work fine. A white background messes up things…

If you need to do this over a non-black background, there are a number of options, including:

[ul]
[li] Render the spheres into a texture (with an alpha channel) then render that over the background with normal overlay blending, i.e. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).
[/li][li] Use two passes and stencilling. Clear the stencil buffer, render the spheres into the stencil buffer, clear the stencilled area to black, render the spheres with additive blending as before.
[/li][li] Use glColorMask() so that the first sphere is only rendered into the red component and the second sphere into the green component.
[/li][/ul]

The last option is the simplest but also the least flexible.

I do not think glColorMask () can be used easily as you mentioned since when rendering the second green ball I want to hold off the red component over the first red ball but want to write the red component as zero over the background

Anyway I went with the second option but without a stencil buffer. I drew the spheres after setting color to black, then drew again into frame buffer with blending GL_ONE GL_ONE.
I got the first sphere right in red but the second sphere turns into yellow even in areas outside sphere 1, cannot figure out why?

Here is my code:



#include <GL/freeglut.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

double radius = 1.0;
double trans = 0.1;
int delay = 160;
int sd = 1;
int sphere;
int redc = 0;
int greenc = 0;

void computeLocation(double r) {

    glMatrixMode(GL_PROJECTION);        // Set projection parameters.
    glLoadIdentity();
    glOrtho(-radius-trans , radius+trans , -radius-trans , radius+trans , -radius - trans , radius + trans);
}

void drawSphere(double r, int lats, int longs) {
    int i, j;
    for(i = 0; i <= lats; i++) {
      double lat0 = M_PI * (-0.5 + (double) (i - 1) / lats);
        double z0  = sin(lat0);
        double zr0 =  cos(lat0);

        double lat1 = M_PI * (-0.5 + (double) i / lats);
        double z1 = sin(lat1);
        double zr1 = cos(lat1);

        glBegin(GL_QUAD_STRIP);
        for(j = 0; j <= longs; j++) {
            double lng = 2 * M_PI * (double) (j - 1) / longs;
            double x = cos(lng);
            double y = sin(lng);

            glNormal3f(x * zr0, y * zr0, z0);
            glVertex3f(r*x * zr0, r*y * zr0, r*z0);
            glNormal3f(x * zr1, y * zr1, z1);
            glVertex3f(r*x * zr1, r*y * zr1, r*z1);
        }
        glEnd();
    }
}

// Initializes information for drawing within OpenGL.
void init() {
    glClearColor(1.0, 1.0, 1.0, 1.0);   // Set window color to white.

    glEnable(GL_DEPTH_TEST);            // Draw only closest surfaces
    glDepthFunc(GL_ALWAYS);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glFrontFace(GL_CW);

    computeLocation(radius);
}

// Draws the current image.
void draw() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear window.
    glShadeModel(GL_SMOOTH);

    glColor4f(0.0, 0.0, 0.0, 1.0);
    if (sd==1||sd==3 || (sd==2 && sphere==1)) {
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      drawSphere(radius, 40, 40); // glutSolidSphere(1.0, 10, 10);
    }
    if (sd==3 || (sd==2 && sphere==2)) {
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glTranslated(trans, 0.0, 0.0);
      drawSphere(radius, 40, 40);
    }

    glDisable(GL_BLEND);
    if (sd==1||sd==3 || (sd==2 && sphere==1)) {
      if (greenc) {
		  glColor4f(0.0, 1.0, 0.0, 1.0);
	  }
	  else {
		  glColor4f(1.0, 0.0, 0.0,1.0);
	  }
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      drawSphere(radius, 40, 40); // glutSolidSphere(1.0, 10, 10);
    }
	
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE); 
    if (sd==3 || (sd==2 && sphere==2)) {
      if (redc) {
	      glColor4f(1.0, 0.0, 0.0, 1.0);
	  }
	  else {
		  glColor4f(0.0, 1.0, 0.0,1.0);
	  }
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glTranslated(trans, 0.0, 0.0);
      drawSphere(radius, 40, 40);
    }

    glutSwapBuffers();
}

// Arranges that the window will be redrawn roughly every 40 ms.
void idle() {
    static int lastTime = 0;                // time of last redraw
    int time = glutGet(GLUT_ELAPSED_TIME);  // current time

    if(lastTime == 0 || time >= lastTime + delay) {
        lastTime = time;
	if (sd==2)
          if (sphere==1) sphere=2;
	  else sphere=1;
        glutPostRedisplay();
    }
}

// When window becomes visible, we want the window to
// continuously repaint itself.
void visible(int vis) {
    glutIdleFunc(vis == GLUT_VISIBLE ? idle : NULL);
}

void keyboard (unsigned char k, int x, int y)
{
  switch (k) {
  case '1': sd = 1;break;
  case '2': sd = 2;sphere=1;break;
  case '3': sd = 3;break;
  case 'r': redc = 1;greenc = 0;break;
  case 'g': greenc = 1;redc = 0;break;
  case 't': greenc = redc = 0;
  }
}

int main(int argc, char **argv) {
  //    radius = strtod(argv[1], NULL);
    if (argc==2) {
       delay = strtod(argv[1], NULL);
    }
    else if (argc==3) {
		int t = strtod(argv[1], NULL);
		if (t > 0) delay = t;
        trans = strtod(argv[2], NULL);
    }	
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition(50, 100);    // Set up display window.
    glutInitWindowSize(600, 600);
    glutCreateWindow("Sphere");

    init();
    glutDisplayFunc(draw);
    glutVisibilityFunc(visible);
    glutKeyboardFunc(keyboard);
    glutMainLoop();
    return 0;
}


Correction, I noticed that the second sphere does get drawn in areas outside the first red sphere. When I set background to black everything works fine. I am puzzled!?

Never mind I found the problem, I should have disabled blending before drawing in black instead of after it…




// Draws the current image.
void draw() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear window.
    glShadeModel(GL_SMOOTH);

    glDisable(GL_BLEND);
    glColor4f(0.0, 0.0, 0.0, 1.0);
    if (sd==1||sd==3 || (sd==2 && sphere==1)) {
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      drawSphere(radius, 40, 40); // glutSolidSphere(1.0, 10, 10);
    }
    if (sd==3 || (sd==2 && sphere==2)) {
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glTranslated(trans, 0.0, 0.0);
      drawSphere(radius, 40, 40);
    }
   
    if (sd==1||sd==3 || (sd==2 && sphere==1)) {
      if (greenc) {
		  glColor4f(0.0, 1.0, 0.0, 1.0);
	  }
	  else {
		  glColor4f(1.0, 0.0, 0.0,1.0);
	  }
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      drawSphere(radius, 40, 40); // glutSolidSphere(1.0, 10, 10);
    }
	
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE); 
    if (sd==3 || (sd==2 && sphere==2)) {
      if (redc) {
	      glColor4f(1.0, 0.0, 0.0, 1.0);
	  }
	  else {
		  glColor4f(0.0, 1.0, 0.0,1.0);
	  }
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glTranslated(trans, 0.0, 0.0);
      drawSphere(radius, 40, 40);
    }

    glutSwapBuffers();
}