今回は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点の値が補完されます。
テクスチャを実際に張るのは円柱座標を使って張っていますが、枝葉の部分なので説明は省略します。
今回は、シェーダとは全く関係なしで、普通にテクスチャを生成しましたが、 リアルタイムで実現していきたいですね。