Kuroyagi飼育日誌

学んだことの備忘録

WebGLによる2Dグラフ描画【その1】

グラフ描画アプリ作成の第一歩としてグラフ描画部分について学びます。


2Dのグラフ描画なのにWebGLを使います。WebGLと言えば3Dの高速・美麗な描画ですが、2Dでもその高速な描画は活きてきます。ちなみに、WebGLでつくられた3Dがどんなものかということが分かる例は以下の記事を覗いてみるといいかもしれません。
 
 
 
【記事1】
liginc.co.jp
 
 
 
先ずはWebGLとな何かについては以下の記事を見ていくと分かると思います。


【記事2】
webglfundamentals.org



【記事3】
wgld.org



ただし、これを読んだからといって直ぐにグラフアプリが作れるかと言われれば私には無理です。


と言うことで、実例と基礎を行ったりきたりして動くものを自分で作れるようになるために以下記事を参考としました。



【記事4】
qiita.com



【記事5】
wordpress.notargs.com



【記事6】
www.youtube.com



【記事7】
github.com



【記事8】
qiita.com



【記事9】
qiita.com
 
 
 
【記事10】
medium.com



このあたりを見比べて作ったのが以下のコードです。


フォルダ内は以下3つのファイルだけでとりあえず動きます。


ソースコード1:index.html】

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>WebGL</title>
  <meta name="Description" content="" />
  <meta name="Keywords"  content="" />

  <link rel="stylesheet" type="text/css" media="screen,print" href="style.css" />


  <script id="vs" type="x-shader/x-vertex">
  attribute vec3 position;

  void main()
  {
    gl_Position = vec4(position, 1.0);
    gl_PointSize = 5.0;
  }
</script>


<script id="fs" type="x-shader/x-fragment">
precision mediump float;

void main()
{
  gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);
}
</script>

<script type="text/javascript" src="index.js"></script>
</head>



<body>
  <canvas id="sin"></canvas>
</body>

</html>

 
 
 
ソースコード2:index.js】

var c, gl;

function initWebGL() {
  c = document.getElementById("sin");
  c.width = 200;
  c.height = 200;
  gl = c.getContext('webgl') || c.getContext('experimental-webgl');
}

function initShaders() {

  var p = gl.createProgram();

  var v = document.getElementById("vs").textContent;
  var f = document.getElementById("fs").textContent;

  var vs = gl.createShader(gl.VERTEX_SHADER);
  var fs = gl.createShader(gl.FRAGMENT_SHADER);

  gl.shaderSource(vs, v);
  gl.shaderSource(fs, f);
  gl.compileShader(vs);
  gl.compileShader(fs);
  gl.attachShader(p, vs);
  gl.attachShader(p, fs);
  gl.linkProgram(p);
  gl.useProgram(p);
  gl.bindAttribLocation(p, 0, "position");
  gl.enableVertexAttribArray(0);
}

function draw(t) {
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  var data = [];
  var MAX = 20;

  // データ列の生成
  for ( var i = 0; i <= MAX; i++ ) {
    var x = 0.9 * (2 * i / MAX - 1.0);
    var y = 0.9 * (Math.sin(2 * Math.PI * i / MAX + t * Math.PI));
    data = data.concat([x, y, 0.0]);
  }

  // 空バッファオブジェクト生成
  gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());


  // シェーダー側の変数をjs側で受け取る
  gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);

  // 描画
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  gl.drawArrays(gl.LINE_STRIP, 0, data.length / 3);
  gl.drawArrays(gl.POINTS, 0, data.length / 3);

  // 更新
  gl.flush();
}

// 時間を計測開始
var startTime = Date.now();

onload = function(){
  initWebGL();
  initShaders();


  setInterval(function(){
    // 経過時間を取得
    var totalTime = (Date.now() - startTime) / 10000.0;
    draw(totalTime);
  }, 10);
}




ソースコード3: style.css

* {
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
}

body {
  background: #fff;
  font: 30px sans-serif;
}

index.htmlを起動するとこんな感じになります。
 
 
 
【画像1】
f:id:cocosuzu:20171207210334p:plain
 
 
 
