2014年07月23日

非等方ボロノイ分割のテスト

前々回の参考文献[1]適応的非等方ボロノイ分割によるステンドグラス風画像の作成にあった非等方ボロノイ分割を試してみることにしました。ボロノイ分割に距離以外に色を加えたものですので、特に難しいことはないかと思います。

入力画像

sphinx.png

原画像が分からなかったので(ちゃんと探していませんが)、[1]のPDFの縮小画像をコピーして拡大して大体同じ大きさにしたものを使用させていただいております。著作権とかで問題がありましたら削除しますのでお知らせください。

実行結果

ランダムに配置した128個のセルを使用していますので、適応的ではないただの非等方ボロノイ分割です。画素数(面積)が1000未満のセルは削除して他のセルに分配しました。

αを変化させたときの結果の違い

avt_alpha0.1_001.png
α=0.1(ほぼ座標)

avt_alpha0.5_002.png
α=0.5 (まだ座標)

avt_alpha1_001.png
α=1.0(ちょうど良い?)

avt_alpha2.5_001.png
α=2.5(色優先?)

avt_alpha5_001.png
α=5.0(色優先)

色がおかしいのはLab変換が間違っているんだと思います。kd-treeとか使って近傍探索しないとボロノイ分割にしても時間かかりすぎですね。
web拍手 by FC2
posted by シンドラー at 23:16 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする

2014年07月21日

ステンドグラス風画像の作成について その2

前々回のノイズから作った凹凸を加えるところまでやってみました。
あぴミクさんを使用させていただいております。

原画像にガウスフィルタ

stained_020.png

Lab画像

stained_019.png

領域分割+エッジ画像

stained_018.png

ノイズ+光源などの計算

stained_017.png

最終結果

stained_016.png

C-Means法の領域分割に時間かかりすぎですね。非等方ボロノイ分割で高速にできるようにした方が良さそうでしょうか。
web拍手 by FC2
posted by シンドラー at 00:12 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする

2014年07月18日

ステンドグラス風画像の作成について その1

ランダムなステンドグラス風画像もいいですが、モチーフというか既存の画像をステンドグラス風にできた方がいいかなと思いました。ステンドグラスとボロノイで検索してみると、非等方ボロノイ分割というものがあるようです[1]。

バイラテラル距離というもので、距離だけではなく、色のCIELAB距離も使って非等方ボロノイ分割をするという方法のようです。

それはそれとして、昔C-Means法で領域分割していた気がしたので、それを使ってステンドグラス風画像を作ってみることにしました。

1. 原画像の読込みとぼかし

とりあえずLenna画像を読み込んで、3x3のガウスフィルタを2回かけました。

stained_009.png

2.CIELABに変換

[2]を参考に変換しましたが、結構大きい値になってしまっているので、計算間違いしているかもしれません。

stained_010.png

3. C-Means法で領域分割

多めですが、1024クラスに分割しました。距離の計算にはx, y座標とL, a, b座標の5次元ベクトルを使用しました。

stained_011.png stained_012.png

4. 色付け

各クラスの平均値で領域を塗りつぶします。

stained_013.png

5.エッジ付け

ID画像のソーベルエッジを作って、前回のセレクタで合成します。

stained_014.png

後は高さマップとノイズと法線マップと光源計算をすれば前回のようになるはずですがとりあえずこの辺で。

[1] 適応的非等方ボロノイ分割によるステンドグラス風画像の作成(PDF直リン注意)
[2] 色空間の変換(3)
web拍手 by FC2
posted by シンドラー at 00:35 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする

2014年07月13日

libnoiseの使い方について その7

今度こそステンドグラス風です。ステンドグラス風の画像を作る方法として、[1]を参考にさせていただきました。

まずはガラスの質感のノイズです。ノイズを50%生成して、ぼかしフィルタを2回かけるということで、このノイズをPhotoshopと同じような感じにすることにしました。

フィルター→ノイズ→ノイズを加えるのパラメータについて

量:○%
分布方法:1. 均等に分布、2. ガウス分布

量って書かれているので、ノイズが載る確率なのかな、と思ったら違うみたいですね[2]。ノイズが載る確率は100%で、載る量の範囲を%で指定するということのようです。25%の場合、255の25%で、元の画素値に±64の範囲で乱数が加算されるようです。ですので、元の画素値を255にしておいて、50%のノイズを加えた場合、乱数は±127の間の数値をとりますが、+の場合は全て255を超えるので255にクリッピングされるため、ノイズが載る確率が50%という風な見た目にすることができるようです。

