p5.js WebGL入門 1 スタート

はじめに

p5.jsで始めるWebGL」でまとめているように、p5.jsでは、WebGL機能を使って、p5.jsのキャンバスで3D表現を行うことができます。この入門シリーズでは、「18.1: Introduction to WebGL in p5.js – WebGL and p5.js Tutorial」に始まる8つのYouTube動画を参考に、p5.jsでの3D表現を見ていきます。

WebGLとは

WebGLのGLはGraphics Libraryの略で、Webブラウザでの3D表現に利用できる標準仕様のことです。WebGLはOpenGLという仕様のブラウザ版であり、コンピュータに備わったGPU(リアルタイムで画像を処理するためのハードウェア、グラフィックカード)にアクセスできるので、Webページのキャンバス描画が高速で行えます。

素のWebGL JavaScriptコード

WebGLで使用できるAPI(JavaScriptコード)はいわゆるローレベルなので、決してやさしくはありません。たとえば「mdn / webgl-examples」ページで公開されているコードを見ると、それが実感できます。

<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <title>WebGL</title>
</head>

<body>
  <!-- https://github.com/mdn/webgl-examples/tree/gh-pages/tutorial/sample2の例 -->
  <canvas id="glcanvas" width="400" height="300">
    canvas要素に対応したブラウザを使用してください。
  </canvas>
  <script src="lib/sylvester.js"></script>
  <script src="lib/glUtils.js"></script>

  <!-- フラグメントシェーダー ピクセルの色を決める-->
  <script id="shader-fs" type="x-shader/x-fragment">
    void main(void) { gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); // 青 }
  </script>
  <!-- OpenGL ES Shading Language -->
  <!-- バーテックスシェーダー 各頂点の位置や形状を定義する-->
  <script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); }
  </script>
  <script>
    // WebGL コンテキスト
    var gl = null;

    var squareVerticesBuffer = null;
    var mvMatrix = null;
    var shaderProgram = null;
    var vertexPositionAttribute = null;
    var perspectiveMatrix = null;

    function init() {
      var canvas = document.getElementById("glcanvas");

      // GL コンテキストを初期化
      gl = initWebGL(canvas);

      // WebGL を使用できる場合に限り、処理を継続
      if (gl) {
        // クリアカラーを黒色、不透明に設定する
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        // 深度テストを有効化
        gl.enable(gl.DEPTH_TEST);
        // 近くにある物体は、遠くにある物体を覆い隠す
        gl.depthFunc(gl.LEQUAL);
        // カラーバッファや深度バッファをクリアする
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      }
      initShaders();
      initBuffers();
      drawScene();

    }

    function initWebGL(canvas) {
      gl = null;
      try {
        // 標準コンテキストの取得を試みる。失敗した場合は、experimental にフォールバックする。
        // 2016 年現在、ChromeやFirefoxなどではexperimental の記述は不要。
        // ただし一部のモバイルブラウザやIE11 等の場合依然としてこの対応が必要となる
        gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
      }
      catch (error) {
        console.log(error);
      }

      // GL コンテキストを取得できない場合は終了する
      if (!gl) {
        alert("WebGL を初期化できません。ブラウザはサポートしていないようです。");
        gl = null;
      }
      return gl;
    }

    //-------------------- シェーダー -------------------------------->
    // シェーダーを初期化
    function initShaders() {
      // シェーダープログラムを読み込む
      var fragmentShader = getShader(gl, "shader-fs");
      var vertexShader = getShader(gl, "shader-vs");

      // シェーダープログラムを作成
      shaderProgram = gl.createProgram();
      // シェーダープログラムとバーテックスシェーダーを関係づける
      gl.attachShader(shaderProgram, vertexShader);
      // シェーダープログラムとフラグメントシェーダーを関係づける
      gl.attachShader(shaderProgram, fragmentShader);
      // シェーダープログラムとリンク
      gl.linkProgram(shaderProgram);

      // シェーダープログラムが正常にリンクされたことを確認する
      if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert("シェーダープログラムを初期化できません。");
      }
      // シェーダープログラムを有効化する
      gl.useProgram(shaderProgram);

      vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
      gl.enableVertexAttribArray(vertexPositionAttribute);
    }

    // 指定されたシェーダープログラムをDOMから取り出してコンパイルしたシェーダープログラムを返すか、
    // シェーダーの読み込みやコンパイルができなかった場合には null を返す。
    function getShader(gl, id) {
        var shaderScript, theSource, currentChild, shader;
        shaderScript = document.getElementById(id);
        if (!shaderScript) {
          return null;
        }
        theSource = "";
        currentChild = shaderScript.firstChild;

        // 指定したIDの要素が見つかった場合は、そのテキストを変数theSourceに格納
        while (currentChild) {
          if (currentChild.nodeType == currentChild.TEXT_NODE) {
            theSource += currentChild.textContent;
          }
          currentChild = currentChild.nextSibling;
        }

        // タイプ(フラグメントかバーテックスか)に応じて、シェーダーを作成
        if (shaderScript.type == "x-shader/x-fragment") {
          shader = gl.createShader(gl.FRAGMENT_SHADER);
        }
        else if (shaderScript.type == "x-shader/x-vertex") {
          shader = gl.createShader(gl.VERTEX_SHADER);
        }
        else {
          // 未知のシェーダータイプ
          return null;
        }
        // シェーダーにソースコードを渡す
        gl.shaderSource(shader, theSource);

        // シェーダーをコンパイル
        gl.compileShader(shader);

        // コンパイルが成功したかを確認
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
          alert("シェーダーのコンパイルでエラーが発生しました: " + gl.getShaderInfoLog(shader));
          return null;
        }
        return shader;
      }
      //<-------------------- シェーダー --------------------------------

    // 正方形の頂点情報を作成
    function initBuffers() {
      // 頂点情報を保存するバッファを作成
      squareVerticesBuffer = gl.createBuffer();
      // バッファをコンテキストに関連付ける
      gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);

      var vertices = [
        1.0, 1.0, 0.0, -1.0, 1.0, 0.0,
        1.0, -1.0, 0.0, -1.0, -1.0, 0.0
      ];
      // 正方形の頂点情報として設定
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    }

    // シーンを描画
    function drawScene() {
      // コンテキストを背景色でクリア
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      // カメラの遠近感を定義
      perspectiveMatrix = makePerspective(45, 640.0 / 480.0, 0.1, 100.0);
      loadIdentity();
      // 正方形の位置を、初期位置への読み込みおよびカメラから 6 ユニット分遠方へ移動したところに定義
      mvTranslate([-0.0, 0.0, -6.0]);
      //正方形の頂点バッファをコンテキストに結びつけ設定
      gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
      gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
      setMatrixUniforms();
      // オブジェクトを描画
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    }

    function loadIdentity() {
      mvMatrix = Matrix.I(4);
    }

    function multMatrix(m) {
      mvMatrix = mvMatrix.x(m);
    }

    function mvTranslate(v) {
      multMatrix(Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4());
    }

    function setMatrixUniforms() {
      var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
      gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten()));
      var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
      gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten()));
    }

    window.addEventListener("load", init, false);
  </script>
