🙄

WebGL入門 ~ three.js ①~

2022/05/24に公開

こんにちは。
5月から、WebGL総本山のWebGLスクールを受講させてもらってます🤔
自分の備忘も兼ねて、学んだことをまとめていこうと思っています。
ポイントのみ書いてしまうのですが、教えてもらったことを写経するのではなく、自分のなかで咀嚼するための記事になるのでご容赦ください・・・(結果的に写経になってるとこもありますが)

WebGLとは

ざっくりいうと「OpenGLをjavascriptから叩けるAPI群」
OpenGLは「グラフィックを描画するためのAPI」で、PCやモバイル、タブレットにも搭載されている。
GPUを利用したレンダリングが行われることで、高速な描画が実現できる。
※javascript自体はCPUで処理される ※javascript自体が高速化するわけではない⚠️

three.jsとは

three.jsはWebGLのラッパーライブラリ。
さまざまな機能をもっているので、ピュアなWebGLを書くよりも最初はおすすめ。
そもそも3Dの考え方が難しいので、まずはthree.jsでWebGLを学んでいきます📝

実習🙌

ソースを全文載せたりしないので、ざっくりいうと、
jsのclass中で、init関数render関数を用意し、初期化→描画しています。

まずは、基礎的なワードと関係をイメージできるように。

私は学生のときに実写を撮っていたので、この辺は割とイメージがつきやすかったのですが、
Scene, Camera, Render の関係を、脳内でイメージするのは難しいと思います。
参考までに、自己解釈をのせておきます。

  • Scene = 現場。現場にないものは撮影できない。
  • Camera = カメラはカメラ。カメラに映らないところは見えない。
  • Render = 撮影・編集したシーンを上映する。

多分だいたい合ってる・・・!

Renderer

レンダラーの初期化 (initに追加)

this.renderer = new THREE.WebGLRenderer();
this.renderer.setClearColor(new THREE.Color(App3.RENDERER_PARAM.clearColor));
this.renderer.setSize(App3.RENDERER_PARAM.width, App3.RENDERER_PARAM.height);
const wrapper = document.querySelector('#webgl');
wrapper.appendChild(this.renderer.domElement);
  • レンダラーの準備
  • 画面をクリアする色の設定
  • レンダラーのサイズを指定
  • canvasを取り出してDOMに追加
this.renderer = new THREE.WebGLRenderer();

用意したcanvasを使う場合はここで引数に渡す。
引数がない場合は内部的にcanvasを準備し、domElementに準備されたcanvasを渡してくれる。

wrapper.appendChild(this.renderer.domElement);

Scene

シーンの初期化 (initに追加)

this.scene = new THREE.Scene();

Camera

カメラの初期化 (initに追加)

this.camera = new THREE.PerspectiveCamera(
  App3.CAMERA_PARAM.fovy,
  App3.CAMERA_PARAM.aspect,
  App3.CAMERA_PARAM.near,
  App3.CAMERA_PARAM.far,
);
this.camera.position.set(
  App3.CAMERA_PARAM.x,
  App3.CAMERA_PARAM.y,
  App3.CAMERA_PARAM.z,
);
this.camera.lookAt(App3.CAMERA_PARAM.lookAt);

カメラのパラメータ

  • fovy = カメラの角度
  • aspect = カメラのアスペクト比。画角
  • near = 深度(手前)
  • far = 深度 (奥)


表示されない(レンダリングされない)と思ったら、「nearより近い」or「farより遠い」だったりします。。CG難しい。

Geometry + Material = Mesh

  • Geometry = 頂点の集合体。形/骨組み、設計
  • Material = 色・質感など
  • Mesh =ジオメトリとマテリアルを組み合わせ

Meshをシーンに追加 (initに追加)

this.geometry = new THREE.BoxGeometry(1.0, 1.0, 1.0);
this.material = new THREE.MeshBasicMaterial(App3.MATERIAL_PARAM);
this.box = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.box);

this.box がメッシュ。メッシュはシーンに追加することで初めて描画される。

シーンとカメラを記述して、(renderに追加)

render() {
  this.renderer.render(this.scene, this.camera);
}

描画!

requestAnimationFrame

ここまでで、three.jsで作られた空間を描画することができました!!🙌

が、しかし。
1回しかレンダリングしていないと、最初の1フレームしか描画されないため、requestAnimationFrameします。
常に(60FPSとか)レンダリングすることで、アニメーションやインタラクションが可能になります。

requestAnimationFrameは、「次のフレームの予約」みたいなものです。

