床井研究室

12月だ

ついに師走です。みんな忙しそうです。というか、みんな出張に行ってしまってます。廊下が静かです。なのに、なぜか私はここにいます。どうしてでしょう。なんだか焦ります。でも、他人と比較してても落ち込むだけです。とりあえず、目の前にあることだけでも片付けようと思います。

とは言いながら、そういうときに限って他のことがしたくなります。目の前の仕事が多いほど、そういう状況になります。いわゆる逃避行動ですね。というわけで、小ネタを2つメモ。どちらもやっつけ仕事で、深く考えてません。

デプスバッファの表示

卒研でデプスバッファを使ったネタをやっている学生さんがいるのですが、そういうときにはデバッグのためにデプスバッファの内容を確かめたくなることがあります。そのために作ったものがあります。

showdepth.h

#ifdef __cplusplus
extern "C" {
#endif
  extern int showdepth(void);
#ifdef __cplusplus
}
#endif

showdepth.c

#include <stdio.h>
#include <stdlib.h>

#if defined(_WIN32)
#  include <GL/glut.h>
#elif defined(__APPLE__)
#  define GL_SILENCE_DEPRECATION
#  include <GLUT/glut.h>
#else
#  define GL_GLEXT_PROTOTYPES
#  include <GL/glut.h>
#endif

#define USERASTERPOS 0

/*
** デプスバッファの表示
*/
int showdepth(void)
{
  GLint view[4];
  GLubyte* buffer;

  /* 現在のビューポートのサイズを得る */
  glGetIntegerv(GL_VIEWPORT, view);

  /* ビューポートのサイズのメモリを確保する */
  buffer = (GLubyte* )malloc(view[2] * view[3]);

  if (buffer) {
#if USERASTERPOS
    GLdouble model[16];
    GLdouble proj[16];
    GLdouble ox, oy, oz;
#endif

    /* 描画終了を待つ */
    glFinish();

    /* デプスバッファの読み込み */
    glReadPixels(view[0], view[1], view[2], view[3],
      GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, buffer);

    /* カラーバッファへの描画 */
#if USERASTERPOS
    glGetDoublev(GL_MODELVIEW_MATRIX, model);
    glGetDoublev(GL_PROJECTION_MATRIX, proj);
    gluUnProject(view[0] + 0.5, view[1] + 0.5, 0.0, model, proj, view,
      &ox, &oy, &oz);
    glRasterPos3d(ox, oy, oz);
#endif
    glDrawPixels(view[2], view[3], GL_LUMINANCE, GL_UNSIGNED_BYTE, buffer);

    /* 画面表示 */
    if (glutGet(GLUT_WINDOW_DOUBLEBUFFER)) glutSwapBuffers();

    /* メモリの解放 */
    free(buffer);

    return 1;
  }

  return 0;
}

まず showdepth.h を読み込んでから、キーボードコールバック等で関数 showdepth() を呼び出します。

...

#include "showdepth.h"

