![見出し画像](https://assets.st-note.com/production/uploads/images/157394419/rectangle_large_type_2_311685da00a0f3d5c3be1588d102ba1e.png?width=1200)
【three.js webGL】2次元カラーマップの作り方
今日はthree.jsで2次元平面上の各点に任意の色を指定することで、意味のある2次元配色を与えることで何かしらの量を可視化する2次元カラーマップの作り方を解説するよ。今回は下図のようなN✕Nの格子を個の小さな正方形を配置してポリゴンを生成し、(N+1)✕(N+1)個の格子点にそれぞれ任意の色を設定するよ。
![](https://assets.st-note.com/img/1728492056-vuy9zGw8C5pUZJ2F6djNfgSH.png?width=1200)
下のアニメーションは各点$$(x,y)$$の値を次の式で設定して、1を赤、-1を青となるように各点の描画色を指定した結果だよ。
$$
F(x,y,t) = \sin(\frac{x}{2}+\omega t) \times \sin(\frac{y}{2}+\omega t)
$$
![](https://assets.st-note.com/production/uploads/images/157371868/picture_pc_64416b6ca9af69f4518d1301f5fc9c43.gif?width=1200)
上記は格子点の値をRGBで指定したけれども、HLSで指定することもできるよ。
![](https://assets.st-note.com/production/uploads/images/157394336/picture_pc_0cec9e60fcf4df80a8814f54117fb7dd.gif?width=1200)
Javascript プログラムソース
2次元カラーマップを実装するJavascript プログラムソースを以下に示すよ。もしよろしければ試してみてください。これからも応援よろしくお願いしまーす。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>2次元カラーマップのテスト</title>
<style>
*{padding:0px; margin:0px}
div#canvas-frame{
width: 1200px; /* 横幅 */
height: 800px; /* 縦幅 */
overflow:hidden;
}
</style>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.168.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.168.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
////////////////////////////////////////////////////////////////////
// windowイベントの定義
////////////////////////////////////////////////////////////////////
window.addEventListener("load", function () {
threeStart(); //Three.jsのスタート関数の実行
});
////////////////////////////////////////////////////////////////////
// Three.jsスタート関数の定義
////////////////////////////////////////////////////////////////////
function threeStart() {
initThree(); //Three.js初期化関数の実行
initLight(); //光源初期化関数の実行
initObject(); //オブジェクト初期化関数の実行
initCamera(); //カメラ初期化関数の実行
loop(); //無限ループ関数の実行
}
////////////////////////////////////////////////////////////////////
// Three.js初期化関数の定義
////////////////////////////////////////////////////////////////////
//グローバル変数の宣言
let renderer, //レンダラーオブジェクト
scene, //シーンオブジェクト
canvasFrame; //キャンバスフレームのDOM要素
function initThree() {
//キャンバスフレームDOM要素の取得
canvasFrame = document.getElementById('canvas-frame');
//レンダラーオブジェクトの生成
renderer = new THREE.WebGLRenderer({ antialias: true });
if (!renderer) alert('Three.js の初期化に失敗しました');
//レンダラーのサイズの設定
renderer.setSize(canvasFrame.clientWidth, canvasFrame.clientHeight);
//キャンバスフレームDOM要素にcanvas要素を追加
canvasFrame.appendChild(renderer.domElement);
//レンダラークリアーカラーの設定
renderer.setClearColor(0x000000, 1.0);
//シーンオブジェクトの生成
scene = new THREE.Scene();
}
////////////////////////////////////////////////////////////////////
// カメラ初期化関数の定義
////////////////////////////////////////////////////////////////////
//グローバル変数の宣言
let camera; //カメラオブジェクト
let trackball;
function initCamera() {
//カメラオブジェクトの生成
camera = new THREE.PerspectiveCamera(30, canvasFrame.clientWidth / canvasFrame.clientHeight, 1, 10000);
//カメラの位置の設定
camera.position.set(0, 0, 2);
//カメラの上ベクトルの設定
camera.up.set(0, 1, 0);
//カメラの中心位置ベクトルの設定
camera.lookAt(new THREE.Vector3(0,0,0)); //トラックボール利用時は自動的に無効
}
////////////////////////////////////////////////////////////////////
// 光源初期化関数の定義
////////////////////////////////////////////////////////////////////
//グローバル変数の宣言
let directionalLight, //平行光源オブジェクト
ambientLight; //環境光オブジェクト
function initLight() {
//平行光源オブジェクトの生成
directionalLight = new THREE.DirectionalLight(0xffffff, 2.0, 0);
//平行光源オブジェクトの位置の設定
directionalLight.position.set(0, -50, 50);
//平行光源オブジェクトのシーンへの追加
scene.add(directionalLight);
//環境光オブジェクトの生成
ambientLight = new THREE.AmbientLight(0x666666);
//環境光オブジェクトのシーンへの追加
scene.add(ambientLight);
}
////////////////////////////////////////////////////////////////////
// オブジェクト初期化関数の定義
////////////////////////////////////////////////////////////////////
let colormap; //カラーマップ2Dオブジェクト
let xmin = -10;
let xmax = 10;
let ymin = -10;
let ymax = 10;
function F( x, y, t ){
let omega = Math.PI/60;
return Math.sin( x/2 + t*omega ) * Math.sin( y/2 + t*omega ) ;
}
function initObject() {
let N = 100;
let values = []
for (let i = 0; i <= N; i++) {
values[ i ] = [];
for (let j = 0; j <= N; j++) {
let x = xmin + (xmax - xmin) * i / N;
let y = ymin + (ymax - ymin) * j / N;
values[i][j] = F( x, y, 0 );
}
}
//2次元カラーマップオブジェクトの生成
colormap = new Colormap2D( {
N : N, //頂点の数
l : 1.0/N, //格子間隔
min : -1, //最小値
max : 1, //最大値
colorParameter : {
backWhite : false, //RGBモード時の0点を白とするフラグ
HSLMode : false, //HSLモード実行のフラグ
H0 : 0.70, //Hの始点(HSLモード時)
Hw : 0.73, //Hの変化の傾き(HSLモード時)
S : 1, //Sの値(HSLモード時)
L : 0.5 //Lの値(HSLモード時)
},
fieldValues :values
});
//カラーマップオブジェクトのシーンへの追加
scene.add( colormap.CG );
}
////////////////////////////////////////////////////////////////////
// 無限ループ関数の定義
////////////////////////////////////////////////////////////////////
//グローバル変数の宣言
let step = 0; //ステップ数
function loop() {
//トラックボールによるカメラオブジェクトのプロパティの更新
//trackball.update();
//ステップ数のインクリメント
let t = step;
let N = colormap.N;
for (let i = 0; i <= N; i++) {
for (let j = 0; j <= N; j++) {
let x = xmin + (xmax - xmin) * i / N;
let y = ymin + (ymax - ymin) * j / N;
colormap.setValue( i, j, F( x, y, t ));
}
}
colormap.updateColormap();
//レンダリング
renderer.render(scene, camera);
//ステップ数のインクリメント
step++;
//「loop()」関数の呼び出し
requestAnimationFrame(loop);
}
////////////////////////////////////////////////////////////////////
// 2次元カラーマップクラスの定義
////////////////////////////////////////////////////////////////////
class Colormap2D {
constructor( parameter ){
parameter = parameter || {};
//格子点数
this.N = parameter.N || 100;
//格子間隔
this.l = parameter.l || 1.0 / this.N;
//場の量の最小値と最大値
this.min = (parameter.min !== undefined)? parameter.min : 0; //最小値
this.max = (parameter.max !== undefined)? parameter.max : 1; //最大値
//色関連パラメータ
let p = parameter.colorParameter || {};
this.colorParameter = {
HSLMode : p.HSLMode || false, //HSLモード実行のフラグ
H0 : (p.H0 !== undefined )? p.H0 : 0.70, //Hの始点(HSLモード時)
Hw : (p.Hw !== undefined )? p.Hw : 0.73, //Hの変化(HSLモード時)
S : (p.S !== undefined )? p.S : 1, //Sの値(HSLモード時)
L : (p.L !== undefined )? p.L : 0.5, //Lの値(HSLモード時)
backWhite : p.backWhite || false //中間値を白とするフラグ(RGBモード時)
};
//場の値(2重配列)
this.fieldValues = parameter.fieldValues || [];
//コンピュータ・グラフィックスオブジェクト
this.CG = null;
//カラーマップの初期化
this.initColormap();
}
//2次元カラーマップを実現するために必要な初期設定を行う
initColormap (){
//形状オブジェクトの宣言
let geometry = new THREE.BufferGeometry();
//アトリビュート変数のサイズを指定
let positions = []; //頂点座標
let colors = []; //頂点色
//頂点座標の準備
for (let i = 0; i <= this.N; i++) {
for (let j = 0; j <= this.N; j++) {
let nx = (-this.N / 2 + i) * this.l;
let ny = (-this.N / 2 + j) * this.l;
let nz = 0;
//頂点座標データの追加
positions.push( nx, ny, nz );
colors.push(0, 0, 0)
}
}
let indices = [];
//面指定配列の準備
for (let i = 0; i < this.N; i++) {
for (let j = 0; j < this.N; j++) {
//頂点番号
let ii = (this.N + 1) * i + j;
//面指定用頂点インデックスを追加
indices.push( ii, ii + (this.N + 1), ii + (this.N + 1) + 1);
//面指定用頂点インデックスを追加
indices.push( ii, ii + (this.N + 1) + 1, ii + 1 );
}
}
//アトリビュート変数に設定
geometry.setIndex( indices );
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
//材質オブジェクトの宣言と生成
let material = new THREE.MeshBasicMaterial({ color:0xFFFFFF, vertexColors: true });
//2次元格子オブジェクトの生成
this.CG = new THREE.Mesh( geometry, material );
//場の量の設定
if( this.fieldValues.length > 0 ){
//カラーマップの配色を更新
this.updateColormap();
} else {
//2重配列の初期化
for (let i = 0; i <= this.N; i++) {
this.fieldValues[ i ] = [];
}
}
}
updateColormap ( ){
for (let i = 0; i <= this.N; i++) {
for (let j = 0; j <= this.N; j++) {
let c = this.getColor( i, j );
//頂点番号
let ii = (this.N + 1) * i + j;
this.CG.geometry.attributes.color.array[3 * ii + 0] = c.r;
this.CG.geometry.attributes.color.array[3 * ii + 1] = c.g;
this.CG.geometry.attributes.color.array[3 * ii + 2] = c.b;
}
}
//頂点色データ更新
this.CG.geometry.attributes.color.needsUpdate = true;
}
getColor ( i ,j ){
//場の量の取得
let f = this.fieldValues[i][j];
//カラーマップ関連プロパティ
let p = this.colorParameter;
if( p.HSLMode ){
let value;
if (f < this.min) value = 0;
else if (f > this.max) value = this.max - this.min;
else value = f - this.min;
let H = p.H0 - ( value / (this.max - this.min) ) * p.Hw;
//L値の取得
let L = p.L;
//HSL形式で指定した色オブジェクトを返す
return new THREE.Color( ).setHSL( H, p.S, L, THREE.SRGBColorSpace );
} else {
//描画範囲の中心値を取得
let c = ( this.max + this.min ) / 2;
//中心値よりも大きい場合
let R = ( f > c )? ( f - c )/( this.max - c ) : 0;
if( R > 1 ) R = 1;
//中心値よりも小さい場合
let B = ( f < c )? (c - f)/( c - this.min ) : 0;
if( B > 1 ) B = 1;
let G;
//中心値の色を白色とする場合
if( p.backWhite ){
G = 1 - ( R + B );
R = R + G;
B = B + G;
} else {
G = 0;
}
return new THREE.Color().setRGB(R, G, B, THREE.SRGBColorSpace);
}
}
setValue ( i , j, value ){
this.fieldValues[i][j] = value || 0;
}
}
</script>
</head>
<body>
<div id="canvas-frame"></div><!-- canvas要素を配置するdiv要素 -->
</body>
</html>