WebGL入門 ~ three.js ①~
こんにちは。
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空間でぐりぐりしてると前後左右がよくわからなくなるので、作成中は表示しておくと便利。
宇宙で迷子にならないように・・・🪐
基底クラス
たとえば、キー操作をしたら、オブジェクトを回転させる、移動させる、変化させたいとき。
(キー操作自体はjavascript
でaddEventLister
して、フラグとか持たせる)
Object3D
という基底クラスがあり、その中にrotationなどのプロパティがある。
予め用意されているプロパティをいじるだけでアニメーションができる!
// this.boxがy軸回転する
// rotation.y = y軸回転 = y軸を固定した状態で回転する
this.box.rotation.y += 0.05;
Mesh
, Camera
なども、このObject3D
を継承している。
Light
ライト、照明ですね💡
(ちなみに、ライトもObject3D
)
ライトを設定しなくても一応画面は見えるのですが、立体感や質感のために追加していきます。
光の種類は、光源の種類 + 反射の仕方(照明効果)
光源の種類は、平行光源や、点光源、光源そのもののことで、
反射の仕方は、拡散光、反射光、環境光などがあります。
three.jsで設定できるライト、その代表的なのをドキュメントから引用👇
- 環境光源(アンビエントライト) AmbientLight
- 平行光源(ディレクショナルライト) DirectionalLight
- 点光源 PointLight
- スポットライト光源 SpotLight
アンビエントライト = 環境光 は、全体的に底上げするような照明になります。
厳密には、現実世界の環境光を再現しているわけではないですが、とりあえず設定するのによさげ。
現実世界的に言うと、「光が複雑に反射しまくって全体的に明るい」状態。
平行光源とは、ディレクショナルライトという言葉の方がイメージしやすいと思うのですが、
「指定した方向から原点に向かって照らす」ライトです。
(方向であって、指定した座標からではない。詳しくは一旦置いとく)
かなーり遠くから照らされているイメージ🌞
点光源は、「指定した座標からあらゆる方向に照らす」ライト。
こちらは、「電球が全方向に発光している」イメージです💡
平行光源や点光源が、オブジェクトに当たって拡散したものを拡散光といいます。
Geometryの種類
BoxGeometry
以外にも色々あるよ。
代表的なのをドキュメントから引用👇
- 平面 CylinderGeometry
- 球体 SphereGeometry
- 直方体 BoxGeometry
- 円錐 ConeGeometry
- 円柱 CylinderGeometry
- ドーナツ形状 TorusGeometry
リサイズ
リサイズしたらcanvasのサイズとカメラのアスペクト比も更新すること。
表示されない部分がでたり、歪んだ画面になったりする。
window.addEventListener('resize', () => {
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
}, false);
GeometryとMaterialは使い回す!
複数の同じようなオブジェクトを配置するときは、新しく作るのはメッシュだけ。
できるだけ、Geometry
とMaterial
は使いまわした方がよいそうです。
余談なのですが、会社の先輩にも言われました。
例えば、「シーンの切り替えのときに、それぞれ板ポリを用意するのではなくて、1枚の板ポリに表示するものを変化させる」みたいなイメージで理解してます。
ソースが大きくなってきたら、Geometry
とMesh
のClassを分けておけば良い感じにかけるのだろう・・・(jsも勉強中です・・・)
意識したいポイント。
かんそう
基礎だけどボリュームがあったので、「まだついていける・・・!汗」と思いながら受講していました。
まだまだこれから、がんばりますー✊
ちなみに、宿題ででた「boxを100個以上」は自分ではこれ👇を作りました。
他の皆さん、すでに意味のある表現されていたり、自分で調べたことを盛り込んでいたり、ライティングや背景を変えてみたり、、、ほんとにクオリティ高かったです!!
自分で調べて色々触ってみるのほんと大事だなと痛感したので、次回はもう少しソースをこねこねしたいと思います!
Discussion