static void keyboard(unsigned char key, int x, int y)
{
  switch (key) {
  case 'd':
  case 'D':
    /* d か D をタイプしたらデプスバッファの内容を表示 */
    showdepth();
    break;
  ...

glDrawPixels() を使っているのですが、表示位置を指定するために glRasterPos*() を使うと表示されたりされなかったりする(表示しようとする画像の左下の位置が誤差でビューポートから外れることがある?)ので、デフォルトでは glRasterPos*() を使っていません。

画面表示の保存

OpenGL の表示を画像ファイルに保存するには、OS の画面キャプチャ機能や、Fraps のような画面キャプチャソフトウェアを使えばいいのですが、これも以前何か必要なことがあって作りました。

saveimage.h

#ifdef __cplusplus
extern "C" {
#endif
  extern int saveimage(void);
#ifdef __cplusplus
}
#endif

saveimage.c

#include <stdio.h>
#include <stdlib.h>

#if defined(_WIN32)
#  define _CRT_SECURE_NO_WARNINGS
#  include <GL/glut.h>
#elif defined(__APPLE__)
#  define GL_SILENCE_DEPRECATION
#  include <GLUT/glut.h>
#else
#  define GL_GLEXT_PROTOTYPES
#  include <GL/glut.h>
#endif

/* 画像解像度 2835 pixel/meter ≒ 72dpi */
#define XRESOLUTION 2835
#define YRESOLUTION 2835

/*
** ウィンドウの内容を BMP 形式で保存
*/
int saveimage(void)
{
  /* BMP/DIB ファイルのヘッダ構造 */
  struct biHeader {
    unsigned char bfType[2];
    unsigned char bfSize[4];
    unsigned char bfReserved1[2];
    unsigned char bfReserved2[2];
    unsigned char bfOffBits[4];
    unsigned char biSize[4];
    unsigned char biWidth[4];
    unsigned char biHeight[4];
    unsigned char biPlanes[2];
    unsigned char biBitCount[2];
    unsigned char biCompression[4];
    unsigned char biSizeImage[4];
    unsigned char biXPelsPerMeter[4];
    unsigned char biYPelsPerMeter[4];
    unsigned char biClrUsed[4];
    unsigned char biClrImportant[4];
  } header;

  GLint view[4];
  GLubyte* image;
  size_t size;

  /* 描画終了を待つ */
  glFinish();

  /* 現在のビューポートのサイズを得る */
  glGetIntegerv(GL_VIEWPORT, view);

  /* ビューポートのサイズのメモリを確保する */
  size = view[2] * view[3] * 3;
  image = (GLubyte* )malloc(size);

  if (image) {
    static int count = 0;
    char filename[20];
    FILE *fp;

    snprintf(filename, sizeof(filename), "img%05d.bmp", count++);

    fp = fopen(filename, "wb");
    if (fp) {
      long temp;

      glReadPixels(view[0], view[1], view[2], view[3],
        GL_BGR, GL_UNSIGNED_BYTE, image);

      /*
      ** BMP は Little Endian なので
      ** 使用マシンの Endian に依存しないよう
      ** 1 byte ずつ設定する
      */
      header.bfType[0] = 'B';
      header.bfType[1] = 'M';

      temp = (long)(size + 54);
      header.bfSize[0] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.bfSize[1] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.bfSize[2] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.bfSize[3] = (unsigned char)(temp & 0xff);

      header.bfReserved1[0] = 0;
      header.bfReserved1[1] = 0;
      header.bfReserved2[0] = 0;
      header.bfReserved2[1] = 0;

      header.bfOffBits[0] = 54;
      header.bfOffBits[1] = 0;
      header.bfOffBits[2] = 0;
      header.bfOffBits[3] = 0;

      header.biSize[0] = 40;
      header.biSize[1] = 0;
      header.biSize[2] = 0;
      header.biSize[3] = 0;

      temp = view[2];
      header.biWidth[0] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biWidth[1] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biWidth[2] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biWidth[3] = (unsigned char)(temp & 0xff);

      temp = view[3];
      header.biHeight[0] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biHeight[1] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biHeight[2] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biHeight[3] = (unsigned char)(temp & 0xff);

      header.biPlanes[0] = 1;
      header.biPlanes[1] = 0;

      header.biBitCount[0] = 24;
      header.biBitCount[1] = 0;

      header.biCompression[0] = 0;
      header.biCompression[1] = 0;
      header.biCompression[2] = 0;
      header.biCompression[3] = 0;

      temp = (long)size;
      header.biSizeImage[0] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biSizeImage[1] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biSizeImage[2] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biSizeImage[3] = (unsigned char)(temp & 0xff);

      temp = XRESOLUTION;
      header.biXPelsPerMeter[0] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biXPelsPerMeter[1] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biXPelsPerMeter[2] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biXPelsPerMeter[3] = (unsigned char)(temp & 0xff);

      temp = YRESOLUTION;
      header.biYPelsPerMeter[0] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biYPelsPerMeter[1] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biYPelsPerMeter[2] = (unsigned char)(temp & 0xff);
      temp >>= 8;
      header.biYPelsPerMeter[3] = (unsigned char)(temp & 0xff);

      header.biClrUsed[0] = 0;
      header.biClrUsed[1] = 0;
      header.biClrUsed[2] = 0;
      header.biClrUsed[3] = 0;

      header.biClrImportant[0] = 0;
      header.biClrImportant[1] = 0;
      header.biClrImportant[2] = 0;
      header.biClrImportant[3] = 0;

      fwrite(&header, sizeof(header), 1, fp);
      fwrite(image, size, 1, fp);

      fclose(fp);

      return 1;
    }
  }

  return 0;
}

まず saveimage.h を読み込んでから、キーボードコールバック等で関数 saveimage() を呼び出します。

...

#include "saveimage.h"

static void keyboard(unsigned char key, int x, int y)
{
  switch (key) {
  case 's':
  case 'S':
    /* s か S をタイプしたら画面表示をファイルに保存 */
    saveimage();
    break;
  ...

画像は img00000.bmp、img00001.bmp というような連番ファイルで保存されます。既に存在する同名のファイルは上書きされてしまいます。