うねうね動きます。上記ファイルではhtml中でscriptとして頂点シェーダとフラグメントシェーダのソース定義しています。html中にあまりごちゃごちゃ書きたくないので、javascripファイルのほうに詰め込もうと【記事10】にあるいかの記述をそのまま使えば良いかなと試してみたのですが、うまく動きませんでした。


ソースコード4: 記事10の参考】

var fSource = [
    “precision mediump float;”,
    “void main(void) {“,
    “gl_FragColor = vec4(“+ rgba.join(“,”) +”);”,
    “}].join(“\n”)


ちなみにこんな感じで書いてみました。


ソースコード5: vs】

  var vs = [
      "attribute vec3 position;",
      "void main(void) {",
          "gl_Position = vec4(position, 1.0);",
      "}"
  ].join("\n");

 
 
 
ソースコード6: fs】

  var fs = [
      "precision mediump float;",
      "void main(void) {",
          "  gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);",
      "}"
  ].join("\n");




これをもともとのjsファイル中のvの代わりにおいてhtmlファイル中の該当箇所を削除してみると上手くいきません。彷徨っていると良い記事を見つけました。
 
 
 
【記事11】
qiita.com
 
 
 
Jadeとか良く分からないのでパス。
 
 
 
【記事12】
WebGL シェーダをどこに置くか? - code snippets



色々とみて、最終的にこんな感じになりました。ちなみにhtmlとjs中でエレメントのidをsinからcanvasに変更しました。



ソースコード7】

var c, gl;

function initWebGL() {
  c = document.getElementById("canvas");
  c.width = 200;
  c.height = 200;
  gl = c.getContext('webgl') || c.getContext('experimental-webgl');
}

function initShaders() {
  // programオブジェクトを作成
  var p = gl.createProgram();

  // シェーダーを作成
  var vs = gl.createShader(gl.VERTEX_SHADER);
  var fs = gl.createShader(gl.FRAGMENT_SHADER);

  // シェーダーのソースコードを定義
  var vSource =
  'attribute vec3 position;' +
  'void main(void) {' +
  'gl_Position = vec4(position, 1.0);' +
  'gl_PointSize = 5.0;' +
  '}';

  var fSource =
  'precision mediump float;'+
  'void main(void) {' +
  'gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);' +
  '}';

  // シェーダーにソースコードをセット
  gl.shaderSource(vs, vSource);
  gl.shaderSource(fs, fSource);

  // コンパイル
  gl.compileShader(vs);
  gl.compileShader(fs);

  // programオブジェクトのシェーダーを設定
  gl.attachShader(p, vs);
  gl.attachShader(p, fs);

  // programのリンク
  gl.linkProgram(p);

  // programの割り当て
  gl.useProgram(p);
  gl.bindAttribLocation(p, 0, "position");
  gl.enableVertexAttribArray(0);

}

function draw_line(t) {
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  var data = [];
  var MAX = 20;

  // データ列の生成
  for ( var i = 0; i <= MAX; i++ ) {
    var x = 0.9 * (2 * i / MAX - 1.0);
    var y = 0.9 * (Math.sin(2 * Math.PI * i / MAX + t * Math.PI));
    data = data.concat([x, y, 0.0]);
  }

  // 空バッファオブジェクト生成
  gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());

  // シェーダー側の変数をjs側で受け取る
  gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);

  // データ列描画
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
  gl.drawArrays(gl.LINE_STRIP, 0, data.length / 3);
  gl.drawArrays(gl.POINTS, 0, data.length / 3);

  // 更新
  gl.flush();
}

// 時間を計測開始
var startTime = Date.now();

onload = function(){
  initWebGL();
  initShaders();

  setInterval(function(){
    // 経過時間を取得
    var totalTime = (Date.now() - startTime) / 10000.0;
    draw_line(totalTime);
  }, 10);
}




これでとりあえず動くようになりました。



ここから目盛を追記しようと思ったのですが、基礎知識が足りないので複数のモデルを描画する方法がよくわかっていません。



次回は二つのモデルを描画する方法について簡単なものを作ってみたいと思います。