Posts Tagged ‘glut’

Depth peeling mini-app

Monday, May 4th, 2015

I’ve been lamenting poor transparency handling in opengl for as long as I’ve used opengl. From a graphics programmer’s perspective, it’s just so frustrating that you can’t just:

glEnable(GL_ALPHA_BLEND_CORRECTLY);

Finally I’ve gotten around to implementing a correct solution. The idea is simple, and, by now, well-known. Render the scene k times. For each pass keep track of the color and depth of the fragment with the smallest depth value which is less than the previous pass’s. In this way, each pass peels off a layer of pixels from front-to-back. Finally render all of these as composite image from back-to-front with the usual alpha blending.

If k is more than the maximum number of overlapping transparent objects then this will give the correct result. In practice if the alpha values aren’t all tiny (everything’s only a bit transparent). Then only a few passes are needed.

Here’s a little 280-line glut app demonstrating this:

// make sure the modern opengl headers are included before any others
#include <OpenGL/gl3.h>
#define __gl_h_
#include <igl/frustum.h>
#include <igl/read_triangle_mesh.h>
#include <igl/opengl/create_shader_program.h>
#include <Eigen/Core>
#include <GLUT/glut.h>
#include <string>

void init_render_to_texture(
  const size_t w, const size_t h, GLuint & tex, GLuint & dtex, GLuint & fbo)
{
  const auto & gen_tex = [](GLuint & tex)
  {
    // http://www.opengl.org/wiki/Framebuffer_Object_Examples#Quick_example.2C_render_to_texture_.282D.29
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  };
  // Generate texture for colors and attached to color component of framebuffer
  gen_tex(tex);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w, h, 0, GL_BGRA, GL_FLOAT, NULL);
  glBindTexture(GL_TEXTURE_2D, 0);
  glGenFramebuffers(1, &fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, fbo);
  // Generate texture for depth and attached to depth component of framebuffer
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
  gen_tex(dtex);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dtex, 0);
  // Clean up
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
  glBindTexture(GL_TEXTURE_2D,0);
}


// For rendering a full-viewport quad, set tex-coord from position
std::string tex_v_shader = R"(
#version 330 core
in vec3 position;
out vec2 tex_coord;
void main()
{
  gl_Position = vec4(position,1.);
  tex_coord = vec2(0.5*(position.x+1), 0.5*(position.y+1));
}
)";
// Render directly from color or depth texture
std::string tex_f_shader = R"(
#version 330 core
in vec2 tex_coord;
out vec4 color;
uniform sampler2D color_texture;
uniform sampler2D depth_texture;
uniform bool show_depth;
void main()
{
  vec4 depth = texture(depth_texture,tex_coord);
  // Mask out background which is set to 1
  if(depth.r<1)
  {
    color = texture(color_texture,tex_coord);
    if(show_depth)
    {
      // Depth of background seems to be set to exactly 1.
      color.rgb = vec3(1,1,1)*(1.-depth.r)/0.006125;
    }
  }else
  {
    discard;
  }
}
)";

// Pass-through vertex shader with projection and model matrices
std::string scene_v_shader = R"(
#version 330 core
uniform mat4 proj;
uniform mat4 model;
in vec3 position;
void main()
{
  gl_Position = proj * model * vec4(position,1.);
}
)";
// Render if first pass or farther than closest frag on last pass
std::string scene_f_shader = R"(
#version 330 core
out vec4 color;
uniform bool first_pass;
uniform float width;
uniform float height;
uniform sampler2D depth_texture;
void main()
{
  color = vec4(0.8,0.4,0.0,0.75);
  color.rgb *= (1.-gl_FragCoord.z)/0.006125;
  if(!first_pass)
  {
    vec2 tex_coord = vec2(float(gl_FragCoord.x)/width,float(gl_FragCoord.y)/height);
    float max_depth = texture(depth_texture,tex_coord).r;
    if(gl_FragCoord.z <= max_depth)
    {
      discard;
    }
  }
}
)";

