Top / 4K Procedural Gfx Monitor / 4KBプロシージャルGFX入門講座 3時限目

4KBプロシージャルGFX入門講座 3時限目

さて、ここからは本格的にシェーダプログラムだけで絵をつくるステップに進む。
従来のグラフィックスプログラムで絵を出す概念とは、おそらく少し違うので注意が必要だ。

従来のグラフィックスプログラムとの違い

従来のグラフィックスプログラム

2時限目まででFragmentProgramによって出力されるグラフィックスの色が決定されることがわかったと思う。
しかし、FragmentProgramは出力されるすべてのピクセルに共通した処理しかかけない制約がある。
いままでのサンプルは頂点から渡された補間された頂点座標を使い、グラデーションなどの滑らかに変化する
グラフィックスを表示してきた。では、四角やら、円やらの図形を表示するにはどのようにすればよいだろうか?
従来のグラフィックスプログラムであれば、四角やら円を書くときは、図形を描き始める座標を指定して書くだろう。

// 従来のグラフィックスプログラム
void main()
{
    Rectangle (0, 0, 0.5, 0.5); // (0, 0)-(0.5,0.5) の座標に矩形を描く
    Circle (-0.5, -0.5, 0.3);   // (-0.5, -0.5)を中心に半径0.3の円を描く
}

これから画像を出力する先を”キャンバス”と定義するなら、”キャンバス”に1つのプログラムとなる。
今から図形を描こうとするときに、基準点が”キャンバス”になっているのでこのようなプログラムを
すんなりと理解できると思う。しかし、この書き方ではピクセルごとに共通した処理では書くことができない。

1ピクセルごとの処理を定義して全体を描く

FragmentProgramだけで絵を描くためには、各ピクセルに共通した処理で絵を表現する必要がある。
各ピクセルに共通した処理を書く必要があるのあれば、基準点を”キャンバス”ではなく”ピクセル単位”
にするのが自然ではないだろうか。そこでピクセル単位で考えてみる。

ピクセル単位で図形を描くことを考えると、そのピクセルが円の中にあるか四角の中にあるか、
それ以外の場合なのかを考えればよい。

この基準の違いはプログラムを書く上で非常に重要となる。
この違いを踏まえて、ピクセル基準のプログラムを書くと以下のようになるだろう。

