tuedaの日記

2011-11-01

[][] モダンなOpenGLではテクスチャー周りがずいぶん綺麗になった

f:id:tueda_wolf:20111101145858p:image

OpenGL 3.3以降はテクスチャー周りがだいぶ整理されて使いやすくなった。

今までの苦労は何だったんだろうと思う。

一番大きな変化はテクスチャーがテクスチャーデータ(Texture Object)と、それをサンプリングするサンプラー(Sampler Object)の2つに分離したこと。

テクスチャーオブジェクトはglGenTextures()で作りバインドしてglTexImage2D()でデータを転送する。

    glGenTextures (1, &texture);
    glBindTexture (GL_TEXTURE_2D, texture);
    glTexImage2D  (GL_TEXTURE_2D, 0, 
                   GL_RGBA, width, height, 0,
                   GL_RGBA, GL_UNSIGNED_BYTE, pixels);

それに対してサンプラーオブジェクトはglGenSamplers()で作りglSamplerParameter()でパラメーターを指定する。

(この時点ではまだバインドする必要はない)

    glGenSamplers       (1, &sampler);
    glSamplerParameteri (sampler, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glSamplerParameteri (sampler, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glSamplerParameteri (sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glSamplerParameteri (sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

以上は初期化フェーズで一回実行すればいい。

描画時は使用するテクスチャーユニット(番号)を決めてglActiveTexture()で有効化し、テクスチャーオブジェクトとサンプラーオブジェクトをそれぞれバインドすれば完了。

    // texture
    glActiveTexture (GL_TEXTURE0);
    glBindTexture   (GL_TEXTURE_2D, texture);
    glBindSampler   (0, sampler);

あと普通はシェーダー側で何番のテクスチャーユニットを使えばいいかをUniform変数で渡す必要がある。

下のコードのtexture_indexはgetUniformLocation()で帰ってきたUniform変数の番号。

    // setup shader uniform
    glUniform1i (texture_index, 0);  // = use GL_TEXTURE0

驚くべきことにglEnalbe(GL_TEXTURE_2D)でテクスチャーを有効にする必要はなくなった。

エラーにはならないが何の効果も及ぼさない。ちなみにglDisable()でも無効化できないようだ。

プログラム側から特定のテクスチャーユニットを無効化するのはできないようだ。

(無効化できるとシェーダー側でtexture()関数を読んだ時何を返せばいいのかという問題がある...)


ちなみにサンプラーをセットしないと”真っ黒”になった。

仕様にはなく未定義だと思う。



OpenGL 3.3〜は人間にやさしい

テクスチャーオブジェクト(TBO)自体は以前のOpenGLから普通に使えていた。

テクスチャー周りの設定がサンプラーオブジェクトで統一され一括して切り替えられるようになった事が大きい。

もうglActiveTexture()とglClientActiveTexture()の違いに悩まされることはない。

テクスチャー座標を送るのにglEnableClientState()を使うこともない。

以前のOpenGLだとテクスチャユニットは0番から順に使わないと動作が未定義だったが、現在のバージョンでは何番でも自由に使っていいようだ。

OpenGL3.3以降はたった3つ覚えるだけでいい。

  • glActiveTexture : 使用するテクスチャーユニットの番号
  • glBindTexture : テクスチャーのデータ
  • glBindSampler : テクスチャーのサンプリング方法

極めて明確で悩む所がない。もうOpenGL3.3より前のOpenGLを使う気になれない。

ソース

いずれどこかできちんとまとめる.

#include <GL/glew.h>
#include <GL/glut.h>
#include <iostream>
#include <fstream>
#include <cassert>
#include "stb_image.hpp"
using namespace std;

const char* VERT_SHADER_FILE = "../vertex-shader.vert";
const char* FRAG_SHADER_FILE = "../fragment-shader.frag";
const char* TEXTURE_IMAGE_FILE = "../image.png";
unsigned int vert_shader;
unsigned int frag_shader;
unsigned int shader_program;
unsigned int pos_index;
unsigned int pos_array;
unsigned int tex_coord_index;
unsigned int tex_coord_array;
unsigned int vertex_arrays;
unsigned int index_array;
unsigned int texture;
unsigned int sampler;
unsigned int texture_index;
float positions[] = { 1.0f, -1.0f, 0.0f,
                      1.0f,  1.0f, 0.0f,
                     -1.0f, -1.0f, 0.0f,
                     -1.0f,  1.0f, 0.0f};
float tex_coords[] = {1.0f, 0.0f,
                      1.0f, 1.0f,
                      0.0f, 0.0f,
                      0.0f, 1.0f};
unsigned int indices[] = {0,1,2,3};

unsigned char* make_dummy_texture (int* width_, int* height_)
{
    int height = 256;
    int width  = 256;
    unsigned char* pixels = (unsigned char*)malloc (width*height*4);
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            pixels[y*(width*4) + x*4 +0] = x % 256;
            pixels[y*(width*4) + x*4 +1] = y % 256;
            pixels[y*(width*4) + x*4 +2] = 0;
            pixels[y*(width*4) + x*4 +3] = 255;
        }
    }
    *width_  = width;
    *height_ = height;
    return pixels;
}

void reshape (int width, int height)
{
    glViewport (0, 0, width, height);
}

char* load_shader (const char* file_name)
{
    ifstream ifs;
    ifs.open (file_name);
    assert (ifs.is_open() == true);

    ifs.seekg (0,ios_base::end);
    int size = ifs.tellg ();
    ifs.seekg (0, ios_base::beg);

    char* buf = (char*)malloc (size+1);
    assert (buf != NULL);

    ifs.read (buf, size);
    buf[size] = 0;  // must be null terminated

    return buf;
}

void print_shader_log (unsigned int shader)
{
    cout << "Shader Compile Error: " << shader << "\n";
    int size, written;
    glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &size);
    if (size > 0) {
        char* log = (char*)malloc(size);
        glGetShaderInfoLog (shader, size, &written, log);
        cout << log << "\n";
        free (log);
    }

}

void print_program_log (unsigned int program)
{
    cout << "Program Link Error: " << program << "\n";
    int size, written;
    glGetProgramiv (program, GL_INFO_LOG_LENGTH, &size);
    if (size > 0) {
        char* log = (char*)malloc(size);
        glGetProgramInfoLog (program, size, &written, log);
        cout << log << "\n";
        free (log);
    }

}

void init ()
{
    glEnable (GL_TEXTURE);

    int status;

    // vertex shader
    vert_shader = glCreateShader (GL_VERTEX_SHADER);
    assert (vert_shader != 0);

    const char* vert_code_array[] = {load_shader (VERT_SHADER_FILE)};
    glShaderSource  (vert_shader, 1, vert_code_array, NULL);
    glCompileShader (vert_shader);

    glGetShaderiv (vert_shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE) {
        print_shader_log (vert_shader);
        exit (0);
    }

    // fragment shader
    frag_shader = glCreateShader (GL_FRAGMENT_SHADER);
    assert (frag_shader != 0);

    const char* frag_code_array[] = {load_shader (FRAG_SHADER_FILE)};
    glShaderSource  (frag_shader, 1, frag_code_array, NULL);
    glCompileShader (frag_shader);

    glGetShaderiv (frag_shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE) {
        print_shader_log (frag_shader);
        exit (0);
    }


    // shader program
    shader_program = glCreateProgram ();
    assert (shader_program != 0);

    glAttachShader (shader_program, vert_shader);
    glAttachShader (shader_program, frag_shader);
    glLinkProgram  (shader_program);

    glGetProgramiv (shader_program, GL_LINK_STATUS, &status);
    if (status == GL_FALSE) {
        print_program_log (shader_program);
        exit (0);
    }
    
    glUseProgram (shader_program);

    // shader variable indices
    pos_index       = glGetAttribLocation  (shader_program, "VertexPosition");
    tex_coord_index = glGetAttribLocation  (shader_program, "VertexTexCoord");
    texture_index   = glGetUniformLocation (shader_program, "Texture2D");

    // setup shader uniform
    glUniform1i     (texture_index, 0);  // = use GL_TEXTURE0

    // setup vertex arrays
    glGenVertexArrays (1, &vertex_arrays);
    glBindVertexArray (vertex_arrays);

    // send vertex array data
    glGenBuffers (1, &pos_array);
    glBindBuffer (GL_ARRAY_BUFFER, pos_array);
    glBufferData (GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);
    glVertexAttribPointer     (pos_index, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray (pos_index);

    glGenBuffers (1, &tex_coord_array);
    glBindBuffer (GL_ARRAY_BUFFER, tex_coord_array);
    glBufferData (GL_ARRAY_BUFFER, sizeof(tex_coords), tex_coords, GL_STATIC_DRAW);
    glVertexAttribPointer     (tex_coord_index, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray (tex_coord_index);

    // and indices
    glGenBuffers (1, &index_array);
    glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, index_array);
    glBufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // setup texture
    int   width, height, comp;
    unsigned char* pixels = stbi_load (TEXTURE_IMAGE_FILE, &width, &height, &comp, 4);
    //unsigned char* pixels = make_dummy_image (&width, &height);
    cout << "texture width  = " << width  << "\n";
    cout << "texture height = " << height << "\n";
    assert (pixels != 0);

    glGenTextures (1, &texture);
    glBindTexture (GL_TEXTURE_2D, texture);
    glTexImage2D  (GL_TEXTURE_2D, 0, 
                   GL_RGBA, width, height, 0,
                   GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    
    // setup sampler
    glGenSamplers       (1, &sampler);
    glSamplerParameteri (sampler, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glSamplerParameteri (sampler, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glSamplerParameteri (sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glSamplerParameteri (sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

}

void display ()
{
    // texture
    glActiveTexture (GL_TEXTURE0);
    glBindTexture   (GL_TEXTURE_2D, texture);
    glBindSampler   (0, sampler);

    // vertices
    glBindVertexArray (vertex_arrays);
    glBindBuffer      (GL_ELEMENT_ARRAY_BUFFER, index_array);

    // draw
    glDrawElements    (GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(indices[0]), GL_UNSIGNED_INT, 0);

    glutSwapBuffers ();
}

void keyboard (unsigned char key, int x, int y)
{
    if (key == 'q') {
        exit (0);
    }
    glutPostRedisplay ();
}

void idle ()
{
    glutPostRedisplay ();
}

int main (int argc, char** argv)
{
    glutInit            (&argc, argv);
    glutInitDisplayMode (GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
    glutCreateWindow    (argv[0]);
    glutDisplayFunc     (display);
    glutReshapeFunc     (reshape);
    glutKeyboardFunc    (keyboard);
    glutIdleFunc        (idle);

    glewInit ();
    init ();
    glutMainLoop ();

    return 0;
}

#version 330
in  vec3 VertexPosition;
in  vec2 VertexTexCoord;
out vec2 TexCoord;

void main ()
{
    gl_Position = vec4(VertexPosition, 1);
    TexCoord    = VertexTexCoord;
}

#version 330
uniform sampler2D Texture2D;
in  vec2 TexCoord;
out vec4 FragColor;


void main ()
{
    FragColor = texture (Texture2D, TexCoord);
}

トラックバック - http://d.hatena.ne.jp/tueda_wolf/20111101/p1