3.2 後面除去


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

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

# 3.2. 後面除去

ポリゴンが裏を向いている場合にはそのポリゴンを描画しないというのが、後面除去のアルゴリズムです。裏を向いているかどうかは、ポリゴンから垂直に伸びる法線ベクトルNと視点からポリゴンへの視線ベクトルVの角度が90度以内かどうかで判定します。
視線ベクトルの算出方法は書籍では書かれていませんが、ここでは視点を原点（0, 0, 0）に置き、そこからポリゴンの重心へ伸びるベクトルを視線ベクトルとしました。
サンプル動画では、後面除去のチェックをオフにすると、一番最後に描画される黄色のポリゴンが裏面も表示されるようになって不自然な状態になります。

![session2_4.gif](https://qiita-image-store.s3.amazonaws.com/0/75307/326b8dde-8b52-0399-0e06-ee2ed5568de8.gif "session2_4.gif")


# サンプルソース

```javascript:session3_2.htmlより
var normal = function(p) {
    /* ポリゴンpの法線ベクトルを返す */
    var v0 = p.v[0];
    var v1 = p.v[1];

    var n = {
        'x': v0.y * v1.z - v0.z * v1.y,
        'y': v0.z * v1.x - v0.x * v1.z,
        'z': v0.x * v1.y - v0.y * v1.x,
        'w': 1
    };
    return n;
}

var eye = function(p) {
    /* 視点（原点）からポリゴンpへの視線ベクトルを返す */
    var ev = {
        'x': (p.v[0].x + p.v[1].x + p.v[2].x) / 3.0,
        'y': (p.v[0].y + p.v[1].y + p.v[2].y) / 3.0,
        'z': (p.v[0].z + p.v[1].z + p.v[2].z) / 3.0,
        'w': 1
    };
    return ev;
}

var main = function() {
    /* p -> polygon（多角形） */
    var v = [];
    v[0] = {'x':     0, 'y':     0, 'z':  4000 + 30000, 'w': 1};
    v[1] = {'x': -3464, 'y':     0, 'z': -2000 + 30000, 'w': 1};
    v[2] = {'x':  1732, 'y':  3000, 'z': -2000 + 30000, 'w': 1};
    v[3] = {'x':  1732, 'y': -3000, 'z': -2000 + 30000, 'w': 1};
    var rt1 = ...; /* z軸に-30,000移動する変換ベクトル */
    var rt2 = ...; /* z軸に30,000移動する変換ベクトル */
    var rx = ...; /* x軸に3度回転する変換ベクトル */
    var ry = ...; /* y軸に2度回転する変換ベクトル */
        
    var draw = function() {
        /* ポリゴンの描画 */
        var p = [];
        p[0] = {'v': [v[0], v[1], v[2]], 'rgb': createRgb(255,   0,   0)};
        p[1] = {'v': [v[0], v[2], v[3]], 'rgb': createRgb(  0, 255,   0)};
        p[2] = {'v': [v[0], v[3], v[1]], 'rgb': createRgb(  0,   0, 255)};
        p[3] = {'v': [v[1], v[3], v[2]], 'rgb': createRgb(255, 255,   0)};
        cls();
        for (var j = 0; j < p.length; j++) {
            if (document.forms[0].enabled.checked) {
                /* 後面除去のチェックを行う */
                var n = normal(p[j]);
                var ev = eye(p[j]);
                if (n.x * ev.x + n.y * ev.y + n.z * ev.z < 0) {
                    continue;
                }
            }
            fillTriangle(p[j]);
        }

        /* 頂点の回転 */
        for (var i = 0; i < v.length; i++) {
            v[i] = multiply(v[i], rt1);
            v[i] = multiply(v[i], rx);
            v[i] = multiply(v[i], ry);
            v[i] = multiply(v[i], rt2);
        }
    };
    var proc = setInterval(draw, 20);
}
```

# おまけ：正四面体の頂点の計算方法

今回のサンプルを作成するにあたり、正四面体の頂点の座標を求めるpythonのスクリプトを作成しました。行列演算を行うためにnumpyライブラリを導入しています。考え方としては次の通りです。

* 原点を正四面体の重心とする
* 原点からz軸方向に伸ばしたベクトルの先頭を頂点1とする
* 頂点1をy軸方向に120度回転したベクトルの先頭を頂点2とする
* 頂点2をz軸方向に120度回転したベクトルの先頭を頂点3とする
* 頂点3をさらにz軸方向に120度回転したベクトルの先頭を頂点4とする

```python:session3_2.py
from numpy import *
from math import *

o = array([0, 0, 0, 0])
s = 120 / 180.0 * pi
ry = array([
    [ cos(s), 0, sin(s), 0],
    [      0, 1,      0, 0],
    [-sin(s), 0, cos(s), 0],
    [      0, 0,      0, 0]
])
rz = array([
    [cos(s), -sin(s), 0, 0],
    [sin(s),  cos(s), 0, 0],
    [     0,       0, 1, 0],
    [     0,       0, 0, 1]
])
v1 = array([0, 0, 4000, 0])
v2 = dot(v1, ry)
v3 = dot(v2, rz)
v4 = dot(v3, rz)
print(v1)
print(v2)
print(v3)
print(v4)
```

実行結果は以下の通りです。

$ python session3_2.py
[   0    0 4000    0]
[-3464.10161514     0.         -2000.             0.        ]
[ 1732.05080757  3000.         -2000.             0.        ]
[ 1732.05080757 -3000.         -2000.             0.        ]