THREE.JSテスト解説・THREE.JS使い方




自作ライブラリ関連

NAS6LIB

ポリゴンテスト & NAS6LIB ヘルプ ドキュメント.htm

ポリゴンテスト & NAS6LIB ヘルプ ドキュメント.zip

同次座標がw,x,y,z,...の順番で定義されたものの説明です。順番が違うだけで、機能に変わりはありませんが・・・


THREE.JS使い方

THREE.JSテスト.htm

THREE.JSテスト.zip

60FPSテスト.htm

60FPSテスト.zip



・./test3js.htm


<!DOCTYPE html>
<html lang="ja">
<head>
<title>THREEJSテスト</title>
  <meta charset="UTF-8" />
  <script src="./javascripts/nas6lib/timer.js"></script>
  <script src="./javascripts/threejs/three.js"></script>

<script>

window.addEventListener("DOMContentLoaded", init);
var TMan = new N6LTimerMan();  //タイマーマネージャー

function init() {
  const width = 500;
  const height = 250;

  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector("#cnv0")
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    45,
    width / height,
    1,
    10000
  );
  camera.position.set(0, 0, 20);

  // 箱を作成
  const geometry = new THREE.BoxGeometry(5, 5, 5);
  const loader = new THREE.TextureLoader();
  const texture = loader.load('./img/koala.jpg');
  const textureF = loader.load('./img/koalaF.jpg');
  // マテリアルにテクスチャーを設定
  const materials = [
    new THREE.MeshStandardMaterial({map: textureF}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture})
  ];
  // メッシュを作成
  const box = new THREE.Mesh(geometry, materials);
  box.position.set(0, 0, 8);
  scene.add(box);

  // 平行光源
  const light = new THREE.DirectionalLight(0xffffff);
  light.intensity = 2; // 光の強さを倍に
  light.position.set(1, 1, 1);
  // シーンに追加
  scene.add(light);

  TMan.add();
  TMan.timer[0].setalerm(function() { Loop(0); }, 50);  //メインループセット


 
  function Loop(id) {

    box.rotation.x += 1.0 * Math.PI / 180.0;//x軸回りに1度回転
    box.rotation.y += 2.0 * Math.PI / 180.0;//y軸回りに2度回転
    box.rotation.z += 3.0 * Math.PI / 180.0;//z軸回りに3度回転

    // レンダリング
    renderer.render(scene, camera);

    TMan.timer[id].setalerm(function() { Loop(id); }, 50);  //メインループセット
  }

<!--
/*
  // 初回実行
  Loop();

  function Loop() {
    requestAnimationFrame(Loop);

    box.rotation.x += 1.0 * Math.PI / 180.0;//x軸回りに1度回転
    box.rotation.y += 2.0 * Math.PI / 180.0;//y軸回りに2度回転
    box.rotation.z += 3.0 * Math.PI / 180.0;//z軸回りに3度回転

    renderer.render(scene, camera);
  }
*/
//-->

}

</script>

<style>
article, aside, dialog, figure, footer, header,
hgroup, menu, nav, section { display: block; }
#cnv0{
    position: absolute;
    float: left;
    top: 640px;
    left: 20px;
    background:#8080b0;
    border: 2px #000000 solid;
}
</style>

</head>

<body text="black" link="#3333cc" vlink="#663399" alink="#cc0000" bgcolor="#faebf1" background="./img/kumausagineko.jpg">
<font size="3">
<br>
<br>
<b>
<h1>THREEJSテスト</h1>
<br>
<div style = 'width:500px; height:250px; border: 0px; overflow:hidden;'>
  <canvas id="cnv0" name="cnv0" width="500" height="250"></canvas>
</div>
<br>
<br>
<br>
</b>
</font>
<br>
<br>
<hr>
<br>
<br>
<a href="./index.htm">戻る</a><br>
<br>
<br>
</body>
</html>






・ヘッダの書き方



<!DOCTYPE html>
<html lang="ja">
<head>
<title>タイトルを入れてください</title>
  <meta charset="UTF-8" />

// ……… 中 略 ………

</head>



これはほとんどコピペでいいです


・コンポーネントの登録


  <script src="./javascripts/nas6lib/timer.js"></script>
  <script src="./javascripts/threejs/three.js"></script>



three.jsなどライブラリコンポーネントを登録してください


・スタイルシート


<style>
article, aside, dialog, figure, footer, header,
hgroup, menu, nav, section { display: block; }
#cnv0{
    position: absolute;
    float: left;
    top: 640px;
    left: 20px;
    background:#8080b0;
    border: 2px #000000 solid;
}
</style>



