AURA 株式会社アウラ

RECRUIT急募!採用エントリー
Three.jsでシェーダー言語を学ぶ 〜バッファーオブジェクト前編〜

スタッフブログ

Staff Blog
  1. ホームページ制作はアウラ:ホーム
  2. スタッフブログ
  3. Three.jsでシェーダー言語を学ぶ 〜バッファーオブジェクト前編〜

Three.jsでシェーダー言語を学ぶ 〜バッファーオブジェクト前編〜

Three.jsでシェーダー言語を学ぶ 〜バッファーオブジェクト前編〜

前回ブログで、WebGLを使ったスライドアニメーションの表現をご紹介しました。

https://www.aura-office.co.jp/blog/webgl1/

この時に紹介したスライドアニメーションもですが、やはりWebGLを使うならシェーダー言語をもっと知らなければいけないと感じ、今回から自身の学びも兼ねて、シェーダー言語の書き方をブログにしていきたいと思います。

シェーダーを書くにあたってWebGLを何もない状態から実装していくのはハードルが高すぎるので、難しいところはThree.jsに任せてしまいます。

今回はシェーダー言語で利用するための頂点データを任意で定義することができるバッファーオブジェクトを使って三角形の描画をしてみたいと思います。今回は実際にシェーダーを書くところまではいきませんが、その前に知っておきたい重要な部分になります。

セットアップ 〜レンダラー、カメラ、シーンの用意〜

まずは三角形のオブジェクトを描画するための土台作りから。
ページの読み込み完了後にレンダラー、カメラ、シーンの初期設定をする関数を実行します。