// ピクセル基準で考えたFragmentシェーダプログラムログラムログラムログラム
varying vec4 p;
void main()
{
    // (0, 0)-(0.5,0.5) の範囲にpがあるか
    if(四角の範囲のピクセルか (pが四角の範囲のピクセルかどうか)
        gl_FragColor vec4(0.0, 0.0, 1.0, 1.0); // 青
        
    // (-0.5, -0.5)中心の半径0.3以内にpがあるか    
    else if (IsCircl (pが円の中にあるかどうか)
        gl_FragColor vec4(0.0, 1.0, 0.0, 1.0); // 緑
        
    // それ以外
    else
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); // 白
}

ちゃんと動くようにプログラムを書いてみると・・・

-- vs.glsl --
// VertexProgram
varying vec4 p;
void main()
{
    gl_Position = gl_Vertex;
    p = gl_Vertex;
}
-- fs.glsl --
// FragmentProgram
varying vec4 p;
void main()
{
    // 背景色はRGBA(1,1,1,1) の白
    vec4 col = vec4(1.0, 1.0, 1.0, 1.0);
    
    // Rectangle (0.0, 0.0)-(0.5,0.5) の座標pがあるか
    if ( (p.x <= 0.5 && p.x >= 0.0)
    &&   (p.y <= 0.5 && p.y >= 0.0) )
    {
        col = vec4(0.0, 0.0, 1.0, 1.0);
    }
    
    // Circle (-0.5, -0.5)中心の半径0.3の座標にpがあるか
    float radius = 0.3;
    vec2  center = vec2(-0.5, -0.5);
    if ( length(p.xy - center) <= radius )
    {
        col = vec4(0.0, 1.0, 0.0, 1.0);
    }
    
    // 色を出力
    gl_FragColor = col;
    return;
}

このプログラムを動かすと以下のよこのプログラムを動かすと以下のようになる。

特にプログラムを解説する必要はないかなぁ?
四角の中にあるかどうかの判定はまぁいいとして、円の中にあるかどうかの判定は
vec4型の変数pを p.xy とすることによってvec2型として、同じvec2型の変数center とから
円の中心からのベクトルを計算し、そのベクトルの長さが半径以下かどうか調べてます。
あとはそれぞれの条件のときにcolに色を代入しておき、最後にgl_FragColor にcol を代入。

1ピクセルを制すものはグラフィックスを制す全ピクセルに共通するFragmentProgramを書く考え方は、かなり重要な考え方だと思う。
従来の"キャンバス"に1つのプログラムを実行するプログラムの考え方はシングルタスクの
プログラムの考え方しかできず、プログラムを並列に実行することは困難である。
しかし、ピクセル単位のプログラムを書いておけば、ピクセル単位で処理は独立しているので
マルチスレッドでプログラムを処理することが可能になる。考え方の基準を変えるだけで、
並列化することが困難であったプログラムも用意に並列化することが可能だろう。
もちろん、ピクセル単位で考えることについてはある程度の慣れは必要だ。
しかし、特にグラフィックスプログラムではピクセル単位で並列に処理することが多く、
昨今のGPUのパワーを最大限に引き出すにはこのピクセル単位の考え方は必須となるだろう。

アスペクト比を修正する

なんか円がつぶれているが、ウインドウのアスペクト比と、4kGfxMonの座標系のアスペクト比が
異なるためである。これはVertexProgramからFragmentProgramに渡すp値をいじってやれば解決する。
以下に修正版を示す。

-- vs.glsl --
// VertexProgram
varying vec4 p;
void main()
{
    gl_Position = gl_Vertex;
    // ウインドウの縦横アスペクトが16:9なので
    // x座標を 16/9 = 1.7778倍 する
    p = gl_Vertex * vec4(1.7778, 1.0, 1.0, 1.0);
}
-- fs.glsl --

 (変更なし)


まぁこんなかんじ。X座標が(-1〜1)だったのが(-1.7778〜1.7778)になっているので注意
アスペクト比の設定は最終的にターゲットとする解像度にあわして設定しておくと良いでしょう。
4kGfxMonのウインドウアスペクト比はメニューから変更することができます。

4kGfxMonメニュー

ウインドウのアスペクト比は4kGfxMonのウインドウ上を右クリックして、コンテキストメニューから
変更することができる。ついでに他のメニューの話もしておこう。

 
[Always On Top] は4kGfxMonのウインドウを常に一番手前にくるように設定できる。
まぁ頻繁にプレビューするときに使ってくれ。
[4:3], [16:9], [16:10] はウインドウのアスペクト比を指定値に固定する機能だ。
4kGfxMonのウインドウをリサイズしたときに自動的に選択されているアスペクト比に固定する。
現在のバージョンではフリーのアスペクトにはできない。
まぁ4KB Procedural Gfxをリリースするときはたいていいずれかのアスペクトのフルスクリーン
でリリースすることが多いからね。とりあえず、こんなもんでいんじゃないかと。
[calc RenderTime] は現在描画している画像を作り出すのにかかる時間を計測する。
10回描画して平均時間を計測するような仕組みだ。結構適当なので目安程度に。
[Export...] は4kGfxMonで作ったシェーダプログラムを実行ファイルにするための手順の1つだ。
ここのついては5時限目に詳しく説明する。

おまけ

ちょこっと改造。工夫次第でいろいろできます。

-- fs.glsl --
// FragmentProgram
varying vec4 p;
void main()
{
    // 背景色はRGBA(1,1,1,1) の白
    vec4 col = vec4(1.0, 1.0, 1.0, 1.0);
    
    // Rectangle (0.5, 0.5)-(0,0) の座標に矩形を描く
    if ( (p.x <= 0.5 && p.x >= 0.0)
    &&   (p.y <= 0.5 && p.y >= 0.0) )
    {
        if (p.x < p.y)
            col = vec4(1.0, 0.0, 0.0, 1.0);
        else
            col = vec4(0.0, 0.0, 1.0, 1.0);
    }
    
    // Circle (-0.5, -0.5)を中心に半径0.3の円を描く
    float radius = 0.3;
    vec2  center = vec2(-0.5, -0.5);
    if ( length(p.xy - center) <= radius )
    {
        float r = length(p.xy - center)/radius;
        col = r * vec4(0.0, 1.0, 0.0, 1.0) +
                (1.0 - r) * vec4(1.0, 0.0, 1.0, 1.0);
    }
    
    // 色を出力
    gl_FragColor = col;
    return;
}