Three.jsでShaderを扱う基本(メモ)
ひとまずこれだけ書けばThree.jsを介してGLSLが表示されるはず、というメモ。
index.html
<script id="vs" type="x-shader/x-vertex">の中に頂点シェーダー、<script id="fs" type="x-shader/x-fragment">の中にフラグメントシェーダーを書く。
Three.jsの読み込みは公式ドキュメントに倣う。
<body>の中は<canvas>のみ。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Three.jsでShaderを扱う基本</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
body { margin: 0; background: #121314; }
</style>
<script id="vs" type="x-shader/x-vertex">
uniform float u_time;
void main( void ) {
gl_Position = vec4(position, sin(u_time) * 0.1 + 1.0);
}
</script>
<script id="fs" type="x-shader/x-fragment">
precision highp float;
uniform float u_time;
uniform vec2 u_mouse;
uniform vec2 u_resolution;
void main( void ) {
vec2 p = ( gl_FragCoord.xy - u_mouse ) / min(u_resolution.x, u_resolution.y);
vec2 color = ( vec2(1.0) + p.xy ) * 0.5;
gl_FragColor = vec4(color, cos(u_time), 1.0);
}
</script>
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../three.module.min.js",
"three/addons/": "https://unpkg.com/three@v0.153.0/examples/jsm/"
}
}
</script>
<script defer src="./main.js" type="module"></script>
</head>
<body>
<canvas id="container"></canvas>
</body>
</html>
main.js
Three.jsをインポート、グローバル変数を用意しておく。
import * as THREE from 'three';
var clock, scene, camera, mesh, renderer, uniforms;
init();
clockはキャンバスが生成されてからの経過時間を保持する(あとでshaderで使う)。
「視野角=60, アスペクト比=ウィンドウ幅/高さ, 最近=1, 最遠=1000」のカメラを用意。
canvasでレンダラーを作って、デバイスピクセル比をdevicePixelRatioに合わせておく。
init()関数
function init() {
const canvas = document.getElementById('container');
const ratio = window.devicePixelRatio;
clock = new THREE.Clock();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
scene.add( camera );
renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true });
renderer.setPixelRatio( ratio );
HTML内に直書きしているコードを使って「vertexShader」と「fragmentShader」を指定。
uniformsにはそのシェーダーで宣言しているuniformを登録、floatなら浮動小数点数、vec2ならTHREE.Vector2()を入れておく。
平面ジオメトリにシェーダーマテリアルを貼ったメッシュを作る。
// GLSL で宣言してる uniform を登録
const shader = {
vs: document.getElementById('vs').textContent,
fs: document.getElementById('fs').textContent
}
uniforms = {
u_time: { type: "f", value: 1.0 },
u_mouse: { type: "v2", value: new THREE.Vector2() },
u_resolution: { type: "v2", value: new THREE.Vector2() }
};
// シェーダーメッシュを置く
const geometry = new THREE.PlaneGeometry( ratio, ratio, 100, 100 );
const material = new THREE.ShaderMaterial({
wireframe: true,
uniforms: uniforms,
vertexShader: shader.vs,
fragmentShader: shader.fs
});
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
イベントリスナを登録したりしておしまい。
resize();
window.addEventListener( 'resize', resize, false );
window.addEventListener( 'mousemove', move, false );
animate();
}
resize()関数
resizeイベントでuniformの「u_resolution」を、windowサイズに合わせて更新。
function resize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;
}
move()関数
mousemoveイベントでuniformの「u_mouse」を、カーソル位置に合わせて更新。
function move(e) {
uniforms.u_mouse.value.x = e.offsetX;
uniforms.u_mouse.value.y = e.offsetY;
}
render()関数
requestAnimationFrameでuniformの「u_time」を、Three.jsのclockオブジェクトから経過した秒数を取得して更新。
function render() {
uniforms.u_time.value += clock.getDelta();
renderer.render( scene, camera );
}
animate()関数
function animate() {
requestAnimationFrame( animate );
render();
}