記事の概要
前回の記事では時間経過による変化を実現しました。
今回はマウスの座標にあわせて変化させます。
画面解像度を取得する
1 色のグラデーションを作ったときの記事で以下のようなシェーダーを書いていました。
#version 300 es
in vec2 aPosition;
out vec2 vTextureCoord;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
vTextureCoord = aPosition * 0.5 + 0.5;
}
#version 300 es
precision mediump float;
in vec2 vTextureCoord;
out vec4 outColor;
void main() {
float r = vTextureCoord.x;
outColor = vec4(r, 0.0, 0.0, 1.0);
}
マウスの座標にあわせて背景色を変化させる際など、今のままだと形状を制御しづらいです。
そのため、少し書き方を変えます。
// シェーダーに渡すデータ
const positionAttributeLocation = gl.getAttribLocation(
shaderProgram,
"aPosition"
);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
+ const resolutionUniformLocation = gl.getUniformLocation(shaderProgram, 'uResolution');
// 描画の実行
- function drawScene() {
+ function drawScene(width: number, height: number) {
+ gl.uniform2f(resolutionUniformLocation, width, height);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+ requestAnimationFrame(() => drawScene(width, height));
}
// canvas のリサイズ
function resizeCanvasToDisplaySize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
- drawScene();
+ drawScene(canvas.width, canvas.height);
}
#version 300 es
in vec2 aPosition;
- out vec2 vTextureCoord;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
-
- vTextureCoord = aPosition * 0.5 + 0.5;
}
#version 300 es
precision mediump float;
- in vec2 vTextureCoord;
+ uniform vec2 uResolution;
out vec4 outColor;
void main() {
- float r = vTextureCoord.x;
+ vec2 uv = gl_FragCoord.xy / uResolution;
- outColor = vec4(r, 0.0, 0.0, 1.0);
+ outColor = vec4(uv.x, 0.0, 0.0, 1.0);
}
ややこしくなっただけに見えるかもしれませんが、後々役立ちます。
最終的には座標を正規化するタイミングを、頂点シェーダーからフラグメントシェーダーに移しただけです。
ただ、どちらかと言えば重要なのは uResolution
という名前で画面の解像度を取得したことです。
マウスの座標を取得し、シェーダーへ送る
まずは TypeScript の変更です。
// canvas要素の取得とWebGL2コンテキストの取得
const canvas = document.getElementById("webgl-canvas") as HTMLCanvasElement;
const gl = canvas.getContext("webgl2") as WebGL2RenderingContext;
// 中略
// シェーダーに渡すデータ
const positionAttributeLocation = gl.getAttribLocation(
shaderProgram,
"aPosition"
);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
const resolutionUniformLocation = gl.getUniformLocation(shaderProgram, 'uResolution');
+ const mousePositionUniformLocation = gl.getUniformLocation(shaderProgram, "uMousePosition");
+
+ let mousePosition = { x: 0, y: 0 };
+
+ function getMousePosition(event: MouseEvent) {
+ mousePosition.x = event.clientX / canvas.width;
+ mousePosition.y = 1.0 - (event.clientY / canvas.height); // Y軸を反転
+ }
+
+ canvas.addEventListener('mousemove', getMousePosition);
// 描画の実行
function drawScene(width: number, height: number) {
gl.uniform2f(resolutionUniformLocation, width, height);
+ gl.uniform2f(mousePositionUniformLocation, mousePosition.x, mousePosition.y);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(() => drawScene(width, height));
}
次にユニフォーム変数として受け取り、uv.x
を uMousePosition.x
に変えてみます。
#version 300 es
precision mediump float;
uniform vec2 uResolution;
+ uniform vec2 uMousePosition;
out vec4 outColor;
void main() {
vec2 uv = gl_FragCoord.xy / uResolution;
- outColor = vec4(uv.x, 0.0, 0.0, 1.0);
+ outColor = vec4(uMousePosition.x, 0.0, 0.0, 1.0);
}
これで、マウスの位置にあわせて色が変わりました。
uv.x |
uMousePosition.x |
---|---|
グリーン成分などに uMousePosition.y
を入れれば、マウスを縦横に動かした際背景色が変わります。
マウスの位置にあわせて色を変える
組み込み関数である distance()
と smoothstep()
を組み合わせ、マウスから 200px の範囲だけ色がつくようにしました。
void main() {
vec2 uv = gl_FragCoord.xy / uResolution;
+ // マウス位置との距離を計算
+ float mouseDistnace = distance(uMousePosition, uv);
+ float mouseColorIntensity = 1.0 - smoothstep(0.0, 200.0 / uResolution.x, mouseDistnace);
- outColor = vec4(uMousePosition.x, 0.0, 0.0, 1.0);
+ outColor = vec4(mouseColorIntensity, 0.0, 0.0, 1.0);
}
しかし、赤い範囲が楕円形になっています。
これが先ほど書いていた「形状を制御しづらい」話です。
修正し、画面のアスペクト比によらず円形となるように変えます。
void main() {
vec2 uv = gl_FragCoord.xy / uResolution;
+ // アスペクト比を考慮したUV座標の計算
+ vec2 aspectCorrectedUV = uv;
+ aspectCorrectedUV.x *= uResolution.x / uResolution.y;
+ // マウス位置のアスペクト比を補正
+ vec2 aspectCorrectedMouse = uMousePosition;
+ aspectCorrectedMouse.x *= uResolution.x / uResolution.y;
// マウス位置との距離を計算
float mouseDistnace = distance(uMousePosition, uv);
float mouseColorIntensity = 1.0 - smoothstep(0.0, 200.0 / uResolution.x, mouseDistnace);
outColor = vec4(mouseColorIntensity, 0.0, 0.0, 1.0);
}
uResolution.x / uResolution.y
をかけることで、1:1 の比率となるようにしました。
最後に
マウスの位置にあわせた変化ができたので、ユーザーの操作にあわせてインタラクティブに画面を変えられそうです。
次回はスクロール関連の記事を書きたいと思います。