</body>

</html>

この長いコードは、sylvester.jsとglUtils.jsというJavaScriptコードを必要とし、ここまでしても描けるのはただの青い矩形です。

p5.jsのWEBGLモード

しかしp5.jsのWEBGLモードを使用すると、このおぞましいコードの山を全部裏に隠してくれます。

// 座標システムの回転に使用する
let angle = 0;

function setup() {
    // キャンバスをWEBGLモードで作成
    createCanvas(400, 300, WEBGL);
    // ラジアンでなく度単位にする
    angleMode(DEGREES);
    fill(0, 0, 255)
}

function draw() {
    // キャンバスの背景色
    background(0);
    // XYZ軸周りに座標システムを回転
    rotateX(angle);
    rotateY(angle * 1.3);
    rotateZ(angle * 0.7);
    // 立方体を描画
    box(100);
    // 角度をインクリメント
    angle += 1;
}

通常のJavaScriptコードと、WebGLを扱うときのJavaScriptコードはかなり違うものになるのですが、p5.jsでは上記コードから分かるように、通常の2Dモードのときとさほど変わりません。p5.jsではcreateCanvas()関数に定数WEBGLを渡すだけで、容易にWebGLの世界に移行できます。

注意:ただし、JavaScriptで本格的に3Dに取り組みたいと思われる方は、three.jsなど、3Dに特化したJavaScriptライブラリを学ぶのが近道でしょう。

p5.jsのWEBGLモードには、2Dモードとは異なる構成要素があります。

  • 3Dジオメトリ
  • カメラ
  • テクスチャ
  • マテリアル
  • ライト

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA