Date: 2020/10/03

〜細かいことはいいんだよ!〜WebGLで3D表示を作ってみよう。

はじめまして。
定期的に新しいことに取り組まないと死んだ魚の目になる呪いにかかった小田垣です。
8月よりシステムエンジニアとして入社しました。
 
今回初めてのブログ投稿です。
これから「細かいことはいいんだよ!」シリーズの連載をはじめたいと思います。

そんな記念すべき第一回目はWebGL

 
エンジニアの方は是非一緒に作ってみてください。
そうでない方はこんなこともできるんだなっと動画だけ見てってください。
それではやっていきます!
 

WebGLってなんぞ?

「細かいことはいいんだよ!」の心情に従ってざっくり説明します。
WebGLはWebブラウザで3D表現をすることができる技術です。
 
こんなものが作れます。
 
今回はHTMLとjavascriptのみを使用してざっくり作成していきます。
 

フォルダ構成

こんな感じでやっていきます。

threeJsPractice
|
|-index.html
|-index.js
めっちゃ単純です。

three.jsの導入

ライブラリを使わずに作っていくのは結構大変です。
世の中には便利なライブラリがあるのでそれをありがたく使わせてもらいましょう!
three.jsというライブラリを使い、今回は簡略化のためにcdnで取得します。
 
最初の一歩としてindex.htmlを以下のように記述します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"integrity="sha512-yNJzAsg5JyP91u+sLHlUDULMBd3hmEiVkYeeN1cQBKaLZ7EyT6oH2u5THNIRM2Fu6VKcZJv+F/QAp1h/qzy9Ow=="crossorigin="anonymous"></script>
<script src="./index.js"></script>
</head>
<body>
</body>
</html>

three.jsの設定

three.jsを利用して何かを表示する時は以下の3つの要素が必要なようです。
・scene
・camera
・renderer
 
index.jsに以下の記述をします。
window.addEventListener('load', init);
function init() {
// シーンの作成
const scene = newTHREE.Scene();
// カメラの作成
const camera = newTHREE.PerspectiveCamera(
  75,  //視野角
  window.innerWidth / window.innerHeight,  //アスペクト比
  0.1,  カメラに映る最小距離
  1000,  カメラに映る再長距離
);
// レンダラーの作成
const renderer = newTHREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
};

これで下準備は完了です。

いよいよ箱を作っていきましょう!

表示する箱を作成

window.addEventListener('load', init);
function init() {
// シーンの作成
const scene = newTHREE.Scene();
// カメラの作成
const camera = newTHREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000,
);
// レンダラーの作成
const renderer = newTHREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
===⭐️ここから追加===
// 箱を作るために箱の頂点や面情報などの形の情報を持つBoxGetmetryオブジェクトが必要
const geometry = newTHREE.BoxGeometry();
// 今回はMeshBasicMaterial(光の影響を受けない均一な塗り潰し)の質感にカラーを指定
const material = newTHREE.MeshBasicMaterial( { color:0x00ff00 } );
// geometryをmaterialを用いて3Dオブジェクトを作成する
const cube = newTHREE.Mesh( geometry, material );
// sceneに追加
scene.add(cube);
camera.position.z = 5; // カメラの位置を設定
renderer.redner(scene, cmaera);
===⭐️ここまで追加===
};

このhtmlファイルをブラウザで表示すると緑の箱が表示されると思います。

おいおい、ただの四角が出ただけじゃないか。

こんなことならcssでかけるよ!

まぁまぁ、あわてないでください。

この四角…ただの四角ではないんです。

今の状態では平面の四角にしか見えないですが、実はちゃんと立体になっています。

それを証明するためにもアニメーションをつけて回転させてみましょう!

animationの設定