// shader id, vertex array object
GLuint scene_p_id=0,tex_p_id;
GLuint VAO,QVAO;
// Number of passes
#define k 4
GLuint tex_id[k],dtex_id[k],fbo_id[k];
// full width/height of window, width/height of viewports
int full_w=1440,full_h=480,w=full_w/(k+2),h=full_h/1;
// Mesh data: RowMajor is important to directly use in OpenGL
typedef Eigen::Matrix< float,Eigen::Dynamic,3,Eigen::RowMajor> MatrixV;
typedef Eigen::Matrix<GLuint,Eigen::Dynamic,3,Eigen::RowMajor> MatrixF;
MatrixV V,QV;
MatrixF F,QF;
int main(int argc, char * argv[])
{
  // Init glut and create window + OpenGL context
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_3_2_CORE_PROFILE|GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH); 
  glutInitWindowSize(full_w,full_h);
  glutCreateWindow("test");
  // Compile shaders
  igl::opengl::create_shader_program(scene_v_shader,scene_f_shader,{},scene_p_id);
  igl::opengl::create_shader_program(tex_v_shader,tex_f_shader,{},tex_p_id);
  // Prepare VAOs
  const auto & vao = [](const MatrixV & V, const MatrixF & F, GLuint & VAO)
  {
    // Generate and attach buffers to vertex array
    glGenVertexArrays(1, &VAO);
    GLuint VBO, EBO;
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float)*V.size(), V.data(), GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*F.size(), F.data(), GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0); 
    glBindVertexArray(0);
  };

  // Read input mesh from file
  igl::read_triangle_mesh(argv[1],V,F);
  V.rowwise() -= V.colwise().mean();
  V /= (V.colwise().maxCoeff()-V.colwise().minCoeff()).maxCoeff();
  vao(V,F,VAO);
  // square
  const MatrixV QV = (MatrixV(4,3)<<-1,-1,0,1,-1,0,1,1,0,-1,1,0).finished();
  const MatrixF QF = (MatrixF(2,3)<< 0,1,2, 0,2,3).finished();
  vao(QV,QF,QVAO);

  // Main display routine
  glutDisplayFunc(
    []()
    {
      // Projection and modelview matrices
      Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
      float near = 0.01;
      float far = 3;
      float top = tan(35./360.*M_PI)*near;
      float right = top * (double)w/(double)h;
      igl::frustum(-right,right,-top,top,near,far,proj);
      Eigen::Affine3f model = Eigen::Affine3f::Identity();
      model.translate(Eigen::Vector3f(0,0,-1.5));
      // spin around
      static size_t count = 0;
      model.rotate(Eigen::AngleAxisf(M_PI/180.*count++,Eigen::Vector3f(0,1,0)));

      glEnable(GL_DEPTH_TEST);
      glViewport(0,0,w,h);
      // select program and attach uniforms
      glUseProgram(scene_p_id);
      GLint proj_loc = glGetUniformLocation(scene_p_id,"proj");
      glUniformMatrix4fv(proj_loc,1,GL_FALSE,proj.data());
      GLint model_loc = glGetUniformLocation(scene_p_id,"model");
      glUniformMatrix4fv(model_loc,1,GL_FALSE,model.matrix().data());
      glUniform1f(glGetUniformLocation(scene_p_id,"width"),w);
      glUniform1f(glGetUniformLocation(scene_p_id,"height"),h);
      glBindVertexArray(VAO);
      glDisable(GL_BLEND);
      for(int pass = 0;pass<k;pass++)
      {
        const bool first_pass = pass == 0;
        glUniform1i(glGetUniformLocation(scene_p_id,"first_pass"),first_pass);
        if(!first_pass)
        {
          glUniform1i(glGetUniformLocation(scene_p_id,"depth_texture"),0);
          glActiveTexture(GL_TEXTURE0 + 0);
          glBindTexture(GL_TEXTURE_2D, dtex_id[pass-1]);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, fbo_id[pass]);
        glClearColor(0.0,0.4,0.7,0.);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawElements(GL_TRIANGLES, F.size(), GL_UNSIGNED_INT, 0);
      }
      // clean up and set to render to screen
      glBindVertexArray(0);
      glBindFramebuffer(GL_FRAMEBUFFER, 0);
      glActiveTexture(GL_TEXTURE0 + 0);
      glBindTexture(GL_TEXTURE_2D,0);

      // Get read to draw quads
      glBindVertexArray(QVAO);
      glClearColor(0.0,0.4,0.7,0.);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glUseProgram(tex_p_id);
      // Draw result of each peel
      for(int pass = 0;pass<k;pass++)
      {
        GLint color_tex_loc = glGetUniformLocation(tex_p_id,"color_texture");
        glUniform1i(color_tex_loc, 0);
        glActiveTexture(GL_TEXTURE0 + 0);
        glBindTexture(GL_TEXTURE_2D, tex_id[pass]);
        GLint depth_tex_loc = glGetUniformLocation(tex_p_id,"depth_texture");
        glUniform1i(depth_tex_loc, 1);
        glActiveTexture(GL_TEXTURE0 + 1);
        glBindTexture(GL_TEXTURE_2D, dtex_id[pass]);
        glViewport(pass*w,0*h,w,h);
        glUniform1i(glGetUniformLocation(tex_p_id,"show_depth"),0);
        glDrawElements(GL_TRIANGLES,6, GL_UNSIGNED_INT, 0);
      }

      // Render final result as composite of all textures
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      glEnable(GL_BLEND);
      glDepthFunc(GL_ALWAYS);
      glViewport(k*w,0*h,w,h);
      glUniform1i(glGetUniformLocation(tex_p_id,"show_depth"),0);
      GLint color_tex_loc = glGetUniformLocation(tex_p_id,"color_texture");
      GLint depth_tex_loc = glGetUniformLocation(tex_p_id,"depth_texture");
      for(int pass = k-1;pass>=0;pass--)
      {
        glUniform1i(color_tex_loc, 0);
        glActiveTexture(GL_TEXTURE0 + 0);
        glBindTexture(GL_TEXTURE_2D, tex_id[pass]);
        glUniform1i(depth_tex_loc, 1);
        glActiveTexture(GL_TEXTURE0 + 1);
        glBindTexture(GL_TEXTURE_2D, dtex_id[pass]);
        glDrawElements(GL_TRIANGLES,6, GL_UNSIGNED_INT, 0);
      }
      glDepthFunc(GL_LESS);
      // Render scene using naive GL_BLEND transparency
      glUseProgram(scene_p_id);
      glBindVertexArray(VAO);
      glViewport((k+1)*w,0*h,w,h);
      glDrawElements(GL_TRIANGLES, F.size(), GL_UNSIGNED_INT, 0);
      glBindVertexArray(0);
      glDisable(GL_BLEND);

      glutSwapBuffers();
      glutPostRedisplay();
    }
    );
  glutReshapeFunc(
    [](int w,int h)
    {
      full_h=h;
      full_w=w;
      ::w=full_w/(k+2);
      ::h=full_h/(1);
      // (re)-initialize textures and buffers
      for(size_t i = 0;i<k;i++)
      {
        init_render_to_texture(::w,::h,tex_id[i],dtex_id[i],fbo_id[i]);
      }
    });
  glutMainLoop();
}

