[simplenoise] 自然な乱数-パーリンノイズの使い方をphina.jsと連携して使ってみる
今回の記事は、 パーリンノイズについて。ただの乱数では表現しきれない、自然物等を描くのに適した乱数です。
以前少しProcessingを触っていた時に、noise()メソッドでこのパーリンノイズを知り、何これ便利!凄い!と、感動したもので、ゲームで何か作るときに使いたいな!と目論んでいたりしました。
少し前に梶井基次郎氏追悼として制作したノベルゲーム『櫻の樹の下には』で、(使いこなせているかどうかはともかく) 『simplenoise』というJSライブラリを使わせていただきつつ、一応ゲーム内で一度使えたので、せっかくなので自分の備忘録も兼ねて、使い方と使用例を書いておきたいと思います。
準備
ライブラリ
まずは、下のページからライブラリをダウンロードしてください。
GitHub – josephg/noisejs: Javascript 2D Perlin & Simplex noise functions
その中のperlin.jsというファイルをscriptタグから読み込んでください。
htmlファイル
<script src='perlin.js'></script>
これでひとまず、パーリンノイズを使う準備はできました。canvasで描画する場合は、このままやればいいのですが、私がいつも使っているphina.jsと一緒に使わせてください。
<script src='https://cdn.jsdelivr.net/gh/phi-jp/phina.js@0.2.2/build/phina.js'></script>
もし、phina.jsをご存じでない方がいらっしゃいましたら、よろしければこちらの記事もご覧ください。
JSファイル
// グローバル領域に展開
phina.globalize();
/*
* メインシーン
*/
phina.define("MainScene",{
// 継承
superClass:"DisplayScene",
// コンストラクタ
init: function(){
// 親クラスの初期化
this.superInit();
// ここに処理を書く
},
// 更新
update: function(){
},
});
/*
* メイン処理
*/
phina.main(() => {
// アプリケーションを生成
const app = GameApp({
// MainSceneから開始
startLabel: "main"
});
// fps表示
//app.enableStats();
// 実行
app.run();
});
パーリンノイズとは
そもそも、パーリンノイズとは、
ようは、ある程度規則性を持った自然な乱数ってことで、理解しておけばいいと思います。
※パーリンノイズそのものの、もっと詳しい解説は、専門の他ページ様に丸投げします。
simplenoiseのメソッド
どのメソッドも、noise.〇〇〇の形で使います。
- simplex2(x, y)
- パーリンノイズではない普通の乱数が返されます。戻り値は、-1~1の浮動小数点であり、パーリンノイズよりも起伏が激しいものになります。
[引数]
x: 数値を入れます。この数値が小さいほど、乱数の変化のスパンが長くなります。
y: 数値を入れます。この数値が小さいほど、乱数の変化のスパンが長くなります。 - simplex3(x, y, z)
- パーリンノイズではない普通の乱数が返されます。戻り値は、-1~1の浮動小数点であり、パーリンノイズよりも起伏が激しいものになります。
[引数]
x: 数値を入れます。この数値が小さいほど、乱数の変化のスパンが長くなります。
y: 数値を入れます。この数値が小さいほど、乱数の変化のスパンが長くなります。
z: 数値を入れます。この数値が小さいほど、乱数の変化のスパンが長くなります。 - perlin2(x, y)
- パーリンノイズが返されます。戻り値は、-1~1の浮動小数点であり、普通の乱数よりもなだらかなものになります。
[引数]…上に同じなので省略 - perlin3(x, y, z)
- パーリンノイズが返されます。戻り値は、-1~1の浮動小数点であり、普通の乱数よりもなだらかなものになります。
[引数]…上に同じなので省略 - seed(val)
- 乱数のシードを設定します。
[引数]
val: 0~1の浮動小数点数か、1~65536の整数を指定します。デフォルト値は0。
ただphina.jsと連携しただけのサンプル
クリックしたら、乱数のシードをリセットし、普通の乱数とパーリンノイズのシードを切り替えます。
※ダウンロードしたsimplenoiseのライブラリのフォルダに一緒に入っているdemo3d.htmlのコードを割と書き直しただけだったりするので、ここで密かにjosephg氏にソースコードをお借りした感謝(と謝罪)を申し上げさせていただきます。
ソースコード
phina.globalize();
const W = 640; // スクリーン横幅の半分
const H = 960; // スクリーン高さの半分
/*
* メインシーン
*/
phina.define("MainScene", {
// 継承
superClass: "DisplayScene",
// コンストラクタ
init: function () {
// 親クラスの初期化
this.superInit();
// 背景色
this.backgroundColor = "#000";
// キャンバス用のRectangleShape
this.c = RectangleShape({
width: W,
height: H,
fill: null,
stroke: null
}).addChildTo(this).setPosition(W / 2 + 8, H / 2 + 8);
// 普通の乱数か、パーリンノイズによる乱数か
this.mode = 'perlin3';
// 描画
this.resetCanvas();
// クリックされた場合
this.onpointstart = () => {
// 普通の乱数かパーリンノイズか切り替え
this.mode = this.mode === 'perlin3' ? 'simplex3' : 'perlin3';
// パーリンノイズのシード設定
noise.seed(Math.random());
// 再描画
this.resetCanvas();
};
},
resetCanvas: function () {
let ctx = this.c.canvas.context;
// イメージデータオブジェクトをRectangleShapeの大きさ分生成
let image = ctx.createImageData(W, H);
// ラスタデータ参照用
let data = image.data;
let f = (this.mode === 'perlin3') ? noise.perlin3 : noise.simplex3;
for (let x = 0; x < W; x++) {
for (let y = 0; y < H; y++) {
let value = f(x / 100, y / 100, 0);
value = (1 + value) * 1.1 * 128;
// 各RGBのセルに同じ値を格納し、白黒にする
let cell = (x + y * W) * 4;
data[cell] = data[cell + 1] = data[cell + 2] = value;
// アルファ値を格納する
data[cell + 3] = 255;
}
}
// イメージデータオブジェクトをRectangleShapeに格納する
ctx.putImageData(image, 0, 0);
}
});
/*
* メイン処理
*/
phina.main(function () {
// アプリケーションを生成
const app = GameApp({
// MainSceneから開始
startLabel: "main"
});
// fps表示
//app.enableStats();
// 実行
app.run();
});
カラフルなサンプル
こちらも、クリックしたら、乱数のシードをリセットし、普通の乱数とパーリンノイズのシードを切り替えます。
ソースコード
phina.globalize();
const W = 640; // スクリーン横幅の半分
const H = 960; // スクリーン高さの半分
/*
* メインシーン
*/
phina.define("MainScene", {
// 継承
superClass: "DisplayScene",
// コンストラクタ
init: function () {
// 親クラスの初期化
this.superInit();
// 背景色
this.backgroundColor = "#000";
// キャンバス用のRectangleShape
this.c = RectangleShape({
width: W,
height: H,
fill: null,
stroke: null
}).addChildTo(this).setPosition(W / 2 + 8, H / 2 + 8);
// 普通の乱数か、パーリンノイズによる乱数か
this.mode = 'perlin3';
// 描画
this.resetCanvas();
// クリックされた場合
this.onpointstart = () => {
// 普通の乱数かパーリンノイズか切り替え
this.mode = this.mode === 'perlin3' ? 'simplex3' : 'perlin3';
// パーリンノイズ設定
noise.seed(Math.random());
// 再描画
this.resetCanvas();
};
},
resetCanvas: function () {
let ctx = this.c.canvas.context;
// イメージデータオブジェクトをRectangleShapeの大きさ分生成
let image = ctx.createImageData(W, H);
// ラスタデータ参照用
let data = image.data;
let f = (this.mode === 'perlin3') ? noise.perlin3 : noise.simplex3;
for (let x = 0; x < W; x++) {
for (let y = 0; y < H; y++) {
let value = f(x * 0.006, y * 0.006, 0);
// 色相
value = (1 + value) * 180;
// 各RGBのセルに同じ値を格納し、白黒にする
let cell = (x + y * W) * 4;
// HSLをRGBへ変換
let rgb = this.hsl2rgb(value, 80, 70);
// red
data[cell] = rgb.r;
// green
data[cell + 1] = rgb.g;
// blue
data[cell + 2] = rgb.b;
// アルファ値を格納する
data[cell + 3] = 255;
}
}
// イメージデータオブジェクトをRectangleShapeに格納する
ctx.putImageData(image, 0, 0);
},
// HSL色空間からRGB色空間へ変換する
// h(hue) : 色相/色合い 0-360度の値
// s(saturation): 彩度/鮮やかさ 0-100%の値
// l(lightness) : 明度/明るさ 0-100%の値
hsl2rgb: function (h, s, l) {
var max, min;
var rgb = { 'r': 0, 'g': 0, 'b': 0 };
if (h === 360) {
h = 0;
}
if (l <= 49) {
max = 2.55 * (l + l * (s / 100));
min = 2.55 * (l - l * (s / 100));
} else {
max = 2.55 * (l + (100 - l) * (s / 100));
min = 2.55 * (l - (100 - l) * (s / 100));
}
if (h < 60) {
rgb.r = max;
rgb.g = min + (max - min) * (h / 60);
rgb.b = min;
} else if (h >= 60 && h < 120) {
rgb.r = min + (max - min) * ((120 - h) / 60);
rgb.g = max;
rgb.b = min;
} else if (h >= 120 && h < 180) {
rgb.r = min;
rgb.g = max;
rgb.b = min + (max - min) * ((h - 120) / 60);
} else if (h >= 180 && h < 240) {
rgb.r = min;
rgb.g = min + (max - min) * ((240 - h) / 60);
rgb.b = max;
} else if (h >= 240 && h < 300) {
rgb.r = min + (max - min) * ((h - 240) / 60);
rgb.g = min;
rgb.b = max;
} else if (h >= 300 && h < 360) {
rgb.r = max;
rgb.g = min;
rgb.b = min + (max - min) * ((360 - h) / 60);
}
rgb.r = Math.round(rgb.r);
rgb.g = Math.round(rgb.g);
rgb.b = Math.round(rgb.b);
return rgb;
}
});
/*
* メイン処理
*/
phina.main(function () {
// アプリケーションを生成
const app = GameApp({
// MainSceneから開始
startLabel: "main"
});
// fps表示
//app.enableStats();
// 実行
app.run();
});
※hsl2rgbのコードは、下記のページ様からコードをほぼ丸パクらせていただきましたので、深く(お詫び)感謝いたしますm(_ _)m
参考にさせていただいたサイト・ページ一覧
- simplenoise – npm
- パーリンノイズを理解する | POSTD
- JavaScriptで取り組むクリエイティブコーディング – パーリンノイズを使いこなせ – ICS MEDIA
- RGBとHSLの相互変換[色見本/サンプル付き]
ご意見・ご感想、また、ここ間違ってるよとか、もっといい方法あるよといったご指摘などございましたら、お手数ですがコメント欄やtwitterアカウントほろほろり(@_horo_horori)へお願いしますm(_ _)m