裸眼 3D ディスプレイ (SHARP LL-151D) を用いた立体視の実現

裸眼 3D ディスプレイの原理

裸眼 3D ディスプレイは, 通常のディスプレイに「レンチキュラーレンズ」と呼ばれる板状のレンズや「視差バリア」と呼ばれる縦縞のフィルタ(アパーチャーグリル)を重ねることによって, 一つの画面で右目と左目に異なる映像を見せるようにしたものです. いずれも画面上に左目用の映像と右目用の映像を交互に表示し, レンチキュラーレンズや視差バリアを通して見ることによって, 左目には左目用の画像だけが, 右目には右目用の画像だけが見えるようになります. この方式では特殊なメガネ等を使う必要はありません. この実験では視差バリア方式のディスプレイを使用します.

レンチキュラーレンズと視差バリア

SHARP LL-151D の構造

液晶ディスプレイ上の1つのピクセル(画素)は, 光の3原色(赤=R,緑=G,青=B)の発光体の組み合わせで構成されています. この1つ1つの発光体のことを, サブピクセルと呼びます. 実験に使用する裸眼 3D ディスプレイ SHARP LL-151D は, このサブピクセルに対応した視差バリアが設けられています.

視差バリアとサブピクセルとの関係

したがって, この裸眼 3D ディスプレイでは, 映像を次のように分けて画面表示する必要があります.

なお SHARP LL-151D の視差バリアは, 実際には液晶パネルとその背後の発光体との間にあります.

実験

裸眼 3D ディスプレイを使用した立体視表示を行う場合, 図形を画面上の偶数番目の縦のラインと奇数番目の縦のラインに分けて描く必要があります. そのために, ここでは OpenGL の glPolygonStipple() という関数を使用します. この関数は図形を描く際に, 図形にかける「マスク」を定義します. マスクの大きさは 32×32 画素で, マスクの値が 1 のところだけ図形が描画されます. 従って次のようなマスクを用意すれば, 画面上の偶数番目の縦のラインや奇数番目の縦のラインだけに図形を描くことができます.

マスク画像

まず最初に, 初期化の関数 init() でこのマスクを作成します. また, GL_POLYGON_STIPPLE を有効にしておきます.

...
 
#include <GL/glut.h>
 
...
 
/*
** マスク用の変数の宣言
*/
static unsigned int emask[32]; /* 偶数番目のライン用マスク */
static unsigned int omask[32]; /* 奇数番目のライン用マスク */
 
...
 
void init(void)
{
  int i;
  
  /* 初期設定 */
  ...
  
  /* マスクを作る */
  for (i = 0; i < 32; ++i) {
    emask[i] = 0xaaaaaaaa;
    omask[i] = 0x55555555;
  }
  
  /* 描画するポリゴンにマスクをかける */
  glEnable(GL_POLYGON_STIPPLE);
}

こうして奇数ラインの R と B と偶数ラインの G に右目からみたシーンを表示し, 偶数ラインの R と B と奇数ラインの G に左目から見たシーンを表示します.

特定の色バッファのみに描画するには, glColorMask() という関数を使用します. glColorMask() の4つの引数はそれぞれ R, G, B, A の色バッファに対応しており, 描画を行う色バッファには GL_TRUE を, 描画を行わない色バッファには GL_FALSE を指定します.

また, この方法では同じシーンを4回描く必要があるので, 手間を省くためにディスプレイリストという機能を使用することにします. ディスプレイリストは, glNewList(番号, ...) 〜 glEndList() の間で実行した OpenGL の命令を, glCallList(番号)を呼び出すことによって再度実行するというものです. このディスプレイリストの番号は, glGenLists() を使って生成し, glDeleteLists() で破棄します.

/*
 * 画面表示
 */
void display(void)
{
  ...
  GLuint list; /* ディスプレイリスト */
  
  /* ディスプレイリストの作成 */
  list = glGenLists(1);
  
  /* すべての色バッファに描画を許可 */
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  
  /* 画面クリア */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  /* モデルビュー変換行列の初期化 */
  glLoadIdentity();
  
  /* 右目の位置と方向 */
  gluLookAt(/* ここは自分で考えてください */);
  
  /* 視点の移動 */
  ...
  
  /* 光源の位置を設定 */
  ...
  
  /* 奇数ラインにR・Bを表示 */
  glPolygonStipple((GLubyte *)omask);
  glColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_TRUE);
  
  /* ディスプレイリストへの登録開始 */
  glNewList(list, GL_COMPILE_AND_EXECUTE);
  glPushMatrix();
  
  /* シーンの描画 */
  ...
  
  /* ディスプレイリストへの登録終了 */
  glPopMatrix();
  glEndList();
  
  /* 偶数ラインにGを表示 */
  glPolygonStipple((GLubyte *)emask);
  glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
  
  /* シーンの描画 */
  glCallList(list);
  
  /* デプスバッファだけをクリア */
  glClear(GL_DEPTH_BUFFER_BIT);
  
  /* モデルビュー変換行列の初期化 */
  glLoadIdentity();
  
  /* 左目の位置と方向 */
  gluLookAt(/* ここは自分で考えてください */);
  
  /* 視点の移動 */
  ...
  
  /* 光源の位置を設定 */
  ...
  
  /* 偶数ラインにR・Bを表示 */
  glPolygonStipple((GLubyte *)emask);
  glColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_TRUE);
  
  /* シーンの描画 */
  glCallList(list);
  
  /* 奇数ラインにGを表示 */
  glPolygonStipple((GLubyte *)omask);
  glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
  
  /* シーンの描画 */
  glCallList(list);
  
  /* ディスプレイリストを破棄する */
  glDeleteLists(list, 1);
  
  ...
}

2つの視点の間隔とそれぞれの位置・方向, および fovy (実験1のサンプルでは 30 に設定されています) は, ディスプレイの表示面の高さ, 表示面との距離, 自分の両目の間隔の実測値から割り出してください.

両眼立体視の原理
本当は gluLookAt() を使わずに, gluPerspective() を glFrustum() に置き換えて左右の目の視野をずらしたほうが厳密なんですが, それだとプログラムが少しややこしくなりますし, glFrustum() の説明もしなきゃいけなくなるので手を抜きます.

最後に, 立体視表示しているウィンドウが小さかったり, 画面上に余計なものが映っていたりするとうまく立体感が得られない場合があるので, 画面をフルスクリーン表示にします.

...
 
int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);
  glutCreateWindow(argv[0]);
  glutFullScreen();
  glutDisplayFunc(display);
  ...
}