実行結果

白色背景に50%のノイズを加えたあと2回3x3のガウスフィルタを掛けました。

stained_001.png

エンボスフィルタについて

パラメータの強さなどが良くわからなかったので、適当に一般的なエンボスフィルタを実装しました。f1-f2+0.5で、f2の場所をパラメータとして設定できるようにした感じです。

実行結果

100度の方向に3ピクセルで離れたところに1.5倍の強さのエンボスフィルタを掛けた結果です。

stained_002.png

libnoiseのselectorのようなもの

libnoiseには、selector moduleというものがあります[3]。これは、2つのノイズを、selectorで指定したノイズの割合でブレンドしているのだと思います。多分。

同じような機能を作って、まずは、前々回作ったボロノイエッジと、ボロノイIDから適当に7色付けた画像をボロノイエッジの明るさの割合でブレンドします。ボロノイエッジがあるところはボロノイエッジのまま、それ以外のところはボロノイIDから適当につけた色が現れることになります。

実行結果

これだけでもステンドグラス風といえばステンドグラス風ですね。

stained_003.png

続いて、高さマップもボロノイエッジと、ガラスの質感のレイヤとを、ボロノイエッジをセレクタとしてブレンドします。

実行結果

エッジの部分にガラスっぽい質感を出さないためにセレクタを使っています。立体感がないですね。

stained_004.png

高さマップから法線ベクトルの生成

前回はnoiseutilsを使いましたが、今回は自前のものです。5x5のソーベルフィルタを使っているぐらいで、他は同じだと思います。

実行結果

stained_005.png

AOの計算

続いて、高さマップと法線マップから、適当に環境遮蔽を計算します。結構前に作ったものなのでパラメータ設定がわからなくて微妙です。

実行結果

stained_006.png

Blinn-Phongの計算

続いて、高さマップと法線ベクトルを使って光の反射を計算します。Blinn-Phongモデルを使っています。メロンのテクスチャを思い出します…。

実行結果

stained_007.png

最終結果

後はこれらの結果を足したり掛けたり色々しました。もっとぱっと一つの処理でできるようにならないと、MMEとかに適用しにくいですね。

stained_008.png

[1] http://azp2.sakuraweb.com/illust/stainedglass/index.html
[2] http://www.hirax.net/keywords/log/%E4%B9%B1%E6%95%B0/latest
[3] http://libnoise.sourceforge.net/glossary/index.html#selectormodule
web拍手 by FC2
posted by シンドラー at 22:40 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする

2014年07月10日

libnoiseの使い方について その6

noiseutilsを使えばライティングの計算等もやってくれるようですが、やはり自前で色々試したい気がするので、高さマップから法線マップを生成する方法についてメモしておきます。まぁ自前でやればいいのですが、今回は一応noiseutilsを使った方法です。

Tutorialにはなかったと思いますが、noiseutilsにはRenderNormalMapというクラスがありますので、これを使えばノーマルマップを出力することができます。以下ソースコードのコメント文です。

ノイズマップから法線マップのレンダリング

このクラスは、ノイズマップオブジェクトから法線ベクトルを含んだ画像をレンダリングします。この画像は、3Dアプリケーションやゲームのためのバンプマップとして使用することができます。

このクラスは、法線ベクトルの(x,y,z)要素を画像の(red, green, blue)チャンネルに変換して格納します。24ビットフルカラー画像ですので、0から255の値をとります。0は法線ベクトルの-1.0を意味して、255は+1.0を意味します。

ノーマルマップをレンダリングする前に、あなたはbump heightを指定する必要があります。bump heightは、空間解像度の割合を指定するものです。例えば、あなたのノイズマップが30メートルの空間解像度を持ち、高さの解像度が1メートルであれば、bump heightには1.0/30.0を設定してください。

ノーマルマップのレンダリング

ノーマルマップを画像にレンダリングするためには、次の手順に従ってください。
1. NoiseMapオブジェクトを、SetSourceNoiseMap()メソッドを使って渡してください。
2. Imageオブジェクトを、SetDestImage()メソッドを使って渡してください。
3. Render()メソッドを呼んでください。

というわけで、他のチュートリアルと同じような使い方でノーマルマップを出力できるようです。

サンプルソースコード
  // Tutorial 3