// ページの読み込み完了後に実行
window.addEventListener('load', function() {
  start();
}

// Three.jsの実行
function start() {
  // レンダラー、シーンの初期化
  initThree();
  // カメラ初期化
  initCamera();
}

レンダラー、シーンの初期化関数

レンダラーとシーンの初期化をする関数です。
renderer, scene, canvasFrameはグローバルで宣言しておきます。

なお、最終的にキャンバス要素に描画するためにはrendererオブジェクトのrender()メソッドを実行する必要がありますが、これは後述するループ処理関数の中に記述します。

var renderer,    // レンダラー
    scene,       // シーン
    canvasFrame; // キャンバス要素

function init() {
  // キャンバス要素のコンテナ要素
  canvasFrame = document.getElementById('frace');
  // レンダラー
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  // レンダラーサイズ
  renderer.setSize(canvasFrame.clientWidth, canvasFrame.clientHeight);
  // DOMにキャンバス要素を追加
  canvasFrame.appendChild(renderer.domElement);
 
  // クリアカラー
  renderer.setClearColor(0xcccccc, 1.0);

  // シーンオブジェクト作成
  scene = new THREE.Scene();
}

カメラの初期化関数

続いてカメラの初期化関数です。
マウス操作でカメラの位置や角度、ズームを操作できるようにThree.jsのソースコードに収録されている「TrackballControls.js」を利用します。

var camera;

function() {
  // カメラオブジェクトの作成
  camera = new THREE.PerspectiveCamera(45, canvasFrame.clientWidth/canvasFrame.clientHeight, 1, 10000);
  camera.position.set(100, 100, 150);
  camera.up.set(0, 0, 1);
  camera.lookAt({x:0, y:0, z:0});

  // Trackballオブジェクトの作成
  trackball = new THREE.TrackballControls(camera, canvasFrame);
  trackball.screen.width = canvasFrame.clientWidth;
  trackball.screen.height = canvasFrame.clientHeight;
  trackball.screen.offsetLeft = canvasFrame.getBoundingClientRect().left;
  trackball.screen.offsetTop = canvasFrame.getBoundingClientRect().top;

  // Trackballの回転
  trackball.noRotate = false;
  trackball.rotateSpeed = 4.0;

  // Trackballの拡大・縮小
  trackball.noZoom = false;
  trackball.zoomSpeed = 4.0;

  // Trackballの操作の慣性
  trackball.staticMoving = true;
  trackball.dynamicDampingFactor = 0.1
}

ループ処理で描画をアニメーションさせる

カメラの初期化でマウス操作ができるようにTrackballControlsライブラリを使いました。
ですが、スクリプトにはまだループ処理が書かれていないので実際にマウス操作をしても実際の描画には反映されていません。ループ処理の関数を追加します。

function loop() {
  // Trackballの更新
  trackbal.update();
  
  // Canvas要素に描画
  renderer.render(scene, camera);
  
  requestAnimationFrame(loop);
}

start()関数にループ処理を追記します。ループ処理関数は最後に実行するようにしましょう。

// Three.jsの実行
function start() {
  // レンダラー、シーンの初期化
  initThree();
  // カメラ初期化
  initCamera();
  // ループ処理
  loop();
}

三角形オブジェクトの描画

それでは今回のメインとなる三角形オブジェクトを追加してきます。まずは関数全体のコードです。

var triangles;
function initObject() {
  // 三角形の描画数
  var n = 5000;
  var geometry = new THREE.BufferGeometry();
  var positions = new Float32Array( n * 3 * 3 ); // 頂点座標
  var colors = new Float32Array( n * 3 * 3 ); // 頂点色
  
  // 描画範囲
  var L = 100;
  for (var i=0; i<positions.length; i+=9) {
    for(var j=0; j<9; j+=3) {
      if (j === 0 ) {
        var x = L * Math.random() - L/2;
        var y = L * Math.random() - L/2;
        var z = L * Math.random() - L/2;
      } else {
        var l = 10;
        var x = positions[i] + (l * (Math.random() - 0.5));
        var y = positions[i+1] + (l * (Math.random() - 0.5));
        var z = positions[i+2] + (l * (Math.random() - 0.5));
      }

      positions[i+j]   = x;
      positions[i+j+1] = y;
      positions[i+j+2] = z;

      var R = (Math.abs(positions[i]) / (L/2));
      var G = (Math.abs(positions[i+1]) / (L/2));
      var B = (Math.abs(positions[i+2]) / (L/2));

      var color = new THREE.Color().setRGB(R,G,B);
      colors[i]     = color.r;
      colors[i+j+1] = color.g;
      colors[i+j+2] = color.b;
    }
  }

  // Set Attribute
  geometry.setAttribute( 'position', new THREE.BufferAttribute(positions, 3));
  geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3));

  var material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: THREE.VertexColors});
  triangles = new THREE.Mesh(geometry, material);
  scene.add(triangles);
}

上記のソースコードを部分毎に見ていきたいと思います。

// 三角形の描画数
var n = 5000;
var geometry = new THREE.BufferGeometry();
var positions = new Float32Array( n * 3 * 3 ); // 頂点座標
var colors = new Float32Array( n * 3 * 3 ); // 頂点色

nは三角形の描画数を指定します。
バッファーオブジェクトを利用するためにTHREE.BufferGeometryクラスを使います。
バッファーオブジェクトは任意の頂点データを定義することができるため、Three.jsで用意されている直方体や球体などの標準の形状オブジェクトでは表現できない複雑なオブジェクトを描画することが可能になります。

positionscolorsはそれぞれ三角形の頂点の座標と頂点色を格納するための32ビット浮動小数点型の配列です。

三角形は3つの頂点で構成される図形です。また1つの頂点を定義するためにはX、Y、Zの3つの座標が必要となります。
そのため必要な配列の格納数は「三角形の数 × 3 × 3」となります。