Running this for an elephant mesh you get:

depth peeling vs gl-blend

The left 4 images are the individual peeled layers, rendered only for demonstration. The next image is the composite (the main result) and for comparison the nasty mess you get if you just try to render your scene with:

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

**Update: ** Turns out to get the right blending you should be careful to turn off GL_BLEND when doing the peeling pass and only turn it on for the texture compositing.

Tiny mesh viewer example using modern OpenGL core profile + Eigen + libigl + GLUT

Saturday, May 2nd, 2015

I was pleased to find out that the glut built in on recent macs does indeed support modern opengl. You can enable it via the initialization with:

glutInitDisplayMode(GLUT_3_2_CORE_PROFILE|GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH); 

If you’re using my patched glut you can also use the string version:

glutInitDisplayString("rgba depth double samples>=8 stencil core");

Together with libigl and Eigen (and made easy with C++11 features) you can write a little mesh viewer in modern opengl code in under 130 lines:

// make sure the modern opengl headers are included before any others
#include <OpenGL/gl3.h>
#define __gl_h_

#include <igl/frustum.h>
#include <igl/read_triangle_mesh.h>
#include <Eigen/Core>
#include <GLUT/glut.h>
#include <string>
#include <iostream>

std::string vertex_shader = R"(
#version 330 core
uniform mat4 proj;
uniform mat4 model;
in vec3 position;
void main()
{
  gl_Position = proj * model * vec4(position,1.);
}
)";
std::string fragment_shader = R"(
#version 330 core
out vec3 color;
void main()
{
  color = vec3(0.8,0.4,0.0);
}
)";

// width, height, shader id, vertex array object
int w=800,h=600;
GLuint prog_id=0;
GLuint VAO;
// Mesh data: RowMajor is important to directly use in OpenGL
Eigen::Matrix< float,Eigen::Dynamic,3,Eigen::RowMajor> V;
Eigen::Matrix<GLuint,Eigen::Dynamic,3,Eigen::RowMajor> F;
int main(int argc, char * argv[])
{
  // Init glut and create window + OpenGL context
  glutInit(&argc,argv);
  //glutInitDisplayString("rgba depth double samples>=8 stencil core");
  glutInitDisplayMode(GLUT_3_2_CORE_PROFILE|GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH); 
  glutInitWindowSize(w,h);
  glutCreateWindow("test");

  // Compile each shader
  const auto & compile_shader = [](const GLint type,const char * str) -> GLuint
  {
    GLuint id = glCreateShader(type);
    glShaderSource(id,1,&str,NULL);
    glCompileShader(id);
    return id;
  };
  GLuint vid = compile_shader(GL_VERTEX_SHADER,vertex_shader.c_str());
  GLuint fid = compile_shader(GL_FRAGMENT_SHADER,fragment_shader.c_str());
  // attach shaders and link
  prog_id = glCreateProgram();
  glAttachShader(prog_id,vid);
  glAttachShader(prog_id,fid);
  glLinkProgram(prog_id);
  GLint status;
  glGetProgramiv(prog_id, GL_LINK_STATUS, &status);
  glDeleteShader(vid);
  glDeleteShader(fid);

  // Read input mesh from file
  igl::read_triangle_mesh(argv[1],V,F);
  V.rowwise() -= V.colwise().mean();
  V /= (V.colwise().maxCoeff()-V.colwise().minCoeff()).maxCoeff();

  // Generate and attach buffers to vertex array
  glGenVertexArrays(1, &VAO);
  GLuint VBO, EBO;
  glGenBuffers(1, &VBO);
  glGenBuffers(1, &EBO);
  glBindVertexArray(VAO);
  glBindBuffer(GL_ARRAY_BUFFER, VBO);
  glBufferData(GL_ARRAY_BUFFER, sizeof(float)*V.size(), V.data(), GL_STATIC_DRAW);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*F.size(), F.data(), GL_STATIC_DRAW);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
  glEnableVertexAttribArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0); 
  glBindVertexArray(0);

  // Main display routine
  glutDisplayFunc(
    []()
    {
      // clear screen and set viewport
      glClearColor(0.0,0.4,0.7,0.);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glViewport(0,0,w,h);

      // Projection and modelview matrices
      Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
      float near = 0.01;
      float far = 100;
      float top = tan(35./360.*M_PI)*near;
      float right = top * (double)::w/(double)::h;
      igl::frustum(-right,right,-top,top,near,far,proj);
      Eigen::Affine3f model = Eigen::Affine3f::Identity();
      model.translate(Eigen::Vector3f(0,0,-1.5));
      // spin around
      static size_t count = 0;
      model.rotate(Eigen::AngleAxisf(0.01*count++,Eigen::Vector3f(0,1,0)));

      // select program and attach uniforms
      glUseProgram(prog_id);
      GLint proj_loc = glGetUniformLocation(prog_id,"proj");
      glUniformMatrix4fv(proj_loc,1,GL_FALSE,proj.data());
      GLint model_loc = glGetUniformLocation(prog_id,"model");
      glUniformMatrix4fv(model_loc,1,GL_FALSE,model.matrix().data());

      // Draw mesh as wireframe
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      glBindVertexArray(VAO);
      glDrawElements(GL_TRIANGLES, F.size(), GL_UNSIGNED_INT, 0);
      glBindVertexArray(0);

      glutSwapBuffers();
      glutPostRedisplay();
    }
    );
  glutReshapeFunc([](int w,int h){::w=w,::h=h;});
  std::cout<<"OpenGL version: "<<glGetString(GL_VERSION)<<std::endl;
  glutMainLoop();
}

