今回はPerlin ノイズを使った年輪のテクスチャを作成します。
Perlin ノイズはそれを題材にしたコンテストが行われたり、
Cg のコーディングコンテストやATIのデモでリアルタイム生成が試みられていて、
ひそかに(?)盛り上がっています。
有用なランダム関数は使い出があるので、勉強しておきましょう。
まぁ、いつものように適当にファイルが入っています。
draw.cpp | メインの描画部分。 |
draw.h | 描画の各関数の定義。 |
bg.cpp | 背景の描画。 |
load.cpp | メッシュのロード。 |
load.h | ロードのインターフェイス。 |
main.h | 基本的な定数など。 |
main.cpp | 描画に関係しないシステム的な部分。 |
あと、モデルと実行ファイル及び、プロジェクトファイルが入っています。
Perlin ノイズとは、ニューヨーク大学の Ken Perlin 氏が開発したノイズです。
Ken Perlin 氏は、「T2」等で使用された 「Noise & Turblence アルゴリズム」の開発で1997年にアカデミー技術賞を受賞しているつわものです。
SIGGRAPH でも毎年のように発表されており、この分野では非常に有名な方です。
Perlin ノイズは、複数のノイズを合成して1つのノイズを作ります。
合成するノイズは、サンプリング周波数が2のべき乗で増加します。
また、このとき乱数のゆれ(振幅)を小さくしていきます。
この「小さくしていく度合い」を Persistance といいます。
Persistance は、ほとんどの場合1以下の小数で、Persistance が小さいほどなだらかなノイズを作ることができます。
なだらかさは、合成するノイズの個数(オクターブ数)でも調整することができます。
なぜ、オクターブというかは、ノイズのサンプリング周波数が2のべき乗であがるから名づけられました。
周波数が2倍高い音は1オクターブ上の音です。
その音楽用語との連想で、オクターブという言葉が使われています。
高いオクターブのノイズは、細かな部分を表現できるので、より細かいノイズを作ることができます。
図に出ている「Persistance 小」ないし、「オクターブ大」の画像が今回のタイトル絵で使っているノイズです。
作成したノイズを10倍して、さらに小数部を採用してギザギザの模様を作ります。
なお、サンプリングする中間では、値の補間をします。
今回はCatmull-Rom スプラインを。補間に用いますが、線形補間や三角関数も補間に用いられます
(あとで気づいたのですが、三角関数はもっとなだらかにつながるように調整されます)。
計算のしやすさや結果の違いの好みで選べばいいでしょう。
それではプログラムを見ていきましょう。
今回は、特別なシェーダは全くありません。
テクスチャーの生成は初期化時に行います。
D3DXFillTexture を使って、算出的に生成します。
draw.c 0275: if( FAILED(hr = lpD3DDev->CreateTexture(512, 512, 1 0276: , D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8 0277: , D3DPOOL_DEFAULT, &pTexture))) return hr; 0278: if( FAILED(hr = D3DXFillTexture(pTexture, MakTexture, NULL))) return hr;
実際のテクスチャの生成は次のようになります。
draw.c 0227: // ---------------------------------------------------------------------------- 0228: // テクスチャーを作成 0229: // ---------------------------------------------------------------------------- 0230: VOID MakTexture(D3DXVECTOR4* pOut, D3DXVECTOR2 *pTexCoord, D3DXVECTOR2 *pTexelSize, LPVOID pData) 0231: { 0232: const float PERSISTANCE = 1.0f/4; 0233: const int OCTAVES = 4; 0234: 0235: float shade = 0.0f; 0236: 0237: // 最大の振幅の大きさ 0238: float amplitude = 1.0f/(((float)pow( PERSISTANCE, OCTAVES ) - 1.f)/(PERSISTANCE - 1.f)); 0239: float frequency = 4.0; 0240: 0241: for(int i=0; i<OCTAVES ; i++){ 0242: shade += amplitude * GetNoiseOctave(i, frequency, pTexCoord->x, pTexCoord->y); 0243: 0244: amplitude *= PERSISTANCE; // 振幅は小さく 0245: frequency *= 2; // 周波数は高くなる 0246: } 0247: 0248: // 年輪ぽく加工する 0249: shade = 10*shade; // 10年分の輪 0250: shade = shade - (int)shade; // 小数部を取り出す 0251: 0252: // 色をつける 0253: pOut->x = 0.80f*shade; // 茶色っぽい色にする 0254: pOut->y = 0.36f*shade; 0255: pOut->z = 0.08f*shade; 0256: pOut->w = 1.0f; 0257: }
振幅の減少度を示す PERSISTANCE と、重ね合わせ回数 OCTAVES を適当に決めておきます。
オクターブの回数だけノイズを足しこんでいきます。
オクターブがあがるごとに、振幅は小さく、周波数は高くしていきます。
あとは、年輪のようにするように、繰り替えしを付けます。具体的には、10倍して、その小数部をとります。
こうすれば、元のノイズが0から1に変化した段階で10個の年輪ができます。
最後に色をつけて出力すれば、1つのテクセルに関する色が求まります。
問題は、ノイズを与える GetNoiseOctave 関数です。この関数は、0から1の大きさで各オクターブに応じたノイズを生成します。
draw.c 0187: float GetNoiseOctave(int octave, float frequency, float x, float y) 0188: { 0189: int ifrequency = (int)frequency; 0190: float t[2] = {frequency * x, frequency * y}; 0191: int no[2] = {(int)t[0], (int)t[1]}; 0192: 0193: int cp[2][4] = { 0194: { (RAND_ARRAY*((no[0]+0)%ifrequency)/ifrequency+octave)%RAND_ARRAY 0195: , (RAND_ARRAY*((no[0]+1)%ifrequency)/ifrequency+octave)%RAND_ARRAY 0196: , (RAND_ARRAY*((no[0]+2)%ifrequency)/ifrequency+octave)%RAND_ARRAY 0197: , (RAND_ARRAY*((no[0]+3)%ifrequency)/ifrequency+octave)%RAND_ARRAY}, 0198: { (RAND_ARRAY*((no[1]+0)%ifrequency)/ifrequency+octave)%RAND_ARRAY 0199: , (RAND_ARRAY*((no[1]+1)%ifrequency)/ifrequency+octave)%RAND_ARRAY 0200: , (RAND_ARRAY*((no[1]+2)%ifrequency)/ifrequency+octave)%RAND_ARRAY 0201: , (RAND_ARRAY*((no[1]+3)%ifrequency)/ifrequency+octave)%RAND_ARRAY}, 0202: }; 0203: 0204: D3DXMATRIX mHeight( 0205: rand_tbl[cp[0][0]][cp[1][0]], 0206: rand_tbl[cp[0][1]][cp[1][0]], 0207: rand_tbl[cp[0][2]][cp[1][0]], 0208: rand_tbl[cp[0][3]][cp[1][0]], 0209: 0210: rand_tbl[cp[0][0]][cp[1][1]], 0211: rand_tbl[cp[0][1]][cp[1][1]], 0212: rand_tbl[cp[0][2]][cp[1][1]], 0213: rand_tbl[cp[0][3]][cp[1][1]], 0214: 0215: rand_tbl[cp[0][0]][cp[1][2]], 0216: rand_tbl[cp[0][1]][cp[1][2]], 0217: rand_tbl[cp[0][2]][cp[1][2]], 0218: rand_tbl[cp[0][3]][cp[1][2]], 0219: 0220: rand_tbl[cp[0][0]][cp[1][3]], 0221: rand_tbl[cp[0][1]][cp[1][3]], 0222: rand_tbl[cp[0][2]][cp[1][3]], 0223: rand_tbl[cp[0][3]][cp[1][3]]); 0224: 0225: return CatmullRomPatch( mHeight, t[0]-(float)no[0], t[1]-(float)no[1]); 0226: }
オクターブごとの違いを表現しましょう。
オクターブが違えば、周波数が高くなります。
周波数は、サンプリングの個数、すなわち繰り返しの回数です。
(no[0], no[1]) は、現在の座標(x, y)が、frequency の繰り返しの中の何番目かを示します。
rand_tbl は、テクセル座標に依存した(同じ座標値を与えた時に、常に同じ乱数を返すような)乱数です。
配列として乱数を用意しました。
また、乱数の値は、使いやすいように、0~1の範囲にしました。
draw.c 0031: const int RAND_ARRAY = 64; 0032: float rand_tbl[RAND_ARRAY][RAND_ARRAY]; 0272: for(i=0;i<RAND_ARRAY;i++) 0273: for(j=0;j<RAND_ARRAY;j++) 0274: rand_tbl[i][j] = (float)rand()/(float)RAND_MAX;
配列の大きさは適当です。小さいと周期性が見えてしまうので、テクスチャサイズとオクターブ数で割った程度の大きさを用意します。
ノイズをなだらかにする関数としては、Catmull-Rom 曲線のパッチを使いました。
引き渡すパラメータの値は、繰り返しの中での0から1の間のパラメータになります。
テクセル座標に応じて、周期的になるように乱数のコントロールポイントを決めます。
但し、オクターブが違えば別の乱数を得るように調整しています。
draw.c 0152: // ---------------------------------------------------------------------------- 0153: // CatmullRom スプライン 0154: // 0155: // 1 ┌ -1 3 -3 1┐┌ Pi-1 ┐ 0156: // xi(t) = ― [t3 t2 t 1]│ 2 -5 4 -1││ Pi │ 0157: // 2 │ -1 0 1 0││ Pi+1 │ 0158: // └ 0 2 0 0┘└ Pi+2 ┘ 0159: // ---------------------------------------------------------------------------- 0160: float CatmullRom(float x0, float x1, float x2, float x3, float t) 0161: { 0162: const D3DXMATRIX m( 0163: -1.0f/2.0f, 3.0f/2.0f, -3.0f/2.0f, 1.0f/2.0f, 0164: 2.0f/2.0f, -5.0f/2.0f, 4.0f/2.0f, -1.0f/2.0f, 0165: -1.0f/2.0f, 0.0f/2.0f, 1.0f/2.0f, 0.0f/2.0f, 0166: 0.0f/2.0f, 2.0f/2.0f, 0.0f/2.0f, 0.0f/2.0f 0167: ); 0168: 0169: float t2 = t*t; 0170: D3DXVECTOR4 vt(t2*t, t2, t, 1.f); 0171: 0172: D3DXVec4Transform(&vt, &vt, &m); 0173: 0174: return x0 * vt.x + x1 * vt.y + x2 * vt.z + x3 * vt.w; 0175: } 0176: // ---------------------------------------------------------------------------- 0177: float CatmullRomPatch(D3DXMATRIX &cp, float s, float t) 0178: { 0179: return CatmullRom( 0180: CatmullRom( cp._11, cp._12, cp._13, cp._14, s), 0181: CatmullRom( cp._21, cp._22, cp._23, cp._24, s), 0182: CatmullRom( cp._31, cp._32, cp._33, cp._34, s), 0183: CatmullRom( cp._41, cp._42, cp._43, cp._44, s), 0184: t); 0185: }
CatmullRomPatch 関数は、Catmull-Rom 曲線を2重に使用することによって、2次元的に補間をします。
パラメータ (s,t) が0から1の間で動くと、コントロールポイントの中央の4点の値が補完されます。
テクスチャを実際に張るのは円柱座標を使って張っていますが、枝葉の部分なので説明は省略します。
今回は、シェーダとは全く関係なしで、普通にテクスチャを生成しましたが、 リアルタイムで実現していきたいですね。