module::Perlin myModule;

utils::NoiseMap heightMap;
utils::NoiseMapBuilderPlane heightMapBuilder;
heightMapBuilder.SetSourceModule (myModule);
heightMapBuilder.SetDestNoiseMap (heightMap);
heightMapBuilder.SetDestSize (256, 256);
heightMapBuilder.SetBounds (2.0, 6.0, 1.0, 5.0);
heightMapBuilder.Build ();
utils::RendererImage renderer;
utils::Image image;
renderer.SetSourceNoiseMap (heightMap);
renderer.SetDestImage (image);
renderer.ClearGradient ();
renderer.AddGradientPoint (-1.0000, utils::Color ( 0, 0, 128, 255)); // deeps
renderer.AddGradientPoint (-0.2500, utils::Color ( 0, 0, 255, 255)); // shallow
renderer.AddGradientPoint ( 0.0000, utils::Color ( 0, 128, 255, 255)); // shore
renderer.AddGradientPoint ( 0.0625, utils::Color (240, 240, 64, 255)); // sand
renderer.AddGradientPoint ( 0.1250, utils::Color ( 32, 160, 0, 255)); // grass
renderer.AddGradientPoint ( 0.3750, utils::Color (224, 224, 0, 255)); // dirt
renderer.AddGradientPoint ( 0.7500, utils::Color (128, 128, 128, 255)); // rock
renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
renderer.EnableLight ();
renderer.SetLightContrast (3.0);
renderer.SetLightBrightness (2.0);
renderer.Render ();

utils::WriterBMP writer;
writer.SetSourceImage (image);
writer.SetDestFilename ("tutorial_texture.bmp");
writer.WriteDestFile ();

// for normal map
utils::RendererNormalMap normalRenderer;
utils::Image normalImage;
normalImage.SetSize(256, 256);
normalRenderer.SetSourceNoiseMap(heightMap);
normalRenderer.SetDestImage(normalImage);
normalRenderer.SetBumpHeight(5.0);
normalRenderer.EnableWrap(false);
normalRenderer.Render();

writer.SetSourceImage(normalImage);
writer.SetDestFilename("tutorial_normal.bmp");
writer.WriteDestFile();

実行結果

RendererImageは空のImageオブジェクトを渡したので良かったのですが、RenderNormalImageは空じゃないImageオブジェクトを渡さないと駄目みたいです。多分。SetBumpHeightの値を変えると、法線ベクトルの向きが変わります。高さマップから法線マップを計算するときに使うあの値ですね。

tutorial_texture_org.bmp tutorial_normal.bmp

サイズを1024x1024にしても256x256分のノーマルマップしか出力されなかったので、noise mapのサイズと同じサイズのノーマルマップしか作れないみたいですね。
web拍手 by FC2
posted by シンドラー at 01:10 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

2014年07月07日

libnoiseの使い方について その5

次回○○やります、と書いて実際やったことの方が少ない気がしますね。先月更新できなかったので反動で頑張りましたが、そろそろ落ち着きますかね。

というわけで、木の3次元テクスチャをMMEで使ってみることにしました。木目調の3次元テクスチャは、[1]のexampleを参考にさせていただきました。GPLなのでGPLですね。

3次元座標でノイズを取得できますので、それを3次元テクスチャとして保存します。以前作成したDevILライブラリを扱うクラスを使って、.ddsファイルとして3次元テクスチャをそのまま出力します。DirectX Texture Toolで512x512x512で非圧縮でミップマップ付きに変換して保存したらunsigned byte: Lのみの8bitなのに150MBになりました・・・。

続いて、MMDで使用するモデルで木目が使えそうなものということで、しらす様の油絵用パレット[2]を使用させていただきました。

MME使用前
wood_mme_001.png

ここで問題になるのが、3次元テクスチャの座標をどうすればいいかということです。UV展開して・・・といったことは面倒ですので、MME側で頂点座標を無理やり0〜1の範囲に線形変換して、それを3次元テクスチャのUV座標として使用することにしました。

また、元のモデル?ではSpecularColorが黒色でしたので、適当に色を設定して、3次元テクスチャの明るさをSpecularのマスクとして使用しています。テクスチャをマスクとして使用するのもよく使われる方法だとは思いますが、多少は効果があるのではないかと思います。

MME使用後
wood_mme_002.png

パレットというか磨いた机みたいな感じですが…。

おまけ


