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