概要
この記事では「three.js超入門」と題して、three.jsの基礎からシェーダーの利用までをやっていきます。
ターゲットは主に「canvas表現を触ったことがないフロントエンドエンジニア」を想定しているので、jsの構文などの説明は省略しています。
three.jsのバージョンは執筆時点で最新のr98を使用します。
three.js超入門 第0回 3Dコンピュータグラフィックスの基礎
three.js超入門 第1回 レンダリングまでの流れ
three.js超入門 第2回 アニメーションと時間ベースでの制御
three.js超入門 第3回 マウスやスクロールでのインタラクション
three.js超入門 第4回 DOM要素との連携
three.js超入門 第5回 シェーダー(GLSL)の基礎
three.js超入門 第6回 ShaderMaterialでメッシュを変形、着色する
three.js超入門 第7回 シェーダーに変数を渡す
three.js超入門 第8回 シェーダーをインタラクティブに動かす
three.js超入門 第9回 シェーダーでテクスチャにエフェクトをかける
three.jsでシェーダーを使うには
マテリアルにShaderMaterial
かRawShaderMaterial
のどちらかを使用します。
ShaderMaterial
は、three.js側で便利な変数(モデルの頂点座標やテクスチャ座標など)をシェーダーに自動で挿入してくれます。
逆にRawShaderMaterial
は、three.js側での補助が一切ないプレーンな状態のシェーダーを記述したいときに使うクラスです。
シェーダーに慣れるまではShaderMaterial
を使用することをおすすめします。
サンプルコード(シェーダーソースを文字列として記述する場合)
GLSLのコードは改行を含むので、JSファイルに直接記述する場合はテンプレート文字列を使うと書きやすいです。
import { ShaderMaterial } from 'three/src/materials/ShaderMaterial';
// 頂点シェーダーのソース
const vertexSource = `
void main() {
gl_Position = vec4(position, 1.0);
}
`;
// ピクセルシェーダーのソース
const fragmentSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
// シェーダーソースを渡してマテリアルを作成
const mat = new ShaderMaterial({
vertexShader: vertexSource,
fragmentShader: fragmentSource
});
サンプルコード(シェーダーソースを別ファイルにする場合)
テンプレート文字列ではエディタのシンタックスハイライトが効かないのと、コードごとにファイルを分けたいので、webpack-glsl-loader
を使ってシェーダーの拡張子(.vert
.frag
.glsl
)を読めるようにします。
// 省略
module.exports = {
// 省略
module: {
rules: [
{
test: /\.(vert|frag|glsl)$/,
use: {
loader: 'webpack-glsl-loader'
}
}
]
}
};
void main() {
gl_Position = vec4(position, 1.0);
}
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
import { ShaderMaterial } from 'three/src/materials/ShaderMaterial';
// シェーダーソース
import vertexSource from './shader.vert';
import fragmentSource from './shader.frag';
// シェーダーソースを渡してマテリアルを作成
const mat = new ShaderMaterial({
vertexShader: vertexSource,
fragmentShader: fragmentSource
});
実際のコード
リポジトリのプロジェクトルートでnpm start
してローカルサーバーを起動し、http://localhost:3000/00_shader_empty/
にアクセスします。
10 x 10の平面メッシュがワイヤーフレームで表示されます。
プロジェクトフォルダのsrc/00_shader_empty/
にシェーダー用のテンプレートを用意しているので、これを書き換えていきましょう。
00_shader_empty
├── Canvas
│ ├── index.js
│ └── shaders
│ ├── shader.frag
│ └── shader.vert
└── index.js
まず、Canvas
クラスの中身はこうなっています。
import { WebGLRenderer } from 'three/src/renderers/WebGLRenderer';
import { OrthographicCamera } from 'three/src/cameras/OrthographicCamera';
import { Scene } from 'three/src/scenes/Scene';
import { PlaneGeometry } from 'three/src/geometries/PlaneGeometry';
import { ShaderMaterial } from 'three/src/materials/ShaderMaterial';
import { MeshBasicMaterial } from 'three/src/materials/MeshBasicMaterial';
import { Mesh } from 'three/src/objects/Mesh';
import { Vector2 } from 'three/src/math/Vector2';
// シェーダーソース
import vertexSource from './shaders/shader.vert';
import fragmentSource from './shaders/shader.frag';
export default class Canvas {
constructor() {
// ウィンドウサイズ
this.w = window.innerWidth;
this.h = window.innerHeight;
// レンダラーを作成
this.renderer = new WebGLRenderer();
this.renderer.setSize(this.w, this.h);// 描画サイズ
this.renderer.setPixelRatio(window.devicePixelRatio);// ピクセル比
// #canvas-containerにレンダラーのcanvasを追加
const container = document.getElementById("canvas-container");
container.appendChild(this.renderer.domElement);
// カメラを作成(背景シェーダーだけならパースいらないので、OrthographicCameraをつかう)
this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, -1);
// シーンを作成
this.scene = new Scene();
// 平面をつくる(幅, 高さ, 横分割数, 縦分割数)
const geo = new PlaneGeometry(2, 2, 10, 10);
// シェーダーソースを渡してマテリアルを作成
const mat = new ShaderMaterial({
vertexShader: vertexSource,
fragmentShader: fragmentSource,
wireframe: true
});
this.mesh = new Mesh(geo, mat);
// メッシュをシーンに追加
this.scene.add(this.mesh);
// 描画ループ開始
this.render();
}
render() {
// 次のフレームを要求
requestAnimationFrame(() => { this.render(); });
// ミリ秒から秒に変換
const sec = performance.now() / 1000;
// 画面に表示
this.renderer.render(this.scene, this.camera);
}
};
今回はページの背景をシェーダー表現にしたいだけなので、ジオメトリには平面のPlaneGeometry
を使用し、カメラはPerspectiveCamera
ではなくOrthographicCamera
(パースのないカメラ)を使用しています。
ファイル上部でimport
したシェーダーソース(shader.vert
shader.frag
)をShaderMaterial
に渡してシェーダーマテリアルを作成しています。
頂点シェーダー
次に頂点シェーダー(shader.vert
)を見てみましょう。
// vertex shader ( 頂点シェーダー )
// このファイルに各頂点ごとの処理を記述します
void main() {
vec3 pos = position;// position: ShaderMaterialで補完される vec3 型(xyz)の変数。ジオメトリの頂点のこと。
gl_Position = vec4( pos, 1.0 );
}
position
は、ShaderMaterial
側で自動的に宣言してくれているvec3
型の変数で、ジオメトリの頂点座標を表しています。
gl_Position
はvec4
型の座標を代入することで頂点の位置を決定するGLSLの組み込み変数です。
頂点シェーダーでメッシュを変形させてみる
void main() {
vec3 pos = position;
pos.y = ( pos.y * 0.5 ) + sin( pos.x * 3.0 ) * 0.5;// 縦を半分のサイズにして、sinでy座標を歪ませる
gl_Position = vec4( pos, 1.0 );
}
たった1行追加するだけでメッシュを変形させることができました。
このように、CPUだといちいちforループを回さないとできないことが、シェーダーを使うとほんの数行で実現できます。すばらしいですね。
※ あとに影響がでるので頂点シェーダーの変更は元に戻しておきましょう。
void main() {
vec3 pos = position;
gl_Position = vec4( pos, 1.0 );
}
ピクセルシェーダー
次にピクセルシェーダーをいじるので、PlaneGeometry
の分割を1 x 1
にして、ワイヤーフレーム表示をオフにしておきます。
// 平面をつくる(幅, 高さ, 横分割数, 縦分割数)
const geo = new PlaneGeometry(2, 2, 1, 1);
// シェーダーマテリアルに GLSL のソースを渡す
const mat = new ShaderMaterial({
vertexShader: vertexSource,
fragmentShader: fragmentSource,
wireframe: false// ワイヤーフレームをオフ
});
ピクセルシェーダー(shader.frag
)を見てみましょう
// fragment shader ( フラグメントシェーダー、ピクセルシェーダー )
// このファイルに各ピクセルごとの処理を記述します
void main() {
vec4 color = vec4(1.0, 1.0, 1.0, 1.0);// rgba
gl_FragColor = color;// gl_FragColor に vec4 型(rgba)の色を入れることでピクセル色を決定する。
}
gl_FragColor
はvec4
型の色を代入することでピクセルの色を決定するGLSLの組み込み変数です。
vec4(1.0, 1.0, 1.0, 1.0)
で、rgba
が全て1.0
となっているので、白色になっています。
ピクセルシェーダーでメッシュの色を変えてみる
void main() {
vec4 color = vec4(1.0, 0.0, 0.0, 1.0);// 赤
gl_FragColor = color;
}
シェーダーの最低限の機能は以上です。
次回はさらに踏み込んで、頂点シェーダーからピクセルシェーダーに変数を渡したり、CPUからシェーダーに変数を渡す方法をやります。