render() {
  requestAnimationFrame(this.render); // これ
  this.renderer.render(this.scene, this.camera);
}

のようにrenderに追加するで、「次のリフレッシュのタイミングで、自分自身(ここではthis.render)を更新してね〜」という予約ができます。

OrbitControls

OrbitControls -> マウスでぐりぐり3D空間をうごかせるやつ。が、three.jsにデフォルトでいるのでimportして追加

import {OrbitControls} from '../lib/OrbitControls.js';

インスタンスを設定 (initに追加)

this.controls;
this.controls = new OrbitControls(this.camera, this.renderer.domElement);

インスタンスを設定 (renderに追加)

this.controls.update();

ヘルパー


ヘルパーと呼ばれる補助機能があり、x, y, zの軸を表示してくれます。
他のオブジェクトと同様に、シーンに追加することで表示できる。

const axesBarLength = 5.0;
this.axesHelper = new THREE.AxesHelper(axesBarLength);
this.scene.add(this.axesHelper);

3D空間でぐりぐりしてると前後左右がよくわからなくなるので、作成中は表示しておくと便利。
宇宙で迷子にならないように・・・🪐

基底クラス

たとえば、キー操作をしたら、オブジェクトを回転させる、移動させる、変化させたいとき。
(キー操作自体はjavascriptaddEventListerして、フラグとか持たせる)

Object3Dという基底クラスがあり、その中にrotationなどのプロパティがある。
予め用意されているプロパティをいじるだけでアニメーションができる!

// this.boxがy軸回転する
// rotation.y = y軸回転 = y軸を固定した状態で回転する
this.box.rotation.y += 0.05;

Mesh, Camera なども、このObject3Dを継承している。

Light

ライト、照明ですね💡
(ちなみに、ライトもObject3D)

ライトを設定しなくても一応画面は見えるのですが、立体感や質感のために追加していきます。
光の種類は、光源の種類 + 反射の仕方(照明効果)
光源の種類は、平行光源や、点光源、光源そのもののことで、
反射の仕方は、拡散光、反射光、環境光などがあります。

three.jsで設定できるライト、その代表的なのをドキュメントから引用👇

アンビエントライト = 環境光 は、全体的に底上げするような照明になります。
厳密には、現実世界の環境光を再現しているわけではないですが、とりあえず設定するのによさげ。
現実世界的に言うと、「光が複雑に反射しまくって全体的に明るい」状態。

平行光源とは、ディレクショナルライトという言葉の方がイメージしやすいと思うのですが、
「指定した方向から原点に向かって照らす」ライトです。
(方向であって、指定した座標からではない。詳しくは一旦置いとく)
かなーり遠くから照らされているイメージ🌞

点光源は、「指定した座標からあらゆる方向に照らす」ライト。
こちらは、「電球が全方向に発光している」イメージです💡

平行光源や点光源が、オブジェクトに当たって拡散したものを拡散光といいます。

Geometryの種類

BoxGeometry以外にも色々あるよ。

代表的なのをドキュメントから引用👇

リサイズ

リサイズしたらcanvasのサイズカメラのアスペクト比も更新すること。
表示されない部分がでたり、歪んだ画面になったりする。

window.addEventListener('resize', () => {
  this.renderer.setSize(window.innerWidth, window.innerHeight);
  this.camera.aspect = window.innerWidth / window.innerHeight;
  this.camera.updateProjectionMatrix();
}, false);

GeometryとMaterialは使い回す!

複数の同じようなオブジェクトを配置するときは、新しく作るのはメッシュだけ。
できるだけ、GeometryMaterialは使いまわした方がよいそうです。

余談なのですが、会社の先輩にも言われました。
例えば、「シーンの切り替えのときに、それぞれ板ポリを用意するのではなくて、1枚の板ポリに表示するものを変化させる」みたいなイメージで理解してます。
ソースが大きくなってきたら、GeometryMeshのClassを分けておけば良い感じにかけるのだろう・・・(jsも勉強中です・・・)
意識したいポイント。

かんそう

基礎だけどボリュームがあったので、「まだついていける・・・!汗」と思いながら受講していました。
まだまだこれから、がんばりますー✊

ちなみに、宿題ででた「boxを100個以上」は自分ではこれ👇を作りました。

他の皆さん、すでに意味のある表現されていたり、自分で調べたことを盛り込んでいたり、ライティングや背景を変えてみたり、、、ほんとにクオリティ高かったです!!
自分で調べて色々触ってみるのほんと大事だなと痛感したので、次回はもう少しソースをこねこねしたいと思います!

Discussion