OpenGLプログラミング/モダンOpenGL イントロダクション

出典: フリー教科書『ウィキブックス(Wikibooks)』
移動先: 案内検索
Our first program

はじめに[編集]

OpenGLに関するドキュメントのほとんどは、特に"固定パイプライン"において、非推奨の機能を使用しています。 OpenGL 2.0 およびそれ以降に付属する プログラマブルパイプライン では、プログラマブルな部分は シェーダ で処理され、 GLSLというCライクな言語で書かれます。

この文書の対象は、OpenGLを学んでいる方で、最新のOpenGLを最初から使用したい方です。 プログラマブルパイプラインはより柔軟ですが、固定パイプラインほどには直感的ではありません。 しかし、最初は 簡単なコード で始めることを確認しておきます。 OpenGL 1.x 向けの NeHe's チュートリアルと同様のアプローチを使用し、例やチュートリアルで プログラマブル・パイプライン の背後にある理論をより良く理解できるようにしています。

最初は頂点配列やシェーダが、古い即時モードや固定レンダリングパイプラインに比べて、扱うのに苦労しそうに見えるかもしれません [1]。 しかし、最終的に、特にバッファ·オブジェクトを使用している場合、あなたのコードは 非常にクリーン になり、グラフィックスも さらに速くなります。

このページのコード例は、 パブリックドメイン にあります。 自由に望むままに手を加えてみましょう。

このドキュメントをお友達にも広めましょう! ウィキブックスはさらなる周知と貢献を歓迎しています:)

備考:

  • 固定パイプラインとプログラマブルパイプラインを混在させることはある程度は可能ですが、固定パイプラインは廃止され、そして OpenGL ES 2.0 (または WebGL 派生) では全く利用できないので、使用しません。
  • 今はOpenGL 3と4もあり、これには特にジオメトリシェーダが導入されていたりもしますが、今回はそれ以前のバージョンに比べて光の進化のみです。2012年現在の モバイルプラットフォーム では利用できないので、今のところはOpenGL 2.0に集中していきます。

Base libraries[編集]

OpenGL, GLUT, およびGLEW を使用する準備ができていることを確認しておく必要があります。

"http://en.wikibooks.org/w/index.php?title=Special:PrefixIndex&prefix=OpenGL+Programming/Installation&"&HYPERLINK "http://en.wikibooks.org/w/index.php?title=Special:PrefixIndex&prefix=OpenGL+Programming/Installation&"prefix=OpenGL+Programming%2FInstallationHYPERLINK "http://en.wikibooks.org/w/index.php?title=Special:PrefixIndex&prefix=OpenGL+Programming/Installation&"&namespace=0 the installation pages をチェックして、あなたのシステムのすべてのものを準備しましょう。

これらのライブラリをOpenGLのスタックに据え付けるために、APIs, Libraries and acronymsをチェックしましょう。

備考: GLUTを選んだのは、ポータブルレイヤーが得られるもののなかで最小限のものだったからです。 高度な機能を使用することはなく、ほとんどすべてのコードは素のOpenGLなので、あなたが新しいゲームやアプリケーションを作成するときに、GLFW、SDL、およびSFMLといった別の、より一般的なライブラリに移行しても問題は無いはずです。 これらのライブラリに切り替える方法を扱う具体的なチュートリアルを書くこともあるかもしれません。

2Dで三角形の表示[編集]

始めはシンプルにいきましょう :) 初めて起動するまでに長い時間をかけてハックするような複雑なプログラムで苦労するのではなく​​、ここでの目標は、一歩一歩を経て改善していけるような、基本的ながらも機能的なプログラムを取得することです、

三角形は、3Dプログラミングにおいて最も基本的な単位です。 実際、あなたがビデオゲームで見るすべては三角形で作られています! 小さいものや、テクスチャのついた三角形もありますが、それでもやはり三角形です :)

