パラメトリック曲面を描くのに適した WebGL ライブラリについて

この記事は [WebGL Advent Calendar 2015](http://qiita.com/advent-calendar/2015/webgl) の６日目の記事です。
------

### パラメトリック曲面について
みなさん、[パラメトリック曲面](http://markun.cs.shinshu-u.ac.jp/learn/cg/cg3/index6.html)という言葉をご存じでしょうか？
２つのパラメータ（時間や角度などの媒介変数）によって決まる曲面のことを、パラメトリック曲面と呼ぶようです。

こんなのです。
![graph.jpg](https://qiita-image-store.s3.amazonaws.com/0/26094/e1994733-f6b1-c9f2-2cfc-887beee0cc61.jpeg)

[Parametrische Flächen und Körper](http://www.3d-meier.de/tut3/Seite0.html)

こんな図形が手軽に描けたらワクワクしてきませんか？

今回は「[lightgl.js](https://github.com/evanw/lightgl.js/)」という軽量 WebGL ライブラリを使いたいと思います。
まずは、三角形からです。

### 一番シンプルな例

```html:index.html
<script src="lightgl.js"></script>
```

```js:index.js
var gl = GL.create();

gl.ondraw = function() {
    gl.clearColor(1.0, 1.0, 1.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.translate(0, 0, -2.5);
    gl.color(0.0, 0.0, 1.0);
    gl.begin(gl.LINE_STRIP);
    gl.vertex( 0.0, 0.5, 0.0);   // 頂点１
    gl.vertex(-0.5,-0.5, 0.0);   // 頂点２
    gl.vertex( 0.5,-0.5, 0.0);   // 頂点３
    gl.end();
};
```
いかがでしょうか？エラーにならなければ三角形が描画されたかと思います。
基本的にパラメトリック曲面は gl.vertex() を用いて、3D 空間にプロットすることで実現します。

次にトーラスを描いてみましょう。

### トーラスの例

![lightgl_js_20150619_011.png](https://qiita-image-store.s3.amazonaws.com/0/26094/2852cbe6-320f-ec08-3d24-0f71a5a74da0.png)

式は以下になります。

```
x = (R + r * cos(v)) * cos(u)
y = (R + r * cos(v)) * sin(u)
z = r * sin(v)

u は 0 ～ 2π
v は 0 ～ 2π
```

```js:index.js
var angle = 0;
var gl = GL.create();

gl.onupdate = function(seconds) {
    angle += 45 * seconds;
};

gl.ondraw = function() {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.loadIdentity();
    gl.translate(0, 0, -2);
    gl.rotate(60, 1, 0, 0);
    gl.rotate(angle, 1, 1, 1);

    // トーラス（Torus）の座標データを用意
    //             1.0 y 
    //              ^  -1.0 
    //              | / z
    //              |/       x
    // -1.0 -----------------> +1.0
    //            / |
    //      +1.0 /  |
    //           -1.0
    // 
    gl.begin(gl.POINTS);
    gl.pointSize(2.0);
    var ustep = Math.PI * 5 / 180;
    var vstep = Math.PI * 5 / 180;
    var R = 0.5;
    var r = 0.2;
    for (var v = 0; v <= 2 * Math.PI; v += vstep) {
        for (var u = 0; u <= 2 * Math.PI; u += ustep) {
            var x = (R + r * Math.cos(v)) * Math.cos(u);
            var y = (R + r * Math.cos(v)) * Math.sin(u);
            var z = r * Math.sin(v);
            gl.color(x + 0.5, y + 0.5, z + 0.5, 1.0);
            gl.vertex(x, y, z);
        }
    }
    gl.end();
};

gl.fullscreen();
gl.animate();
```

### lightgl.js の特徴

今回、lightgl.js を選んだのは以下の理由によります。

- ライブラリが軽量（100KB以下）
- 少ないコードで WebGL が使えるようになる。
- 書き方が OpenGL に似ている（OpenGL を知っていれば移植が簡単）
- 軽量ラッパである為、無駄が少ない（組み込み関数、組み込みシェーダ等は最低限のもしか用意されていない為、軽い）

ライブラリを使わないスクラッチの WebGL だと、先述のような簡単な三角形でも[100行は軽く超える](https://msdn.microsoft.com/library/dn302469.aspx)ので、ライブラリの恩恵が感じられるのではないでしょうか。

### OpenGL / lightgl.js / processin.js との比較

|OpenGL       |lightgl.js    |processing.js   |
|:------------|:-------------|:---------------|
|float        |var           |var             |
|int          |var           |var             |
|sin()        |Math.sin()    |Math.sin()      |
|cos()        |Math.cos()    |Math.cos()      |
|glBegin()    |gl.begin()    |p.beginShape()  |
|glEnd()      |gl.end()      |p.endShape()    |
|glColor3d()  |gl.color()    |p.stroke()※    |
|glPointSize()|gl.pointSize()|p.strokeWeight()|
|glVertex3d() |gl.vertex()   |p.vertex()      |
|GL_POINTS    |gl.POINTS     |p.POINTS        |
|GL_LINES     |gl.LINES      |p.LINES         |
|GL_LINE_STRIP|gl.LINE_STRIP |p.LINE_STRIP    |

※ processing.js の stroke 関数の色指定は 0～1 でなく 0～255 である為、注意。

### パラメトリック曲面およびその他サンプル

ということで、色々と描画してみました。パラメトリック曲面でないものもありますが、ご了承ください。

|タイトル                |イメージ                    |[lightgl.js](https://github.com/evanw/lightgl.js/)|[processing.js](https://github.com/processing-js/processing-js)|[Three.js](https://github.com/mrdoob/three.js/)|WebGL                       |
|:-----------------------|--------------------------------------------------------------------------------------------|:------------------------------:|:------------------------------:|:------------------------------:|:-------------------------------:|
|三角形                  |![三角形](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/triangle.jpg)              |[Link](https://cx20.github.io/jsdo.it-archives/cx20/rm4H)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/yRfJ)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/yCyD)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/oaQC)|
|リサージュ図形          |![リサージュ図形](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/lissajous.gif)      |[Link](https://cx20.github.io/jsdo.it-archives/cx20/4kX2)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/qLmr)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/QQAP)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/9Dru)|
|３次元リサージュ図形    |![３次元リサージュ図形](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/lissajous3d.jpg)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/5RXU)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/qLuh)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/075N)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/2UUa)|
|ハーモノグラフ          |![ハーモノグラフ](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/harmonograph.gif)      |[Link](https://cx20.github.io/jsdo.it-archives/cx20/ry8D)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/spb3)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/GbyF)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/synR)|
|３次元関数              |![３次元関数](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/3d.jpg)          |[Link](https://cx20.github.io/jsdo.it-archives/cx20/vHqB)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/OuEB)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/qf2Z)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/8H2X)|
|波動方程式              |![波動方程式](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/wave-equation.gif)          |[Link](https://cx20.github.io/jsdo.it-archives/cx20/5VBl)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/Ko9b)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/r7EJ)|-                               |
|ローマ曲面              |![ローマ曲面](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/roman.jpg)          |[Link](https://cx20.github.io/jsdo.it-archives/cx20/v0rS)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/AXqw)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/qcoh)|-                               |
|貝殻曲面                |![貝殻曲面](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/seashell.gif)            |[Link](https://cx20.github.io/jsdo.it-archives/cx20/lyR5)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/QyLv)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/YuLf)|-                               |
|リンゴ曲面              |![リンゴ曲面](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/apple.jpg)          |[Link](https://cx20.github.io/jsdo.it-archives/cx20/pejt)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/oyuL7)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/sAZvW)|-                               |
|スライム曲面            |![スライム曲面](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/slime.jpg)       |[Link](https://cx20.github.io/jsdo.it-archives/cx20/vo3eN)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/ktw0)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/ksyv)|-                               |
|ハート曲面              |![ハート曲面](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/torus.jpg)         |[Link](https://cx20.github.io/jsdo.it-archives/cx20/woba)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/mVP0)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/wzAv)|-                               |
|トーラス                |![トーラス](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/torus.jpg)            |[Link](https://cx20.github.io/jsdo.it-archives/cx20/sssl)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/6Qrb)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/CHKo)|-                               |
|Wave Ball               |![Wave Ball](https://cx20.github.io/webgl-parametric-surface-examples/assets/screenshot/wave-ball.jpg)           |[Link](https://cx20.github.io/jsdo.it-archives/cx20/1YU6)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/SVEe)|[Link](https://cx20.github.io/jsdo.it-archives/cx20/MLYQ)|-                               |

### 追記（パフォーマンスについて）

コメント欄にて @emadurandal さんに教えて頂いたのですが、gl.vertex() は３次元空間にプロットするという意味合いでは分かりやすいのですが、パフォーマンスの観点からすると、かなり非効率なやり方のようです。
WebGL 的なテクニックとしては、VBO（頂点バッファオブジェクト）を使うことが推奨されているようです。

詳しくは、@emadurandal さんの９日目の記事「[GPU本来の性能を引き出すWebGL頂点データ作成法](http://qiita.com/emadurandal/items/0bb83b545670475f51a3) 」を参照下さい。

また、lightgl.js でも VBO による描画モードが用意されています。サンプルについては、下記の「VBO 編」をご覧ください。

> 各種 WebGL ライブラリで基本図形を表示してみる
> http://qiita.com/cx20/items/0fa19c96aa6470d98807