4.2. Gouraudシェーティング


3Dの基本的な知識を理解したくて、『３次元CGの基礎と応用［新訂版］（ http://goo.gl/GKoQ4y ）』という書籍を購入しました。薄くて読みやすそうな本なのですがサンプルソースがないので、勉強がてらサンプルを作りながら読み進めようと思います。誰でもすぐに追試ができるよう、HTML + Javascriptでサンプルを作っていきます。

完全なサンプルはこちら
https://github.com/nakaken0629/3dstudy

# 4.2. Gouraudシェーティング

Gouraudシェーディングとは、ポリゴン内部を各頂点の色の平均値で描画することで、一枚のポリゴンの内部をより滑らか（＝スムース）に見せる手法です。頂点の色は、次のアルゴリズムで計算します。

1. 頂点の元の色をあらかじめ用意しておく（今回のサンプルでは全てRGB=#00FF00の緑色としている）
2. 頂点を含む全てのポリゴンの法線ベクトルの平均を取ったベクトルを計算して、頂点の法線ベクトルとする
3. 光線（このサンプルではRGB=#FFFFFFの白色）と頂点の法線をなす角度で頂点の見た目の色を決定する

そしてポリゴンの中の全ての点について、以下のアルゴリズムで色を決定します。

1. スクリーン座標で変換された全てのポリゴンについて、全ての辺の色を二点間の平均として計算する。
2. 2.3.「３角形面を描く」で作成したスキャンラインアルゴリズムを使って左と右の交点の色の平均で二点間を描画する

サンプルでは、Gouraudシェーディングが有効になっているときに、頂点を中心にグラデーションがかかっているのがわかると思います（少しモアレが見えているのは、gif画像として256色に減色されているため）。

![session4_2.gif](https://qiita-image-store.s3.amazonaws.com/0/75307/187b6892-6cba-b1d2-2e63-34f40baf4652.gif "session4_2.gif")

# サンプルソース

頂点の色を計算するロジックです。

```javascript:session4_2.htmlより抜粋
        /* pは、[頂点1, 頂点2, 頂点3]配列の配列です */
        /* ポリゴンの法線の定義 */
        var pn = [];
        for (var i = 0; i < p.length; i++) {
            pn[i] = normal(p[i]);
        }

        /* 頂点の法線の定義 */
        var vn = [];
        for (var i = 0; i < v.length; i++) {
            var list = [];
            for (var j = 0; j < p.length; j++) {
                for (var k = 0; k < p[j].length; k++) {
                    if (p[j][k] == v[i]) {
                        list.push(pn[j]);
                        break;
                    }
                }
            }
            vn[i] = averageVector(list);
        }

        /* 頂点の色の定義 */
        c = {'r': 0, 'g': 255, 'b': 0};
        var vc = [];
        for (var i = 0; i < v.length; i++) {
            var rate = (vn[i].x * light.x + vn[i].y * light.y + vn[i].z * light.z)
                / Math.sqrt(Math.pow(vn[i].x, 2) + Math.pow(vn[i].y, 2) + Math.pow(vn[i].z, 2))
                / Math.sqrt(Math.pow(light.x, 2) + Math.pow(light.y, 2) + Math.pow(light.z, 2));
            rate = rate > 0 ? rate : 0;
            vc[i] = {
                'r': Math.floor(c.r * light.r / 255.0 * rate),
                'g': Math.floor(c.g * light.g / 255.0 * rate),
                'b': Math.floor(c.b * light.b / 255.0 * rate)
            };
        }
```

これはある線分に含まれる点の色を、線分の両端を構成する頂点の色から計算するロジックです。

```javascript:session4_2.htmlより抜粋
/* 辺を構成する２点の距離で平均した色を返す（Gouraudシェーディング） */
var averageColorY = function(i, y, sy, pc) {
    var i1 = i;
    var i2 = (i + 1) % sy.length;
    var y1 = sy[i1];
    var y2 = sy[i2];
    var r = 1 / (y1 - y2) * (pc[i1].r * (y - y2) + pc[i2].r * (y1 - y));
    var g = 1 / (y1 - y2) * (pc[i1].g * (y - y2) + pc[i2].g * (y1 - y));
    var b = 1 / (y1 - y2) * (pc[i1].b * (y - y2) + pc[i2].b * (y1 - y));
    return {
        'r': Math.floor(r),
        'g': Math.floor(g),
        'b': Math.floor(b)
    };
}
```

これはポリゴン内部の点の色を、その左右にある線分との交点の色の平均から計算するロジックです。

```javascript:session4_2.htmlより抜粋
/* 左右の辺の交点の色の平均を返す（Gouraudシェーディング） */
var averageColorX = function(xl, xr, x, cl, cr) {
    var r = 0;
    var g = 0;
    var b = 0;
    var rate1 = Math.abs((xl - x) / (xr - xl));
    var rate2 = Math.abs((xr - x) / (xr - xl));
    var r = cl.r * rate1 + cr.r * rate2;
    var g = cl.g * rate1 + cr.g * rate2;
    var b = cl.b * rate1 + cr.b * rate2;
    return {
        'r': Math.floor(r),
        'g': Math.floor(g),
        'b': Math.floor(b)
    };
};
```


# 補足１：ピクセルサイズについて

JavaScriptで真面目に計算するとかなり遅くなってしまったので、今回のサンプルでは1dot = 4pxとして計算しています。単純計算で16倍早く表示されています。
また、4x4でピクセルを描画すると少し隙間が空いてしまうので、4x7として少し重なり合うように描画しています。