目次
はじめに
「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ジオメトリ
- カメラ
- テクスチャ
- マテリアル
- ライト