window.addEventListener('load', init);
function init() {
// シーンの作成
const scene = newTHREE.Scene();
// カメラの作成
const camera = newTHREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000,
);
// レンダラーの作成
const renderer = newTHREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 箱を作るために箱の頂点や面情報などの形の情報を持つBoxGetmetryオブジェクトが必要
const geometry = newTHREE.BoxGeometry();
// 今回はMeshBasicMaterial(光の影響を受けない均一な塗り潰し)の質感にカラーを指定
const material = newTHREE.MeshBasicMaterial( { color:0x00ff00 } );
// geometryをmaterialを用いて3Dオブジェクトを作成する
const cube = newTHREE.Mesh( geometry, material );
// sceneに追加
scene.add(cube);
camera.position.z = 5; // カメラの位置を設定
===⭐️ここから追加===
const animate = function () {
  requestAnimationFrame(animate)
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}
animate();
===⭐️ここまで追加===

アニメーションの設定を追加しました。

index.htmlを読み込んでみましょう!

「細かいことはいいんだよ!」精神でざっくり解説します。

requestAnimationFrameの引数には再描画する前に呼び出す関数を指定します。
animateメソッドを実行すると、作成した箱をx軸、y軸方向に少し回転させて再描画しています。
つまり、以下のような動きになります。
 
画面が読み込まれる
->animate関数実行
->requestAnimationFrameでanimate関数を設定
->箱をちょっと回転
->再描画
->animate関数がもう一度実行(requestAnimationFrameで設定していたおかげ!)
->requestAnimationFrameでanimate関数を設定
->箱をちょっと回転
->再描画
->...

 

と繰り返されます。
この連続によって箱が動いているように見えます。
(試しにanimate関数内でconsole.logなどで出力してみてください。ログがめっちゃ出ます)
 
ちなみにsetIntervalとかでもいいんじゃないの?って声もあると思います。
使い所にもよるかと思いますが、別タブを開いている時などを考えるとrequestAnimationFrameの方が良さそうです。
気になる方はrequestAnimationFrameについて調べてみてください。
今回は細かいことは抜きにするのでこれ以上深追いしません。
 
さて、これでクルクル回る箱が作成できました。
ただなんか味気ないですね。
✨光を追加したいと思います✨

✨光の追加✨

materialの作成をランバートシェーディングに従ったマテリアルに変更します。
 
 
……
 
………はい。出ました。難しそうなカタカナ。
 
脱線しますが難しそうなカタカナは個人的に嫌いです。
わかりにくいし、伝わりにくい。
 
ランバートシェーディングは光の反射を表現するモデルです。
 
ランバートシェーディングって名前の光の反射の仕方に従った箱に変更するということです。
 
const material…の部分を以下に変更してください。
const material = new THREE.MeshLambertMaterial( { color: 0x00ff00 } );

光の反射を考えた箱ですのでもちろん光源が必要です。

three.jsには光源が用意されています。追加してみましょう!

const cube…の下に以下の行を追加してください。
// ライトの作成
const light = new THREE.DirectionalLight(0XFFFFFF, 1);
light.position.set(0, 10, 50);
scene.add(light);
これでもう一度index.htmlを読み込んでみましょう!
おお!
なんかちょっとリッチになりました!
ちなみにライトの位置を可視化することもできます。(今回は触れません。)
 
このようにthree.jsを使えばhtmlとjavascriptだけで3Dのアニメーションをブラウザで表現できます。
楽しいですね!
表現の幅を広げられることができてワクワクします。

おわりに

今回three.jsを使用して、3Dのアニメーションを作成しました。

簡単な例ですが、これからの開発に応用できそうだなと感じています。

何より作っていて楽しい!

また、Three.jsを使うのに座標の話だとか回転の話だとか、少なからず数学の知識も出てきます。

学生の頃勉強しといてよかったなーと思う瞬間でした。

「なんで数学なんて勉強するの?役に立つの?」

と聞かれても、「将来のため」と自信を持って答えられるようになりました。

長くなるので説明までしませんが、土星のようなものも作れました。

画像を用意していないので、それっぽい色を当てただけですが、土星の表面を示す画像を作ればもっとそれっぽくできます。

three.jsには四角だけでなく球体やリングまで用意されているので、それを組み合わせれば手軽に作成できそうですね!

それでは良い開発ライフを!