3Dテクスチャつけてない上に多分今回のモデル以外ではまともに使えないと思いますが一応参考までに。
Texture3DTest.fx

[1] http://libnoise.sourceforge.net/examples/textures/index.html
[2] 【MMD-OMF4】油絵用パレットっぽいもの
web拍手 by FC2
posted by シンドラー at 01:05 | Comment(0) | TrackBack(0) | MMD | このブログの読者になる | 更新情報をチェックする

2014年07月06日

libnoiseの使い方について その4

チュートリアルもとりあえず終わったので、適当に使い方を試してみます。今回はVoronoiモジュールです[1]。今回からはnoiseutilsは使わない方針で行きます。

Voronoiモジュールは、Voronoiで宣言して、GetValue関数で値がとれます。

    noise::module::Voronoi myModule;
    double value = myModule.GetValue(0.5, 8.3, 2.0);


これで、-1.0〜1.0までの乱数が返ってきます。値の範囲を変えたい場合は、SetDisplacement関数を使用します。

    noise::module::Voronoi myModule;
    myModule.SetDisplacement(100.0);
    double value = myModule.GetValue(0.5, 8.3, 2.0);

他にも、乱数の種を設定したり、細かさを決めるための周波数などが設定できます。

    noise::module::Voronoi myModule;
    myModule.SetDisplacement(100.0);
    myModule.SetSeed(10);
    myModule.SetFrequency(2.0);

    double value = myModule.GetValue(0.5, 8.3, 2.0);

もう一つ、EnableDistanceという関数があります。これは、計算結果に距離を加算するかどうかのフラグになります。

    noise::module::Voronoi myModule;
    myModule.SetDisplacement(100.0);
    myModule.SetSeed(10);
    myModule.SetFrequency(2.0);
    myModule.EnableDistance(true);
    double value = myModule.GetValue(0.5, 8.3, 2.0);

このフラグを設定した場合(左)と設定しなかった場合(右)では下図のような違いがあります。

voronoi_distance1.png voronoi_distance0.png

で、右図を見てもらうとわかるのですが、乱数を整数に切り詰めてから計算していますので、隣り合う領域で同じ値になってしまっているところがあったりします。そのため、領域を識別する番号のようなものや、領域と領域のエッジをこのままではきれいに計算することができません。

オープンソースですので、自分で書き換えることにしました。GetValue関数の中を見てみると、xCandidate, yCandidate, zCandidateというdouble型の値を使っていたので、これを取得できる関数を追加しました。

    double GetValue(double x, double y, double z, double candidate[3]);

この値を画像化したものは下図のようになりました。

voronoi.png

同じ領域は同じ値になっていますので、各ピクセル毎にクラスタリングをしてIDを割り振っていきます。

voronoi_id.png

これでエッジを計算しやすくなりました。

voronoi_edge.png

後、EnableDistanceをON/OFFした画像の差分をとれば、Worley noiseに使えそうな値になります。

voronoi_sub2.png voronoi_sub1.png

libnoiseでは3次元座標でGetValueできますので、次回は3次元のステンドグラス風テクスチャでも作ってみようかと思います。

[1] http://libnoise.sourceforge.net/docs/classnoise_1_1module_1_1Voronoi.html
web拍手 by FC2
posted by シンドラー at 02:45 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

2014年07月05日

libnoiseの使い方について その3

Tutorial 3の次ですがTutorial 8です。

球面noise-map builderの設定
これまでは、平面noise-map builderを使ってきました。平面ビルダは、2次元の平面に沿ったノイズを生成していました。libnoiseは3次元の入力値で計算しますので、noiseutilsライブラリのいくつかのビルダは、3次元の表面に沿ったノイズを生成できます。

このチュートリアルでは、球面noise-map builderを使用します。このビルダは、球の表面に沿ったノイズを生成します。そのため、平面座標系(x,z)の代わりに、球面座標系を入力します。これにより、座標系を2次元に展開することができますので、2次元の高さマップとして保存することができます。この処理のことを球面マッピングと呼びます。

球面ノイズマップビルダを作成するために、NoiseMapBuilderPlaneオブジェクトを、NoiseMapBuilderSphereオブジェクトに書き換えてください。

このチュートリアルの準備のために、次のコードを記述してください。

#include <noise/noise.h>
#include "noiseutils.h"

using namespace noise;

