この記事の概要
前回の記事では、まずは canvas
を用意して単色で塗りつぶしました。
このままでは div
要素を配置して background-color
を指定するのとなんら変わりません。
今回は、canvas
内に単色の板ポリを配置することまでを実施します。
全体像
ステップバイステップだと、途中まではエラーしか出ません。
そのため、いきなり完成したコードの全体像を載せます。
// 前回のコード
const canvas = document.getElementById("webgl-canvas") as HTMLCanvasElement;
const gl = canvas.getContext("webgl2") as WebGL2RenderingContext;
// シェーダープログラムの作成
const vertexShaderSource = `#version 300 es
in vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
}`;
const fragmentShaderSource = `#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
outColor = vec4(1.0, 0.0, 0.0, 1.0); // 赤色
}`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER) as WebGLShader;
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) as WebGLShader;
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
const shaderProgram = gl.createProgram() as WebGLProgram;
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
// バッファの作成と設定
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// canvasの幅と高さの半分の大きさの矩形の頂点データ
const positions = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
-0.5, 0.5,
0.5, 0.5,
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "aPosition");
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
// 前回のコード
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 新たに追加した描画のためのコード
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
見た目はこのようになっています。
シェーダープログラムの作成
シェーダーというものを使って、どんな頂点をもつ要素に、どんな色を塗るかを指定します。
今回は記事タイトルにもあるように板ポリ(厚みを持たないただの四角い板)を赤一色で塗ります。
シェーダーの定義
シェーダーには vertex shader
と fragment shader
の 2 種類があります。
vertex shader
に頂点を、fragment shader
に色を指定します。
const vertexShaderSource = `#version 300 es
in vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
}`;
const fragmentShaderSource = `#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
outColor = vec4(1.0, 0.0, 0.0, 1.0); // 赤色
}`;
vertexShaderSource
には in vec2 position
や gl_Position = vec4(position, 0.0, 1.0)
という記述があります。
これは後で JavaScript から頂点データを受け取り、平面的にマッピングするためのコードです。
gl_Position
の 3 つ目の引数の 0.0
は奥行き方向を表しています。
いくつかの座標の配列を XY 平面にプロットする、と捉えていてください。
fragmentShaderSource
の outColor
には r, g, b, a
を指定しています。
Web デザインではよく HEX 値を使いますが、ここではそれぞれの値を 0 から 1 にマッピングすることに注意してください。
gl_Position
が組み込みの変数であることや、void main()
という関数が必須なことなど、初見だと難しく感じることも多いかもしれません。
まずはとりあえず真似をして、書式そのものに慣れるのも重要に思います。
シェーダーオブジェクトの作成とコンパイル
vertex shader
と fragment shader
のそれぞれのコードは。WebGLのシェーダーオブジェクトに関連付けられコンパイルされます。
const vertexShader = gl.createShader(gl.VERTEX_SHADER) as WebGLShader;
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) as WebGLShader;
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
シェーダープログラムの作成とリンク
最終的に、シェーダーは 1 つのシェーダープログラムに結合されます。
const shaderProgram = gl.createProgram() as WebGLProgram;
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
バッファの作成と設定
バッファとは、頂点の位置や色といったデータを格納するためのメモリ領域です。
後で頂点データをシェーダーに渡すために使用します。
バッファの作成
新しいバッファオブジェクトを作成します。
const positionBuffer = gl.createBuffer();
バッファのバインド
先ほど作成した positionBuffer
をアクティブなバッファとしてバインドします。
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.ARRAY_BUFFER
は、バッファが頂点のデータを含むことを示しています。
頂点データの設定
四角形の頂点データを Float32Array
の配列に格納します。
このデータを gl.bufferData
を使って、バッファに設定します。
const positions = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
-0.5, 0.5,
0.5, 0.5,
]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
画面の横方向が -1 から 1、縦方向も -1 から 1 にマッピングされているので、この指定だと画面の半分の位置
配列の順番が大切で、例えば以下のように指定してしまうと……。
const positions = new Float32Array([
-0.5, -0.5,
0.5, -0.5,
0.5, 0.5, // 4 行目と入れ替えた
-0.5, 0.5, // 3 行目と入れ替えた
]);
結果がこうなってしまいます。
厳密に言えばこの後記載する TRIANGLE_STRIP
の設定とも組み合わさってこの見た目になっているのですが、とにかく「頂点の順番は適当ではいけない」と認識しておけば大丈夫だと思います。
属性ポインタの設定
vertex shader
で定義した position
属性にデータを結びつけています。
const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "aPosition");
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
描画の実行
前回の記事のコードに 1 行追加しています。
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
「三角形を組み合わせて、4 つの頂点を使って、四角形を描画する」といった内容です。
最後に
随分と長いコードを書きましたが、まだ見た目はこれです(記事の前半に載せた画像の再掲)。
しかも手元で動かしてもらうと分かると思いますが、赤い四角形のフチがボケボケ(?)です。
次はそのあたりを修正していきます。
リッチな表現を実装するまで、まだ先は長い……。
次の記事はこちらです。