glsl での三角形の補間を二次関数で行う

はじめに

glsl の補間のモードには flat と smooth とがあります。 flat は一定の値での補間で smooth は線形な補間です。 この補間の機能はテクスチャの座標を決めたり、色のグラデーションに使われたりします。 この機能もう少し色々使えそうな気がして二次関数で補間するというのを試してみました。

実験用に書いたコードはこれです

https://github.com/bigsleep/interpolation-experiment

面積座標について

三角形要素内部での線形な補間は面積座標というものを使って表せます。 これは有限要素法やったことがある人は聞いたことがあると思います。

面積座標は三角形内部の点と各頂点を結んでできる三つの三角形の面積の比で表されます。

f:id:tkaaad97:20180318231610p:plain

{
L_i = S_i / S
}

三角形内部でのあるパラメーターの値は各頂点での値と面積座標を掛けて足し合わせて求められます。

{
\displaystyle u = \sum_{i=1}^{3} u_i L_i
}

glsl で面積座標を使うには

バーテックスシェーダで vec3 の out パラメーター  \boldsymbol{L} を用意して

頂点1では { \boldsymbol{L} = (L_1, L_2, L_3) = (1, 0, 0) }

頂点2では { \boldsymbol{L} = (L_1, L_2, L_3) = (0, 1, 0) }

頂点3では { \boldsymbol{L} = (L_1, L_2, L_3) = (0, 0, 1) }

として smooth モードで補間するとフラグメントシェーダーで in パラメーターとして使えます。

例えば下のようにシェーダを書くと三角形の縁の部分で色を変えることなどができます。

(glsl のバージョンによっては gl_VertexID を 3 で割った余りで頂点インデックスを計算することができると思うんですが webgl のためか上手くいかなかったので無理矢理な書き方になってしまってます。)

  • バーテックスシェーダ
attribute float idx;
varying vec3 L;

void main() {
    L = vec3(0.0);
    if (idx == 0.0) {
        L[0] = 1.0;
    } else if (idx == 1.0) {
        L[1] = 1.0;
    } else {
        L[2] = 1.0;
    }
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
  • フラグメントシェーダ
varying vec3 L;

void main()
{
    float a = 0.1;
    gl_FragColor = (L[0] <= a || L[1] <= a || L[2] <= a) ? vec4(1.0, 0.0, 0.0, 1.0) : vec4(1.0);
}

f:id:tkaaad97:20180318231124p:plain

この図では三角形二つ表示しています。

三角形二次要素

有限要素法の本を見返してみたところ三角形二次要素についても書かれていました。

三角形二次要素は二次関数を使うため節点が六個必要になります。

三つの頂点に加えて辺の中央に残り三つの節点を選ぶのが一般的なようです。

f:id:tkaaad97:20180318231615p:plain

有限要素法の本によると二次要素の場合の補間関数は面積座標を使って下のように表せます。 (プログラムにする都合により添字の順番など変えています。)

{
\displaystyle u = \sum_{i = 1}^3 (u_i L_i (2 L_i - 1) + 4 u_{ i + 3 } L_i L_{ (i  + 1) \bmod 3 + 1})
}

これを分割してバーテックスシェーダの  \boldsymbol{L}, \boldsymbol{v}, \boldsymbol{w} の三つの vec3 out パラメーターとして表します。

頂点1では

{
\boldsymbol{L} = (L_1, L_2, L_3) = (1, 0, 0)\\
\boldsymbol{v} = (u_1 L_1, u_2 L_2, u_3 L_3) = (u_{1}, 0, 0)\\
\boldsymbol{w} = (u_4 L_1, u_5 L_2, u_6 L_3) = (u_{4}, 0, 0)\\
}

頂点2では

{
\boldsymbol{L} = (L_1, L_2, L_3) = (0, 1, 0)\\
\boldsymbol{v} = (u_1 L_1, u_2 L_2, u_3 L_3) = (0, u_{2}, 0)\\
\boldsymbol{w} = (u_4 L_1, u_5 L_2, u_6 L_3) = (0, u_{5}, 0)\\
}

頂点3では

{
\boldsymbol{L} = (L_1, L_2, L_3) = (0, 0, 1)\\
\boldsymbol{v} = (u_1 L_1, u_2 L_2, u_3 L_3) = (0, 0, u_{3})\\
\boldsymbol{w} = (u_4 L_1, u_5 L_2, u_6 L_3) = (0, 0, u_{6})\\
}

フラグメントシェーダで  \boldsymbol{L}, \boldsymbol{v}, \boldsymbol{w} パラメーターから補間値を計算します。

{
\displaystyle u = \sum_{i = 1}^3 v_i ((2 L_i - 1) + 4 w_i L_{(i + 1) \bmod 3 + 1})
}

シェーダ

  • バーテックスシェーダ
attribute float idx;
attribute float ui;
attribute float uj;
varying vec3 L;
varying vec3 v;
varying vec3 w;

void main() {
    L = vec3(0.0);
    if (idx == 0.0) {
        L[0] = 1.0;
        v[0] = ui;
        w[0] = uj;
    } else if (idx == 1.0) {
        L[1] = 1.0;
        v[1] = ui;
        w[1] = uj;
    } else {
        L[2] = 1.0;
        v[2] = ui;
        w[2] = uj;
    }
    gl_Position = projectionMatrix * modelViewMatrix vec4(position, 1.0);
}
  • フラグメントシェーダ
varying vec3 L;
varying vec3 v;
varying vec3 w;

void main()
{
    float u = 0.0;
    u += v[0] * (2.0 * L[0] - 1.0) + 4.0 * w[0] * L[1];
    u += v[1] * (2.0 * L[1] - 1.0) + 4.0 * w[1] * L[2];
    u += v[2] * (2.0 * L[2] - 1.0) + 4.0 * w[2] * L[0];
    gl_FragColor = vec4(vec3(u), 1.0);
}

出力結果

f:id:tkaaad97:20180318231130p:plain

参考

学生のときに読んだ 有限要素法概説 という本を参考にさせていただきました。

株式会社サイエンス社 株式会社新世社 株式会社数理工学社

三角形二次要素はよく使うと思われるので他のネット上の資料や本にも書かれていると思います。

まとめ

面積座標を利用して glsl で三角形の補間を二次関数でやってみました。

いまいちあってるか自信がないですがこの方法でもできるのではないかと思います。

ただこの方法で効率がいいかなどはよくわかってません。

実際に使えそうなところとしては有限要素法などの計算の結果を表示する場合などがあると思います。