概要
この記事では「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回 シェーダーでテクスチャにエフェクトをかける
今回は、前回書いた発光する円をマウス座標やクリックイベントでインタラクティブにうごかしてみます。
uniform
変数でマウス座標を渡す
前回と第3回でやった内容のおさらいです。
今回はマウス座標をシェーダーに渡すため、ウィンドウ座標系(左上原点)からテクスチャ座標系(左下原点)に変換します。
// 省略
export default class Canvas {
constructor() {
// 省略
// マウス座標
this.mouse = new Vector2(0.5, 0.5);
// uniform変数を定義
this.uniforms = {
uAspect: {
value: this.w / this.h
},
uTime: {
value: 0.0
},
uMouse: {
value: this.mouse
}
};
// 省略
}
// 省略
mouseMoved(x, y) {
// 左上原点から左下原点に変換
this.mouse.x = x / this.w;
this.mouse.y = 1.0 - (y / this.h);
}
};
varying vec2 vUv;
uniform float uAspect;
uniform float uTime;
uniform vec2 uMouse;// マウス座標
void main() {
vec2 uv = vec2( vUv.x * uAspect, vUv.y );
vec2 center = vec2( uMouse.x * uAspect, uMouse.y );// アスペクト補正したマウス座標
float radius = 0.05 + sin( uTime ) * 0.025;
float lightness = radius / length( uv - center );
// lightness = clamp( lightness, 0.0, 1.0 );
vec4 color = vec4( vec3( lightness ), 1.0 );
color *= vec4( 0.2, 1.0, 0.5, 1.0 );
gl_FragColor = color;
}
Page
クラスでwindow
のmousemove
イベントが発生したときにcanvas
のmouseMoved
関数を呼びます。
import Canvas from './Canvas';
export default class Page00 {
constructor() {
const canvas = new Canvas();
window.addEventListener('mousemove', e => {
canvas.mouseMoved(e.clientX, e.clientY);
});
}
};
簡易イージングでなめらかに追いかける
普通にマウス座標を割り当てるだけだとちょっと味気ないので、簡易的なイージングを挟んでマウス座標をゆっくり追いかけるようにしましょう。
// 省略
export default class Canvas {
constructor() {
// 省略
// uniform変数を定義
this.uniforms = {
uAspect: {
value: this.w / this.h
},
uTime: {
value: 0.0
},
uMouse: {
value: new Vector2(0.5, 0.5)// this.mouse とは別のベクトル
}
};
// 省略
}
render() {
requestAnimationFrame(() => { this.render(); });
const sec = performance.now() / 1000;
this.uniforms.uTime.value = sec;
// シェーダーに渡すマウスを更新
this.uniforms.uMouse.value.lerp(this.mouse, 0.2);
this.renderer.render(this.scene, this.camera);
}
// 省略
};
GIFだとちょっとわかりづらいですが、マウス座標をぬるっと追いかけるようになりました。
vecA.lerp(vecB, 0.2);
と書くと、vecA
をvecB
の位置に20%
近づけるという意味になります。
この処理が毎フレーム呼ばれているので、現在位置から目標位置までの差分の20%
ずつ近づいていきます。
目標位置までの距離が近くなるほど、当然差分の20%
の量も小さくなるので、だんだんゆっくりになります。
上記はVector
クラスでの書き方ですが、float
値で同じ処理を書く場合はval += (targetVal - val) * 0.2;
になります。
クリック中に半径を大きくする
targetRadius
という変数を作り、mousePressed
で最大値に、mouseReleased
でデフォルトに設定します。
uRadius
というuniform
変数を作ってtargetRadius
の値を追いかけさせます。
// 省略
export default class Canvas {
constructor() {
// 省略
this.mouse = new Vector2(0.5, 0.5);
this.targetRadius = 0.005;// 半径の目標値
// uniform変数を定義
this.uniforms = {
uAspect: {
value: this.w / this.h
},
uTime: {
value: 0.0
},
uMouse: {
value: new Vector2(0.5, 0.5)
},
uRadius: {
value: this.targetRadius
}
};
// 省略
}
render() {
requestAnimationFrame(() => { this.render(); });
const sec = performance.now() / 1000;
this.uniforms.uTime.value = sec;
this.uniforms.uMouse.value.lerp(this.mouse, 0.2);
// シェーダーに渡す半径を更新
this.uniforms.uRadius.value += (this.targetRadius - this.uniforms.uRadius.value) * 0.2;
this.renderer.render(this.scene, this.camera);
}
// 省略
mousePressed(x, y) {
this.mouseMoved(x, y);
this.targetRadius = 0.25;// マウスを押したら半径の目標値を大きく
}
mouseReleased(x, y) {
this.mouseMoved(x, y);
this.targetRadius = 0.005;// マウスを押したら半径の目標値をデフォルト値に
}
};
シェーダー側では、radius
だったところをuRadius
に置き換えます。
varying vec2 vUv;
uniform float uAspect;
uniform float uTime;
uniform vec2 uMouse;
uniform float uRadius;// 半径
void main() {
vec2 uv = vec2( vUv.x * uAspect, vUv.y );
vec2 center = vec2( uMouse.x * uAspect, uMouse.y );
float lightness = uRadius / length( uv - center );// 半径を uRadius に変更
// lightness = clamp( lightness, 0.0, 1.0 );
vec4 color = vec4( vec3( lightness ), 1.0 );
color *= vec4( 0.2, 1.0, 0.5, 1.0 );
gl_FragColor = color;
}
Page
クラスにイベントリスナを追加して、mousedown
でcanvas.mousePressed
、mouseup
でcanvas.mouseReleased
を呼びます。
import Canvas from './Canvas';
export default class Page00 {
constructor() {
const canvas = new Canvas();
window.addEventListener('mousemove', e => {
canvas.mouseMoved(e.clientX, e.clientY);
});
window.addEventListener('mousedown', e => {
canvas.mousePressed(e.clientX, e.clientY);
});
window.addEventListener('mouseup', e => {
canvas.mouseReleased(e.clientX, e.clientY);
});
}
};
簡易イージングを使うと段階的な目標値の変化に対して値がスムーズに移動してくれるので便利ですね。
次回はシェーダーでテクスチャにエフェクトをかける方法を紹介します。