« 奇跡の人 | メイン | 坂の上の雲(2) »

2006年02月19日

RenderMonkey(3) Perlin Noise

今回はこちらのサイトにある Perin Noise を作ってみます。原理は、例えば以下のサイトで紹介されている情報を参考にしてみます。

Perlin Noise
http://freespace.virgin.net/hugo.elias/models/m_perlin.htm

基本的には周波数と振幅が反比例する関数の加算を行うことになります。周波数が 2 倍になったら振幅を 1/2 に、といった感じです。

RenderMonkey で実装のテストを行いますが、RenderMonkey には最初から NoiseVolume.dds というボリュームノイズのテクスチャファイルがあります。これを使ってしまうと Perlin Noise を作る部分がなくなってしまうので Random3D.dds を Perlin Noise の種として使います。ちなみに NoiseVolume.dds は 128x128x128 、Random3D.dds は 64x64x64 のボリュームテクスチャです。以下の画像は Random3D.dds を 2 倍にして見た目のサイズをあわせています。

NoiseRandom.jpg
fig.1 NoiseVolume.dds と Random3D.dds

さて、まずは Random3D.dds の表示からです。これがリンク先の noise にあたります。説明は Vertex/Fragment Program を参照しながら行うのでとりあえず、シェーダから。

uniform vec3 fvLightPosition;
uniform vec3 fvEyePosition;

uniform float fScale;

varying vec3 Texcoord3d;
varying vec3 ViewDirection;
varying vec3 LightDirection;
varying vec3 Normal;
   
void main( void )
{
    gl_Position = ftransform();
    Texcoord3d    = gl_Vertex.xyz * fScale;
     
    vec4 fvObjectPosition = gl_ModelViewMatrix * gl_Vertex;

    ViewDirection  = fvEyePosition - fvObjectPosition.xyz;
    LightDirection = fvLightPosition - fvObjectPosition.xyz;
    Normal         = gl_NormalMatrix * gl_Normal;
}
fig.2 Vertex Program
uniform vec4 fvSpecular;
uniform vec4 fvDiffuse;
uniform float fSpecularPower;

uniform bool bMono;

uniform float fNoiseSel;
uniform vec4 fvNoise1Color;
uniform vec4 fvNoise2Color;
uniform vec4 fvNoise2Amb;
uniform vec4 fvNoise3Color;
uniform vec4 fvNoise3Amb;
uniform vec4 fvNoise4Color;
uniform vec4 fvNoise4Amb;

uniform sampler3D volumeMap;

varying vec3 Texcoord3d;
varying vec3 ViewDirection;
varying vec3 LightDirection;
varying vec3 Normal;

float amplitude( float octave )
{
    return 0.5/octave;
}

vec4 perlin_noise( float octave )
{
    return texture3D( volumeMap, Texcoord3d*octave )*amplitude(octave);
}

vec4 zero_center_perlin_noise( float octave )
{
    return perlin_noise( octave )-amplitude(octave)*0.5;
}

void main( void )
{
    vec3  fvLightDirection = normalize( LightDirection );
    vec3  fvNormal         = normalize( Normal );
    float fNDotL           = dot( fvNormal, fvLightDirection ); 
    
    vec3  fvReflection     = normalize( ( ( 2.0 * fvNormal ) * fNDotL ) - fvLightDirection ); 
    vec3  fvViewDirection  = normalize( ViewDirection );
    float fRDotV           = max( 0.0, dot( fvReflection, fvViewDirection ) );
    
    vec4  fvBaseColor;
    vec4  fvAmbient = vec4(0.0, 0.0, 0.0, 0.0);
    
    if( fNoiseSel < 0.25 )
    {
        fvBaseColor = texture3D( volumeMap, Texcoord3d );
        if( !bMono )
            fvBaseColor *= fvNoise1Color;
    }
    else if( fNoiseSel < 0.5 )
    {
        fvBaseColor = perlin_noise( 1.0 )
                    + perlin_noise( 2.0 )
                    + perlin_noise( 4.0 )
                    + perlin_noise( 8.0 );
        if( !bMono )
        {
            fvBaseColor *= fvNoise2Color;
            fvAmbient = fvNoise2Amb;
        }
    }
    else if( fNoiseSel < 0.75 )
    {
        fvBaseColor = zero_center_perlin_noise( 1.0 )
                    + zero_center_perlin_noise( 2.0 )
                    + zero_center_perlin_noise( 4.0 )
                    + zero_center_perlin_noise( 8.0 );
        fvBaseColor = abs(fvBaseColor)+amplitude( 1.0 )/2.0;
        if( !bMono )
        {
            fvBaseColor = 1.0-fvBaseColor;
            fvBaseColor *= fvNoise3Color;
            fvAmbient = fvNoise3Amb;
        }
    }
    else
    {
        fvBaseColor = perlin_noise( 1.0 )
                    + perlin_noise( 2.0 )
                    + perlin_noise( 4.0 )
                    + perlin_noise( 8.0 );
        float ring = fract(20.0 * Texcoord3d.x + fvBaseColor.x);
        ring *= 4.0 * (1.0 - ring);
        ring = pow(ring, 2.0);
        if( !bMono )
            fvBaseColor = mix(fvNoise4Amb, fvNoise4Color, ring);
        else
            fvBaseColor = vec4(ring,ring,ring,ring);
    }
    
    vec4  fvTotalAmbient   = fvAmbient; 
    vec4  fvTotalDiffuse   = fvDiffuse * fNDotL * fvBaseColor; 
    vec4  fvTotalSpecular  = fvSpecular * ( pow( fRDotV, fSpecularPower ) );
    
    gl_FragColor = ( fvTotalAmbient + fvTotalDiffuse + fvTotalSpecular );
}
fig.3 Fragment Program

ノイズは 4 種類あるため、変数をスイッチとして使っています。fNoiseSel が 0.25 区間ごとに noise, sum 1/f(noise), sum1/f(|noise|), sin(x + sum 1/f(|noise|) をそれぞれあらわすことにしています(厳密には 4 つめは sin 関数は使っていませんがリンク先の表記を用いてます)。またノイズのみのパターンとするかディフューズカラーをつけるかを bMono で選択できるようにしています。モデルに関しては Shpere, Cube, Teapot から選択が可能です。このプログラムからできるノイズは以下の様になります(これら画像はノイズパターンとディフューズカラーをつけた 2 種類を後でまとめたもので、実際にこのような絵が出るようには作っていません)。

Noise1.jpgNoise4.jpg
Noise2.jpgNoise3.jpg
fig.4 それぞれの noise

Vertex Program の方は Textured Phong ほぼそのままで、Texcoord3d に対して fScale をかけた gl_Vertex を格納する部分が追加してあります。これは他の RenderMonkey のサンプルでも多用されている手法で、前回説明したモデルの大きさにテクスチャ座標が関係するため、外部から調整できるようにしています。

そして Perlin Noise の要は Fragment Program です。{ fNoiseSel | [0, 0.25) } では単純に texture3D( volumeMap, Texcoord3d ) によって Random3D.dds をサンプリングしています。ここはテクスチャのフィルタリング GL_LINEAR により線形補間されたサンプリングが行われています。

区間 { fNoiseSel | [0.25, 0.5) } では sum 1/f(noise) を作っています。ここでは単純に 4 オクターブ分を加算しています。オクターブが上がる毎に周波数は 2 倍にし、振幅は 1/2 にしています。振幅の 1/2 は単純でわかりやすいですが、周波数が 2 倍はテクスチャ座標が 2 倍になったと考えています。例えば [0.0, 1.0] 区間のテクスチャ座標があった場合、これを [0.0, 2.0] 区間であると見立てることによって周波数 2 倍を考えています。なのでリピートした乱数になってしまっています。

区間 { fNoiseSel | [0.5, 0.75) } は sum 1/f(|noise|) です。テクスチャの特性上サンプリングされる値は [0.0, 1.0] であり、perlin_noise() 関数ではオクターブ毎に [0.0, 0.5], [0.0, 0.25], [0.0, 0.125], ... の値を返しています。これから [-0.25, 0.25], [-0.125, 0.125], ... を返す zero_center_perlin_noise() を使うようにします。ここでも 4 オクターブ分を加算して絶対値をとり [0.0 1.0] の範囲に戻しています。ディフューズカラーにするときにはこのままではうまくカラーが作れなかったので 1.0-the_noise としてディフューズカラーをかけています(それ以前に何か誤解がありそう・・・)。

最後は、リンク先では sin(x + sum 1/f(|noise|) と表記されていますが sin を使ってうまく実現できなかったため、fract などを使ってそれっぽくしてみました。ベースは Wood.rfx です。

というわけで RenderMonkey の .rtx ファイルはこれです。
_test_perlin_noise.lzh
これだけで疲れてしまった。。エントリの作り方を見直そう。。


投稿者 napier : 2006年02月19日 23:55


トラックバック

このエントリーのトラックバックURL:
http://will.squares.net/mt/mt-modified-tb.cgi/292