プログラムマブルパイプラインで三角形を表示するために、最低限必要なものは:

  • アプリケーションをビルドするためのMakefile
  • OpenGLとヘルパーライブラリの初期化
  • 三角形の3つの頂点の座標を持つ配列
  • GLSLプログラムと:
    • 頂点シェーダ:各頂点を個別に渡し、画面上(2D)の座標を計算する
    • フラグメント(ピクセル)シェーダ:OpenGLが三角形に含まれている各画素を渡し、その色を計算する
  • 頂点シェーダに頂点を渡す

Makefile[編集]

私たちの例のための 'make' を設定するのは非常に簡単です。これを 'Makefile' のファイルに書きます:

LDLIBS=-lglut -lGLEW -lGL
all: triangle

アプリケーションをコンパイルするには、ターミナルで次のように入力します:

make

もう少し派手なMakefileは次のようになります:

CC=g++
LDLIBS=-lglut -lGLEW -lGL
all: triangle
clean:
        rm -f *.o triangle
.PHONY: all clean

このようにすると、 'make clean' とタイプすることで、三角形のバイナリおよび生成されることのある中間オブジェクトファイルを削除できます。 そこにある .PHONY ルールは、 "all" と "clean" がファイルではないということをmakeに伝えるためのもので、そうすることで実際に同じディレクトリ内にそのような名前のファイルがある場合でも混乱しなくなります。.

別のプログラミング環境を使用したい場合は、Setting Up OpenGL セクションを参照してください。

初期化[編集]

triangle.cファイルを作成してみましょう:

/* Using the standard output for fprintf */
#include <stdio.h>
#include <stdlib.h>
/* Use glew.h instead of gl.h to get all the GL prototypes declared */
#include <GL/glew.h>
/* Using the GLUT library for the base windowing setup */
#include <GL/glut.h>
/* ADD GLOBAL VARIABLES HERE LATER */

int init_resources(void)
{
  /* FILLED IN LATER */
  return 1;
}

void onDisplay()
{
  /* FILLED IN LATER */
}

void free_resources()
{
  /* FILLED IN LATER */
}

int main(int argc, char* argv[])
{
  /* Glut-related initialising functions */
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);
  glutInitWindowSize(640, 480);
  glutCreateWindow("My First Triangle");

  /* Extension wrangler initialising */
  GLenum glew_status = glewInit();
  if (glew_status != GLEW_OK)
  {
    fprintf(stderr, "Error: %s\n", glewGetErrorString(glew_status));
    return EXIT_FAILURE;
  }

  /* When all init functions runs without errors,
  the program can initialise the resources */
  if (1 == init_resources())
  {
    /* We can display it if everything goes OK */
    glutDisplayFunc(onDisplay);
    glutMainLoop();
  }

  /* If the program exits in the usual way,
  free resources and exit with a success */
  free_resources();
  return EXIT_SUCCESS;
}

init_resourcesでは、GLSLのプログラムを作成します。 onDisplayでは、三角形を描画します。 In free_resourcesでは、GLSLのプログラムを破棄します。

頂点配列[編集]

最初の三角形は2Dで表示されます - そのうちもっと複雑なものへと踏み入っていきましょう。 私たちは三角形を、その3点の2D (x,y) 座標で記述します。 デフォルトでは、OpenGLの座標は [-1, 1] の範囲内にあります。.

  GLfloat triangle_vertices[] = {
     0.0,  0.8,
    -0.8, -0.8,
     0.8, -0.8
  };

今のところは、このデータ構造を記憶にとどめておいて、後ほどコードの中に書いていきましょう。 注:座標は-1と+1の間にありますが、ウィンドウは正方形ではありません!次のレッスンで、アスペクト比を固定する方法を見ていきます。

頂点シェーダ[編集]

このGLSLプログラムは、配列の各点をひとつひとつ取得し、それらを画面上のどこに置くのかを伝えます。 この場合、ポイントは既に2Dスクリーン座標になっているので、変更しません。私たちのGLSLプログラムは、このようになります:

