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();
}

この記事が気に入ったらサポートをしてみませんか?