int main (int argc, char** argv)
{
module::Perlin myModule;

utils::NoiseMap heightMap;

utils::RendererImage renderer;
utils::Image image;
renderer.SetSourceNoiseMap (heightMap);
renderer.SetDestImage (image);
renderer.ClearGradient ();
renderer.AddGradientPoint (-1.0000, utils::Color ( 0, 0, 128, 255)); // deeps
renderer.AddGradientPoint (-0.2500, utils::Color ( 0, 0, 255, 255)); // shallow
renderer.AddGradientPoint ( 0.0000, utils::Color ( 0, 128, 255, 255)); // shore
renderer.AddGradientPoint ( 0.0625, utils::Color (240, 240, 64, 255)); // sand
renderer.AddGradientPoint ( 0.1250, utils::Color ( 32, 160, 0, 255)); // grass
renderer.AddGradientPoint ( 0.3750, utils::Color (224, 224, 0, 255)); // dirt
renderer.AddGradientPoint ( 0.7500, utils::Color (128, 128, 128, 255)); // rock
renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
renderer.EnableLight ();
renderer.SetLightContrast (3.0);
renderer.SetLightBrightness (2.0);
renderer.Render ();

utils::WriterBMP writer;
writer.SetSourceImage (image);
writer.SetDestFilename ("tutorial.bmp");
writer.WriteDestFile ();

return 0;
}

このソースコードは、Tutorial 3のものと似ていますが、NoiseMapBuilderPlaneオブジェクトに関連する部分が削除されています。

それではNoiseMapBuilderSphereオブジェクトを作成し、Perlin-noiseモジュールに渡しましょう。次のコードを追加してください。

  utils::NoiseMap heightMap;
utils::NoiseMapBuilderSphere heightMapBuilder;
heightMapBuilder.SetSourceModule (myModule);
heightMapBuilder.SetDestNoiseMap (heightMap);


次に、生成する地形の高さマップのサイズを指定します。球面高さマップを生成しますので、重なる部分を減らすために、幅は高さの2倍とします。このチュートリアルでは、512x256点とします。次のコードを追加してください:

  heightMapBuilder.SetDestNoiseMap (heightMap);
heightMapBuilder.SetDestSize (512, 256);

次に、(緯度、経度)を設定する必要があります。惑星全体のようなノイズを生成したい場合は、全ての領域(−90〜90度、−180〜180度)を指定する必要があります。

  heightMapBuilder.SetDestSize (512, 256);
heightMapBuilder.SetBounds (-90.0, 90.0, -180.0, 180.0);

東半球と北半球は正の値、西半球と南半球は負の値となります。

次のコードを追加してビルドすれば、球面高さマップが作成できます。

  heightMapBuilder.SetBounds (-90.0, 90.0, -180.0, 180.0);
heightMapBuilder.Build ();


これで準備が整いました。コンパイルして実行してtutorial.bmpを開いてください。

tutorial_sphere.bmp

この画像はいくつかの興味深い属性を持っています:

・右と左のエッジはシームレスにつながっています。

・地平線から離れるにしたがって歪んでいます。(これは球を覆うために現れる歪みです。)

Celestia[4]のようなソフトウェアを使用すれば、歪みがなく途切れもない惑星を作ることができます([3]の図参照)。

Celestiaを持っていて、solarsys.sscを編集することができるのであれば、上記のような画像を作ることができます。(solarsys.sscの編集はこのチュートリアルの範囲外です。)

ズームイン
このセクションでは、惑星の一部の狭い領域をズームすることができます。bounding rectangleを設定して範囲を限定しましょう。

  heightMapBuilder.SetDestNoiseMap (heightMap);
heightMapBuilder.SetDestSize (512, 256);
heightMapBuilder.SetBounds (0.0, 30.0, 50.0, 80.0);
heightMapBuilder.Build ();

このbounding rectangleで指定した範囲は正方形ですので、地形の高さマップも正方形にするべきです。次のように修正してください:

  heightMapBuilder.SetDestNoiseMap (heightMap);
heightMapBuilder.SetDestSize (256, 256);
heightMapBuilder.SetBounds (0.0, 30.0, 50.0, 80.0);
heightMapBuilder.Build ();

もう一度コンパイルして実行してみましょう。tutorial.bmpは次のようになっているでしょう:

tutorial_plane2.bmp

不運なことに、この地形の高さマップは、ズームに耐えられるほど詳細ではなかったようです。これはPerlin-noiseを生成する際のoctavesの値が小さかったからだと思われます。より細かくするために、octavesの値を大きくしてみましょう:

  module::Perlin myModule;