適宜スタイルシートをいじってください


・ターゲットキャンバス


<div style = 'width:500px; height:250px; border: 0px; overflow:hidden;'>
  <canvas id="cnv0" name="cnv0" width="500" height="250"></canvas>
</div>



<div>で<canvas>をくくっているのは、レイアウト合わせのためです
<body>タグ内に記述してください


・スクリプト部分



<script>

window.addEventListener("DOMContentLoaded", init);
var TMan = new N6LTimerMan();  //タイマーマネージャー

function init() {
  const width = 500;
  const height = 250;

  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector("#cnv0")
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    45,
    width / height,
    1,
    10000
  );
  camera.position.set(0, 0, 20);

  // 箱を作成
  const geometry = new THREE.BoxGeometry(5, 5, 5);
  const loader = new THREE.TextureLoader();
  const texture = loader.load('./img/koala.jpg');
  const textureF = loader.load('./img/koalaF.jpg');
  // マテリアルにテクスチャーを設定
  const materials = [
    new THREE.MeshStandardMaterial({map: textureF}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture})
  ];
  // メッシュを作成
  const box = new THREE.Mesh(geometry, materials);
  box.position.set(0, 0, 8);
  scene.add(box);

  // 平行光源
  const light = new THREE.DirectionalLight(0xffffff);
  light.intensity = 2; // 光の強さを倍に
  light.position.set(1, 1, 1);
  // シーンに追加
  scene.add(light);

  TMan.add();
  TMan.timer[0].setalerm(function() { Loop(0); }, 50);  //メインループセット


 
  function Loop(id) {

    box.rotation.x += 1.0 * Math.PI / 180.0;//x軸回りに1度回転
    box.rotation.y += 2.0 * Math.PI / 180.0;//y軸回りに2度回転
    box.rotation.z += 3.0 * Math.PI / 180.0;//z軸回りに3度回転

    // レンダリング
    renderer.render(scene, camera);

    TMan.timer[id].setalerm(function() { Loop(id); }, 50);  //メインループセット
  }

<!--
/*
  // 初回実行
  Loop();

  function Loop() {
    requestAnimationFrame(Loop);

    box.rotation.x += 1.0 * Math.PI / 180.0;//x軸回りに1度回転
    box.rotation.y += 2.0 * Math.PI / 180.0;//y軸回りに2度回転
    box.rotation.z += 3.0 * Math.PI / 180.0;//z軸回りに3度回転

    renderer.render(scene, camera);
  }
*/
//-->

}

</script>


3D環境を作ってメッシュを作ってシーンを登録してレンダリングです
通常はコメントアウトされた
requestAnimationFrame(Loop);
がある方のLoop関数のように記述しますが
THREE.JSが60FPSの安定にこだわっているのか
リソースを解放してくれず少しビジーになるので重めです
で、N6LTimerManを利用しています
おらの環境ではrequestAnimationFrameが
cpu使用率が30%でしたが
N6LTimerManでループを回したら
cpu使用率が15%でした


追記
おらの以前のPCでは処理が遅くてrequestAnimationFrameの描画スパンよりもJavaScript+rendering処理が長くなって
パフォーマンスが発揮できていなかったのが原因みたいでした
描画スパンへの時刻合わせをするのとタイマーでベタに詰め込むのがどちらが良いかと考えて
常にJavaScript+rendering処理が描画スパン(16ms)よりも軽いのならばrequestAnimationFrameを普通に使った方が良いのでしょうが
それよりも重い処理がある場合は描画ごとに毎回スリープをかけるよりはタイマーでベタに詰め込んだ方が良いのではないかと思いました

軽い(最適化済みな)プログラムならばrequestAnimationFrameを使い

重いプログラムならばタイマーでベタに詰め込め

がおらは良いような気がします



N6LVector.To3JS(b)
N6LVector.From3JS(ary)
N6LMatrix.To3JS()
N6LMatrix.From3JS(ary)

引数:ary:Array():b:4→3の時、true

N6L←→THREEコンバーターを追加しました


基本的にこの書き方で大体のものは作れると思います





60FPSテスト

<input type='text' id='fps' name='fps' size='16' value='60' readonly> FPS<br>
このタグに1s毎のフレーム数を流し込みます
キャンバスとこのテキストボックスを<div>でくくっておきます

グローバルポジションに
var f = 0;
var dt = Math.floor(1000 / 60);
をtest3js.htmに追加します

TMan.changeinterval(dt / 2); //タイマーチェック間隔変更