#version 120
attribute vec2 coord2d;
void main(void) {
  gl_Position = vec4(coord2d, 0.0, 1.0);
}
Wikipedia
ウィキペディアGLSL#Versionsの記事があります。
  • #version 120 はv1.20の意味で、GLSLのバージョンはOpenGL 2.1です。
  • OpenGL ES 2 のGLSL2のGLSLもGLSL V1.20に基づいていますが、そのバージョンは1.00です (#version 100) [2].
  • coord2d は現時点の頂点です。入力変数であり、Cコードで宣言する必要があります
  • gl_Position は最終的な画面上の位置です。組み込みの出力変数です
  • vec4x座標と y座標と、そのあとz 座標として 0 を受け取っています。 最後のひとつ、w=1.0 は同次座標のためのものです ( transformation matricesで使われる)。

さて、このシェーダをOpenGLにコンパイルさせる必要があります。mainの上で init_resources をスタートさせましょう:

/*
Function: init_resources
Receives: void
Returns: int
This function creates all GLSL related stuff
explained in this example.
Returns 1 when all is ok, 0 with a displayed error
*/
int init_resources(void)
{
  GLint compile_ok = GL_FALSE, link_ok = GL_FALSE;

  GLuint vs = glCreateShader(GL_VERTEX_SHADER);
  const char *vs_source = 
#ifdef GL_ES_VERSION_2_0
    "#version 100\n"  // OpenGL ES 2.0
#else
    "#version 120\n"  // OpenGL 2.1
#endif
    "attribute vec2 coord2d;                  "
    "void main(void) {                        "
    "  gl_Position = vec4(coord2d, 0.0, 1.0); "
    "}";
  glShaderSource(vs, 1, &vs_source, NULL);
  glCompileShader(vs);
  glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_ok);
  if (0 == compile_ok)
  {
    fprintf(stderr, "Error in vertex shader\n");
    return 0;
  }

glShaderSourceに文字列としてソースを渡しています (後ほど別のより便利なやりかたでシェーダーコードを読み込んでいきます)。 型は GL_VERTEX_SHADERに指定しています。

フラグメントシェーダ[編集]

OpenGLに画面の位置の3点を持たせると、その間のスペースを埋めて三角形を作ります。 3点間の画素それぞれについて、フラグメントシェーダが呼び出されます。 私たちのフラグメントシェーダでは、各ピクセルを青色に染めたいと伝えています:

#version 120
void main(void) {
  gl_FragColor[0] = 0.0;
  gl_FragColor[1] = 0.0;
  gl_FragColor[2] = 1.0;
}

同様にそれをGL_FRAGMENT_SHADER型でコンパイルします。 init_resources 手続きを継続していきましょう:

  GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
  const char *fs_source =
    "#version 120           \n"
    "void main(void) {        "
    "  gl_FragColor[0] = 0.0; "
    "  gl_FragColor[1] = 0.0; "
    "  gl_FragColor[2] = 1.0; "
    "}";
  glShaderSource(fs, 1, &fs_source, NULL);
  glCompileShader(fs);
  glGetShaderiv(fs, GL_COMPILE_STATUS, &compile_ok);
  if (!compile_ok) {
    fprintf(stderr, "Error in fragment shader\n");
    return 0;
  }

GLSLプログラム[編集]

GLSLプログラムは、頂点シェーダとフラグメントシェーダの組み合わせです。 通常、それらは一緒にはたらき、そして頂点シェーダがフラグメントシェーダに追加情報を渡すこともできます。

#includeの下にグローバル変数を作成し、プログラムハンドルを格納します:

GLuint program;

こちらは、プログラム内の頂点シェーダーとフラグメントシェーダーを link する方法です。 init_resources 手続きに続けて:

  program = glCreateProgram();
  glAttachShader(program, vs);
  glAttachShader(program, fs);
  glLinkProgram(program);
  glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
  if (!link_ok) {
    fprintf(stderr, "glLinkProgram:");
    return 0;
  }

頂点シェーダに三角形の頂点をわたす[編集]

前述のとおり、三角形の各頂点はcoord2d属性を使用して頂点シェーダに渡します。 こちらがそれをCコードで宣言する方法です。

まず、第2のグローバル変数を作成しましょう:

GLint attribute_coord2d;

init_resources手続きを以下で締めくくりましょう:

  const char* attribute_name = "coord2d";
  attribute_coord2d = glGetAttribLocation(program, attribute_name);
  if (attribute_coord2d == -1) {
    fprintf(stderr, "Could not bind attribute %s\n", attribute_name);
    return 0;
  }

  return 1;
}

