Wednesday, 3rd February 2010
Some time ago, I posted about using interlaced video to display 3D images. Whilst the idea works very nicely in theory, it's quite tricky to get modern video cards to generate interlaced video at a variety of resolutions and refresh rates. My card limits me to 1920×1080 at i30 or 1920×1080 at i25, and only lets me use this mode on my LCD when I really need it on a CRT. Even if you can coax the video card to switch to a particular mode, this is quite a fragile state of affairs as full-screen games will switch to a different (and likely progressively scanned) mode.
3D glasses adaptor with line blanker prototype
An alternative is to build an external bit of hardware that simulates an interlaced video mode from a progressive one. The easiest way of doing that is to switch off the RGB signals on alternate scanlines, blanking odd scanlines in one frame and even scanlines in the next. This type of circuit is appropriately named a line blanker, and my current implementation is shown above. It sits between the PC and the monitor, and uses a pair of flip-flops which toggle state on vsync or hsync signals from the PC. The output from the vsync flip-flop is used to control which eye is open and which is shut on the LCD glasses, and is also combined with the hsync flip-flop to switch the RGB signal lines on or off on alternate lines using a THS7375 video amplifier. Unfortunately, this amplifier is only available as TSSOP, which isn't much fun to solder if you don't have the proper equipment; I made a stab at it with a regular iron, the smallest tip I could find, lots of no-clean flux and some solder braid. I have been informed that solder paste makes things considerably easier, so will have to try that next time.
My cheap LCD glasses lack any form of internal circuitry, merely offering two LCD panels wired directly to a 3.5mm stereo jack, and so I'm using the 4030 exclusive-OR gate oscillator circuit to drive them.
The adaptor provides one switch to swap the left and right eyes in case they are reversed, and another is provided to disable the line blanking circuit (useful for genuine interlaced video modes or alternate frame 3D). You can download a schematic of the circuit here as a PDF.
I've been using these glasses to play Quake in 3D, which is good fun but an experience that was sadly marred by a number of bugs and quirks in Quake's 3D mode.
WinQuake, demonstrating the crosshair bug and excessive stereo separation of the weapon
The most obvious problems in the above screenshot are the migratory crosshair (appearing 25% of the way down the screen instead of vertically centred) and the excessive stereo separation of the player's weapon.
If the console variable LCD_X is non-zero, Quake halves the viewport height then doubles what it thinks is the stride of the graphics buffer. This causes it to skip every other scanline when rendering. Instead of rendering once, as normal, it translates the camera in one direction, renders, then offsets the start of the graphics buffer by one scanline, translates the camera in the other direction then renders again. This results in the two views (one for each eye) being interleaved into a single image.
The crosshair is added after the 3D view is rendered (in fact, Quake just prints a '+' sign in the middle of the screen using its text routines), which explains its incorrect position – Quake doesn't take the previously halved height of the display into consideration, causing the crosshair to be drawn with a vertical position of half of half the height of the screen. That's pretty easy to fix – if LCD_X is non-zero, multiply all previously halved heights and Y offsets by two before rendering the crosshair to compensate.
WinQuake, demonstrating the DirectDraw corruption bug
A slightly more serious bug is illustrated above. When using the DirectDraw renderer (the default in full-screen mode), the display is corrupted. This can be fixed by passing -dibonly to the engine, but it would be nice to fix it.
After a bit of digging, it appeared that the vid structure, which stores fields such as the address of the graphics buffer and its stride, was being modified between calls to the renderer. It seemed to be reverting to the actual properties of the graphics buffer (i.e. it pointed to the top of the buffer and stored the correct stride of the image, not the doubled one). Further digging identified VID_LockBuffer() as the culprit; this does nothing if you're using the dib rendering mode, but locks the buffer and updates the vid structure in other access modes. Fortunately, you can call this function as many times as you like (as long as you call VID_UnlockBuffer() a corresponding number of times) – it only locks the surface and updates vid the first time you call it. By surrounding the entire 3D rendering routine in a VID_LockBuffer()…VID_UnlockBuffer() pair, vid is left well alone, and Quake renders correctly in full-screen once again.
The final issue was the extreme stereo separation of the player weapon, caused by its proximity to the camera – it does make the game quite uncomfortable to play. The game moves the camera and weapon to the player's position, then applies some simple transformations to implement view/weapon bobbing, before rendering anything. Applying the same camera offset and rotation to the player weapon as the camera when generating the two 3D views put the weapon slap bang in the middle of the screen, as it would appear in regular "2D" Quake. This gives it the impression of a carboard cutout, and can put it behind/"inside" walls and floors when you walk up to them; I've added a console variable, LCD_VIEWMODEL_SCALE, that can be used to interpolate between the default 3D WinQuake view (value: 1) and the cardboard cutout view (value: 0).
WinQuake with the 3D fixes applied
You can download the replacement WinQuake from here – you can just overwrite any existing executable. (You will also need the VC++ 2008 SP1 runtimes, if you do not already have them). Source code is included, and should build in VC++ 2008 SP1 (MASM only appears to be included in SP1, which is required to compile Quake's extensive collection of assembly source files).
If you don't have a copy of Quake, I recorded its looping demos in 3D and uploaded them to YouTube. This was before I made the above fixes, so there's no crosshair or player weapon model in the videos – if you have access to YouTube-compatible 3D glasses or crossable eyes, click here.