TMan.add();
TMan.timer[0].setalerm(function() { Loop(0); }, dt); //メインループセット
TMan.add();
TMan.timer[1].setalerm(function() { FPSLoop(1); }, 1000);

dt / 2 でタイマーチェックして
メインループをdtで回すようにして
FPS用の1sアラームを追加します

function Loop(id) {

f++;

// ……… 中 略 ………

TMan.timer[id].setalerm(function() { Loop(id); }, dt); //メインループセット
}

メインループにフレームカウンタを追加します

function FPSLoop(id) {
txt = document.querySelector("#fps");
txt.value = f;
f = 0;
TMan.timer[id].setalerm(function() { FPSLoop(id); }, 1000);
}

1sアラームはこのように記述してテキストボックスへフレーム数を更新します

・./fps60.htm


<!DOCTYPE html>
<html lang="ja">
<head>
<title>60FPSテスト</title>
  <meta charset="UTF-8" />
  <script src="./javascripts/nas6lib/timer.js"></script>
  <script src="./javascripts/threejs/three.js"></script>

<script>

window.addEventListener("DOMContentLoaded", init);
var TMan = new N6LTimerMan();  //タイマーマネージャー
var f = 0;
var dt = Math.floor(1000 / 60);


function FPSLoop(id) {
   txt = document.querySelector("#fps");
   txt.value = f;
   f = 0;
   TMan.timer[id].setalerm(function() { FPSLoop(id); }, 1000);
}

function init() {
  const width = 500;
  const height = 250;

  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector("#cnv0")
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    45,
    width / height,
    1,
    10000
  );
  camera.position.set(0, 0, 20);

  // 箱を作成
  const geometry = new THREE.BoxGeometry(5, 5, 5);
  const loader = new THREE.TextureLoader();
  const texture = loader.load('./img/koala.jpg');
  const textureF = loader.load('./img/koalaF.jpg');
  // マテリアルにテクスチャーを設定
  const materials = [
    new THREE.MeshStandardMaterial({map: textureF}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture}),
    new THREE.MeshStandardMaterial({map: texture})
  ];
  // メッシュを作成
  const box = new THREE.Mesh(geometry, materials);
  box.position.set(0, 0, 8);
  scene.add(box);

  // 平行光源
  const light = new THREE.DirectionalLight(0xffffff);
  light.intensity = 2; // 光の強さを倍に
  light.position.set(1, 1, 1);
  // シーンに追加
  scene.add(light);


  TMan.changeinterval(dt / 2); //タイマーチェック間隔変更

  TMan.add();
  TMan.timer[0].setalerm(function() { Loop(0); }, dt);  //メインループセット
  TMan.add();
  TMan.timer[1].setalerm(function() { FPSLoop(1); }, 1000);  //1sループセット


 
  function Loop(id) {

    f++;

    box.rotation.x += 1.0 * Math.PI / 180.0;//x軸回りに1度回転
    box.rotation.y += 2.0 * Math.PI / 180.0;//y軸回りに2度回転
    box.rotation.z += 3.0 * Math.PI / 180.0;//z軸回りに3度回転

    // レンダリング
    renderer.render(scene, camera);

    TMan.timer[id].setalerm(function() { Loop(id); }, dt);  //メインループセット
  }

}

</script>

<style>
article, aside, dialog, figure, footer, header,
hgroup, menu, nav, section { display: block; }
#cnv0{
    position: absolute;
    float: left;
    top: 640px;
    left: 20px;
    background:#8080b0;
    border: 2px #000000 solid;
}
</style>

</head>

<body text="black" link="#3333cc" vlink="#663399" alink="#cc0000" bgcolor="#faebf1" background="./img/kumausagineko.jpg">
<font size="3">
<br>
<br>
<b>
<h1>60FPSテスト</h1>
<br>
<div>
<div style = 'width:500px; height:250px; border: 0px; overflow:hidden;'>
  <canvas id="cnv0" name="cnv0" width="500" height="250"></canvas>
</div><br>
<input type='text' id='fps' name='fps' size='16' value='60' readonly> FPS<br>
</div>
<br>
<br>
<br>
</b>
</font>
<br>
<br>
<hr>
<br>
<br>
<a href="./index.htm">戻る</a><br>
<br>
<br>
</body>
</html>



このようになっていればオッケーです





N6LTimerManを用いたjavascriptマルチスレッドテスト.htm

N6LTimerManを用いたjavascriptマルチスレッドテスト.zip




N6LTimerManを用いればjavascript上でマルチスレッドを
簡単に実装できます

・./mttest.htm(スクリプト部分)


