読者です 読者をやめる 読者になる 読者になる

OpenGLをC++11でラップしてみんとてするなり 第4回

OpenGL C++

●glEnable()/glDisable()/glIsEnabled()の使い方
 まず最初に、サンプルとなるコードを提示します。

main.cpp

#include "glall.h"
#include "glut/Window.h"

/**
 * glut::Windowを継承してウィンドウを作る
 */
class Window : public glut::Window
{
public :
  Window(const char *name) : glut::Window(name) {}

protected :
  virtual void reshape(gl::Context &context, int width, int height) override
  {
    glViewport(0, 0, width, height) ;
  }

  virtual void display(gl::Context &context) override
  {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ;
    glEnable(GL_DEPTH_TEST) ;

    // 奥行きを持たせた四角形を表示
    glBegin(GL_TRIANGLE_FAN) ;
    glColor3ub(255, 255, 255) ;
    glVertex3d(-1, -1, -1) ;
    glVertex3d(-1, +1, -1) ;
    glColor3ub(128, 128, 128) ;
    glVertex3d(+1, +1, +1) ;
    glVertex3d(+1, -1, +1) ;
    glEnd() ;

    // デプステストを無効化して、Z=0に四角形を表示
    glDisable(GL_DEPTH_TEST) ;
    drawRect(-0.8, +0.8, +0.3, +0.8) ;
    fillRect(-0.8, +0.8, +0.3, +0.8) ;

    // デプステストを有効化して、Z=0に四角形を表示
    glEnable(GL_DEPTH_TEST) ;
    drawRect(-0.8, +0.8, -0.8, -0.3) ;
    fillRect(-0.8, +0.8, -0.8, -0.3) ;

    glFinish() ;
  }

private :
  void drawRect(double l, double r, double b, double t)
  {
    glLineWidth(3) ;
    glColor3ub(255, 0, 0) ;
    glBegin(GL_LINE_LOOP) ;
    glVertex3d(l, b, 0) ;
    glVertex3d(l, t, 0) ;
    glVertex3d(r, t, 0) ;
    glVertex3d(r, b, 0) ;
    glEnd() ;
  }
  
  void fillRect(double l, double r, double b, double t)
  {
    glColor3ub(255, 0, 255) ;
    glBegin(GL_TRIANGLE_FAN) ;
    glVertex3d(l, b, 0) ;
    glVertex3d(l, t, 0) ;
    glVertex3d(r, t, 0) ;
    glVertex3d(r, b, 0) ;
    glEnd() ;
  }
} ;


int main(int argc, char *argv[])
{
  glutInit(&argc, argv) ;
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);

  Window w("OpenGLをC++11でラップしてみんとてするなり") ;

  glutMainLoop() ;

  return 0;
}

 これを実行すると、上側の長方形はデプステストを行わないため全体が表示され、下側はデプステストが行われて右半分のみ表示されるはずです。


 次に、drawRect()を以下のように修正し、赤枠だけはデプステストを行わないようにします。赤枠は、隠れている部分の形を知るためにつけたいわけです。

  void drawRect(double l, double r, double b, double t)
  {
    glDisable(GL_DEPTH_TEST) ;  // この1行を追加
    glLineWidth(3) ;
    glColor3ub(255, 0, 0) ;
    glBegin(GL_LINE_LOOP) ;
    glVertex3d(l, b, 0) ;
    glVertex3d(l, t, 0) ;
    glVertex3d(r, t, 0) ;
    glVertex3d(r, b, 0) ;
    glEnd() ;
  }

 当然ながら、こうなります。


 glDisable()の影響は関数外まで及んでいるために次のfillRect()までデプステストがなくなってしまったのが原因ですので、次のようにすれば目的が達成できます。

  void drawRect(double l, double r, double b, double t)
  {
    GLboolean depth_test_enabled = glIsEnabled(GL_DEPTH_TEST) ;
    
    glDisable(GL_DEPTH_TEST) ;
    glLineWidth(3) ;
    glColor3ub(255, 0, 0) ;
    glBegin(GL_LINE_LOOP) ;
    glVertex3d(l, b, 0) ;
    glVertex3d(l, t, 0) ;
    glVertex3d(r, t, 0) ;
    glVertex3d(r, b, 0) ;
    glEnd() ;

    if (depth_test_enabled == GL_TRUE) {
      glEnable(GL_DEPTH_TEST) ;
    }
  }


 これをもう少しC++っぽく書くと、こうなります。

  void drawRect(double l, double r, double b, double t)
  {
    DepthTest test(false) ;

    glLineWidth(3) ;
    glColor3ub(255, 0, 0) ;
    glBegin(GL_LINE_LOOP) ;
    glVertex3d(l, b, 0) ;
    glVertex3d(l, t, 0) ;
    glVertex3d(r, t, 0) ;
    glVertex3d(r, b, 0) ;
    glEnd() ;
  }

 クラスDepthTestのコンストラクタで現在値を取得&新しい値を設定し、デストラクタで元に戻すのはC++的には基本。

