Hi everyone,
I am using OpenGL to build some basic psychological experiments, and many of these are done using stereoscopic displays. In this case, I am drawing 3D arrows on the screen at various different times, and having users respond to the direction of them.
I am having a problem with being unable to perceive differences in depth plane of objects in relationship to each other. The objects do appear ‘3D’ and ‘pop out’, but I cannot perceive depth differences. I am using an orthographic projection matrix (there seems to be a difference in depth on the perspective projection, but this is likely due solely the additional depth cue of the perspective…) I am primarily using GLUT.
There’s enough code where it isn’t practical to share all of the code right now, but below are a few of the areas I think may be related to the issue. However, if anyone wants to dive into the full set of code, I can share that as well. If any additional information would be helpful, or other snippets, please let me know.
Please feel free to ask anything that you think will help you understand what I am doing, and I’ll provide whatever I can to help.
(Disclaimer: I realize that much of my code is inefficient and not the neatest)
Here is the Display() function, with some omissions to simplify.
xyz eyePosition;
GLdouble left = 0.1;
GLdouble right = 0.1;
GLdouble Far = 100000;
GLdouble ratio = camera.getRatio();
GLdouble radians = DTOR * camera.getAperture() / 2;
GLdouble Near = 0.0001;
GLdouble wd2 = Near * tan(radians);
GLdouble ndfl = Near / camera.getFocalLength();
GLdouble top = wd2;
GLdouble bottom = -wd2;
glDrawBuffer(GL_BACK_LEFT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawBuffer(GL_BACK_RIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
CROSSPRODUCT(camera.viewDirection, camera.viewUpDirection, eyePosition);
Normalize(&eyePosition);
eyePosition.x *= camera.getEyeSeparation() / 1.5;
eyePosition.y *= camera.getEyeSeparation() / 1.5;
eyePosition.z *= camera.getEyeSeparation() / 1.5;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
left = -ratio * wd2 - 0.5 * camera.getEyeSeparation() * ndfl;
right = ratio * wd2 - 0.5 * camera.getEyeSeparation() * ndfl;
top = wd2;
bottom = -wd2;
if (experiment.condition == 0) //Perspective
{
glFrustum(left, right, bottom, top, Near, Far);
}
else if (experiment.condition == 1) //Orthographic
{
glOrtho(left, right, bottom, top, Near, Far);
}
glMatrixMode(GL_MODELVIEW);
glDrawBuffer(GL_BACK_RIGHT);
glLoadIdentity();
gluLookAt(camera.viewPosition.x + eyePosition.x, camera.viewPosition.y + eyePosition.y, camera.viewPosition.z + eyePosition.z,
camera.viewPosition.x + eyePosition.x + camera.viewDirection.x,
camera.viewPosition.y + eyePosition.y + camera.viewDirection.y,
camera.viewPosition.z + eyePosition.z + camera.viewDirection.z,
camera.viewUpDirection.x, camera.viewUpDirection.y, camera.viewUpDirection.z);
Lighting();
DrawStimuli(); //This function is where I actually draw the arrows. I'll add this separately below
//This is all repeated for the other eye, in the positive direction
And here is the DrawStimuli() function
GLfloat specular[4] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat shiny[1] = { 5.0 };
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shiny);
//I have a class called 'arrow' that contains the x,y,z coordinates for a cube (stem of the arrow) and four triangles (head of the arrow). These values are all initialized here, but omitted from this post
//The experiment class contains most of the world coordinates, etc.
experiment.xWidth = 0.1f/10000.0f;
experiment.yHeight = 0.0f;
experiment.trial.DistZDepth = 0.0f;
experiment.trial.TargetZDepth = 0.0f;
experiment.zInitialDepth = -6.0f; //This is initial, starting depth
experiment.planeDistance = 5.0f;
//There are 9 different cases here, depending on which depth plane for dist and target, but I'm only showing 2 for simplicity
switch (experiment.orderVector[experiment.trial.trialNumber] % 9)
{
case 0:
experiment.trial.DistZDepth = -experiment.planeDistance + experiment.zInitialDepth;
experiment.trial.distPlane = 1;
experiment.trial.targetPlane = 1;
experiment.trial.TargetZDepth = -experiment.planeDistance + experiment.zInitialDepth;
break;
case 1:
experiment.trial.DistZDepth = -experiment.planeDistance + experiment.zInitialDepth;
experiment.trial.distPlane = 1;
experiment.trial.targetPlane = 2;
experiment.trial.TargetZDepth = 0.1f + experiment.zInitialDepth;
break;
}
glPushMatrix();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //Clear screen and depth buffers
//Build distractor arrows
if (experiment.trial.distDirection == NEUTRAL)
{
//Build d1 arrow (far left)
glLoadIdentity(); //Reset the view to the center of the screen
glTranslatef(-(experiment.xWidth*0.5f), experiment.yHeight, experiment.trial.DistZDepth);
buildArrow(neutralDistractor);
//Build d2 arrow (middle left)
glLoadIdentity(); //Reset the view to the center of the screen
glTranslatef(-(experiment.xWidth*0.25f), experiment.yHeight, experiment.trial.DistZDepth);
buildArrow(neutralDistractor);
//Build d3 arrow (middle right)
glLoadIdentity(); //Reset the view to the center of the screen
glTranslatef((experiment.xWidth*0.25f), experiment.yHeight, experiment.trial.DistZDepth);
buildArrow(neutralDistractor);
//Build d4 arrow (far right)
glLoadIdentity(); //Reset the view to the center of the screen
glTranslatef((experiment.xWidth*0.5f), experiment.yHeight, experiment.trial.DistZDepth);
buildArrow(neutralDistractor);
}
//There are two other directions (left and right) that repeat this code but build different arrows in the buildArrow(arrow) function. Omitted for simplicity.
//Update type to Target
rightArrow.type = TARGET;
leftArrow.type = TARGET;
//Build target arrow
if (experiment.trial.targetDirection == LEFT)
{
glLoadIdentity(); //Reset the view to the center of the screen
glTranslatef(0.0f, experiment.yHeight, experiment.trial.TargetZDepth);
buildArrow(leftArrow);
}
else if (experiment.trial.targetDirection == RIGHT)
{
glLoadIdentity(); //Reset the view to the center of the screen
glTranslatef(0.0f, experiment.yHeight, experiment.trial.TargetZDepth);
buildArrow(rightArrow);
}
glPopMatrix();
Finally, here is the buildArrow function. It’s messy and possibly unclear without seeing the entire arrow class… but here it is!
GLvoid buildArrow(Arrow a)
{
xyz n;
n = CalculateNormal(a.t1.p1, a.t1.p2, a.t1.p3);
glBegin(GL_TRIANGLES); //Base
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.t1.p1.x, a.t1.p1.y, a.t1.p1.z);
glVertex3f(a.t1.p2.x, a.t1.p2.y, a.t1.p2.z);
glVertex3f(a.t1.p3.x, a.t1.p3.y, a.t1.p3.z);
glEnd();
n = CalculateNormal(a.t2.p1, a.t2.p2, a.t2.p3);
glBegin(GL_TRIANGLES); //Top
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.t2.p1.x, a.t2.p1.y, a.t2.p1.z);
glVertex3f(a.t2.p2.x, a.t2.p2.y, a.t2.p2.z);
glVertex3f(a.t2.p3.x, a.t2.p3.y, a.t2.p3.z);
glEnd();
n = CalculateNormal(a.t3.p1, a.t3.p2, a.t3.p3);
glBegin(GL_TRIANGLES); //Bottom
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.t3.p1.x, a.t3.p1.y, a.t3.p1.z);
glVertex3f(a.t3.p2.x, a.t3.p2.y, a.t3.p2.z);
glVertex3f(a.t3.p3.x, a.t3.p3.y, a.t3.p3.z);
glEnd();
n = CalculateNormal(a.t4.p1, a.t4.p2, a.t4.p3);
glBegin(GL_TRIANGLES); //Back
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.t4.p1.x, a.t4.p1.y, a.t4.p1.z);
glVertex3f(a.t4.p2.x, a.t4.p2.y, a.t4.p2.z);
glVertex3f(a.t4.p3.x, a.t4.p3.y, a.t4.p3.z);
glEnd();
//In this iteration, we are only drawing the quads if it is a distractor and the type is neutral
if (a.type == DISTRACTOR && experiment.trial.distDirection == NEUTRAL)
{
//glColor3f(0.2f, 0.2f, 0.2f);
n = CalculateNormal(a.s1.p1, a.s1.p2, a.s1.p3);
glBegin(GL_QUADS); //Top
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.s1.p1.x, a.s1.p1.y, a.s1.p1.z);
glVertex3f(a.s1.p2.x, a.s1.p2.y, a.s1.p2.z);
glVertex3f(a.s1.p3.x, a.s1.p3.y, a.s1.p3.z);
glVertex3f(a.s1.p4.x, a.s1.p4.y, a.s1.p4.z);
glEnd();
n = CalculateNormal(a.s2.p1, a.s2.p2, a.s2.p3);
glBegin(GL_QUADS); //Back
glNormal3f(n.x, n.y, n.z);
//glColor3f(0.0f, 0.0f, 0.0f);
glVertex3f(a.s2.p1.x, a.s2.p1.y, a.s2.p1.z);
glVertex3f(a.s2.p2.x, a.s2.p2.y, a.s2.p2.z);
glVertex3f(a.s2.p3.x, a.s2.p3.y, a.s2.p3.z);
glVertex3f(a.s2.p4.x, a.s2.p4.y, a.s2.p4.z);
glEnd();
n = CalculateNormal(a.s3.p1, a.s3.p2, a.s3.p3);
glBegin(GL_QUADS); //Bottom
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.s3.p1.x, a.s3.p1.y, a.s3.p1.z);
glVertex3f(a.s3.p2.x, a.s3.p2.y, a.s3.p2.z);
glVertex3f(a.s3.p3.x, a.s3.p3.y, a.s3.p3.z);
glVertex3f(a.s3.p4.x, a.s3.p4.y, a.s3.p4.z);
glEnd();
n = CalculateNormal(a.s4.p1, a.s4.p2, a.s4.p3);
glBegin(GL_QUADS); //Front
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.s4.p1.x, a.s4.p1.y, a.s4.p1.z);
glVertex3f(a.s4.p2.x, a.s4.p2.y, a.s4.p2.z);
glVertex3f(a.s4.p3.x, a.s4.p3.y, a.s4.p3.z);
glVertex3f(a.s4.p4.x, a.s4.p4.y, a.s4.p4.z);
glEnd();
n = CalculateNormal(a.s5.p1, a.s5.p2, a.s5.p3);
glBegin(GL_QUADS); //Left
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.s5.p1.x, a.s5.p1.y, a.s5.p1.z);
glVertex3f(a.s5.p2.x, a.s5.p2.y, a.s5.p2.z);
glVertex3f(a.s5.p3.x, a.s5.p3.y, a.s5.p3.z);
glVertex3f(a.s5.p4.x, a.s5.p4.y, a.s5.p4.z);
glEnd();
n = CalculateNormal(a.s6.p1, a.s6.p2, a.s6.p3);
glBegin(GL_QUADS); //Right
glNormal3f(n.x, n.y, n.z);
glVertex3f(a.s6.p1.x, a.s6.p1.y, a.s6.p1.z);
glVertex3f(a.s6.p2.x, a.s6.p2.y, a.s6.p2.z);
glVertex3f(a.s6.p3.x, a.s6.p3.y, a.s6.p3.z);
glVertex3f(a.s6.p4.x, a.s6.p4.y, a.s6.p4.z);
glEnd();
}
}
EDIT: Forgot to include some important stuff.
Video Card: NVidia Quadro K600
Monitor: Acer GD235HZ
OS: Windows 7
3D Glasses: Nvidia 3D Vision