Compiling and running the nVidia dual depth peeling example on Mac OS X

Alec Jacobson

September 27, 2013

weblog/

Achieving order-independent transparency is one of the biggest embarrassments to real-time graphics. We want something like:

glEnable(GL_SLOWER_BUT_CORRECT_TRANSPARENCY);

But instead we often resort to complicated, dirty hacks, expensive sorting with isn't even correct, or giving up and living with ugly order-dependent alpha blending.

There are a few good ways to do it right and "Order Independent Transparency" and the follow-up "Order Independent Transparency with Dual Depth Peeling" are clever solutions.

Dual depth peeling demo running on mac

I finally managed to get there 2008(!) demo code running on my iMac (with AMD Radeon HD 6970M OpenGL Engine) running Mac OS X 10.7. This is quite a feat considering the OpenGL support on my machine is circa 1999.

Here are the steps to get this demo running. Download the source code.

Travel to the source directory

cd dual_depth_peeling/OpenGL/src/dual_depth_peeling

and create a Makefile with:

.PHONY: all

CXX=g++

all: obj dual_depth_peeling

CFLAGS += -O3 -Wall -DNDEBUG -Winvalid-pch -m64 -fopenmp -msse4.2

CPP_FILES=$(wildcard *.cpp)
OBJ_FILES=$(addprefix obj/,$(notdir $(CPP_FILES:.cpp=.o)))


NVIDIA_COMMON_INC=-I../../common/include/ -I../../common/nvModel/include

OPENGL_LIB=-framework OpenGL
GLUT_LIB=-framework GLUT

LIB+=$(OPENGL_LIB) $(GLUT_LIB)
INC+=$(NVIDIA_COMMON_INC)

dual_depth_peeling: $(OBJ_FILES)
    ${CXX} $(CFLAGS) -o $@ $(OBJ_FILES) $(LIB)

obj:
    mkdir -p obj

obj/%.o: %.cpp %.h
    ${CXX} $(CFLAGS) -o $@ -c $< $(INC) 

obj/%.o: %.cpp
    ${CXX} $(CFLAGS) -o $@ -c $< $(INC)

clean:
    rm -f $(OBJ_FILES)
    rm -f dual_depth_peeling

Before making we need to fix some bugs and compiler issues in the included math code. My edits are of the form:

// Alec
// Original code
New code

In nvMatrix.h:

// Alec
//for (int i = 0; i < 4; i++) v[i] = element(i,r);
for (int i = 0; i < 4; i++) v[i] = element(i,c);

In nvQuaternion.h

// Alec
//mult_vec(vec3(src_and_dst), src_and_dst);
mult_vec(vec3<T>(src_and_dst), src_and_dst);

and

// Alec
//vec3 axis;
vec3<T> axis;

and

// Alec
//return  &q[0];
return  &_array[0];

and

// Alec
//q0 = q[0];
//q1 = q[1];
//q2 = q[2];
//q3 = q[3];
q0 = _array[0];
q1 = _array[1];
q2 = _array[2];
q3 = _array[3];

and

  // Alec
  //vec3 v;
  vec3<T> v;

In nvShaderUtils.h and other files later replace

#include <GL/glew.h> 

with

// Alec
#ifdef __APPLE__
#include <OpenGL/GL.h>
#else   
#include <GL/glew.h>
#endif

In nvVector.h there are a bunch of lines that look like:

T::value_type ...

which produce errors like

../../common/include/nvVector.h:681:5: Error: error: need 'typename' before 'T:: value_type' because 'T' is a dependent scope

replace these lines with something like

typename T::value_type ...

also

// Alec
//return *this;
return lhs;

The Makefile we wrote is setup not to use the included nvModel.h. The original demo only included the header and a binary. Instead I got the header and cpp files: nvModel.h, nvModel.cc, nvModelObj.cc and nvModelQuery.cc from this random website I found by googling "nvModel.cpp". Download those files into dual_depth_peeling/OpenGL/src/dual_depth_peeling and rename the *.cc files into *.cpp.

