Shaderを扱う基本(メモ)

ひとまずこれだけ書けばGLSLが表示されるはず、というメモ。

index.html

<script id="vs" type="x-shader/x-vertex">の中に頂点シェーダー、<script id="fs" type="x-shader/x-fragment">の中にフラグメントシェーダーを書く。
<body>の中は<canvas>のみ。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>JavaScriptで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">
attribute vec4 a_position;
void main( void ) {
	gl_Position = vec4( a_position );
}
</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 defer src="./main.js"></script>
</head>
<body>
<canvas id="container"></canvas>
</body>
</html>

main.js

グローバル変数を用意、キャンバスをwindowサイズと合わせておく。

const canvas = document.getElementById('container');
const gl = canvas.getContext('webgl');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

HTML内に直書きしているコードを使って「vertexShader」と「fragmentShader」をJavaScriptで使えるようにする

const vertexShaderSource = document.getElementById('vs').textContent;
const fragmentShaderSource = document.getElementById('fs').textContent;
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);

const u_time = gl.getUniformLocation(program, 'u_time');
const u_mouse = gl.getUniformLocation(program, 'u_mouse');
const u_resolution = gl.getUniformLocation(program, 'u_resolution');
const a_position = gl.getAttribLocation(program, 'a_position');

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

const positions = [ -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, ];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

イベントリスナを登録したりしておしまい。

resize();
window.addEventListener('resize', resize, false);

window.addEventListener('mousemove', move, false);

requestAnimationFrame(render);

createShader()関数

シェーダーを作って返す

function createShader(gl, type, source) {
	const shader = gl.createShader(type);
	gl.shaderSource(shader, source);
	gl.compileShader(shader);
	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
		console.error('Shader compilation failed:', gl.getShaderInfoLog(shader));
		gl.deleteShader(shader);
		return null;
	}
	return shader;
}

createProgram()関数

プログラムを作って返す

function createProgram(gl, vertexShader, fragmentShader) {
	const program = gl.createProgram();
	gl.attachShader(program, vertexShader);
	gl.attachShader(program, fragmentShader);
	gl.linkProgram(program);
	if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
		console.error('Program linking failed:', gl.getProgramInfoLog(program));
		gl.deleteProgram(program);
		return null;
	}
	return program;
}

resize()関数

resizeイベントでキャンバスを、windowサイズに合わせて更新。

function resize() {
	canvas.width = window.innerWidth;
	canvas.height = window.innerHeight;
	gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}

move()関数

mousemoveイベントでuniformの「u_mouse」を、カーソル位置に合わせて更新。

function move(e) {
	gl.uniform2f(u_mouse, e.offsetX, e.offsetY);
}

render()関数

requestAnimationFrameで、uniformの「u_time」を0.001秒ずつ更新。

function render(time) {
	time *= 0.001;

	gl.clear(gl.COLOR_BUFFER_BIT);

	gl.useProgram(program);

	gl.uniform1f(u_time, time);
	gl.uniform2f(u_resolution, canvas.width, canvas.height);

	gl.enableVertexAttribArray(a_position);
	gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
	gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);

	gl.drawArrays(gl.TRIANGLES, 0, 6);
    
	requestAnimationFrame(render);
}

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