0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

webGLで簡単な単色正方形を作る

Last updated at Posted at 2025-07-04

はじめに

 webGLで単色正方形を作る。簡単なシェーダーを作成し、400x400のキャンバスを単色で塗りつぶす。

コード全文

index.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>code0</title>
    <style>
      body{
        margin:0;
      }
      main{
        height:100dvh;
        display:flex;
        justify-content:center;
        align-items:center;
      }
    </style>
  </head>
  <body>
    <main>
      <canvas id="canvas0"></canvas>
    </main>
    <script src="main.js"></script>
  </body>
</html>
main.js
function loadSketch0(){
  // 1. vsを作る
  const vs =
`#version 300 es
const vec2[4] pos = vec2[](
  vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0)
);
void main(){
  vec2 p = pos[gl_VertexID];
  gl_Position = vec4(p, 0.0, 1.0);
}
`;

  // 2. fsを作る
  const fs =
`#version 300 es
precision highp float;
out vec4 fragColor;
void main(){
  fragColor = vec4(0.0, 0.5, 1.0, 1.0);
}
`;

  // 3. コンテキスト
  const cvs = document.getElementById("canvas0");
  cvs.width = 400;
  cvs.height = 400;
  const gl = cvs.getContext('webgl2');

  // 4.1. shader compile (vs)
  const vsShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vsShader, vs);
  gl.compileShader(vsShader);

  if(!gl.getShaderParameter(vsShader, gl.COMPILE_STATUS)){
    console.log("vertex shaderの作成に失敗しました");
    console.error(gl.getShaderInfoLog(vsShader));
  }

  // 4.2. shader compile (fs)
  const fsShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fsShader, fs);
  gl.compileShader(fsShader);

  if(!gl.getShaderParameter(fsShader, gl.COMPILE_STATUS)){
    console.log("fragment shaderの作成に失敗しました");
    console.error(gl.getShaderInfoLog(fsShader));
  }

  // 5. program
  const pg = gl.createProgram();

  // 6. attach and linking
  gl.attachShader(pg, vsShader);
  gl.attachShader(pg, fsShader);
  gl.linkProgram(pg);

  if(!gl.getProgramParameter(pg, gl.LINK_STATUS)){
    console.log("programのlinkに失敗しました");
    console.error(gl.getProgramInfoLog(program));
  }

  // 7. drawCall
  gl.useProgram(pg);

  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  gl.flush();
}

document.addEventListener("DOMContentLoaded", loadSketch0);

実行結果

fgrhehe.png

解説

htmlとcssについて

 キャンバス要素をmainタグで挟んでいる。main要素の縦方向の長さはdvhの100%で画面いっぱい。display:flexを使うとキャンバスを簡単に中央に配置できる。キャンバスの大きさはjsサイドで決めている。

シェーダーについて

 webGLではシェーダーと呼ばれる文字列を使って描画する。これらはC言語をベースとしたglslという言語で書かれる。バーテックスシェーダで頂点の位置を決めてドローコールを元にして三角形を生成、さらにフラグメントシェーダでそこに色を付ける。

vs
#version 300 es
const vec2[4] pos = vec2[](
  vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0)
);
void main(){
  vec2 p = pos[gl_VertexID];
  gl_Position = vec4(p, 0.0, 1.0);
}

 今回はwebgl2でやるので最初にversion 300 esを追記する。webgl2ではこれを省いて旧webglで描画することも一応許されているが、特にメリットが無いのでwebgl2で作成する。もう対応してないデバイスもほぼ無いと思うので。あっても無視する。
 定数の4つのvec2からなる配列を用意する。gl_VertexIDはドローコールに用いられる定数で、今回は0,1,2,3で描画するのでそれらが入る。つまり左下、右下、左上、右上の位置座標が入る。どこで描画してるかというと正規化デバイス座標系である。これは上下左右が±1で、x軸正方向が右、y軸正方向が上、高校でおなじみの座標系。
 ドローコールはTRIANGLE_STRIPを用いている。

  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

これは「0から4つの数字(0,1,2,3)でTRIANGLE_STRIPにより三角形を生成せよ」という命令で、その場合「0,1,2」と「2,1,3」で三角形が作られる。三角形の頂点の正の向きは反時計回りである(x軸を最短でy軸に重ねる向き)。だからイメージすると分かるが直角二等辺三角形が2枚できる。これで画面全体がおおわれる。