myModule.SetOctaveCount (10);

もう一度コンパイルして実行すると、次のような結果が得られます:

tutorial_plane_detail2.bmp

このレンダリング結果は、ひとつ前のレンダリング結果より細かいことに注意してください。

結論
このチュートリアルでは、惑星全体を生成するのに適した球面高さマップの作成を行いました。そして、3Dアプリケーションで、球場のオブジェクトにシームレスにラッピングすることで、高さマップの結果を確認しました。また、特定の狭い領域にズーミングして、さらにそれを詳細化しました。

これですべてのチュートリアルが終わって、あなたは広大な世界を作るためのlibnoiseの使い方を十分学ぶことができたでしょう。何ができるか考えてみてください。

[1] libnoise
[2] qknight / libnoise
[3] Tutorial 8
[4] Celestia

web拍手 by FC2
posted by シンドラー at 18:58 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

libnoiseの使い方 その2

Tutorial 3の続きです。

画像のカラー化
このセクションでは、リアルなカラーリングの画像にしましょう。レンダラでは、カラーをグラディエントで変化させることができます。このチュートリアルでは、下図のような割り当てを使用します([3]参照)。

図を見ればわかるように、0より小さい領域は水の中です。水面近くは砂の色にし、次は草の緑です。緑からダート、岩へと変化して、最後は雪の白です。
Colorオブジェクトを渡すことによって、レンダラで勾配を作ることができます。Colorコンストラクタは、RGBAコンポーネントを持っていて、0〜255の範囲で指定します。WindowsのBitmapファイルは透明度αをサポートしていないので、今回全て255に設定しています。このコードは下記のようになります。

renderer.SetDestImage (image);
renderer.ClearGradient ();
renderer.AddGradientPoint (-1.0000, utils::Color ( 0, 0, 128, 255)); // deeps
renderer.AddGradientPoint (-0.2500, utils::Color ( 0, 0, 255, 255)); // shallow
renderer.AddGradientPoint ( 0.0000, utils::Color ( 0, 128, 255, 255)); // shore
renderer.AddGradientPoint ( 0.0625, utils::Color (240, 240, 64, 255)); // sand
renderer.AddGradientPoint ( 0.1250, utils::Color ( 32, 160, 0, 255)); // grass
renderer.AddGradientPoint ( 0.3750, utils::Color (224, 224, 0, 255)); // dirt
renderer.AddGradientPoint ( 0.7500, utils::Color (128, 128, 128, 255)); // rock
renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow

renderer.Render ();

コンパイルして実行してtutorial.bmpを開いてみましょう。下図のような良い結果が得られるでしょう。

tutorial_color.bmp

光源の効果を追加
このセクションでは、光源の効果を追加します。次のコードを追加してください。

  renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
renderer.EnableLight ();
renderer.Render ();

コンパイルして実行すると、以下のような結果が得られます。

tutorial_light.bmp

人工的な光源を追加することで、地形の高さの凹凸が少し浮かび上がりましたが、あまりコントラストが高くありません。コントラストを3倍にしてみましょう。

  renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
renderer.EnableLight ();
renderer.SetLightContrast (3.0); // Triple the contrast
renderer.Render ();

コンパイル・実行して画像を開くと、よりコントラストがはっきりした画像が見えるでしょう。

tutorial_light3.bmp

この画像は良い感じですが、少し暗いです。光源を追加しても、元の明るさの最大値を超えないような計算になっているからです。これでは光源を追加する前とあまり変わりません。
しかしながら、明るさを増すことはできます。光源の明るさを2倍にしてみましょう:

  renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
renderer.EnableLight ();
renderer.SetLightContrast (3.0); // Triple the contrast
renderer.SetLightBrightness (2.0); // Double the brightness
renderer.Render ();

コンパイルして実行すれば、より明るい画像が得られるでしょう。

tutorial_light2.bmp

変更できる光源のパラメータは他にもたくさんあります。いくつか自分で変更してみましょう:

・光源の方向を変更してみましょう。度単位で角度をSetLightAzimuth()関数に渡してください。0度が東、90度が北、180度が西、270度が南です。デフォルトでは45度(北東)です。

・光源の高さを変更してみましょう。度単位で角度をSetLightElevation()関数に渡してください。0度が地平線、90度が頭上になります。デフォルトは45度です。