// 描画範囲
var L = 100;
for (var i=0; i<positions.length; i+=9) {
  for(var j=0; j<9; j+=3) {
    if (j === 0 ) {
      var x = L * Math.random() - L/2;
      var y = L * Math.random() - L/2;
      var z = L * Math.random() - L/2;
    } else {
      var l = 10;
      var x = positions[i] + (l * (Math.random() - 0.5));
      var y = positions[i+1] + (l * (Math.random() - 0.5));
      var z = positions[i+2] + (l * (Math.random() - 0.5));
    }

    positions[i+j]   = x;
    positions[i+j+1] = y;
    positions[i+j+2] = z;

    var R = (Math.abs(positions[i]) / (L/2));
    var G = (Math.abs(positions[i+1]) / (L/2));
    var B = (Math.abs(positions[i+2]) / (L/2));

    var color = new THREE.Color().setRGB(R,G,B);
    colors[i]     = color.r;
    colors[i+j+1] = color.g;
    colors[i+j+2] = color.b;
  }
}

Lは三角形の描画範囲を指定します。

forのループ処理の中ではそれぞれの頂点の座標と色を決めています。
for (var i=0; i<positions.length; i+=9) では1つの三角形の必要な9つの座標ごとにループを回します。
さらにその中で for (var j=0; j<9; j+=3)   で1つの頂点に必要な3つのX、Y、Z座標ごとにループを回します。

下の部分では三角形の最初の1頂点を基準とし、残り2点の座標を決めています。

if (j === 0 ) {
  var x = L * Math.random() - L/2;
  var y = L * Math.random() - L/2;
  var z = L * Math.random() - L/2;
} else {
  var l = 10; //三角形のサイズ
  var x = positions[i] + (l * (Math.random() - 0.5));
  var y = positions[i+1] + (l * (Math.random() - 0.5));
  var z = positions[i+2] + (l * (Math.random() - 0.5));
}

positions[i+j]   = x;
positions[i+j+1] = y;
positions[i+j+2] = z;

頂点色はぞれぞれ頂点座標の位置によって色を決めています。

  • X座標の絶対値 = R(赤)
  • y座標の絶対値 = G(緑)
  • Z座標の絶対値 = B(青)

頂点間の色は線形補完されるため、三角形はグラデーションがかった色になります。

var R = (Math.abs(positions[i]) / (L/2));
var G = (Math.abs(positions[i+1]) / (L/2));
var B = (Math.abs(positions[i+2]) / (L/2));

var color = new THREE.Color().setRGB(R,G,B);
colors[i]     = color.r;
colors[i+j+1] = color.g;
colors[i+j+2] = color.b;

上記で用意できた三角形の頂点座標と頂点色をバッファーオブジェクトに渡します。
座標と色情報を渡すにはsetAttributeメソッドを使います。

// Set Attribute
geometry.setAttribute( 'position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3));

THREE.MeshBasicMaterialで三角形オブジェクトの材質を決めます。
side: THREE.DoubleSideは三角形の表と裏の両面を描画する指定です。
vertexColors: trueは頂点色を有効化する指定です。

var material = new THREE.MeshBasicMaterial({
  side: THREE.DoubleSide,
  vertexColors: THREE.VertexColors
});

geometry(形状)とmaterial(材質)が決まったらTHREE.Meshに渡してオブジェクトを生成し、シーンに追加します。

triangles = new THREE.Mesh(geometry, material);
scene.add(triangles);

最後にオブジェクトの描画用にinitObject()関数を用意し,start()関数内で実行するようにします。

// Three.jsの実行
function start() {
  // レンダラー、シーンの初期化
  initThree();
  // カメラ初期化
  initCamera();
  // オブジェクト初期化
  initObject();
  // ループ処理
  loop();
}

出来上がり

バッファーオブジェクトを利用することで大量の三角形を描画することができました。
今回はシェーダー言語を扱うまでいきませんでしたが、次回以降紹介できたらと思います。

参考書籍

今回ご紹介した内容は「three.jsによるHTML5 3Dグラフィックス[改訂版] 下」を参考にさせていただきました。
Three.jsを用いて3Dグラフィックスの実装方法を学べる良書です。

お電話でのお問い合わせはこちら:06-6292-8577。受付時間は平日9:30~18:30 インターネットからは24時間受付中!お問い合わせフォームはこちら
RECRUIT急募!採用エントリー