Oh, I see it now
The SDK is in C:\IWEARSDK\
Thank you very much.The way the SDK documentation and sample say to do stereo is wrong, or at least not optimal.
The way they do it, everything will pop out of the screen, and nothing will be behind the screen. That's not a huge problem when using the VR920 because the (virtual) screen is 9 feet away. So it won't look as bad as on a desktop monitor which is 60cm away. But still, if you want the sky and distant objects to have a real sense of depth, you should render part of the scene behind the screen. Also objects too far in front of the screen cause eye strain. To minimise eye strain, half (or more) of the scene should be behind the screen.
There are two stereoscopic parameters that are important. The documentation describes separation, which is the strength of the 3D effect. It is the distance between your eyes. In real life the actual distance between your eyes is 6.4 cm, but normally people use a slightly lower value for stereoscopic rendering because it is very hard for your eyes to focus on a screen 9 feet away, while they look at an object 1 foot away or 100 feet away. It is good to let the user adjust this value. Separation is achieved by moving the camera sideways.
The missing parameter is convergence, which I like to call screen depth. It determines which objects appear in front of the screen and which objects appear behind the screen. For maximum realism objects further than 9 feet away should be behind the screen, and objects closer than 9 feet away should be in front of the screen. Although some other values feel better so it is good to let the user adjust it. Convergence is achieved by using an off-axis projection matrix. You can't use a normal projection matrix. The following is the WRONG way to do it:
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 2000.0f );
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
That creates a projection matrix for a (vertical) FOV of 45 degrees (pi/4), with the centre of the view drawn in the centre of the screen. The problem is that the centre of the view should NOT be in the centre of the screen. The middle of your right eye is actually looking somewhere in the right side of the screen, while the middle of your left eye is actually looking somewhere in the left side of the screen. So you need to use a different projection matrix for each eye, and you need to use an "off axis projection".
In AssaultCube, this is what I would use if I wanted to do it correctly (pardon my OpenGL). This code sets the projection matrix. I have combined the camera moving for the two eyes, and the convergence both into the projection matrix, so you can use the exact same view matrix for the two eyes (this is projection matrix abuse, but it makes our life much easier).
/* Note that there are 3 planes... the near clipping plane, the screen plane, and the far clipping plane. The screen plane is the screen itself, which should be about 9 feet away. The near clipping plane is the plane close to the camera which prevents it from drawing anything too close to the camera. It should be about 1 or 2 feet away. The far clipping plane is the plane that prevents it from drawing anything too far away. It should be a couple of miles away. To create the off-axis projection matrix, OpenGL and Direct3D require us to specify the left, right, top, and bottom coordinates of the view as what they would be on the near clipping plane. ScreenDist and sep control the stereoscopic parameters by specifying how far away the screen is and how far apart your eyes are in game units. */
// fovy is the vertical field of view IN RADIANS
// zNear is the near clipping plane distance
// ScreenDist is the distance of the screen, which should be about 9 feet
// zFar is the far clipping plane distance
// sep should be 6.4 cm, or a user specified interoccular distance (distance between eyes)
// all those distances need to be converted to game units.
float aspect = 4.0f / 3.0f; // 4:3 aspect ratio
float HalfHeight = zNear * tan(fovy/2);
float zNearDividedByScreenDist = zNear / ScreenDist;
float left, right, top, bottom;
if (righteye) {
left = -(aspect * HalfHeight) - 0.5 * sep * zNearDividedByScreenDist;
right = aspect * HalfHeight - 0.5 * sep * zNearDividedByScreenDist;
} else {
left = -(aspect * HalfHeight) + 0.5 * sep * zNearDividedByScreenDist;
right = aspect * HalfHeight + 0.5 * sep * zNearDividedByScreenDist;
}
top = HalfHeight;
bottom = -HalfHeight;
glFrustum(right,left,bottom,top,zNear,zFar);
if (righteye) {
glTranslated(-0.5*sep,0,0); // move world left, same as moving camera right
} else {
glTranslated(0.5*sep,0,0); // move world right, same as moving camera left
}
The problem with doing it correctly like that, is that crosshairs don't work! To get crosshairs to work correctly, you can either move the position where the gun fires from so it fires from level with your right eye and also move the crosshair so it is not in the middle of the screen but rather is level with the middle of your right eye; OR you need to change it so that the right eye is in the middle of your head, and the left eye is 6.4 cm to the left of the middle. The second option is easier, since you don't need to change the firing code, and the crosshair is still in the middle of the screen. In both cases you should only draw the crosshair on the right eye's view. If you don't want a crosshair (there are better alternatives) you don't have to use either method. Note that the second option isn't rendering the scene strictly correctly, but I find it still looks good. Here is how I used the second option in AssaultCube:
// fovy is the vertical field of view IN RADIANS
// zNear is the near clipping plane distance
// ScreenDist is the distance of the screen, which should be about 9 feet
// zFar is the far clipping plane distance
// sep should be 6.4 cm, or a user specified interoccular distance (distance between eyes)
// all those distances need to be converted to game units.
float aspect = 4.0f / 3.0f; // 4:3 aspect ratio
float HalfHeight = zNear * tan(fovy/2);
float zNearDividedByScreenDist = zNear / ScreenDist;
float left, right, top, bottom;
if (righteye) {
left = -(aspect * HalfHeight); // - 0.5 * sep * zNearDividedByScreenDist;
right = aspect * HalfHeight; // - 0.5 * sep * zNearDividedByScreenDist;
} else {
left = -(aspect * HalfHeight) + 1.0 * sep * zNearDividedByScreenDist;
right = aspect * HalfHeight + 1.0 * sep * zNearDividedByScreenDist;
}
top = HalfHeight;
bottom = -HalfHeight;
glFrustum(right,left,bottom,top,zNear,zFar);
if (righteye) {
//glTranslated(-0.5*sep,0,0); // move world left, same as moving camera right
} else {
glTranslated(1.0*sep,0,0); // move world right, same as moving camera left
}
Notice how the right eye is no longer moved at all, but the left eye is moved double. This version is only for games that need a crosshair in the middle of the screen. For other games, use the original code above it.