スポンサーリンク

GLSLを試す (1)

glBegin - glEndが大好きなのだけれどそろそろモダンOpenGLにも触っておかないとまずいということでVBOと頂点シェーダとフラグメントシェーダの基礎を触っておく。

ややこしいのは、VBO(=頂点バッファ)のコードと頂点シェーダ関連コードが、直接的な関連性が低いのに密接に絡み合っているところ。

CPU側ソースコード

glut初期化コード

glutを使わないのなら勿論必要ない。ただしglewを使うのでその初期化が必要。

余談だけれどもgl.hを使うのならinclude順はglew.h→gl.hとなる。さらにwindowsではgl.hの前にwindows.hを入れなければならない。つまりwgl等でウィンドウを作成する場合、

#include <windows.h>
#include <gl/glew.h>
#include <gl/GL.h>

のような順番になる。この例ではglutだけなのでgl.hは自分でincludeしない。ただしglut.hの中でgl.hがincludeされているので、順番はglew.h→glut.hでなければならない。さもなくば

fatal error C1189: #error: gl.h included before glew.h

と言われる。

#pragma warning(disable:4996)

#pragma comment(lib,"glew32.lib")


#include <gl/glew.h>
#include <GL/glut.h>

#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>


void init(void);
void display(void);

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA);
  glutCreateWindow(argv[0]);
  glutDisplayFunc(display);
glewInit(); // glewの初期化
init(); glutMainLoop(); return 0; }

初期化コード呼び出し元

void init(void)
{

  //頂点データと色情報の作成
  prepare_buffers();

  //頂点シェーダの準備
  prepare_vertex_shader();

  //フラグメントシェーダの準備
  prepare_fragment_shader();

  //プログラムのリンク
  link_program();

}

頂点データとバッファの作成コード

今までglBegin~glEndで囲んでいたデータを配列として作成して先にGPUへ転送しておく。

//バッファとデータ
typedef GLfloat points_t[3];
GLuint vertexbuffer;//バッファのIDを格納する変数
GLuint colorbuffer;//バッファのIDを格納する変数
points_t position[3];
points_t color[3];

//////////////////////////////////////////////////
void prepare_buffers() {

  //頂点座標
  position[0][0] = 0;
  position[0][1] = 0.5;
  position[0][2] = 0;

  position[1][0] = 0.5;
  position[1][1] = -0.5;
  position[1][2] = 0;

  position[2][0] = -0.5;
  position[2][1] = -0.5;
  position[2][2] = 0;


  //色
  color[0][0] = 1.0;
  color[0][1] = 0.0;
  color[0][2] = 0.0;

  color[1][0] = 0.0;
  color[1][1] = 1.0;
  color[1][2] = 0.0;

  color[2][0] = 0.0;
  color[2][1] = 0.0;
  color[2][2] = 1.0;

  glGenBuffers(1, &vertexbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
  glBufferData(
GL_ARRAY_BUFFER,
     
3 * 3 * sizeof(GLfloat),
position,
GL_STATIC_DRAW); glGenBuffers(1, &colorbuffer); glBindBuffer(GL_ARRAY_BUFFER, colorbuffer); glBufferData(
GL_ARRAY_BUFFER,
3 * 3 * sizeof(GLfloat),
color,
GL_STATIC_DRAW); }

頂点シェーダの準備

クソ長く見えるがやっていることは
①glCreateShaderでシェーダのIDを作成
②glShaderSourceでシェーダのソースコードをシェーダに教える
③glCompileShaderでシェーダをコンパイル
④glGetShaderivでシェーダのコンパイルに成功したかを確認
⑤glGetShaderInfoLogでエラー内容を確認

だけで本質的には十行程度。ファイル読み込み部がコードの長さを異常に長くみせている。本質とは直接関係ないのに。

GLuint VertexShaderID;

void
prepare_vertex_shader() { ////////////////////////////////////////////// // シェーダを作ります // (作るといっても宣言みたいなもの) VertexShaderID = glCreateShader(GL_VERTEX_SHADER); //////////////////////////////////////////// // ファイルから頂点シェーダを読み込みます。
// 注意 ここはSTLのifstreamとかstringstreamの使い方の話で、
// OpenGL命令は一つも無い。
const char * vertex_file_path = "C:\\test\\verts.sh"; std::string VertexShaderCode; std::ifstream VertexShaderStream(vertex_file_path, std::ios::in); if (VertexShaderStream.is_open()) { std::stringstream sstr; sstr << VertexShaderStream.rdbuf(); VertexShaderCode = sstr.str(); VertexShaderStream.close(); } //////////////////////////////////////////// // 頂点シェーダをコンパイルします。 printf("Compiling shader : %s\n", vertex_file_path); char const * VertexSourcePointer = VertexShaderCode.c_str(); glShaderSource(VertexShaderID, 1, &VertexSourcePointer, NULL); glCompileShader(VertexShaderID); //////////////////////////////////////////// // エラーチェック GLint Result = GL_FALSE; int InfoLogLength; // 頂点シェーダをチェックします。 glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if (Result == FALSE) { std::vector<char> VertexShaderErrorMessage(InfoLogLength); glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]); fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]); } }

あとエラー処理が行数を嵩張らせている。普通(?)OpenGLで描画とかするときに、エラーを無視してもなんとなく上手く動いているように見えることも多いけれど、シェーダのコンパイルをする以上、コンパイルエラーをちゃんと見ないとデバッグ出来ないのでエラー処理はちゃんと入れないとまずい。

これはエラーを見る癖をつけましょうとかの精神論ではなく、見ないと何故動かないのかが全くわからないから省きようがない

フラグメントシェーダの準備

頂点シェーダとの違いは、読み込むファイルと、glCreateShaderに渡す定数がGL_FRAGMENT_SHADERになっているところぐらいで、ほぼ同じ。

GLuint FragmentShaderID;

void
prepare_fragment_shader() { ///////////////////////////////////////////// // シェーダを作ります。 FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); ///////////////////////////////////////////// // ファイルからフラグメントシェーダを読み込みます。 const char * fragment_file_path = "C:\\test\\frags.sh"; std::string FragmentShaderCode; std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in); if (FragmentShaderStream.is_open()) { std::stringstream sstr; sstr << FragmentShaderStream.rdbuf(); FragmentShaderCode = sstr.str(); FragmentShaderStream.close(); } ///////////////////////////////////////////// // フラグメントシェーダをコンパイルします。 printf("Compiling shader : %s\n", fragment_file_path); char const * FragmentSourcePointer = FragmentShaderCode.c_str(); glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer, NULL); glCompileShader(FragmentShaderID); GLint Result = GL_FALSE; int InfoLogLength; /////////////////////////////////////////////
/
/ フラグメントシェーダをチェックします。 glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if (Result == GL_FALSE) { std::vector<char> FragmentShaderErrorMessage(InfoLogLength); glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]); fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]); } }

プログラムのリンク

頂点シェーダとフラグメントシェーダのセットをProgramとして一つに束ねる。

ここもやっていることは、
①glCreateProgram でプログラムIDを取得
②glAttachShader でシェーダを登録
③glLinkProgram プログラムをリンク
④glGetProgramiv でプログラムのエラーをチェック
⑤glGetProgramInfoLog でプログラムのエラーの内容をチェックする

GLuint ProgramID;

void
link_program() { GLint Result = GL_FALSE; int InfoLogLength; //////////////////////////////////////// // プログラムをリンクします。 fprintf(stdout, "Linking program\n"); ProgramID = glCreateProgram(); glAttachShader(ProgramID, VertexShaderID); glAttachShader(ProgramID, FragmentShaderID); glLinkProgram(ProgramID); //////////////////////////////////////// // プログラムをチェックします。 glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result); glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength); std::vector<char> ProgramErrorMessage((std::max)(InfoLogLength, int(1))); glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]); fprintf(stdout, "%s\n", &ProgramErrorMessage[0]); }

シェーダ側ソースコード

頂点シェーダ (verts.sh)

拡張子は何でもいい。shだとシェルスクリプトと勘違いしそうなら.verts、.fragsなどでいい。

#version 460 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 incolor;

out vec4 vertexColor;

uniform mat4 gl_ModelViewMatrix;
uniform mat4 gl_ProjectionMatrix;

void main()
{
  gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * vec4(aPos, 1.0);
  vertexColor = vec4(incolor, 1.0);
}

フラグメントシェーダ(frags.sh)

#version 460 core

out vec4 FragColor;
  
in vec4 vertexColor;

void main()
{
  FragColor = vertexColor;
} 

CPU側 描画

void display(void)
{
  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT);

  glLoadIdentity();
  glTranslated(0.5, 0.0, 0.0);//平行移動

  // シェーダを使う
  glUseProgram(ProgramID);

  // 頂点バッファ:頂点
  glEnableVertexAttribArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
  glVertexAttribPointer(
    0,                  // シェーダ内のlayoutとあわせる
    3,                  // 1要素の要素数(x,y,z)で3要素
    GL_FLOAT,           // タイプ
    GL_FALSE,           // 正規化しない(データが整数型の時)
    0,                  // ストライド
    (void*)0            // 配列バッファオフセット
  );

  // カラーバッファを有効にする
  glEnableVertexAttribArray(1);
  glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
  glVertexAttribPointer(
    1,                  // シェーダ内のlayoutとあわせる
    3,                  // 1要素の要素数(r,g,b)で3要素
    GL_FLOAT,           // タイプ
    GL_FALSE,           // 正規化しない(データが整数型の時)
    0,                  // ストライド
    (void*)0            // 配列バッファオフセット
  );

  // 三角形を描きます!
  glDrawArrays(GL_TRIANGLES, 0, 3);

  glDisableVertexAttribArray(0);//バッファを無効にする
  glDisableVertexAttribArray(1);

  glFlush();
}

その他

後半へ続く・・・


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: