thee.jsあり/なしで見た目や実装の違いを比較してみた

# はじめに
[WebGL Advent Calendar 2016](http://qiita.com/advent-calendar/2016/webgl)の22日目の記事になります。
今回はthree.jsの有無でどれほど見た目や実装に差がでるのか比較してみます。

three.jsとはwebGLをサポートした3DCGライブラリです。
webGLの実装する場合、ほとんどの方が使われているのではないでしょうか。
three.jsにどれだけ恩恵を受けているか、three.jsの中身がどうなっているのか少しでも理解したくて書きました。

ちなみに、はじめて生webGLに触ったためthree.jsなしのソースは自信がないです。間違っていたらご指摘お願いします...!!

# 準備
見た目が同じになるthree.jsあり/なしのデモを用意します。
立方体を描画しただけの簡単なデモです。
・[three.jsあり](http://youhe.jp/demo/is_threejs/1/)
・[three.jsなし](http://youhe.jp/demo/is_threejs/0/)
※three.jsなしでは行列の操作に'sylvester.js'と'glUtils.js'を使っています。

#見た目の比較
（左）three.jsあり　（右）three.jsなし
![ew.png](https://qiita-image-store.s3.amazonaws.com/0/84990/d075dcca-edc8-1eed-63ad-077874dc14c8.png)

three.jsありの方が、辺がジャギります。
これは、three.js内で描画を軽量化させる処理を行っているためらしい。


# ソースコードの比較
全体のコードを比較すると大変なので気になった部分だけ書きます。
### ・立方体
```thee.jsあり
  // CubeGeometryで立方体を定義
  var geometry = new THREE.CubeGeometry(10, 10, 10);

  // 各面の色を定義
  var materials = [
    new THREE.MeshBasicMaterial({color: 0x0000ff}),
    new THREE.MeshBasicMaterial({color: 0xff00ff}),
    new THREE.MeshBasicMaterial({color: 0x00ff00}),
    new THREE.MeshBasicMaterial({color: 0x00ffff}),
    new THREE.MeshBasicMaterial({color: 0xff0000}),
    new THREE.MeshBasicMaterial({color: 0xffff00}),
  ];
  var material = new THREE.MeshFaceMaterial(materials);
  var obj = new THREE.Mesh(geometry, material);
```

```three.jsなし
  // 各頂点の位置を配列で定義
  var vertices = [
    // front
    -1.0, -1.0,  1.0,
     1.0, -1.0,  1.0,
     1.0,  1.0,  1.0,
    -1.0,  1.0,  1.0,
    // back
    -1.0, -1.0, -1.0,
    -1.0,  1.0, -1.0,
     1.0,  1.0, -1.0,
     1.0, -1.0, -1.0,
    // top
    -1.0,  1.0, -1.0,
    -1.0,  1.0,  1.0,
     1.0,  1.0,  1.0,
     1.0,  1.0, -1.0,
    // bottom
    -1.0, -1.0, -1.0,
     1.0, -1.0, -1.0,
     1.0, -1.0,  1.0,
    -1.0, -1.0,  1.0,
    // right
     1.0, -1.0, -1.0,
     1.0,  1.0, -1.0,
     1.0,  1.0,  1.0,
     1.0, -1.0,  1.0,
    // left
    -1.0, -1.0, -1.0,
    -1.0, -1.0,  1.0,
    -1.0,  1.0,  1.0,
    -1.0,  1.0, -1.0
  ];

  // 各面の色を定義
  var colors = [
    [1.0,  0.0,  0.0,  1.0],
    [1.0,  1.0,  0.0,  1.0],
    [0.0,  1.0,  0.0,  1.0],
    [0.0,  1.0,  1.0,  1.0],
    [0.0,  0.0,  1.0,  1.0],
    [1.0,  0.0,  1.0,  1.0]
  ];

  // 各面の色を各頂点の色情報に変換
  var generatedColors = [];
  for (j=0; j<6; j++) {
    var c = colors[j];
    for (var i=0; i<4; i++) {
      generatedColors = generatedColors.concat(c);
    }
  }

  // 各面を２つの三角形として作成し
  // 各三角形の位置を指定するために、頂点の配列を示す
  var obj_vertexIndices = [
    0,  1,  2,      0,  2,  3,    // front
    4,  5,  6,      4,  6,  7,    // back
    8,  9,  10,     8,  10, 11,   // top
    12, 13, 14,     12, 14, 15,   // bottom
    16, 17, 18,     16, 18, 19,   // right
    20, 21, 22,     20, 22, 23    // left
  ];

  var obj_verticesBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, obj_verticesBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

  var obj_erticesColorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, obj_erticesColorBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(generatedColors), gl.STATIC_DRAW);

  var obj_erticesIndexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj_erticesIndexBuffer);

  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(obj_vertexIndices), gl.STATIC_DRAW);
```
three.jsなしのコードがだいぶ長くなりました。
オブジェクトの頂点、面を定義するのが大変です。
色の指定には00〜ffではなく0〜1で書かなければなりません。


### ・シーン,カメラ,レンダラー
```three.jsあり
  // scene
  var scene = new THREE.Scene();

  // camera
  var camera = new THREE.PerspectiveCamera(45, 640 / 480, 1, 2000);
  camera.position.set(0, 0, 0);
  camera.lookAt(new THREE.Vector3(0, 0, 0));

  // renderer
  var renderer = new THREE.WebGLRenderer({
    canvas: canvas,
  });
  renderer.setSize(640, 480);
  renderer.setClearColor(new THREE.Color(0x000000, 0));
```
```three.jsなし
 var shaderProgram;
 var vertexPositionAttribute;
 var vertexColorAttribute;
 var gl;
 gl = canvas.getContext('experimental-webgl');
 gl.clearColor(0.0, 0.0, 0.0, 1.0);
 gl.enable(gl.DEPTH_TEST);
 initShader(gl);

 function initShader(gl)
 {
   var fragmentShader = getShader(gl, 'shader-fs');
   var vertexShader = getShader(gl, 'shader-vs');
   shaderProgram = gl.createProgram();
   gl.attachShader(shaderProgram, vertexShader);
   gl.attachShader(shaderProgram, fragmentShader);
   gl.linkProgram(shaderProgram);
   gl.useProgram(shaderProgram);

   vertexPositionAttribute = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
   gl.enableVertexAttribArray(vertexPositionAttribute);

   vertexColorAttribute = gl.getAttribLocation(shaderProgram, 'aVertexColor');
   gl.enableVertexAttribArray(vertexColorAttribute);
 }

 function getShader(gl, id) {
   var shaderScript = document.getElementById(id);
   var theSource = '';
   var currentChild = shaderScript.firstChild;

   while(currentChild) {
     if (currentChild.nodeType == 3) {
       theSource += currentChild.textContent;
     }
     currentChild = currentChild.nextSibling;
   }
 }
```
これ以外にcameraの設定はrenderの中で決めていて
```makePerspective(45, 640 / 480, 1, 2000);```
となります。
シーン,カメラ,レンダラーごとに分けて説明したかったのですが、
分けづらくて諦めました...ゴメンナサイ

また、three.jsなしはこれに加えてシェーダーの記述が必要です。
ほぼ定形文みたいなやつ。

```three.jsなし　shader
<script id="shader-fs" type="x-shader/x-fragment">
  varying lowp vec4 vColor;

  void main(void) {
    gl_FragColor = vColor;
  }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
  attribute vec3 aVertexPosition;
  attribute vec4 aVertexColor;

  uniform mat4 uMVMatrix;
  uniform mat4 uPMatrix;

  varying lowp vec4 vColor;

  void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
    vColor = aVertexColor;
  }
</script>
```

# 感想
こんな感じでthree.jsになると記述量がだいぶ増えます。
three.jsなしで実装を行うのは予想通り大変でした。
ただし、オブジェクトの定義などは比較しやすく構造もわかりやすかったかなと思います。
three.jsのdocumentとソースを読んでもっとwebGLや3DCGの世界の理解を深めていきたいです。

# 参考にしたサイト
・WebGL を用いた 3D オブジェクトの作成
https://developer.mozilla.org/ja/docs/Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL