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 : 23:55 | トラックバック


2006年02月12日

RenderMonkey(2) テクスチャパラメータ

新規にワークスペースを作ってみます。これはとても簡単で、RenderMonkey を立ち上げた後に [Effect Workspace で右クリック] → [Add Default Effect] → [DirectX/OpenGL] でそれぞれ選択が可能です。この雛型を選択するだけでそのまま動作するエフェクトが作成されます。

AddDefaultEffect.jpg
fig.1 Add Default Effect

例えば Textured Bump を選択します。デフォルト状態ではモデルは Sphere.3ds が選択されています[fig.2(a)]。モデルは前回示したように簡単に変更することができます[fig.2(b)]。

model_shpere.jpg
fig.2 (a) モデル変更前

model_teapot.jpg
fig.2 (b) モデル変更後

また Vertex Program / Fragment Program も自動的に生成されているので、あとは好きなように編集をすることが可能です。

uniform vec3 fvLightPosition;
uniform vec3 fvEyePosition;

varying vec2 Texcoord;
varying vec3 ViewDirection;
varying vec3 LightDirection;
   
attribute vec3 rm_Binormal;
attribute vec3 rm_Tangent;
   
void main( void )
{
   gl_Position = ftransform();
   Texcoord    = gl_MultiTexCoord0.xy;
    
   vec4 fvObjectPosition = gl_ModelViewMatrix * gl_Vertex;
   
   vec3 fvViewDirection  = fvEyePosition - fvObjectPosition.xyz;
   vec3 fvLightDirection = fvLightPosition - fvObjectPosition.xyz;
     
   vec3 fvNormal         = gl_NormalMatrix * gl_Normal;
   vec3 fvBinormal       = gl_NormalMatrix * rm_Binormal;
   vec3 fvTangent        = gl_NormalMatrix * rm_Tangent;
      
   ViewDirection.x  = dot( fvTangent, fvViewDirection );
   ViewDirection.y  = dot( fvBinormal, fvViewDirection );
   ViewDirection.z  = dot( fvNormal, fvViewDirection );
   
   LightDirection.x  = dot( fvTangent, fvLightDirection.xyz );
   LightDirection.y  = dot( fvBinormal, fvLightDirection.xyz );
   LightDirection.z  = dot( fvNormal, fvLightDirection.xyz );
   
}
fig.3 Vertex Program
uniform vec4 fvAmbient;
uniform vec4 fvSpecular;
uniform vec4 fvDiffuse;
uniform float fSpecularPower;

uniform sampler2D baseMap;
uniform sampler2D bumpMap;

varying vec2 Texcoord;
varying vec3 ViewDirection;
varying vec3 LightDirection;

void main( void )
{
   vec3  fvLightDirection = normalize( LightDirection );
   vec3  fvNormal         = normalize( ( texture2D( bumpMap, Texcoord ).xyz * 2.0 ) - 1.0 );
   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      = texture2D( baseMap, Texcoord );
   
   vec4  fvTotalAmbient   = fvAmbient * fvBaseColor; 
   vec4  fvTotalDiffuse   = fvDiffuse * fNDotL * fvBaseColor; 
   vec4  fvTotalSpecular  = fvSpecular * ( pow( fRDotV, fSpecularPower ) );
  
   gl_FragColor = ( fvTotalAmbient + fvTotalDiffuse + fvTotalSpecular );
       
}
fig.4 Fragment Program

これだけでも shader プログラムの取り掛かりとしては非常に敷居が低くなっています。

RenderMonkey 以外にも shader プログラムのコードを自動生成してくるソフトとし ShaderGen があります[fig.5]。このソフトは OpenGL の固定機能を OpenGL Shading Language に置き換えてくれるもので、従来の OpenGL の固定機能を知っている人が shader プログラムを始める場合には有用なソフトだと思います。

ShaderGen-3.0.jpg
fig.5 ShaderGen 3.0

さてここまでは前フリで、今回は基本となるテクスチャの扱いを簡単にまとめます。ワークスペースは新規に Screen-Aligned Quad を選択します[fig.6]。

Screen-AlignedQuad.jpg
fig.6 Screen-Aligned Quad

ここでテクスチャを base.tga から ati.tga に変更します。これはテクスチャの向きを確認するためです。base.tga から ati.tga に変更するとテクスチャの向きが上下が逆になっていることがわかります。これを直す方法はいろいろとありますが、ここでは Vertex Program を変更します。

varying vec2  texCoord;

void main(void)
{
   gl_Position = vec4( gl_Vertex.xy, 0.0, 1.0 );
   gl_Position = sign( gl_Position );
    
   // Texture coordinate for screen aligned (in correct range):
//   texCoord = (vec2( gl_Position.x, - gl_Position.y ) + vec2( 1.0 ) ) / vec2( 2.0 );
   texCoord = (vec2( gl_Position.x, gl_Position.y ) + vec2( 1.0 ) ) / vec2( 2.0 );
}
fig.7 Vertex Program の変更

また、これだけでは何なので Fragment Program も変更してみます。

uniform sampler2D Texture0;

varying vec2 texCoord;

void main(void)
{
//    gl_FragColor = texture2D( Texture0, texCoord );
    gl_FragColor = texture2D( Texture0, texCoord*2.0 );
}
fig.8 Fragment Program の変更

ここまでの変更で fig.9 の状態になっているはずです。

Screen-AlignedQuad_01.jpg
fig.9 texture, Vertex Program, Fragment Program 変更

さてここからが本題で、今回注目するのは Texture Parameter です。これは Texture0 をダブルクリックかマウス右クリックで Edit を選択します[fig.10]。

Screen-AlignedQuad_02.jpg
fig.10 Texture State Editor の選択

ここでは glTexParam{i|f|v}, glTexEnv{i|f|v} に関連するパラメータの設定を行うことができます。デフォルトでは GL_TextureMinify = GL_LINEAR_MIPMAP_LINEAR, GL_TextureMagnify = GL_LINEAR が設定されています[fig.11]。

Screen-AlignedQuad_03.jpg
fig.11 Texture State Editor デフォルト値

さてここで Fragment Program に戻り、以下の様に編集してみます[fig.12]。

uniform sampler2D Texture0;

varying vec2 texCoord;

void main(void)
{
//    gl_FragColor = texture2D( Texture0, texCoord );
    gl_FragColor = texture2D( Texture0, fract(texCoord*2.0) );
}
fig.12 Fragment Program の変更 2

この変更を行うと表示されているテクスチャの中央にスジが入るのが確認できると思います[fig.13]。

Screen-AlignedQuad_04.jpg
fig.13 スジが現れた状態

最初に RenderMonkey を使ったとき、何故テクスチャ座標の算出に fract, floor などを使うとこのスジが現れるのかがわかりませんでした。そのときにはまだ Texture State Editor の出し方がわからなかったため RenderMonkey のバグかとも思いましたが、実際にはこれは GL_TextureMinify が影響しています。というわけで Texture State Editor に戻り GL_TextureMinify を GL_LINEAR に変更します[fig.14]。

Screen-AlignedQuad_05.jpg
fig.14 GL_TextureMinify の変更

この変更により GL_LINEAR_MIPMAP_LINEAR の効果は得られなくなりますがテスクチャのスジは消せます。MIPMAP 画像が見れないので RenderMonkey がどんな MIPMAP を生成しているか謎ですが、うまく MIPMAP のレベル調整ができれば実際のプログラム上では問題はないでしょう。

私はこの問題にはまり「RenderMonkey の fract, floor ってバグってる?」と思いいろいろと検索をしてしました。結果的に web 上では情報が見つからなかったのですが、この問題のおかげでちょっとは RenderMonkey に慣れることができたな、と思います。

また、これ以外のパラメータも変更してみて下さい。特に fract を外して GL_TextureWrap{S|T|R} を変更すると普通に REPEAT/CLAMP 系の動作を確認することができます。


投稿者 napier : 19:08 | トラックバック


RenderMonkey(1) モデルデータとシェーダプログラム

RenderMonkey 付属のモデルデータは 3ds Max の .3ds ファイルが多くあります。どういった形状なのかを確認するためには Max などのモデラソフトが必要ですが、ここでは簡単に Metasequoia を利用してみます。Metasequoia にはシェアウェア版と無償版がありますが、無償版では .3ds ファイルは開けないようなのでシェアウェア版を使います。Metasequoia を使う理由は形状と大きさを見るためです。

RnderMonkey_Metasequoia.jpg
fig.1 RenderMonkey1.6 と Metasequoia2.3.4

このキャプチャ画像では Metasequoia は [表示] → [グリッドの設定] でメッシュの大きさを 100 にしています。ここで確認できるように RenderMonkey 付属のデータは float で 100.0 前後のデータであることがわかります。

先を急ぐ前に Metasequoia での .3ds ファイルのロードに関して。標準で .3ds をオープンしようとすると Metasequoia は Max の座標軸で開こうとします[fig.2(a)]。これを OpenGL の座標にあわせるために 3D Atelier を選択します[fig.2(b)(c)]。[fig.1] は 3D Atelier の座標軸で開いた状態です。ここでの RenderMonkey は Anisotropic.rfx を開いており、そのときの視点に関する情報は Camera ダイアログに表示されています。どちらとも OpenGL の座標系で統一されていることが確認できます。

open_3DStudio.jpgopen_axis.jpgopen_3DAtelier.jpg
fig.2(a) 3dsMax 座標軸fig.2(b) 座標軸選択fig.2(c) 3D Atelier 座標軸

.3ds ファイルが確認できるようになったところで RenderMonkey 付属のデータの確認をしてみます[fig.3]。大体 100.0 程度のモデルであることがわかります。この大きさは shader を記述する際に必要な数値となります。

RnderMonkey_models.jpg
fig.3 主なモデルデータ

注意点として、ここで示した 100.0 前後のモデル以外に 1.0 前後のモデルもあります。cube で言えば Cube.3ds は 2.0 で TexturedCube.3ds は 100.0 前後の大きさです。

さて、モデルの大きさと shader に関してだけ記述しておきます。cube を例にとり、まずモデルを読み込みます。大きさの違いを見るために Cube.3ds と TexturedCube.3ds を使います[fig.4]。

AddModel.jpg
fig.4 モデルの追加

ここで shader との関連を見るために、Vertex Program と Fragment Program を次のように修正します。変数などはそのままにしておきます。

uniform float noiseRate;
uniform mat4 view_proj_matrix;


attribute vec3 rm_Binormal;
attribute vec3 rm_Tangent;

varying vec3 vNormal;
varying vec3 vTangent;
varying vec3 vBinormal;
varying vec3 vViewVec;
varying vec3 vPos;

void main(void)
{
   gl_Position = ftransform();
   vPos      = gl_Vertex.xyz * noiseRate;
/*
   vTangent  = gl_NormalMatrix * rm_Tangent;
   vNormal   = gl_NormalMatrix * gl_Normal;
   vBinormal = gl_NormalMatrix * rm_Binormal;
    
   vViewVec.xyz    = vec3(  gl_ModelViewMatrix * gl_Vertex );
   // OpenGL has a different handedness, so we need to flip the z.
   vViewVec.z  = -vViewVec.z;
   
   vPos      = gl_Vertex.xyz * noiseRate;
*/   
}
fig.5 Vertex Program
uniform sampler3D Noise;
uniform vec4 lightDir;
uniform vec4 gloss;
uniform vec4 color;
uniform float noiseScale;

varying vec3 vNormal;
varying vec3 vTangent;
varying vec3 vBinormal;
varying vec3 vViewVec;
varying vec3 vPos;


varying vec3 pos;void main(void)
{
   gl_FragColor = texture3D(Noise, vPos);
/*
   vec3 viewVec = normalize(vViewVec);
   vec3 oglLightDir = vec3(lightDir.x, lightDir.y, -lightDir.z);

   // Grab an angle to rotate the tangent around the normal
   // This is done to create a slightly bumpy feeling
   float angle = noiseScale * (texture3D(Noise, vPos) - 0.5).x;

   // Our angle is within the [-PI, PI] range and we need both
   // the sine and cosine. Perfect for the sincos function,
   // which will save us some hardware instructions over separate
   // sin and cos calls.
   float cosA, sinA;
   sinA = sin(angle);
   cosA = cos(angle);

   // Rotate tangent around the normal
   vec3 tang =  sinA * vTangent + cosA * vBinormal;

   // Do the anisotropic lighting
   float diffuse = clamp( dot( oglLightDir.xyz, vNormal ), 0.0, 1.0 );
   float cs = -dot(viewVec, tang);
   float sn = sqrt(1.0 - cs * cs);
   float cl =  dot(oglLightDir.xyz, tang);
   float sl = sqrt(1.0 - cl * cl);
   float specular = pow( clamp( (cs * cl + sn * sl), 0.0, 1.0 ), 32.0);

   // Output the results
   gl_FragColor = diffuse * color + gloss * specular;
*/    
}
fig.6 Fragment Program

あとはリファレンスノードを Cube もしくは TexturedCube に変更します。ここでは Cube にしてみます[fig.7]。

ChangeReferenceNode.jpg
fig.7 リファレンスノード変更

Cube はサイズが 2.0 なので視点位置を変更します。また、これにあわせて noiseRate の値を変更してみます[fig.8]。Vertex Program で設定した vPos により頂点座標がそのままテクスチャ座標になっているので、ここではテクスチャ座標が [-1, 1] になり 2 回リピートしているのがわかります。

ChangeNoiseRate.jpg
fig.8 noiseRate 変更

再度リファレンスノードを変更し、今度は TexturedCube にしてみると、モデルの大きさと shader パラメータの関連がわかると思います[fig.9]。

TexturedCube.jpg
fig.9 リファレンスノードを TexturedCube に変更

意外と簡単ですが、やはりモデルデータの大きさを RenderMonkey だけでわかれば楽だったろうに、と思いますね。


投稿者 napier : 01:12 | トラックバック


2006年01月29日

RenderMonkey

久しぶりに使ってみようと思い、同梱されているサンプルをインデックス化しました(ドキュメントにあればする必要もなかったのですが・・・)。サンプルは 1.6 のものです。

アーカイブになるとページが重くなるのでリンクにしてあります。
RenderMonkey 1.6

RenderMonkey 本家はこちら。
http://www.ati.com/developer/rendermonkey/index.html


投稿者 napier : 02:27 | トラックバック