●DepthTestクラスの作り方
 このDepthTestクラスを実際に作るとなると、こんな感じになるかと思います。
 C++11の機能、コンストラクタの委譲を使う事で、コンストラクタから自分自身の別のコンストラクタを呼ぶようにしています。

gl/DepthTest.h

#ifndef gl__DepthTest_h
#define gl__DepthTest_h

#include "../noncopyable.h"

namespace gl
{
  class DepthTest : noncopyable<DepthTest>
  {
  public :
    DepthTest() ;
    DepthTest(bool enabled) ;

    ~DepthTest() ;

    DepthTest& operator =(bool enabled) ;

    operator bool() const ;

  private :
    bool m_initial_value ;
  } ;
}
#endif

gl/DepthTest.cpp

#include "DepthTest.h"
#include "../glall.h"

gl::DepthTest::DepthTest()
{
  m_initial_value = (bool)(*this) ;
}

gl::DepthTest::DepthTest(bool enabled)
  : DepthTest()
{
  if (m_initial_value != enabled) *this = enabled ;
}

gl::DepthTest::~DepthTest()
{
  *this = m_initial_value ;
}

gl::DepthTest& gl::DepthTest::operator =(bool enabled)
{
  if (enabled) glEnable(GL_DEPTH_TEST) ;
  else         glDisable(GL_DEPTH_TEST) ;

  return *this ;
}

gl::DepthTest::operator bool() const
{
  return (glIsEnabled(GL_DEPTH_TEST) != GL_FALSE) ? true : false ;
}

 あたかも単なるbool型であるかのように、bool型に変換したり、bool型を代入したりできるようにしたため、使い勝手はよいはずです。
 しかし、至るところにGL_DEPTH_TESTが出てくるのが気に食わないので、こうしてしまいましょう。

#ifndef gl__Enabler_h
#define gl__Enabler_h

#include "../noncopyable.h"
#include "../glall.h"

namespace gl
{
  template <int FlagName>
  class Enabler : noncopyable<Enabler<FlagName>>
  {
  public :
    Enabler() : m_initial_value(*this) {}
    Enabler(bool enabled)
      : Enabler()
    {
      if (m_initial_value != enabled) *this = enabled ;
    }

    ~Enabler()
    {
      *this = m_initial_value ;
    }

    Enabler& operator =(bool enabled)
    {
      if (enabled) glEnable(FlagName) ;
      else         glDisable(FlagName) ;
      
      return *this ;
    }

    operator bool() const
    {
      return (glIsEnabled(FlagName) != GL_FALSE) ? true : false ;
    }

  private :
    bool m_initial_value ;
  } ;

  typedef Enabler<GL_DEPTH_TEST> DepthTest ;
}

#endif

 これで、テンプレート引数を差し替えるだけで、全てのフラグに対応できるようになりました。
 ただ、当初の目的は「コンテキストクラス内で全てのGL処理を賄う」でしたので、DepthTestのインスタンスをgl::Context経由で作るように変更したいと思います。
 が、今回はソースが多くて長くなりましたので、その辺は次回に回します。