This produces:

cheburashka modern opengl

and on my mac outputs:

OpenGL version: 4.1 INTEL-10.6.20

Compile Embree without GLUT

Monday, September 8th, 2014

Trying to compile embree on a server, I was notified that I didn’t have GLUT installed:

CMake Error at /home2/alecjaco/share/cmake-2.8/Modules/FindPackageHandleStandardArgs.cmake:97 (MESSAGE):
  Could NOT find GLUT (missing: GLUT_glut_LIBRARY GLUT_INCLUDE_DIR)

I shouldn’t need this because I don’t care about visualizing the results. To get around this requirement I simply comment out the following line in the top level CMakeLists.txt:

ADD_SUBDIRECTORY(examples)

becomes

#ADD_SUBDIRECTORY(examples)

Render to Mac OS X clipboard from GLUT OpenGL app

Saturday, January 25th, 2014

Here’s a proof of concept application that renders the current viewport to the mac os x clipboard. Then you can paste into Preview.app or Photoshop etc. Save the following in render_to_clipboard.m:

#include <GLUT/GLUT.h>
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#include <stdlib.h>
#include <unistd.h>

void reshape(int width,int height)
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glViewport(0,0,width,height);
  gluPerspective(40, (double)width/height, 1, 10);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0,0,3, 0,0,0, 0,1,0);
  glTranslatef(0, 0.0f, -1);
}

bool buffer_to_clipboard(
  const GLubyte * pixels, 
  const int w, 
  const int h, 
  const int components)
{
  const int length = w*h*components;
  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  // http://stackoverflow.com/a/3416891/148668
  size_t bufferLength = w * h * components;
  CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, bufferLength, NULL);
  size_t bitsPerComponent = 8;
  size_t bitsPerPixel = bitsPerComponent * components;
  size_t bytesPerRow = components * w;
  CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
  CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast;
  CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;

  CGImageRef iref = CGImageCreate(
    w, 
    h, 
    bitsPerComponent, 
    bitsPerPixel, 
    bytesPerRow, 
    colorSpaceRef, 
    bitmapInfo, 
    provider,   // data provider
    NULL,       // decode
    YES,        // should interpolate
    renderingIntent);

  NSImage * temp_image = [[NSImage alloc] initWithCGImage:iref size:NSMakeSize(w, h)];

  // Painfully flip image across x-axis
  NSSize newSize = NSMakeSize(w,h);
  NSImage *image = [[NSImage alloc] initWithSize:newSize];
  [image lockFocus];
  NSAffineTransform *flip = [NSAffineTransform transform];
  [flip translateXBy: 0 yBy: h];
  [flip scaleXBy:1 yBy:-1];
  [flip concat];
  NSRect r1 = NSMakeRect(0, 0, w,h);
  [[temp_image bestRepresentationForRect:r1 context:nil hints:nil] drawInRect: r1];
  [image unlockFocus];

  BOOL copied = false;
  if (image != nil)
  {
    NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
    [pasteboard clearContents];
    NSArray *copiedObjects = [NSArray arrayWithObject:image];
    copied = [pasteboard writeObjects:copiedObjects];
    [pasteboard release];
  }
  [image release];
  [pool release];
  return true;
}

bool render_to_clipboard()
{
  GLenum format = GL_RGBA;
  int components = 4;
  int VP[4];
  glGetIntegerv(GL_VIEWPORT,VP);
  // OpenGL by default tries to read data in multiples of 4, if our data is
  // only RGB or BGR and the width is not divible by 4 then we need to alert
  // opengl
  if((VP[2] % 4) != 0 && 
   (format == GL_RGB || 
    format == GL_BGR))
  {
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
  }
  GLubyte *pixels;
  pixels = (unsigned char *) malloc (VP[2]* VP[3]* components);
  glReadPixels( VP[0], VP[1], VP[2], VP[3], format, GL_UNSIGNED_BYTE, pixels);
  return buffer_to_clipboard(pixels, VP[2], VP[3], components);
}

void display()
{
  glClearColor(1,0,0,1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glutWireTeapot(1.0);
  glutSwapBuffers();
  render_to_clipboard();
}

void exitCB()
{
  exit(0);
}

int main(int argc, char * argv[])
{
  glutInit(&argc,argv);
  glutInitDisplayString( "rgba depth double samples>=8 ");
  glutInitWindowSize(401,301);
  glutCreateWindow("test");
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  // Render once and quit
  glutIdleFunc(exitCB);
  glutMainLoop();
  return 0;
}

Then you can compile with

cc -o render_to_clipboard render_to_clipboard.m -framework GLUT -framework OpenGL -lobjc -framework Foundation -framework AppKit

And run with:

./render_to_clipboard

This will flash a GLUT window, render once and quit. You can then paste your frame, which should be:

render teaport to mac os x clipboard

C++11 lambda functions and glut C-style callbacks

Sunday, January 5th, 2014

I’ve been enjoying the “new” features in C++11. Lambda functions make it much easier to organize code with spurious static functions. I recently realized I can use these for glut’s C-style callbacks. For example, I often pass AntTweakBar’s callback for passive mouse move events with:

glutPassiveMotionFunc(TwEventMouseMotionGLUT);

But if I want to force a refresh of the display after each mouse move I need to write my own mouse move function. With lambda’s this is easy to do inline:

glutPassiveMotionFunc(
  [](int x, int y)
  {
    TwEventMouseMotionGLUT(x,y);
    glutPostRedisplay();
  });

I had a little trouble using lambda’s for glut’s timer function. One way to make a time-synced, continuous draw loop is to use a recursive timer. Typically something like this:

static void timer(int ms)
{
  glutPostRedisplay();
  glutTimerFunc(ms,timer,ms);
}

int main()
{
  ...
  glutTimerFunc(33,timer,33);
  ...
  glutMainLoop();
  ...
}

With lambda’s I hoped I could write something as simple as

auto timer = [&timer](int ms)
{
  glutPostRedisplay();
  glutTimerFunc(ms,timer,ms);
};
glutTimerFunc(33,timer,33);

However, auto can’t be used like this. I get an error:

error: variable 'auto timer' with 'auto' type used in its own initializer

The problem is that to capture and use timer the compiler needs to know its type. I thought I could fix this by giving its type as a function object like this:

std::function<void(int)> timer = [&] (int ms)
{
  glutTimerFunc(ms, timer, ms);
  glutPostRedisplay();
};
glutTimerFunc(33, timer, 33);

But then I get errors because function objects cannot be cast to C-style function pointers:

error: cannot convert 'std::function<void(int)>' to 'void (*)(int)' for argument '2' to 'void glutTimerFunc(unsigned int, void (*)(int), int)'

Interestingly, it seemed that if timer didn’t capture and invoke itself (or anything else in the local scope) then it can be cast to a function pointer (that’s why the top example inside glutPassiveMotionFunc works). This seems to be because the lambda expression lives effectively at the global level.

My friend, Kenshi, managed a work around for my timer problems by using a “bounce” function (what’s in a name? “Double callback”? “Leapfrog”?). His works like this:

static std::function<void(int)> timer_bounce;
auto timer = [] (int ms) {
  timer_bounce(ms);
};
timer_bounce = [&] (int ms) {
  glutTimerFunc(ms, timer, ms);
  glutPostRedisplay();
};
glutTimerFunc(33, timer, 33);

Notice a couple important parts. timer_bounce is defined before timer so that timer may call it and then timer may int turn call timerbounce. timer_bounce is declared as static, placing at the global level in terms of lifetime.

Patch for GLUT on Mac OS X for horizontal scrolling and command key modifier

Thursday, October 24th, 2013

I recently found out about a patch for the GLUT framework on Mac OS X to provide support for mouse wheel scrolling. This is great, because I was really getting tired of the X11 windows that freeGLUT uses.

I followed these instructions for building from source: grab the source from Apple and apply the 3 patches.

Unfortunately I still got compilation errors on Mac OS X 10.7.5 with Xcode 4.

Type 'id <NSMenuItem>' does not conform to the 'NSCopying' protocol

I fixed this, I changed the line in GLUTApplication.m that looks like:

- (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem

to

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem

Now it compiles. These patches only add support for vertical scrolling. To add horizontal scrolling I changed the scrollWheel function in GLUTView.m to this:

- (void)scrollWheel: (NSEvent *)theEvent
{
   if(_mouseFunc) {
      NSPoint location = [self convertMouseLocationToBacking: [theEvent locationInWindow]];

      float deltaY = [theEvent deltaY];
      float deltaX = [theEvent deltaX];
      int buttonID = GLUT_WHEEL_UP;
      if(deltaY < 0) {
         deltaY = -deltaY;
         buttonID = GLUT_WHEEL_DOWN;
      }else if(deltaY > 0)
      {
         buttonID = GLUT_WHEEL_UP;
      }else if(deltaX < 0)
      {
         deltaY = -deltaX;
         buttonID = GLUT_WHEEL_RIGHT;
      }else if(deltaX > 0)
      {
         deltaY = deltaX;
         buttonID = GLUT_WHEEL_LEFT;
      }

      int x = rint(location.x);
      int y = rint(location.y);

      __glutSetWindow(self);
      int i;
      for(i = 0; i < deltaY; i++) {
//       (*_mouseFunc)(buttonID, GLUT_DOWN, x, y);
         (*_mouseFunc)(buttonID, GLUT_UP, x, y);
      }
   }
}

and add the following definitions in glut.h:

#define GLUT_WHEEL_LEFT 5
#define GLUT_WHEEL_RIGHT 6

While I was at it, I also added support for catching the command (aka apple, aka ⌘) key. To do this I add the following lines to glutGetModifiers in macx_modifier.m:

   if(__glutModifierMask & NSCommandKeyMask)
      modifiers |= GLUT_ACTIVE_COMMAND;

and again added a respective definition in glut.h:

#define GLUT_ACTIVE_COMMAND             8

Now I compile and replace the GLUT.framework directory in /System/Library/Frameworks/ and I’m reading to go!

Download xcodeproject of patched glut

Update: On 10.9 OS X Mavericks, I’m getting complaints from Xcode that garbage collection is no longer supported and that I should switch to ARC. I chose “Not now”, but then got a compile error complaining:

error: garbage collection is no longer supported

I fixed this, following this advice, by removing lines like

                           GCC_ENABLE_OBJC_GC = supported;

from GLUT_External.xcodeproj/project.pbxproj. I’ve updated the zipped project appropriately.

Update: On 10.10 OS X Yosemite my hack for getting modifier keys during mouse move and scroll events with Carbon “expired”. Rather than figure out how to continue using Carbon, I’ve patched Glut again to reveal modifier flags during mouse move and mouse scroll events. This means that you can call glutGetModifiers during your glutPassiveMotionFunc, glutMotionFunc and when glutMouseFunc fires on a scroll event. I’ve updated the zipped project by appropriately enclosing these function calls between:

__glutModifierMask = [theEvent modifierFlags];
... // call func
__glutModifierMask = ~0;

**Update: ** Based on another patch I’ve updated mine to enable easy access to the core profile.

**Update: ** I’ve basically been tracking my changes via a .zip file hosted on this blog. That’s not very safe. Now, I’ve uploaded all of the code to a github repo. You can still download everything as a zip or just clone the repo.

Check yo’ stencil bits in GLUT

Friday, October 11th, 2013

I just wasted a lot of time wondering why I couldn’t get any interesting combinations of glStenilOp and glStencilFunc to work correctly. Turns out I did not have the stencil buffer enabled in my GLUT application. I found this out by adding:

int i;
glGetIntegerv(GL_STENCIL_BITS, &i);
cout<<"GL_STENCIL_BITS: "<<i<<endl;

which displayed:

GL_STENCIL_BITS: 0

I fixed this by changing

glutInitDisplayString( "rgba depth double samples>=8");

to

glutInitDisplayString( "rgba depth double samples>=8 stencil"); 

Now things are working as expected.

Stop X11 app from bouncing dock icon indefinitely

Monday, September 30th, 2013

Freeglut (unfortunately) uses X11 as a windowing system. This means ugly, non-native windowing on mac os x, but also seems to mean that it doesn’t play nicely with .app bundling. Bundled X11 apps create two dock icons. One for the individual app and then one for X11 (if it’s not already there). Upon appearing, the applications icon bounces indefinitely even though the X11 windows have opened and are working properly. It seems the only way to fix this is to trigger some event that’s caught by the Carbon or Cocoa API of Mac OS X.

So here’s an example of my hacky solution.

#include <OpenGL/OpenGL.h>
#include <GLUT/glut.h>

#include <CoreFoundation/CoreFoundation.h>
#include <objc/objc.h>
#include <objc/objc-runtime.h>
#include <iostream>
// Stop bouncing dock icon.
// 
static inline void cocoa_says_stop_bouncing()
{
  id app = NULL;
  id pool = objc_getClass("NSAutoreleasePool");
  if (!pool)
  {
      std::cerr << "Unable to get NSAutoreleasePool!\nAborting\n";
      return;
  }
  pool = objc_msgSend(pool, sel_registerName("alloc"));
  if (!pool)
  {
      std::cerr << "Unable to create NSAutoreleasePool...\nAborting...\n";
      return;
  }
  pool = objc_msgSend(pool, sel_registerName("init"));
  app = objc_msgSend(objc_getClass("NSApplication"),
    sel_registerName("sharedApplication"));
  id req = objc_msgSend(app,sel_getUid("requestUserAttention:"),0);
  objc_msgSend(app,sel_getUid("cancelUserAttentionRequest:"),req);
  objc_msgSend(pool, sel_registerName("release"));
}

void display()
{
  glClearColor(1,0.8,0.8,1);
  glClear(GL_COLOR_BUFFER_BIT);
  glutSwapBuffers();
  glutPostRedisplay();
}

int main(int argc, char** argv)
{
  glutInit(&argc,argv);
  glutCreateWindow("test");
  // Must come after glutCreateWindow
  cocoa_says_stop_bouncing();
  glutDisplayFunc(display);
  glutMainLoop();
  return 0;
}

Assuming you install freeglut with macports, then compile with:

g++ -framework Cocoa -o test test1.cpp -L/opt/local/lib -lglut -framework OpenGL

Then bundle into a test.app directory using

build_app test

and then double-click on your app and stand back, relieved that the bouncing has ceased.

Source, Other

Display all glut cursors

Monday, September 23rd, 2013

Here’s a little program to see all of the glut cursors available on your implementation.

#include <GLUT/glut.h>
#include <OpenGL/OpenGL.h>
#include <iostream>
#include <map>
#include <cstring>

std::map<int,const char *> m;
std::map<int,const char *>::const_iterator c;
const char * GLUT_CURSOR_STR[23]
{
  "GLUT_CURSOR_RIGHT_ARROW",        
  "GLUT_CURSOR_LEFT_ARROW",       
  "GLUT_CURSOR_INFO", 
  "GLUT_CURSOR_DESTROY",    
  "GLUT_CURSOR_HELP", 
  "GLUT_CURSOR_CYCLE",  
  "GLUT_CURSOR_SPRAY",  
  "GLUT_CURSOR_WAIT", 
  "GLUT_CURSOR_TEXT", 
  "GLUT_CURSOR_CROSSHAIR",      
  "GLUT_CURSOR_UP_DOWN",    
  "GLUT_CURSOR_LEFT_RIGHT",       
  "GLUT_CURSOR_TOP_SIDE",     
  "GLUT_CURSOR_BOTTOM_SIDE",        
  "GLUT_CURSOR_LEFT_SIDE",      
  "GLUT_CURSOR_RIGHT_SIDE",       
  "GLUT_CURSOR_TOP_LEFT_CORNER",            
  "GLUT_CURSOR_TOP_RIGHT_CORNER",             
  "GLUT_CURSOR_BOTTOM_RIGHT_CORNER",                
  "GLUT_CURSOR_BOTTOM_LEFT_CORNER",               
  "GLUT_CURSOR_FULL_CROSSHAIR",           
  "GLUT_CURSOR_NONE", 
  "GLUT_CURSOR_INHERIT"
};

int width,height;
void reshape(int width,int height)
{
  ::width = width;
  ::height = height;
  glViewport(0,0,width,height);
}

void display()
{
  using namespace std;
  glClearColor(1,1,1,0);
  glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

  glMatrixMode( GL_PROJECTION ) ;
  glPushMatrix() ; // save
  glLoadIdentity();// and clear
  glMatrixMode( GL_MODELVIEW ) ;
  glPushMatrix() ;
  glLoadIdentity();
  gluOrtho2D(0,width,0,height);
  glDisable( GL_DEPTH_TEST ) ; // also disable the depth test so renders on top
  glTranslated(10,height/2,0);
  glScaled(.09,.09,.09);
  glLineWidth(2);

  //glRasterPos2f( 0,0 ) ; // center of screen. (-1,0) is center left.
  glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
  char buf[300];
  sprintf( buf,"%s", c->second ) ;
  const char * p = buf ;
  do glutStrokeCharacter( GLUT_STROKE_ROMAN, *p ); while( *(++p) ) ;
  glutSwapBuffers();
  glutPostRedisplay();
}


void key(unsigned char k, int, int)
{
  if(k == ' ')
  {
    if(++c == m.end())
    {
      c = m.begin();
    }
    glutSetCursor(c->first);
  }
}

int main(int argc, char * argv[])
{
  using namespace std;
  m[GLUT_CURSOR_RIGHT_ARROW] = "GLUT_CURSOR_RIGHT_ARROW";        
  m[GLUT_CURSOR_LEFT_ARROW] = "GLUT_CURSOR_LEFT_ARROW";       
  m[GLUT_CURSOR_INFO] = "GLUT_CURSOR_INFO"; 
  m[GLUT_CURSOR_DESTROY] = "GLUT_CURSOR_DESTROY";    
  m[GLUT_CURSOR_HELP] = "GLUT_CURSOR_HELP"; 
  m[GLUT_CURSOR_CYCLE] = "GLUT_CURSOR_CYCLE";  
  m[GLUT_CURSOR_SPRAY] = "GLUT_CURSOR_SPRAY";  
  m[GLUT_CURSOR_WAIT] = "GLUT_CURSOR_WAIT"; 
  m[GLUT_CURSOR_TEXT] = "GLUT_CURSOR_TEXT"; 
  m[GLUT_CURSOR_CROSSHAIR] = "GLUT_CURSOR_CROSSHAIR";      
  m[GLUT_CURSOR_UP_DOWN] = "GLUT_CURSOR_UP_DOWN";    
  m[GLUT_CURSOR_LEFT_RIGHT] = "GLUT_CURSOR_LEFT_RIGHT";       
  m[GLUT_CURSOR_TOP_SIDE] = "GLUT_CURSOR_TOP_SIDE";     
  m[GLUT_CURSOR_BOTTOM_SIDE] = "GLUT_CURSOR_BOTTOM_SIDE";        
  m[GLUT_CURSOR_LEFT_SIDE] = "GLUT_CURSOR_LEFT_SIDE";      
  m[GLUT_CURSOR_RIGHT_SIDE] = "GLUT_CURSOR_RIGHT_SIDE";       
  m[GLUT_CURSOR_TOP_LEFT_CORNER] = "GLUT_CURSOR_TOP_LEFT_CORNER";            
  m[GLUT_CURSOR_TOP_RIGHT_CORNER] = "GLUT_CURSOR_TOP_RIGHT_CORNER";             
  m[GLUT_CURSOR_BOTTOM_RIGHT_CORNER] = "GLUT_CURSOR_BOTTOM_RIGHT_CORNER";                
  m[GLUT_CURSOR_BOTTOM_LEFT_CORNER] = "GLUT_CURSOR_BOTTOM_LEFT_CORNER";               
  m[GLUT_CURSOR_FULL_CROSSHAIR] = "GLUT_CURSOR_FULL_CROSSHAIR";           
  m[GLUT_CURSOR_NONE] = "GLUT_CURSOR_NONE"; 
  m[GLUT_CURSOR_INHERIT] = "GLUT_CURSOR_INHERIT";
  c = m.begin();

  glutInit(&argc,argv);
  glutInitDisplayString( "rgba depth double samples>=8 ");
  glutInitWindowSize(300,70);
  glutCreateWindow("cursor");
  glutKeyboardFunc(key);
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutMainLoop();
  return 0;
}

On my Mac OS X machine, the built-in apple implementation looks like this:

glut cursors mac

whereas freeglut using X11 looks like

freeglut cursors mac

Notable freeglut doesn’t manage to make the cursor disappear with GLUT_CURSOR_NONE, but it does have a much better GLUT_CURSOR_DESTROY.

Cheap tricks for OpenGL transparency

Wednesday, September 5th, 2012

I’ve been experimenting with some hacks using basic OpenGL commands to achieve high/good-quality transparency (alpha-blending).

I’ll compare 4 methods.

  1. Traditional: For this one (and all the others) we turn on alpha blending:
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    

    and then just use a normal depth test without back face culling in a single pass:

    
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = alpha
    

    single pass teapot
    single pass teapot
    Notice two things. The handle is not visible in the second picture and we have zebra stripe artifacts in the first picture due to alpha blending without sorting. Sorting is slow and view-dependent and won’t even guarantee correct results. So instead we’ll see how far we can get with cheap tricks.

  2. GL_GREATER: This is a 3-pass attempt.
    
    double f = 0.75; // Or some other factor
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = f*alpha
    
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_GREATER);
    // render teapot with alpha = alpha
    
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = (alpha-f*alpha)/(1.0-f*alpha);
    

    This often produces reasonable results but the idea is a bit strange. It’s sort like depth peeling from opposite ends. Hoping that if we only have two layers, we’ll catch both of them.
    three pass teapot greater
    three pass teapot greater
    Notice now we have the handle showing up in the second image, but we still have ordering artifacts. Admittedly a lot of these would go away with back face culling, but then we don’t get two faced rending (blue inside, gold outside).

  3. ALWAYS: This method will seem like a step back because it introduces more ordering artifacts due to the two-face rendering, but the concept is a bit simpler.
    
    double f = 0.75; // Or some other factor
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_ALWAYS);
    // render teapot with alpha = f*alpha
    
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = (alpha-f*alpha)/(1.0-f*alpha);
    
  4. The idea is again the same that by taking two passes we first pick up a little bit of everything albeit not sorted in the correct order. Then we finish with one pass that ensures that the top layer is correct.
    three pass teapot greater
    three pass teapot greater
    We’re getting artifacts from the ordering in the first image, but I’ll show in a second that these are just coming from not handling the two-face rendering correctly. What we see in both cases is that the bottom of the teapot shows clearly through the bottom.

  5. QUINTUPLE PASS: This is the final solution that I arrived at. It involves five passes, which using display lists should be OK for many real-time applications.
    
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LESS);
    // render teapot with alpha = 0, to prime the depth buffer
    
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
    glDepthFunc(GL_ALWAYS);
    // render teapot with alpha = f*alpha
    
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = (alpha-f*alpha)/(1.0-f*alpha)
    
    
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK;
    glDepthFunc(GL_ALWAYS);
    // render teapot with alpha = f*alpha
    
    // There's a trade off here. With culling enabled then a perfectly
    // opaque object (alpha=1) may be wrong. With it disabled, ordering
    // artifacts may appear
    // glEnable(GL_CULL_FACE);
    // glCullFace(GL_BACK);
    glDisable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    // render teapot with alpha = (alpha-f*alpha)/(1.0-f*alpha)
    

    The idea is that first we prepare the depth buffer with the final depth values, then we use the previous method but just to show the inside “back-facing” surface, then we do the same to show the front.
    three pass teapot greater
    three pass teapot greater

  6. Now we get two very nice renderings that even participate with the background well. We can continuously blend our alpha value to 1 and expect to see a perfectly correct opaque rendering. We can notice some small ordering artifacts in the top image near the handle. If we know we want a transparent object then we can flip back face culling on for the last pass to get rid of this. But as noticed this will give an incorrect result when alpha ≈ 1.