LoginSignup
6
4

More than 5 years have passed since last update.

webgl2.0でGPGPUする

Posted at

概要

webgl2.0からtransformFeedbackが利用できるようになったので、GPUに計算させた結果をjavascript側に戻すことができます。
これを利用して、最近話題になっているGPGPUをすることができます。

まぁwebgl1.0の場合でもgl.ReadPixelsでfragmentShaderの計算結果を戻すことはできますけどね。

非常に簡単なサンプル

とりあえず簡単なサンプルはこんな感じです。

index.html
<!DOCTYPE html>
<html style="width:100%;height:100%">
  <head>
    <meta charset="UTF-8" />
    <title>bsTest</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <div id="result"></div>
    <script type="text/vertex-shader" id="vs">#version 300 es
out float result;

void main() {
  result = 1.0 + 1.0;
}
    </script>
    <script type="text/fragment-shader" id="fs">#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
  outColor = vec4(1.0);
}
    </script>
    <script type="text/javascript">
window.onload = () => {
  var canvas = document.getElementById("canvas");
  var result = document.getElementById("result");

  var gl2 = canvas.getContext("webgl2");
  if(!gl2) {
    throw new Error("webgl2.0 is not supported.");
  }
  // vertex shader
  var vs = gl2.createShader(gl2.VERTEX_SHADER);
  gl2.shaderSource(vs, document.getElementById("vs").innerText);
  gl2.compileShader(vs);
  if(!gl2.getShaderParameter(vs, gl2.COMPILE_STATUS)) {
    throw new Error("failed to make shader.");
  }
  // fragment shader
  var fs =gl2.createShader(gl2.FRAGMENT_SHADER);
  gl2.shaderSource(fs, document.getElementById("fs").innerText);
  gl2.compileShader(fs);
  if(!gl2.getShaderParameter(fs, gl2.COMPILE_STATUS)) {
    throw new Error("failed to make shader.");
  }
  // programつくる
  var program = gl2.createProgram();
  gl2.attachShader(program, vs);
  gl2.attachShader(program, fs);
  gl2.transformFeedbackVaryings(program, ["result"], gl2.SEPARATE_ATTRIBS);
  gl2.linkProgram(program);

  if(!gl2.getProgramParameter(program, gl2.LINK_STATUS)) {
    throw new Error("failed to make program.");
  }
  gl2.useProgram(program);
  var vTransformFeedback = gl2.createBuffer();
  var transformFeedback = gl2.createTransformFeedback();
  gl2.bindBuffer(gl2.ARRAY_BUFFER, vTransformFeedback);
  gl2.bufferData(gl2.ARRAY_BUFFER, Float32Array.BYTES_PER_ELEMENT, gl2.DYNAMIC_COPY);
  gl2.bindBuffer(gl2.ARRAY_BUFFER, null);

  // あとは描画を実施する
  gl2.bindTransformFeedback(gl2.TRANSFORM_FEEDBACK, transformFeedback);
  gl2.bindBufferBase(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, vTransformFeedback);
  gl2.beginTransformFeedback(gl2.POINTS);
  gl2.drawArrays(gl2.POINTS, 0, 1);
  gl2.endTransformFeedback();

  // 結果を取り出す
  var array = new Float32Array(1);
  gl2.getBufferSubData(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, array);
  console.log(array);
  result.innerText = "ok:" + array[0];
}
    </script>
  </body>
</html>

とりあえずこのコードでは、GPUに1.0 + 1.0を計算させてます。
もちろん答えは2.0になります。

解説

  1. vertex演算のコードを作成し、GPUに託す
  2. fragment演算のコードを作成し、GPUに託す
  3. 情報データを準備してGPUに渡す
  4. 実際の描画を実施する(普通はここで描画する)
  5. 描画での計算結果をtransformFeedbackでjs側に戻す

という動作をします。

vertex shader

    <script type="text/vertex-shader" id="vs">#version 300 es
out float result;

void main() {
  result = 1.0 + 1.0;
}
    </script>

main関数の中身が実施されるもの。
out float resultで出力で利用する変数を定義
ということをやってます。
今回は1.0 + 1.0を計算してますが

void main() {
  result = 2.0 * 3.0;
}

とかにすると2 x 3を計算させることができます。

fragment shader

    <script type="text/fragment-shader" id="fs">#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
  outColor = vec4(1.0);
}
    </script>

実際の色を出力するときに利用するプログラムです。
一応定義しますが、今回は利用しないので、どうでもいいです。
とりあえずrgba = (1.0, 1.0, 1.0, 1.0);真っ白にしてあります。

下準備

  var canvas = document.getElementById("canvas");
  var result = document.getElementById("result");

  var gl2 = canvas.getContext("webgl2");
  if(!gl2) {
    throw new Error("webgl2.0 is not supported.");
  }
  // vertex shader
  var vs = gl2.createShader(gl2.VERTEX_SHADER);
  gl2.shaderSource(vs, document.getElementById("vs").innerText);
  gl2.compileShader(vs);
  if(!gl2.getShaderParameter(vs, gl2.COMPILE_STATUS)) {
    throw new Error("failed to make shader.");
  }
  // fragment shader
  var fs =gl2.createShader(gl2.FRAGMENT_SHADER);
  gl2.shaderSource(fs, document.getElementById("fs").innerText);
  gl2.compileShader(fs);
  if(!gl2.getShaderParameter(fs, gl2.COMPILE_STATUS)) {
    throw new Error("failed to make shader.");
  }
  // programつくる
  var program = gl2.createProgram();
  gl2.attachShader(program, vs);
  gl2.attachShader(program, fs);
  gl2.transformFeedbackVaryings(program, ["result"], gl2.SEPARATE_ATTRIBS);
  gl2.linkProgram(program);

  if(!gl2.getProgramParameter(program, gl2.LINK_STATUS)) {
    throw new Error("failed to make program.");
  }
  gl2.useProgram(program);
  var vTransformFeedback = gl2.createBuffer();
  var transformFeedback = gl2.createTransformFeedback();
  gl2.bindBuffer(gl2.ARRAY_BUFFER, vTransformFeedback);
  gl2.bufferData(gl2.ARRAY_BUFFER, Float32Array.BYTES_PER_ELEMENT, gl2.DYNAMIC_COPY);
  gl2.bindBuffer(gl2.ARRAY_BUFFER, null);

これらはwebgl2.0の下準備処理です。
コンテキストをcanvasから取得して
vertexとfragmentのshaderプログラムを作成 -> プログラムソース設定 -> コンパイル -> 処理確認として
動画プログラムを2つのshaderからつくって
transformFeedbackを準備するということをやってます。

実際に計算させる。

  // あとは描画を実施する
  gl2.bindTransformFeedback(gl2.TRANSFORM_FEEDBACK, transformFeedback);
  gl2.bindBufferBase(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, vTransformFeedback);
  gl2.beginTransformFeedback(gl2.POINTS);
  gl2.drawArrays(gl2.POINTS, 0, 1);
  gl2.endTransformFeedback();

  // 結果を取り出す
  var array = new Float32Array(1);
  gl2.getBufferSubData(gl2.TRANSFORM_FEEDBACK_BUFFER, 0, array);
  console.log(array);
  result.innerText = "ok:" + array[0];

普通のwebglの場合はrequestAnimationFrameやsetIntervalの処理の中で実施するwebglの描画処理の部分です。
今回はいきなりtransformFeedbackを発動させてdrawArraysで描画を1つの頂点についてのみ実施しています。

これでとりあえずGPUに計算を実施させてその結果をjavascript側(CPU側)で取得できました。

6
4
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
6
4