TRIANGLE_STRIP.png

 gl_Positionに4つの頂点の位置が入る。3番目の座標はとりあえず無視で。基本的に2次元ならここは常に0である。4つ目も通常は常に1である。

 次にフラグメントシェーダで色を付ける。

fs
#version 300 es
precision highp float;
out vec4 fragColor;
void main(){
  fragColor = vec4(0.0, 0.5, 1.0, 1.0);
}

outで指定されるのはvec4の色のベクトルである。それを0,0.5,1,1の水色にしているので、そういう色で三角形が塗られることになる。これで出来ました。なおprecision highp floatの指定はフラグメントシェーダの場合必須で、これはシェーダー内で使う小数の精度を決めている。

シェーダーを作る

 次にmain.jsでシェーダーを作るところ。


  // 3. コンテキスト
  const cvs = document.getElementById("canvas0");
  cvs.width = 400;
  cvs.height = 400;
  const gl = cvs.getContext('webgl2');

  // 4.1. shader compile (vs)
  const vsShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vsShader, vs);
  gl.compileShader(vsShader);

  if(!gl.getShaderParameter(vsShader, gl.COMPILE_STATUS)){
    console.log("vertex shaderの作成に失敗しました");
    console.error(gl.getShaderInfoLog(vsShader));
  }

  // 4.2. shader compile (fs)
  const fsShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fsShader, fs);
  gl.compileShader(fsShader);

  if(!gl.getShaderParameter(fsShader, gl.COMPILE_STATUS)){
    console.log("fragment shaderの作成に失敗しました");
    console.error(gl.getShaderInfoLog(fsShader));
  }

 まずキャンバスからwebgl2のコンテキストを取る。サイズは400x400で指定する。createShaderに引数を入れるとシェーダーオブジェクトが生成される。文字列をそのまま使うわけにはいかないので、入れ物が必要なんだね。ここに文字列を当てはめるのがshaderSourceという関数で、当てはめたらcompileShaderでシェーダーオブジェクトを完成させる。この際にエラーが出ないかどうかをgetShaderParameterで調べている。getShaderInfoLogで内容を知ることができる。

 実際にやってみようか。

vs
#version 300 es
const vec2[4] pos = vec2[](
  vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0)
);
void main(){
  vec2 p = pos[gl_VertexID]
  gl_Position = vec4(p, 0.0, 1.0);
}

どこがまずいかわかりますか?エラーが出ます。

vertex shaderの作成に失敗しました main.js:36:13
ERROR: 0:7: 'gl_Position' : syntax error
main.js:37:13
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:37
    (非同期: EventListener.handleEvent)
    <匿名> file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
programのlinkに失敗しました main.js:59:13
Uncaught ReferenceError: program is not defined
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:60
    EventListener.handleEvent* file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
main.js:60:40
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:60
    (非同期: EventListener.handleEvent)
    <匿名> file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
WebGL warning: linkProgram: Must have a compiled vertex shader attached:
SHADER_INFO_LOG:
ERROR: 0:7: 'gl_Position' : syntax error

 答えはpos[gl_VertexID]の後のセミコロンの排除。これが無いとえらーをくらう。セミコロンは分の区切りなのでpythonなどと違って絶対に省略できない。自分がjs書いてるときセミコロンを欠かさないのは単に几帳面なのと、glslと違うルールで書くのが面倒だから。pythonでは書かないけど。それもpythonのルールに従ってるだけ。これをやるとgl_Positionが正しく認識されないのでエラーになる。

 次に...

vs
#version 300 es
const vec2[4] pos = vec2[](
  vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0)
);
void main(){
  vec2 p = pos[gl_VertexID];
  gl_Position = vec(p, 0.0, 1.0);
}

これでエラーが出る。

vertex shaderの作成に失敗しました main.js:36:13
ERROR: 0:7: 'vec' : no matching overloaded function found
ERROR: 0:7: '=' : dimension mismatch
ERROR: 0:7: 'assign' : cannot convert from 'const mediump float' to 'Position highp 4-component vector of float'
main.js:37:13
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:37
    (非同期: EventListener.handleEvent)
    <匿名> file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
programのlinkに失敗しました main.js:59:13
Uncaught ReferenceError: program is not defined
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:60
    EventListener.handleEvent* file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