・光源の色を設定してみましょう。SetLightColor()関数にColorオブジェクトを渡してください。

地形の高さマップのタイリング
今回、地形のとても狭い領域だけレンダリングしました。coherent-noiseは全ての方向に無限に広げることができるので、実際の地形もほぼ無限に近い領域です。

このチュートリアルでは、(2,1)から(6,5)の領域だけ抜き出していました。

このセクションでは、“タイル”のようにちょうど同じ大きさだけ右に移動した領域に変更してみましょう([3]の図参照)。座標としては(6,1)から(10,5)になります。
コードを下記のように変更してみましょう。

  utils::NoiseMap heightMap;
utils::NoiseMapBuilderPlane heightMapBuilder;
heightMapBuilder.SetSourceModule (myModule);
heightMapBuilder.SetDestNoiseMap (heightMap);
heightMapBuilder.SetDestSize (256, 256);
heightMapBuilder.SetBounds (6.0, 10.0, 1.0, 5.0);
heightMapBuilder.Build ();

元のtutorial.bmpを別名保存して、コンパイル・実行して新しい画像を作成しましょう。下図のような結果が得られます。

tutorial_light2_left.bmp

二つを並べると、シームレスにつながっていることがわかるでしょう:

tutorial_light2.bmptutorial_light2_left.bmp

これが、coherent-noiseを使う利点の一つです。伝統的なsubdivisionの手法より高速に、必要な領域の分だけ高さマップを作ることができます。

結論
このチュートリアルでは、地形の高さマップを作って、画像として出力して、カラー化して、シームレスにつながる2枚の高さマップを作りました。

次のチュートリアルに行く前に、あなたは色々試してみたいと思うかもしれません。画像のサイズや、カラーグラディエントの変更、光源の変更など、色々変更できます。bounding rectangleを違う座標にすることで、地形を探索することもできます。

[1] libnoise
[2] qknight / libnoise
[3] Tutorial 3

web拍手 by FC2
posted by シンドラー at 01:04 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

2014年07月04日

libnoiseの使い方について その1

ぎりぎり一ヶ月以内の更新です。Seesaaは一ヶ月更新がなくても広告出ないみたいですが。

自前でノイズ生成していますが、ライブラリを使った方が楽かなと思って探してみました。LGPLのlibnoise[1]がありましたので少し使い方をメモしておこうと思います。2007年ぐらいから更新されていないようで、VC++6.0だったりしてコンパイルが大変です。

GitにCMakeに対応したバージョンを置いてくださっている方がいましたので、そちらを使用することにします[2]。CMakeで64ビットでコンパイルすれば後は[1]からヘッダやユーティリティなどをダウンロードして準備完了です。Tutorial 1 と 2 は飛ばして 3 からちょっと試していきたいと思います。

Tutorial 3: 地形の高さマップの生成とレンダリング[3]

ここでは、noiseutilsライブラリを使用して地形の高さマップを作ります。ユーティリティライブラリには色々便利な機能があります。

・noise mapクラス:
 noise moduleを使ってcoherent-noiseを生成して、2次元のfloat値として格納する機能があります。

・noise-map builderクラスたち:
 ↑と同じようにノイズを生成してマップを埋めます。ただ、異なる数学的なオブジェクト(平面、円柱、球)などの形状に対応しています。

・imageクラス:
 2次元配列に色を格納します。

・image-rendererクラスたち:
 ノイズマップの内容を画像にレンダリングします。それぞれのクラスは異なった方法でレンダリングします。

・file writerクラスたち:
 ノイズマップや画像をファイルに書き出すクラスたちです。

noiseutilsライブラリといいますが、内容はnoiseutils.hとnoiseutils.cppですので、noiseutils.cppをプロジェクトに追加する必要があります。noiseutilsはLGPLですが、このようにプロジェクトに組み込んでしまうとGPLになる気がしますね。まぁプログラムを配布する予定はないので別に問題ないですが。探せばlibとdllにしたnoiseutilsがどこかにあるかもしれません。

地形の高さマップの作成

前のチュートリアル(Tutorial 2)で作成したコードを開いてください。下記のような内容になっているはずです:

#include <iostream>
#include <noise/noise.h>

using namespace noise;

int main (int argc, char** argv)
{
module::Perlin myModule;
double value = myModule.GetValue (14.50, 20.25, 75.75);
std::cout << value << std::endl;
return 0;
}

