canvas要素のサイズを変更する際に知っておくべき点について説明します。
canvasには2種類のサイズがあります。 ひとつめは、canvasの「描画バッファーのサイズ」です。これは、canvas内のピクセル数です。 ふたつめは、canvasの「表示サイズ」です。canvasの表示上のサイズは、CSS(style)で決定します。
canvasの「描画バッファーのサイズ」を設定する方法は2種類あります。 ひとつめは、HTMLを使う方法です。
<canvas id="c" width="400" height="300"></canvas>
ふたつめは、JavaScriptを使う方法です。こちらの方法では、HTML側は
<canvas id="c" ></canvas>
JavaScript側は、
var canvas = document.querySelector("#c");
canvas.width = 400;
canvas.height = 300;
といったコードになります。
canvasの「表示サイズ」については、 「canvasの表示サイズに影響を与えるCSSが定義されてない場合」は、 「描画バッファーと同じサイズ」になります。上の例で言えば、 「描画バッファのサイズ」は400x300で、 「表示サイズ」も同じく、400x300となります。
別の例として「描画バッファが10x15ピクセル、表示サイズが400x300のcanvas」 を定義する場合はこんなコードになります。
<canvas id="c" width="10" height="15" style="width: 400px; height: 300px;"></canvas>
同じ意味で、こんな風に書くこともできます。
<style>
#c {
width: 400px;
height: 300px;
}
</style>
<canvas id="c" width="10" height="15"></canvas>
描画バッファと表示上のサイズが違う場合どうなるか、実際に見てみましょう。 下の例では、「描画バッファが10x15ピクセル、表示サイズが400x300のcanvas」 で、「1ピクセル幅の直線」が回転しています。
なぜぼやけているのでしょうか?この例でブラウザは、 「10x15ピクセルで描かれたcanvasを400x300ピクセルに引き伸ばす」 ということをやっているのですが、 一般にブラウザは、引き伸ばしを行う際にフィルタをかけるためです。
では、canvasをウィンドウいっぱいにリサイズしたい時にはどうすればよいでしょうか? まず、CSSを使ってウィンドウいっぱいに引き伸ばしてみましょう。 コードはこんな感じになります。
<html>
<head>
<style>
/* border幅は0にしておく */
body {
border: 0;
background-color: white;
}
/* canvasを「ビューポート」のサイズにする */
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<style>
</head>
<body>
<canvas id="c"></canvas>
</body>
</html>
リサイズに際してやるべきことは、「ブラウザーがcanvasの表示サイズを
引き伸ばしたら、とにかくそのサイズに描画バッファーのサイズを合わせる」ことだけです。
これを実現するには、clientWidth
とclientHeight
を使います。
これはHTMLのすべての要素が持っているプロパティです。
これを使えばその要素が実際に表示されているサイズをJavaScriptから取得できます。
function resize(canvas) {
// ブラウザがcanvasを表示しているサイズを調べる。
var displayWidth = canvas.clientWidth;
var displayHeight = canvas.clientHeight;
// canvasの「描画バッファーのサイズ」と「表示サイズ」が異なるかどうか確認する。
if (canvas.width != displayWidth ||
canvas.height != displayHeight) {
// サイズが違っていたら、描画バッファーのサイズを
// 表示サイズと同じサイズに合わせる。
canvas.width = displayWidth;
canvas.height = displayHeight;
}
}
WebGLアプリケーションの多くはアニメーションする ので、この関数はレンダリングの直前に呼び出すのがよいでしょう。 このタイミングであれば描画の直前にcanvasを適切なサイズに合わせることができます。
function drawScene() {
resize(gl.canvas);
...
以上の修正を加えて実行するとこのようになります。
何かおかしいですね。表示領域いっぱいに直線が表示されないのはなぜでしょうか?
結論から言えば、canvasをリサイズしたら同時にgl.viewport
を呼んでビューポートを
設定する必要があったためです。
gl.viewport
の役目は、「WebGLがクリップ空間座標(-1~+1)をピクセルに変換する」際に
「どう変換するか」と「canvas内のどの範囲で変換するか」を指定することです。
最初にWebGLコンテキストを取得する時には、ビューポートのサイズがcanvasのサイズと同じに
なるようにWebGLが自動的にセットするのですが、それ以降の管理はあなたに任されています。
つまり、canvasのサイズ変更があった時には、あなたが明示的にビューポートの設定を更新しなければなりません。
ではそのようにコードを変更してみます。WebGLコンテキストはcanvasへの参照を 持っているので、そのサイズをリサイズ処理に反映させましょう。
function drawScene() {
resize(gl.canvas);
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
...
これでうまく動きました。
「別のウィンドウ」で開いてウィンドウサイズを変更しても、 回転する直線はいつも画面いっぱいに描画されるのを確認してみてください。
ここで「なぜWebGLはcanvasサイズを変更したときビューポートを自動的に更新してくれないの?」 という疑問があるかと思います。 それに対する答えは「あなたがビューポートをなぜ、どう使いたいのか、 WebGLにはわからないから」となります。 「フレームバッファーへの描画」など、通常と異なるビューポートサイズを使う状況は いくつかあります。 WebGLには「あなたが何を意図しているか」を判断する手段がないので、 ビューポートの設定を「あなたが望むように」自動で更新することはできないのです。
WebGLプログラムをたくさん見ていくとわかりますが、 canvasのサイズ設定やリサイズのやり方、考え方はいろいろなものがあります。 もし興味があれば、上で紹介したやり方を私が好んでいる理由を説明した記事があるので確認してみてください。