main.js:60:40
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:60
    (非同期: EventListener.handleEvent)
    <匿名> file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
WebGL warning: linkProgram: Must have a compiled vertex shader attached:
SHADER_INFO_LOG:
ERROR: 0:7: 'vec' : no matching overloaded function found
ERROR: 0:7: '=' : dimension mismatch
ERROR: 0:7: 'assign' : cannot convert from 'const mediump float' to 'Position highp 4-component vector of float'

 vecなんて関数は知らんというわけである。もちろん直接定義すれば可能。glsl内で関数を定義する方法については割愛する。vec4とちゃんと書かないと駄目というわけ。

 フラグメントシェーダのケース。

fs
#version 300 es

out vec4 fragColor;
void main(){
  fragColor = vec4(0.0, 0.5, 1.0, 1.0);
}

 precision宣言をまるごとカット。結果はこちら:

fragment shaderの作成に失敗しました main.js:46:13
ERROR: 0:3: '' : No precision specified for (float)
main.js:47:13
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:47
    (非同期: EventListener.handleEvent)
    <匿名> file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
programのlinkに失敗しました main.js:59:13
Uncaught ReferenceError: program is not defined
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:60
    EventListener.handleEvent* file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
main.js:60:40
    loadSketch0 file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:60
    (非同期: EventListener.handleEvent)
    <匿名> file:///C:/Users/sea_g/OneDrive/ドキュメント/fisce.net/webGL/code/code0/main.js:70
WebGL warning: linkProgram: Must have a compiled fragment shader attached:
SHADER_INFO_LOG:
ERROR: 0:3: '' : No precision specified for (float)

precisionの宣言が無いことをダイレクトに注意される。floatについて宣言してください、と親切に回答をくれる。ありがたい。

プログラムの作成

 シェーダーコンパイルに成功したら次はプログラムを作る。リンクという作業で、vsとfsをつなげて一つのプログラムを完成させる。

  // 5. program
  const pg = gl.createProgram();

  // 6. attach and linking
  gl.attachShader(pg, vsShader);
  gl.attachShader(pg, fsShader);
  gl.linkProgram(pg);

  if(!gl.getProgramParameter(pg, gl.LINK_STATUS)){
    console.log("programのlinkに失敗しました");
    console.error(gl.getProgramInfoLog(program));
  }

 createProgramでプログラムオブジェクトを作り、attachShaderでシェーダーを当てはめる。順番は自由。シェーダーオブジェクトは自分がvsかfsかを知っているので機構が勝手に判断する。あとはlinkProgramするとそれらがつながれてプログラムが完成する。
 この場合もエラーを調べることができてgetProgramParameterという。リンクステータスを調べるとリンクに失敗したかどうかを判定できる。getProgramInfoLogを使って内容を知れる。vsとfsの双方がコンパイルされていれば問題ないかというとそうでもないが、ここでは触れない。

ドローコール

 プログラムができたのでuseProgramで起動させよう。

  // 7. drawCall
  gl.useProgram(pg);

  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  gl.flush();

 今回はドローコール以外何もしないのでドローコールするだけである。さっき説明したTRIANGLE_STRIPと0と4で描画する。最後におまじないのflush. お疲れ様でした。

おわりに

 おわりです。

gl定数について

 gl.VERTEX_SHADERやgl.FRAGMENT_SHADERが出てきたが、これらは定数である。実は数で、console.logで値を調べることができる。しかし通常は数で運用することはないだろう。裏技的にその数そのものを利用したコードを書くこともあるにはある。コンテキストが無くてもアクセスしたい場合はそういう方法に頼るかもしれない。

  console.log(gl.VERTEX_SHADER); // 35633
  console.log(gl.FRAGMENT_SHADER); // 35632

fsの方が小さいのは意外ですね。

今回の登場人物

gl.createShader リンク:gl.createShader
構文:createShader(type)
引数:typeはgl.VERTEX_SHADERもしくはgl.FRAGMENT_SHADER
返値:WebGLShaderオブジェクト。
gl.shaderSource リンク:gl.shaderSource
構文:shaderSource(shader, source)
引数:shaderはWebGLShaderオブジェクト、sourceはシェーダー文字列。
返値:ありません
gl.compileShader リンク:gl.compileShader
構文:compileShader(shader)
引数:WebGLShaderオブジェクト
返値:なし
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?