2013年6月23日日曜日

SDLでOpenGL4.2

必要環境
  • SDL2 - OpenGLの拡張に対応している
  • GLEW - OpenGLの拡張部分のエントリポイント(関数群)を記述している

最初に注意点ですが、 OpenGL4.2ではglBegin(),glEnd(),glVertex**が使えません。
環境によっては使えるようですが、非推奨のようです。
描画には、VAO(Vertex Array Object)やVBO(Vertex Buffer Object)が必要になります。
また、VBOの表示には最低1つのVAOが必要になるようです。

  1. 初期化
      SDL_Window* window_;
      SDL_GLContext gl_context_;
      Effect::Ptr shader_;
      bool Init() {
        //SDLの初期化
        if (SDL_Init(SDL_INIT_VIDEO) < 0) {
          std::cerr << "Init video failed.\n";
          return false;
        }
      
        //利用するOpenGLのバージョンを設定
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
        //深度バッファの設定
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
        //ダブルバッファの設定
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    
        //ウィンドウの生成
        const int WINDOW_X      = 100;
        const int WINDOW_Y      = 100;
        const int WINDOW_WIDTH  = 800;
        const int WINDOW_HEIGHT = 600;
        window_ = SDL_CreateWindow(
          "sample",
          WINDOW_X,
          WINDOW_Y,
          WINDOW_WIDTH,
          WINDOW_HEIGHT,
          SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
    
        if(window_ == nullptr) {
          std::cerr << "CreateWindow failed.\n";
          return false;
        }
    
        //OpenGLコンテキストの生成
        gl_context_ = SDL_GL_CreateContext(window_);
        
        //描画を同期
        SDL_GL_SetSwapInterval(1);
        
        //GLEW(OpenGL拡張)の初期化
        if (glewInit() != GLEW_OK) {
          std::cout << "Init GLEW failed.\n";
          return false;
        }
        return true;
      }
    
    ここでは、SDLの初期化、ウィンドウの生成、GLEWの初期化を行なっています。
    SDL_Init(SDL_INIT_VIDEO)は、SDLの初期化です。
    SDL_GL_SetAttribute(,)では、SDLからOpenGLの初期化に必要な設定をしています。
    SDL_CreateWindow(...)で、ウィンドウを生成しています。SDL2からは返り値がSDL_Window*に変更されているようです。
    SDL_GL_CreateContext()でウィンドウに対するOpenGLコンテキストを生成しています。 SDL_GL_SetSwapInterval(1)とすると、OpenGLの描画が垂直同期に合わせるようになります。 glewInit()で、GLEWを初期化し、OpenGLの拡張が全て利用できるようになります。
  2. シェーダの準備
      GLuint shader_id_;
      GLuint vertex_shader_id_;
      GLuint fragment_shader_id_;
      void InitShader(const std::string& vertex_shader_file, const std::string& fragment_shader_file) {
        if (vertex_shader_file.empty() || fragment_shader_file.empty()) {
          return;
        }
        //頂点シェーダの生成
        vertex_shader_id_ = glCreateShader(GL_VERTEX_SHADER);
        //フラグメントシェーダの生成
        fragment_shader_id_ = glCreateShader(GL_FRAGMENT_SHADER);
        //ソースコードの読み込み
        std::string vertex_source = ReadFile(vertex_shader_file);
        std::string fragment_source = ReadFile(fragment_shader_file);
        const char* v_source = vertex_source.c_str();
        const char* f_source = fragment_source.c_str();
        if (vertex_source.empty() || fragment_source.empty()) {
          return;
        }
        //シェーダとソースをバインド
        glShaderSource(vertex_shader_id_, 1, &v_source, 0);
        glShaderSource(fragment_shader_id_, 1, &f_source, 0);
        //シェーダをコンパイル
        glCompileShader(vertex_shader_id_);
        glCompileShader(fragment_shader_id_);
        //シェーダプログラムの生成
        shader_id_ = glCreateProgram();
        //利用するシェーダを登録
        glAttachShader(shader_id_, vertex_shader_id_);
        glAttachShader(shader_id_, fragment_shader_id_);
        //各シェーダの接続
        glLinkProgram(shader_id_);
      }
    
    シェーダ初期化の基本的な流れはシェーダの生成→ソースコードの読み込み→コンパイルです。
    そして、生成されたシェーダを「Program」として、リンクします。ここでの「Program」はパイプラインの流れのようなものです。
    glCreateShaderでシェーダの生成をします。引数で、シェーダの種類を指定しています。
    GL_VERTEX_SHADER​頂点シェーダ
    GL_TESS_CONTROL_SHADER​
    GL_TESS_EVALUATION_SHADER​
    GL_GEOMETRY_SHADER​ジオメトリシェーダ
    GL_FRAGMENT_SHADER​フラグメントシェーダ(ピクセルシェーダ)
    GL_COMPUTE_SHADER​コンピュートシェーダ
    glShaderSourceでシェーダとソースコードをバインドします。
    glCompileShaderでバインドされたソースコードをコンパイルします。
    「Program」は、どのシェーダを利用するかの流れを格納したものです。HLSLにおけるpassのようなものだと思われます。
    glCreateProgramで「Program」を生成します。
    glAttachShaderで「Program」で利用するシェーダを登録します。
    同種類のシェーダを複数登録することは可能だそうです。 これによりシェーダプログラムの分割ができます。
    glLinkProgramで登録されたシェーダを接続します。
  3. 描画
        //画面の消去
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        //シェーダの利用を開始する
        glUseProgram(shader_id_);
    
        //ここに描画処理
    
        //シェーダの利用を終了する
        glUseProgram(0);
        //画面に描画する
        SDL_GL_SwapWindow(window_);
    
    描画時にglUseProgramを呼び出して、使用するシェーダをしていします。
    オブジェクトの描画については、glBegin,glEndなどのコードが使えない環境では長くなるので、別に記述します。
  4. shader.vs
    void main() {
      /*gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;*/
      /* gl_ModelViewProjectionMatrixとgl_VertexはOpenGL4.2では利用できないようです*/
    }
    
    頂点シェーダプログラムの例です。
    デフォルトの入出力変数を使って、座標変換を行なっています。
    座標変換用の行列も自分で指定する必要があるようです。
  5. shader.ps
    void main() {
      /*gl_FragColor = vec4(1.0);*/
      /*gl_FragColorも利用できないようです*/
    }
    
    フラグメントシェーダの例です。
    全部白にしています。

サンプルコード

参考:http://www.opengl.org/wiki/Tutorials

0 件のコメント:

コメントを投稿