noiseutilsを使うように書き換えます。

#include <noise/noise.h>
#include "noiseutils.h"

using namespace noise;

int main (int argc, char** argv)
{
module::Perlin myModule;

utils::Noisemap heightMap;

return 0;
}

次に、planar noise-map builderを作ります。これで平面のノイズマップを作ることができます。
そのために、NoiseMapBuilderPlaneクラスのインスタンスを生成し、Perlin-noiseモジュールを渡してcoherent-noiseを作ります。

  utils::NoiseMap heightMap;
utils::NoiseMapBuilderPlane heightMapBuilder;
heightMapBuilder.SetSourceModule (myModule);
heightMapBuilder.SetDestNoiseMap (heightMap);

次に、サイズを指定してノイズマップを生成します。今回は256x256点とします。

  utils::NoiseMap heightMap;
utils::NoiseMapBuilderPlane heightMapBuilder;
heightMapBuilder.SetSourceModule (myModule);
heightMapBuilder.SetDestNoiseMap (heightMap);
heightMapBuilder.SetDestSize (256, 256);

次に、生成するデータが座標的にどの辺りの平面なのかを指定する必要があります。これはbounding rectangleで指定します。builderは、指定した領域の中で256x256の計65,536点のcoherent-noise値を生成します。このチュートリアルでは、(2,1)から(6,5)を囲む四角を指定します(図は[3]参照)。コードでは次のように書きます。

  utils::NoiseMap heightMap;
utils::NoiseMapBuilderPlane heightMapBuilder;
heightMapBuilder.SetSourceModule (myModule);
heightMapBuilder.SetDestNoiseMap (heightMap);
heightMapBuilder.SetDestSize (256, 256);
heightMapBuilder.SetBounds (2.0, 6.0, 1.0, 5.0);

この関数の引数は、lower-x, upper-x, lower-z, upper-zの順で指定することに注意してください。これでノイズマップをビルドする準備ができました。次のコードを追加してください。

  utils::NoiseMap heightMap;
utils::NoiseMapBuilderPlane heightMapBuilder;
heightMapBuilder.SetSourceModule (myModule);
heightMapBuilder.SetDestNoiseMap (heightMap);
heightMapBuilder.SetDestSize (256, 256);
heightMapBuilder.SetBounds (2.0, 6.0, 1.0, 5.0);
heightMapBuilder.Build ();

これで地形の高さマップが生成されました。Perlin-noiseモジュールは一般的に-1から1の範囲の値を出力しますので、高さマップも同じ幅になっています。

地形の高さマップのレンダリング

プログラムを実行しても、何も起こりません。なぜなら、高さマップは生成しましたが、画像にレンダリングしていないからです。そのために、RendererImageクラスのインスタンスを生成します。レンダリングした画像を受け取るための空の画像オブジェクトも必要となります。

レンダラでレンダリングする前に、SetSourceNoiseMap()関数を使って、レンダラに地形の高さマップを渡してあげる必要があります。同様に、SetDestImage()関数を使用して、空の画像オブジェクトも渡してあげます。

  heightMapBuilder.SetBounds (2.0, 6.0, 1.0, 5.0);
heightMapBuilder.Build ();

utils::RendererImage renderer;
utils::Image image;
renderer.SetSourceNoiseMap (heightMap);
renderer.SetDestImage (image);

これでレンダラの準備が終わりました。画像にレンダリングします。

  utils::RendererImage renderer;
utils::Image image;
renderer.SetSourceNoiseMap (heightMap);
renderer.SetDestImage (image);
renderer.Render ();


画像をファイルに出力

画像のレンダリングをしましたので、その結果をBMPファイルに出力します。次のコードを追加してください。

  renderer.SetDestImage (image);
renderer.Render ();

utils::WriterBMP writer;
writer.SetSourceImage (image);
writer.SetDestFilename ("tutorial.bmp");
writer.WriteDestFile ();

これですべての準備が整いました。コンパイルして実行してください。tutorial.bmpというファイルがカレントディレクトリにできているはずです。

tutorial.bmpを開いてみましょう。地形の高さマップがグレースケールでレンダリングされているのを確認できるでしょう。

実行結果

tutorial.bmp

ちょっと長いので続きは次の記事にしたいと思います。

[1] libnoise
[2] qknight / libnoise
[3] Tutorial 3

web拍手 by FC2
posted by シンドラー at 21:24 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする