TOP > プログラミング関連 > SFML非公式翻訳トップ > 【非公式翻訳】SFML2.2チュートリアル > グラフィックスモジュール > シェーダーで特殊効果

[原文(公式)]

◆◆ シェーダーで特殊効果 ◆◆
非公式日本語翻訳
イントロ
シェーダーとは何か? 一種のプログラム言語です。グラフィックカード上で直接実行されます。 シェーダーを使うと、描画のプロセスを柔軟&シンプルに記述できます。 その柔軟&シンプルさときたら、OpenGL の既存の描画方法を上回るほどなのだ!

そういうわけでシェーダーは、通常の OpenGL の機能だけで実現するのは (不可能ではないにしても)複雑になってしまうような特殊効果を記述するために使われます。 パーピクセル・ライティングとか、シャドウとか。

最近のグラフィックカードや新しいバージョンの OpenGL では全面的にシェーダーがベースになってきていて、 お馴染みの描画方法(いわゆる「固定機能パイプライン」)は使われなくなりつつあります。

シェーダーを記述するには GLSL という言語を使います。
みんなの大好きな C言語にソックリな顔をしてます。
(ちなみに GLSL は OpenGL Shading Language の略)

シェーダーには2種類あります。 頂点シェーダーと、フラグメントシェーダー(またはピクセルシェーダー)です。
頂点シェーダーはジオメトリのトランスフォームを行います。 フラグメントシェーダーはピクセル単位での処理を行います。 どんな効果を実現したいかによって、どちらか片方だけのシェーダーを使ってもいいし、両方使うこともできます。

シェーダーの仕組みや使い方を理解するには、レンダリングパイプラインのイロハを理解する必要があります。 それと、GLSLプログラミングのことも。
道は遠いですが、いいチュートリアルやサンプルを見つけてね! SFML の SDK の中にも シェーダーのサンプルがあるから、よかったら見てみてね。

このチュートリアルでは、あくまでも SFML 側の機能に焦点を当てます。 つまり、シェーダーを読み込んで使う方法です。シェーダーそのものの書き方については、触れません。
シェーダーを読み込む
SFML でシェーダーを表すクラスは sf::Shader です。
これ1つで頂点シェーダーとフラグメントシェーダーを両方扱えます。 sf::Shader クラスのインスタンスは両シェーダーのコンビネーションです(片方が不要なら片方だけでも OK)。

シェーダーが一般的になってきているとは言っても、まだシェーダーをサポートしてないグラフィックカードもあります。 なので、プログラムの中で最初にするのは、シェーダーが使えるかどうかのチェックです:
if (!sf::Shader::isAvailable())
{
    // シェーダーはサポートされてません……
}
sf::Shader::isAvailable() が false になる場合、シェーダーは使えません。

シェーダーを読み込むには HDD上のファイルを読み込むのが基本です。
loadFromFile() 関数を使います。
sf::Shader shader;

// 頂点シェーダーだけを読み込む
if (!shader.loadFromFile("vertex_shader.vert", sf::Shader::Vertex))
{
    // error...
}

// フラグメントシェーダーだけを読み込む
if (!shader.loadFromFile("fragment_shader.frag", sf::Shader::Fragment))
{
    // error...
}

// 両方のシェーダーを読み込む
if (!shader.loadFromFile("vertex_shader.vert", "fragment_shader.frag"))
{
    // error...
}
シェーダーは単なるテキストファイルです(C++ などのプログラムと同じように)。
なので、拡張子はあんまり関係ありません。好きなようにつけて OK です。
".vert" や ".frag" というのは単なる習慣です。
この loadFromFile関数なんだけど、ときどき、よくわかんない理由で失敗しちゃうんだ。 そんなときは、まず、エラーメッセージをチェックしてね。コンソールに出力されてるよ。 さて、なんていってるかな? 「unable to open file」? そんなときは、カレントディレクトリを確認してね。 カレントディレクトリっていうのは、キミのアプリケーションがファイルを読み込むときの、 相対パスの出発点になるフォルダのことだよ。 どうかな? キミの思ってる通りのフォルダかな? アプリケーションをエクスプローラーから起動しているときは、 そのアプリケーションが置いてあるフォルダがカレントディレクトリなんだけど、 IDE(Visual Studio とか、Code::Blocksとか)から起動してるときは、 そのプロジェクトのフォルダになってるかもしれないね。 きっとプロジェクトの設定でカンタンに変更できると思うよ。

訳者注:「スプライトとテクスチャ」の loadFromFileに関する注意書きと同じ文面です(原文が)。
シェーダーは文字列で直接記述することもできます。loadFromMemory() 関数を使います。 シェーダーをソースコードの中に埋め込んでおきたいときには、この読み込み方法を使うとよいです。
const std::string vertexShader = \
    "void main()" \
    "{" \
    "    ..." \
    "}";

const std::string fragmentShader = \
    "void main()" \
    "{" \
    "    ..." \
    "}";

// 頂点シェーダーだけを読み込む
if (!shader.loadFromMemory(vertexShader, sf::Shader::Vertex))
{
    // error...
}

// フラグメントシェーダーだけを読み込む
if (!shader.loadFromMemory(fragmentShader, sf::Shader::Fragment))
{
    // error...
}

// 両シェーダーを読み込む
if (!shader.loadFromMemory(vertexShader, fragmentShader))
{
    // error...
}
そしてもう1つの読み込み方。ストリームから読み込むこともできます。 使う関数は loadFromStream() です。 SFML の他のリソースクラスと同じ要領ですね。

もし読み込みに失敗するときは、標準出力(コンソール)に出ているエラーメッセージを見てください。 GLSL からの詳しいレポートが表示されてるはずです。
シェーダーを使う
使うのは簡単です。
draw() 関数の引数に渡すだけです。
window.draw ( sprite, &shader );
シェーダーに変数を渡す
他のプログラム言語と同じように、シェーダーもパラメータを持つことができます。 パラメータの内容次第で、実行するたびに違う結果になる、というわけです。
パラメータはシェーダーの内部で、グローバルな変数として宣言します。いわゆる「ユニフォーム変数」です。
uniform float myvar;

void main()
{
    // myvar を使った処理
}
ユニフォーム変数は C++ 側からセットします。
sf::Shader クラスに、変数の型ごとに setParameter() 関数がたくさん用意されてるので、使い分けてください。
shader.setParameter ( "myvar", 5.0f );
以下、SFML が setParameter でサポートしている 型の一覧です:
GLSL のコンパイラは、使われていない変数を見つけると省略してコンパイルします (ここでの「使われてない」というのは、頂点やピクセルの計算に関係しない、という意味)。 そういう場合、setParameter() の実行時に "Failed to find variable "xxx" in shader" みたいなメッセージが出ることがありますが、 驚かないで。
最小限のシェーダー
ここでは GLSL の書き方のお勉強はしないのですけど、 でも、SFML から シェーダーにどんなインプットが行われていて、プログラマとして何をすべきなのかは 覚えておかねばならないのであります。
頂点シェーダー
SFML には頂点を表す sf::Vertex という構造体があります。 この頂点は「2D座標」と「色」と「テクスチャ座標」を持ってます。 まさにこれらが、頂点シェーダーにインプットされるデータです。 頂点シェーダ側で「gl_Vertex」「gl_Color」「gl_MultiTexCoord0」といった変数があらかじめ定義されていて、 それらのデータを受け取るようになっています(プログラマが宣言する必要はない)。
void main()
{
    // 頂点の座標をトランスフォーム
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

    // テクスチャ座標をトランスフォーム
    gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;

    // 頂点色をフラグメントシェーダに渡す
    gl_FrontColor = gl_Color;
}
座標は ModelView行列で変換される必要があります。 ModelView行列とは、オブジェクトを現在のビュー座標系に位置づける変換行列です。

テクスチャ座標はテクスチャ行列で変換される必要があります (この行列はプログラマが気にする必要はありません。SFML の内部実装の裏話です)。

色はフラグメントシェーダー側にそのまま渡せば OK です。
もちろん、必要なければ色やテクスチャ座標は無視して構いません。

これらの変数がグラフィックカードでジオメトリ内のピクセルに適用され、 フラグメントシェーダーに渡されます。
フラグメントシェーダー
フラグメントシェーダーも似たような感じです。
シェーダーが受け取るデータは「テクスチャ座標」と「ピクセルの色」です。
「座標」はありません。 ここまで処理が進んだら、グラフィックカードはすでにピクセルの最終的な座標を計算し終えているからです。
それと、テクスチャを貼っている場合は、テクスチャも必要です。
uniform sampler2D texture;

void main()
{
    // テクスチャ内のピクセル情報を取得
    vec4 pixel = texture2D(texture, gl_TexCoord[0].xy);

    // 色を乗算
    gl_FragColor = gl_Color * pixel;
}
テクスチャは現在のテクスチャが自動で適用される、といいのですが、自動では適用されません。 C++の側から明示的に設定してあげる必要があります。
でも、2Dオブジェクトごとに別々のテクスチャがありますよね? それを描画のたびに毎回取得して、シェーダーに渡すのはプログラマにとって、とても大変なことです。
そんなわけで! あなたの代わりにそのお仕事をしてくれる 特別な setParameter() 関数を紹介します。 いやー、SFMLって気が利きますね。
shader.setParameter("texture", sf::Shader::CurrentTexture);
これで、描画中のオブジェクトのテクスチャが、随時、シェーダーの変数に設定されるようになります。 新たなオブジェクトが描画されるたびに SFML は、対応するシェーダーの変数を更新してくれます。

実際に動いているシェーダーの素敵なサンプルが見たいときには、 SFML SDK の "examples" フォルダを覗いてみてね。
sf::Shader を OpenGL のコードで使う
描画処理に SFML の2Dオブジェクトを使わずに OpenGL を使っているお友達もいるかもしれませんね。 そんなあなたにも、やっぱり sf::Shader は強い味方。
sf::Shader は OpenGLのシェーダーのラッパーになって、OpenGL のオブジェクトとあなたを仲良しにしてくれます。

sf::Shader を有効にして描画するには、 スタティック関数の bind() をコールします(glUseProgram に該当)。
sf::Shader shader;
...

// シェーダーをバインド
sf::Shader::bind(&shader);

/*
    ……あなたの OpenGL プログラミング……
*/

// シェーダーのバインドを解除
sf::Shader::bind(NULL);