Go back to dual_depth_peeling/OpenGL/src/dual_depth_peeling and find and replace lines like

#include <GL/glew.h>
#include <GL/glut.h>

with

// Alec
#ifdef __APPLE__
#include <OpenGL/GL.h>
#include <GLUT/glut.h>
#else   
#include <GL/glew.h>
#include <GL/glut.h>
#endif  

and change the include to find our new nvModel.h

// Alec 
//#include <nvModel.h>
#include "nvModel.h"

In dual_depth_peeling.cpp change the following lines:

        // Alec
        //glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_FLOAT_RG32_NV, g_imageWidth, g_imageHeight,
        glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RG32F, g_imageWidth, g_imageHeight,

and

        // Alec
        //glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH_COMPONENT32F_NV,
        glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH_COMPONENT,

and

// Alec
//glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_FLOAT_R32_NV,
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_R32F,

and

                //Alec
                //g_opacity = max(g_opacity, 0.0);
                g_opacity = std::max(g_opacity, 0.0f);

and

                //Alec
                //g_opacity = min(g_opacity, 1.0);
                g_opacity = std::min(g_opacity, 1.0f);

and put and ifndef around the glew stuff

        //Alec
#ifndef __APPLE__
        if (glewInit() != GLEW_OK)
        {
                printf("glewInit failed. Exiting...\n");
                exit(1);
        }

    if (!glewIsSupported( "GL_VERSION_2_0 "
                          "GL_ARB_texture_rectangle "
                                                  "GL_ARB_texture_float "
                                                  "GL_NV_float_buffer "
                          "GL_NV_depth_buffer_float "))
    {
        printf("Unable to load the necessary extensions\n");
                printf("This sample requires:\n");
                printf("OpenGL version 2.0\n");
                printf("GL_ARB_texture_rectangle\n");
                printf("GL_ARB_texture_float\n");
                printf("GL_NV_float_buffer\n");
                printf("GL_NV_depth_buffer_float\n");
                printf("Exiting...\n");
        exit(1);
    }
#endif

We're almost done. If you now issue:

make
./dual_depth_peeling

You'll see that it compiles and runs but it fails to compile the shaders. So now we fix compatibility issues with the shaders. Comment any extension lines like this:

// Alec
//#extension ARB_draw_buffers : require

and change any sampleRECT and textureRECT lines like this

// Alec
//uniform samplerRECT TempTex;
uniform sampler2DRect TempTex;

and this

    // Alec
    //gl_FragColor = textureRect(TempTex, gl_FragCoord.xy);
    gl_FragColor = texture2DRect(TempTex, gl_FragCoord.xy);

Fix any issues with comparisons between floats and ints like this:

    // Alec
    //if (gl_FragColor.a == 0) discard;
    if (gl_FragColor.a == 0.0) discard;

Finally fix problems with operations not acting on vec3 and vec4 like this:

    // Alec
    //gl_FragColor.rgb = frontColor + backColor * alphaMultiplier;
    gl_FragColor.rgb = frontColor.rgb + backColor * alphaMultiplier;

also in shaders/shade_fragment.glsl swap fmod for mod like this:

    //Alec
    //color.rgb = (fmod(i, 2.0) == 0) ? vec3(.4,.85,.0) : vec3(1.0);
    color.rgb = (mod(i, 2.0) == 0.0) ? vec3(.4,.85,.0) : vec3(1.0);

That ought to do it. Looking through the code this probably won't be the absolute easiest thing to integrate into my apps, but I'm happy to finally really see something that works on my own machine.

Here's a table of the results. It compares this method to two naive methods for order independent alpha blending: averaging and summing. Those naive methods have obvious flaws.

Dual depth peeling compared to averaging and summing Update: To skip all these steps and just trust me. Download my edited source.