WebGL入門 ~ three.js ②~
こんにちは。
受講しているWebGLスクールの備忘録なので、前の記事を読んでいただいた方がわかりやすいかもです。
第1回の記事はこちら→🙄WebGL入門 ~ three.js ①~
さて、今回も前回にひきつづき、「three.jsをベースにWebGLに慣れていこう」というかんじ。
ポイントとしては
- カメラの種類
- 回転
- フォグ
- テクスチャ
- マテリアル(透明度)
- ポストプロセス
もりもり💪
カメラの種類
- Perspective
- 透視投影
- シーンを四角錐で切り取るイメージ
- パース(奥行き、遠近感)がかかった描画になる
- Orthographic
-
平行投影
-
シーンを直方体で切り取るイメージ
-
パース(奥行き、遠近感)がかからない描画になる
-
どういう時に使うのか?
- ゲームっぽさ、トイっぽさ ⇒ 非現実的な質感の演出
- インターフェース部分
- 2D的表現
-
前回はPerspective
で説明していたので、Orthographic
を使う時は、カメラのパラメータが変わる。
PerspectiveCamera (前回)
イメージはこんな感じ
this.camera = new THREE.PerspectiveCamera(
App3.CAMERA_PARAM.fovy,
App3.CAMERA_PARAM.aspect,
App3.CAMERA_PARAM.near,
App3.CAMERA_PARAM.far,
);
描画するとこんな感じ
OrthographicCamera
イメージはこんな感じ
this.camera = new THREE.OrthographicCamera(
App3.CAMERA_PARAM.left,
App3.CAMERA_PARAM.right,
App3.CAMERA_PARAM.top,
App3.CAMERA_PARAM.bottom,
App3.CAMERA_PARAM.near,
App3.CAMERA_PARAM.far
);
fovy
, aspect
ではなく、上下左右の位置を指定して空間を切り取る。nearとfarは同じ。
描画するとこんな感じ
回転
Object3D
クラスには、 position
や rotation
が備わっている。
position
で平行移動、 rotation
で回転ができる。
が、「時計の針のように回転」のように、移動+回転が思うようにいかないことがある。
「特定の位置を軸に回転させる」が意外と難しい!
ポイントは
- 回転が先か?移動が先か?
three.jsの世界では、「必ず回転が先におこる」
記述が「移動→回転」の順で書いてあってもダメ! - 回転は世界全体を原点を中心に回転させる
オブジェクトの単位ではなく、世界全体を、原点を中心に回転する。
three.js
の場合は、オブジェクトごとに、世界全体を回転する処理を実行しているので、それぞれがrotation
プロパティを持っている
移動と回転をおこうなう際の順番
-
Object3D
に属するオブジェクトを、世界にひとつだけ配置する - 原点を中心に世界全体を回転させる
- オブジェクトを移動させる
- 次のオブジェクトを、配置する・・・・の繰り返し
回転処理のまとめ
- 回転と移動は順番によって挙動がかわる
-
three.js
では回転が常に先におこる - 回転を加えると世界全体が回転する
- three.jsでは
Object3D
のインスタンスごとに処理している
じゃあどうやって「時計の針のように回転」を実現するのか??
-> オブジェクトを入れ子にして、移動と回転をわける
-> 入れ子にする ⇒ Group
オブジェクトをつかう!!
-> 順番としては、子→親 の順に優先して反映されるので、子供Objectを移動させ、親Groupで回転させる
Group
クラス
オブジェクトをグループかして、ひとつの Object3D
のインスタンスとして扱うことができるクラス。
いままで scene
に追加していたオブジェクトを group
に追加して、 scene
を group
に追加する。
this.group = new THREE.Group();
this.scene.add(this.group); // グループをシーンに追加
this.group.add(torus); // グループにオブジェクトを追加
フォグ
フォグ = 霧
this.scene.fog = new THREE.Fog(
App3.FOG_PARAM.fogColor,
App3.FOG_PARAM.fogNear,
App3.FOG_PARAM.fogFar
);
シーンに add
ではなく、 this.scene.fog
のように指定する。
カメラからの距離に応じて、オブジェクトにフォグが適応される。
パラメータは以下
- 色
- 開始位置
- 終了位置
注意点!
ソース的にはシーンに対してフォグを適用しているが、
フォグはオブジェクトに対してかかる(空間にかかるわけではない)ので、
背景色をフォグの色と合わせる必要がある!
背景色とマッチしていないフォグをかけた状態
OrthographicCamera, OrbitControl との共存?
OrthographicCamera
でフォグをかけると、オブジェクトごとに濃淡がない。
これはなぜ・・・?奥行きがないからなの・・・??
※わかりやすいので背景とフォグの色変えてます
まず、カメラによってOrbitControl(マウスで視点ぐりぐりするやつ)の仕組みが違うらしい!
-
PerspectiveCamera
のOrbitControl
のズームはカメラが近づいてる
-
OrthographicCamera
のOrbitControl
のズームは、うつす空間を小さくしている
-> フォグはカメラからの距離で描画されているので、 OrthographicCamera
との相性が悪い
-> かけられないわけではない。 OrbitControl
を使わず、カメラオブジェクトを動かせば濃淡が出せる
テクスチャ
テクスチャ = オブジェクトに貼り付けるビットマップ
テクスチャのリソースとして使えるもの
- 画像(jpgやpng)
- 動画
- Canvas2Dで描画したビットマップ
- バイナリデータから模様作ることもできる。
など、などなど。。。
まずは画像を貼り付けてみる。(マッピングともいう)
非同期読み込み
ブラウザでは、リソースを読み込み落としてきてからじゃないとテクスチャをつかえない。
three.jsには ローダーと呼ばれるヘルパー実装がある。
new THREE.TextureLoader()
を使うのがおすすめ。
クラスに load
関数を追加する。
TexrureLoader
の load
プロパティで画像の読み込んだ後にで処理してくれる。
load() {
return new Promise((resolve) => {
const imagePath = './sample.jpg';
// テクスチャ用のローダーのインスタンスを生成
const loader = new THREE.TextureLoader();
// ローダーの load メソッドに読み込む画像のパスと、ロード完了時のコールバックを指定
loader.load(imagePath, (texture) => {
this.texture = texture;
resolve();
});
});
}
(クラスの外) load
内の Promise
がここに渡ってくる
window.addEventListener('DOMContentLoaded', () => {
const app = new App3();
app.load()
.then(() => {
app.init();
app.render();
});
}, false);
init
とrender
は load
が終わっってから処理する
init
関数に以下を追加
this.material = new THREE.MeshPhongMaterial(App3.MATERIAL_PARAM);
this.material.map = this.texture;
初期化したテクスチャはマテリアルの map
プロパティに設定する。
注意点
three.js既存の機能では以下は難しいので、自分で開発する必要がある。。
- 絵の貼られ方は、
Geometory
側が持っている設定 - 1つの
Material
には、1つのtexture
しか貼れない
テクスチャサイズに関して
- そもそものWebGLでは、「2のn乗」が推奨されている
- three.jsでは、自動で「2のn乗」にリサイズしてくれるので、自由なサイズで問題ない。
- リサイズが走らないので、最初から「2のn乗」サイズにしておく方が良さそう!
マテリアル(透明度)
半透明と深度テスト
半透明な3DCGは、ほんとうにやっかい!!
CGの基本的な原理である深度テストとはこんな仕組み。
- 深度 = カメラから見た時の深さ・奥行き
- 前後関係を計算して、描画する/しないがきまる
- 手前におきたいA → 奥におきたいB の順に配置する
→ 深度テストが発生する
→ Bのかぶっている部分は捨てられる
- 手前におきたいA → 奥におきたいB の順に配置する
👆オブジェクトが半透明だと、奥にあるオブジェクト(もしくはポリゴン) が表示されたり、されなかったりする・・・?
これは、記述の順番によって深度テストがおきるから。
また、オブジェクトごとではなく、ポリゴンごとに深度テストがおきるため、1つのオブジェクトでも裏側のポリゴンが見えない現象がおきることも。
- 手前におきたいA → 奥におきたいB の順に配置する
→ 深度テスト
→ ポリゴンが欠ける - 奥におきたいB → 手前におきたいA の順に配置する
→ 半透明 の上に 半透明 がちゃんと表示できる
バックフェイスカリング
- ポリゴンの裏面を非表示にする機能
- 描画負荷を抑えることができる
- three.jsに限らず、CGで一般的に使われる概念
- three.jsではマテリアルに統合されており、
side
というプロパティに何を設定しているかによって変わる。
設定できる値: FrontSide, BackSide, DoubleSideなど
https://threejs.org/docs/#api/en/materials/Material.shadowSide
ポストプロセス
一度出来上がった絵を事後的に加工すること
(写真にフィルターをかけるイメージ)
three.jsには エフェクトコンポーザーというのがあり、これを使うことで比較的簡単に加工ができる。
-
シーンAをかく→ Bという方法でAを加工しながら、描画 という手順をいいかんじにしてくれる
-
パス(Pass) という単位で描画する処理を指定できる
- RenderPass ⇒ シーンをレンダリングするパス (だいたいこれが最初)
- GlitchPass,dotScreenPass など ⇒ 加工するパス※色々ある
-
パスからパスにわたしていく。順番によっても表現が変わる
-
最後に
this.glitchPass.renderToScreen = true;
して描画する。(省略した場合、最後のパスが画面に自動的に出力されるようになる) -
追加すればするほど、高負荷にはなるので、用法用量を考えてね
three.jsの追加機能なので、importする
import {EffectComposer} from '../lib/EffectComposer.js';
import {RenderPass} from '../lib/RenderPass.js';
import {GlitchPass} from '../lib/GlitchPass.js';
init
でレンダーしたものを加工する、コンポーザーの設定をする
// 1. コンポーザーにレンダラを渡して初期化する
this.composer = new EffectComposer(this.renderer);
// 2. コンポーザーに、まず最初に「レンダーパス」を設定する
this.renderPass = new RenderPass(this.scene, this.camera);
this.composer.addPass(this.renderPass);
// 3. コンポーザーに第2のパスとして「グリッチパス」を設定する
this.glitchPass = new GlitchPass();
this.composer.addPass(this.glitchPass);
// 4. グリッチパスまで終わったら画面に描画結果を出すよう指示する
this.glitchPass.renderToScreen = true;
// ※省略した場合、最後のパスが画面に自動的に出力されるようになる
render
に追加
this.composer.render();
レンダラーではなく、コンポーザーに対して描画を指示する。
かんそう
今回も学びがもりもりで、ついていくのでいっぱいでしたが、
1回目のように「覚えること多い〜💦」というより、「知っておくといいよ!」なポイントだったので、楽しむ&広がる感じでした。
なんだかんだまだ宿題やってないや。やったら記事にスクショ追加します・・・!
Discussion