window.addEventListener("DOMContentLoaded", init);
var TMan = new N6LTimerMan();  //タイマーマネージャー
var pos = [ new N6LVector(4, true), new N6LVector(4, true), new N6LVector(4, true), new N6LVector(4, true)];
var th = [0, 0, 0, 0];
var dt = [50, 100, 150, 500];
var spd = 5.0;
var cnt = 0;
var div = 72;
var sph3;

function init() {
  const width = 500;
  const height = 250;

  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector("#cnv0")
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    45,
    width / height,
    1,
    10000
  );
  camera.position.set(0, 0, 20);

  // 球を作成
  const sph0geometry = new THREE.SphereGeometry(1, 128, 128);
  // マテリアルに色を設定
  const sph0material = new THREE.MeshBasicMaterial({ color: '#ff0000'});
  // メッシュを作成
  const sph0 = new THREE.Mesh(sph0geometry, sph0material);
  sph0.position.set(0, 6, 0);
  pos[0] = new N6LVector([1, 0, 6, 0], true);
  // 球を作成
  const sph1geometry = new THREE.SphereGeometry(1, 128, 128);
  // マテリアルに色を設定
  const sph1material = new THREE.MeshBasicMaterial({ color: '#00ff00'});
  // メッシュを作成
  const sph1 = new THREE.Mesh(sph1geometry, sph1material);
  sph1.position.set(0, 2, 0);
  pos[1] = new N6LVector([1, 0, 2, 0], true);
  // 球を作成
  const sph2geometry = new THREE.SphereGeometry(1, 128, 128);
  // マテリアルに色を設定
  const sph2material = new THREE.MeshBasicMaterial({ color: '#0000ff'});
  // メッシュを作成
  const sph2 = new THREE.Mesh(sph2geometry, sph2material);
  sph2.position.set(0, -2, 0);
  pos[2] = new N6LVector([1, 0, -2, 0], true);
  // 球を作成
  const sph3geometry = new THREE.SphereGeometry(1, 128, 128);
  // マテリアルに色を設定
  const sph3material = new THREE.MeshBasicMaterial({ color: '#808080'});
  // メッシュを作成
  sph3 = new THREE.Mesh(sph3geometry, sph3material);
  sph3.position.set(0, -6, 0);
  pos[3] = new N6LVector([1, 0, -6, 0], true);
  scene.add(sph0);
  scene.add(sph1);
  scene.add(sph2);
  scene.add(sph3);

  // 平行光源
  const light = new THREE.DirectionalLight(0xffffff);
  light.position.set(1, 1, 1);
  // シーンに追加
  scene.add(light);

  //タイマー作成
  TMan.add();
  TMan.add();
  TMan.add();
  TMan.add();
  TMan.add();
  
  //初回実行
  Loop0(0);
  Loop1(1);
  Loop2(2);
  Loop3(3);
  RDLoop(4);



  function RDLoop(id) {
    
    // レンダリング
    renderer.render(scene, camera);
    TMan.timer[id].setalerm(function() { RDLoop(id); }, 50);
  }
 
  function Loop0(id) {

    th[id] += spd * Math.PI / 180.0;
    sph0.position.set(5 * Math.sin(th[id]) + pos[id].x[1], 6, 0);

    TMan.timer[id].setalerm(function() { Loop0(id); }, dt[id]);
  }
 
  function Loop1(id) {

    th[id] += spd * Math.PI / 180.0;
    sph1.position.set(5 * Math.sin(th[id]) + pos[id].x[1], 2, 0);

    TMan.timer[id].setalerm(function() { Loop1(id); }, dt[id]);
  }
 
  function Loop2(id) {

    th[id] += spd * Math.PI / 180.0;
    sph2.position.set(5 * Math.sin(th[id]) + pos[id].x[1], -2, 0);

    TMan.timer[id].setalerm(function() { Loop2(id); }, dt[id]);
  }

  function Loop3(id) {

    th[id] += spd * Math.PI / 180.0;

    var col1 = new N6LHsv(0, [255, 255, 0, 0]);
    var col2 = new N6LHsv(0, [255, 255, 0, 0]);
    var col = col1.HsvGrd(div, cnt, col2.ahsv, 1);
    var str = col.Str();
    cnt++;

    sph3.material.color.set(str);
    sph3.position.set(5 * Math.sin(th[id]) + pos[id].x[1], -6, 0);

    var c = (Math.cos(th[id]) + 1.0) / 2.0;
    dt[id] = 50  + c * 450; //50~500[ms]の可変タイマー

    TMan.timer[id].setalerm(function() { Loop3(id); }, dt[id]);
  }

}




var TMan = new N6LTimerMan();  //タイマーマネージャー
var pos = [ new N6LVector(4, true), new N6LVector(4, true), new N6LVector(4, true)];
var th = [0, 0, 0];
var dt = [50, 100, 150, 500];
var spd = 5.0;



タイマーマネージャーと球の位置(pos)角度(th)速度(spd)マルチスレッド間隔(dt)
を宣言してます


var cnt = 0;
var div = 72;
var sph3;



球の色変えに使います

init()において3Dシーンを作成して


//タイマー作成
TMan.add();
TMan.add();
TMan.add();
TMan.add();
TMan.add();



タイマーを5つ作成して


//初回実行
Loop0(0);
Loop1(1);
Loop2(2);
Loop3(3);
RDLoop(4);



それぞれ初回実行します


function RDLoop(id) {
    
  // レンダリング
  renderer.render(scene, camera);
  TMan.timer[id].setalerm(function() { RDLoop(id); }, 50);
}



これはレンダリングスレッドです

Loop0(),Loop1(),Loop2(),Loop3()はそれぞれの球の動きのスレッドです

Loop0()を見ていきます


function Loop0(id) {

  th[id] += spd * Math.PI / 180.0;
  sph0.position.set(5 * Math.sin(th[id]) + pos[id].x[1], 6, 0);

  TMan.timer[id].setalerm(function() { Loop0(id); }, dt[id]);
}



th[id] += spd * Math.PI / 180.0;

で、th[id]を角度spd度加算しています

sph0.position.set(5 * Math.sin(th[id]) + pos[id].x[1], 6, 0);

で、角度から計算された位置を球に適用しています

TMan.timer[id].setalerm(function() { Loop0(id); }, dt[id]);

で、dt[id]間隔でLoop0(id)を再呼び出ししています

var dt = [50, 100, 150, 500];
はこう宣言されていたので、つまり
Loop0は50ms、Loop1は100ms、Loop2は150ms、毎に呼び出されます


  function Loop3(id) {

    th[id] += spd * Math.PI / 180.0;

    var col1 = new N6LHsv(0, [255, 255, 0, 0]);
    var col2 = new N6LHsv(0, [255, 255, 0, 0]);
    var col = col1.HsvGrd(div, cnt, col2.ahsv, 1);
    var str = col.Str();
    cnt++;

    sph3.material.color.set(str);
    sph3.position.set(5 * Math.sin(th[id]) + pos[id].x[1], -6, 0);

    var c = (Math.cos(th[id]) + 1.0) / 2.0;
    dt[id] = 50  + c * 450; //50~500[ms]の可変タイマー

    TMan.timer[id].setalerm(function() { Loop3(id); }, dt[id]);
  }



Loop3は
col1(ARGB:FFFF0000,AHSV:100,0,100,100)からcol2(ARGB:FFFF0000,AHSV:100,0,100,100)まで
RGB回りのdiv(72)分割のHSVグラデーションを作っています

sph3.material.color.set(str);
で、色を適用しています

var c = (Math.cos(th[id]) + 1.0) / 2.0;
dt[id] = 50 + c * 450; //50~500[ms]の可変タイマー
とコードを追加しているので50~500[ms]の可変タイマーとなっています

と、N6LTimerManを使えばとても簡単にマルチスレッドを実現できます

なお、N6LTimerManのコアでソフトウェアタイマーを1つだけ実行して
N6LTimerManが管理するタイマーにコアがそれぞれに分配実行しているので
なるべくリソースを圧迫しないようにしています
なので、N6LTimer.add()を何回もしてもあまり問題はありません
ま、もちろん間隔の短いタイマーは負荷がかかります

N6LTimerManのコアの動作は
コアのタイマーチェックスレッドを
N6LTimerManにおける最高速のソフトウェアタイマー1つで
繰り返し実行して
時間計測して
各タイマーのsetalerm()の設定時間が経過したら
各タイマーの登録された関数を呼び出す
とそれだけをしています

N6LTimerMan.changeinterval(INT); //タイマーチェック間隔変更
でコアのタイマーチェック間隔を設定できます





THREE.JSプログラミング講座
3Dプログラミング講座
NAS6LIB
THREE.JSテスト解説・THREE.JS使い方
THREE.JS examplesをいじってみた(フレネル反射透過シェーダー)
THREE.JS (半透明シェーダー)
THREE.JS 3D演算で必要な計算(具体例)★とても重要★
THREE.JS THREE-VRM をいじってみた

<<prev 戻る : THREE.JS examplesをいじってみた(フレネル反射透過シェーダー) next>>





戻る