床井研究室

シェーダで小細工

シェーダが使えるようになって、本当にいろんなことができるようになりました。バーテックスシェーダーにスキニングや変形アニメーションを実装するのも楽しいですし、フラグメントシェーダで色を重ねたりテクスチャのサンプリングに凝ったりするのも楽しくて仕方ありません。でも、やってみたいことはいろいろあるんですけど、自分にとってはどうも「遊び」の域を出ることができず、仕事に結び付けられずにいます。アイデア出てこい、どんどこどん(←わかるかなぁ!!、わっかんねぇだろうなぁ!!)。

あ、これは松鶴家千とせという芸人さんのギャグです、って説明しないと通じないかも。しかし、この人の Web ページがあるのも驚いたんですけど、この人のブログを見て更に驚きました。「DEC さんの Alpha が使用されていました」ってセリフが、どうしてもこの芸人さんと結び付きません。

輪郭線抽出

前にやったトゥーンシェーディングでは、テクスチャを使って輪郭線の抽出を行っていました。この方法は簡単なんですけど、結果がどうもきれいじゃありません。かなり以前に見た ATI の 9700 NPR Hatching というデモでは、デプスバッファとかさまざまな情報を使って輪郭線の抽出を行っていたので、前回デプスバッファをサンプリングするプログラムを作ったことですし、これを使ってちょっと遊んでみることにします。

デプスバッファに Sobel オペレータをかけてみる

輪郭線の抽出には様々な方法がありますが、ここでは Sobel オペレータ物理のかぎしっぽさん)を使ってみます。前回作ったプログラム showdepth のフラグメントシェーダのソースプログラム showdepth.frag を、以下の内容(sobel.frag)に書き換えてみてください。

#version 120

// sobel.frag

uniform sampler2D depth;
const float dx = 0.001953125;
const float dy = 0.001953125;

float peek(const in float x, const in float y)
{
  return texture2D(depth, vec2(x, y)).r;
}

void main(void)
{
  float x = gl_TexCoord[0].x;
  float y = gl_TexCoord[0].y;

  mat3 m = mat3(
    peek(x - dx, y - dy), peek(x, y - dy), peek(x + dx, y - dy),
    peek(x - dx, y     ), peek(x, y     ), peek(x + dx, y     ),
    peek(x - dx, y + dy), peek(x, y + dy), peek(x + dx, y + dy)
  );

  vec2 h = vec2(
    m[0][0] - m[0][2] + (m[1][0] - m[1][2]) * 2.0 + m[2][0] - m[2][2],
    m[0][0] - m[2][0] + (m[0][1] - m[2][1]) * 2.0 + m[0][2] - m[2][2]
  );

  float d = 1.0 - length(h);
  gl_FragColor = vec4(vec3(d), 1.0);
}

なんか、泥臭いプログラムですね。もっとうまく書けそうな気がするのですが、とりあえずこの辺で許してください。定数の 0.001953125 というのは 1 / 512、すなわちテクスチャ座標における画素の間隔です。この値を変えると線の太さが変わります。これは、こういう結果になります。

Sobel オペレータ

この最後の部分を次のように書き換えると、この結果を2値化できます。

  ...

  float d = step(0.9, 1.0 - length(h));
  gl_FragColor = vec4(vec3(d), 1.0);
}

こんな感じになります。

Sobel オペレータの結果を2値化

微分してみる

フラグメントシェーダを次のように書き換えて、微分もしてみました。

#version 120

// differential.frag
uniform sampler2D texture;
const float dx = 0.001953125;
const float dy = 0.001953125;

float peek(const in float x, const in float y)
{
  return texture2D(texture, vec2(x, y)).r;
}

void main(void)
{
  float x = gl_TexCoord[0].x;
  float y = gl_TexCoord[0].y;
  float b = peek(x, y);
  float d = abs(b - peek(x + dx, y))
          + abs(b - peek(x, y + dy));
  gl_FragColor = vec4(vec3(1.0 - d), 1.0);
}

これは、こういう結果になります。

微分オペレータ

Roberts もやってみる

ここまできたら、ついでに Roberts オペレータもやってみました。

#version 120

// roberts.frag

uniform sampler2D texture;
const float dx = 0.001953125;
const float dy = 0.001953125;

float peek(const in float x, const in float y)
{
  return texture2D(texture, vec2(x, y)).r;
}

void main(void)
{
  float x = gl_TexCoord[0].x;
  float y = gl_TexCoord[0].y;
  float d = abs(peek(x, y) - peek(x + dx, y + dy))
          + abs(peek(x + dx, y) - peek(x, y + dy));
  gl_FragColor = vec4(vec3(1.0 - d), 1.0);
}

こういう結果が得られました。

Roberts オペレータ

いろいろやってみたい

今回はデプスバッファしか使ってませんけど、カラーバッファや法線ベクトルの補間値などと組み合わせると、更にいろんなことができそうです。オレンジブックにも、もっと高度な例がたくさん載っているので、負けないくらい面白いものを考えてみたいですね。アイデア出てこい、どんどこどん。