これでもう、三角形の頂点を頂点シェーダに渡すことができます。onDisplay 手続きを書いていきましょう。各セクションはコメントで説明されています:

void onDisplay()
{
  /* Clear the background as white */
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT);

  glUseProgram(program);
  glEnableVertexAttribArray(attribute_coord2d);
  GLfloat triangle_vertices[] = {
     0.0,  0.8,
    -0.8, -0.8,
     0.8, -0.8,
  };
  /* Describe our vertices array to OpenGL (it can't guess its format automatically) */
  glVertexAttribPointer(
    attribute_coord2d, // attribute
    2,                 // number of elements per vertex, here (x,y)
    GL_FLOAT,          // the type of each element
    GL_FALSE,          // take our values as-is
    0,                 // no extra data between each position
    triangle_vertices  // pointer to the C array
  );

  /* Push each element in buffer_vertices to the vertex shader */
  glDrawArrays(GL_TRIANGLES, 0, 3);
  glDisableVertexAttribArray(attribute_coord2d);

  /* Display the result */
  glutSwapBuffers();
}

glVertexAttribPointer は、 init_resources で作成したデータ·バッファからの各頂点を取得して、頂点シェーダに渡すようOpenGLに指示します。 これらの頂点は、各ポイントの画面上の位置を定義し、形成する三角形のピクセルにはフラグメントシェーダによって色がつけられます。

備考:次のチュートリアルでは、頂点バッファオブジェクトという、少し複雑な、グラフィックカードに頂点を格納するための新しい方法を紹介します。

唯一残っている部分は free_resourcesで、プログラムを終了するときにクリーンアップするためのものです。 このような特定のケースではそれほど重要ではありませんが、アプリケーションをこのような方法で構成しておくのは良いことです:

void free_resources()
{
  glDeleteProgram(program);
}

最初のOpenGL 2.0プログラムは完成です!

この方法でうまくいかない場合[編集]

次のチュートリアルでは、最初のミニマルなコードにさらなる堅牢性を加えていきます。 tut02 のコードを是非お試しください (最下段のコードへのリンクを参照)。

実験![編集]

Our first program, with another fragment shader

このコードでお気軽に実験をしてみましょう:

  • 三角形2つを表示することによって、正方形を作ってみよう
  • 色を変更してみよう
  • 使用している各機能のOpenGLのmanページを読もう:
  • このコードをフラグメントシェーダで使用してみよう - それはどんなことをする?
gl_FragColor[0] = gl_FragCoord.x/640.0;
gl_FragColor[1] = gl_FragCoord.y/480.0;
gl_FragColor[2] = 0.5;

次のチュートリアルでは、いくつかのユーティリティ関数を追加して、簡単にシェーダを書いたりデバッグできるようにしていきます。

Notes[編集]

  1. ^ Since so many 3D features were removed in OpenGL 2, some interestingly define it as a 2D rasteration engine!
  2. ^ Cf. テンプレート:Cite web - The OpenGL ES Shading Language (also known as GLSL ES or ESSL) is based on the OpenGL Shading Language (GLSL) version 1.20