【OSL入門】MayaでArnoldのシェーダーを書く②~標準関数とグローバル変数~

OSLコードとシェーダーノード、グレースケールの3D球体を並べた「OSL入門②」のアイキャッチ Tech

はじめに

前回の記事: 【OSL入門】MayaでArnoldのシェーダーを書く①

前回の記事では、MayaのaiOslShaderでOSLを実行する基本手順を解説しました。

今回は、OSLの標準関数と、位置・法線・視線方向を取得するグローバル変数を扱います。

最後に法線と視線の内積を使い、球体の輪郭が明るくなるフレネル風マスクを実装します。

標準関数

前回の記事では、輝度を四則演算により求めました。
しかし、四則演算だけではできることに限界があります。

OSL ではたくさんの関数が用意されており、計算に使用できます。

いくつか例を挙げると、以下のようなものがあります。

関数用途
cos(), sin(), acos(), asin(), atan()…三角関数を求める関数
pow(), log(), log10(), exp(), sqrt(), mod()累乗、対数、平方根等を求める関数
floor(), ceil(), trunc(), round()丸め関数(切り捨てや四捨五入など)
cross(), length(), distance(), reflect()ベクトルの外積や長さ、距離などを求める関数
smoothstep(), noise()エルミート補間やノイズ関数
texture(), substr()テクスチャや文字列操作などその他色々

例えば、floor関数を用いることで、小数を切り捨てることができます。

float value = 4.3;
float result = floor(value);  // 結果: 4.0

length関数はベクトルの長さを求めることができます。

vector vec = vector(1.0, 2.0, 3.0);
float result = length(vec);  // 結果: 3.741657

実は前回四則演算によって求めた輝度も、luminance()という関数を用いることで簡単に計算することができます。
前回書いたコードをluminance()に置き換えると以下のようになります。

shader sample_shader(
    color inputColor = color(1.0, 0.0, 0.0),
    output color outColor = 0)
{
    float Y = luminance(inputColor);  // 変更点
    outColor = color(Y);
}

関数の種類やその使い方は OSL のドキュメント(7. Standard Library Functions)で確認できます。

7. Standard Library Functions – Open Shading Language 1.16.0 documentation

グローバル変数

レンダリングする点のことをシェーディングポイントといいますが、このシェーディングポイントのさまざまな情報はシェーダーを書く上でとても重要です。

例えば法線情報が分かれば、上を向いた面だけ色を変えたり、ライトとの角度を計算して明るさを求めたりできます。

OSL ではシェーディングポイントの情報をグローバル変数を介して取得することができます。

グローバル変数は、自分で宣言しなくても特定の情報を参照できるようにあらかじめ用意された変数です。

例えば、シェーディングポイントの座標は、グローバル変数 P を参照することで取得できます。

OSLはレンダリングするシェーディングポイントごとに計算処理が行われ、そのポイントの座標が変数Pから取得されます。
※ピクセルごとにシェーディングポイントが1つずつとは限りません。アンチエイリアス、反射、間接光などにより、複数回評価される可能性があります。

以下は座標によってレンダリングされる色を変える例です。

Maya ArnoldでOSLのグローバル変数Pの座標値をRGBとして球体に出力した結果
shader sample_shader(
    output color outColor = 0
){
    outColor = color(P);
}

このOSLでは、例えば P が(1.0, 0.0, 0.0)の時、RGB=(1.0, 0.0, 0.0)となり、赤くレンダリングされます。
※ P は0~1の範囲に収まらないことに注意してください。座標値を可視化する簡易例として扱います。

サンプルコードからわかる通り、P は通常の変数とは異なり事前に宣言することなく使用することができます。

代表的なグローバル変数をいくつか例示します。

変数名説明
Ppointシェーディングポイントの座標
Ivector入射レイからシェーディングポイントに向かうベクトル
N, Ngnormal法線ベクトル。Ng はスムージングやバンプが反映されない
u, vfloatシェーディングポイントのUV座標(厳密にはパラメトリック座標、また、テクスチャの原点は左上隅であることに注意。テクスチャについては次回記事で扱います。)

グローバル変数はOSL のドキュメント(6.6. Global variables)で確認できます。

6. Language Syntax – Open Shading Language 1.16.0 documentation

フレネル風シェーダー

フレネル反射

フレネル反射とは、光が媒質の境界面へ入射する角度や屈折率によって反射率が変化する現象です。

消灯したスマートフォンの画面を正面から見ると、反射はあまり目立ちませんが、画面を傾けて浅い角度から見ると、周囲の景色が強く映り込みます。
これもフレネル反射による現象です。

本記事では厳密なフレネル式ではなく、標準関数やグローバル変数を使用して、メッシュの面がカメラの方を向いていないほど明るくなるフレネル”風”シェーダーを作成します。

内積

”面がカメラの方向を向いているかどうか”は、内積を計算することで求めることができます。

内積は簡単に言えば、2つの矢印がどのくらい向きが似ているかを求める計算です。

CG 分野において頻出する演算なので覚えておくといいですが、OSL では、標準関数dot()を用いることで簡単に計算することができます。
2つのベクトルを v1, v2 とすると、以下のように書くことで内積を求めることができます。

vector v1 = vector(1.0, 0.0, 0.0);
vector v2 = vector(0.0, 1.0, 0.0);

float d = dot(v1, v2);

v1、v2 の長さが1、つまり単位ベクトルであれば、内積の結果は-1.0から1.0の間の値をとります。

2つのベクトルの向きが真逆に近くなるほど-1.0に、向きが近くなるほど1.0に近づきます。
2つのベクトルが垂直(直角)のとき、内積は0になります。

フレネル風シェーダーでは、シェーディングポイントにおける法線ベクトルとシェーディングポイントから見たカメラの方向ベクトルの内積をとり、これを色として出力します。

球面上のシェーディングポイントPと法線N、入射方向Iの関係を示す模式図

これにより、メッシュの面がカメラの方向を向くほど暗くなる表現が可能です。

実装

以下のコードから始めます。

shader fresnel_shader(
    output color outColor = 0
){
}

法線ベクトルと、カメラの方向ベクトルは、グローバル変数 N、I で参照することができます。

ただし、I はシェーディングポイントからカメラに向かうベクトルではなく、カメラからシェーディングポイントに向かうベクトルなので、-(マイナス)をつけて逆向きにします。
※正確には、Iは入射レイがシェーディングポイントへ向かう方向であり、反射や屈折などの二次レイではカメラからとは限りません。

-I; // シェーディングポイントからカメラに向かうベクトル

念のため、 I と N は関数 normalize() で明示的に正規化(単位ベクトルに変換)しておきます。

-normalize(I);  // カメラに向かう長さが1のベクトル
normalize(N);  // 長さが1の法線ベクトル

関数 dot() を使用して、この2つのベクトルの内積を求めます。

dot( normalize(N), -normalize(I) );

また、正規化した法線と視線方向の内積は、-1.0から1.0の範囲になります。
今回はカメラ側を向いている面だけを使用するため、clamp()で0.0から1.0の範囲に制限して変数へ代入します。
負の値は0.0として扱われます。

float d = clamp(dot(normalize(N), -normalize(I)), 0.0, 1.0);

変数 d は、カメラの方を向いている面ほど1.0に近づきます。
R, G, B 値にそのまま d を設定してしまうと、球体の輪郭に近づくほど暗くなってしまうので、 1.0 – d として、色変化を逆にします。

outColor = color(1.0 - d, 1.0 - d, 1.0 - d); 

事前に変数に代入して以下のように書くこともできます。

float rim = 1.0 - d;
outColor = color(rim, rim, rim);

さらに、色 C の R、G、B の値が同じ場合は、color(R, G, B) の代わりに、省略して color(C) と書くことができます。

outColor = color(1.0 - d);

ここまでの内容を踏まえて OSL を書くと、以下のようになります。

shader fresnel_shader(
    output color outColor = 0
){
    float d = clamp(dot(normalize(N), -normalize(I)), 0.0, 1.0);
    outColor = color(1.0 - d);
}

前回記事の手順で aiOslShader を作成し、Code 欄へ完成コードを入力してコンパイルします。
outColor を Shading Group または使用するマテリアルへ接続し、球体へ割り当てて Arnold でレンダリングすると、外側に行くほど白くレンダリングされます。

Maya Arnoldでフレネル風OSLシェーダーを球体に適用し、輪郭ほど白く表示した結果

調整用の変数を導入することで、幅を調整できます。
以下のコードでは、標準関数 pow と、調整用の入力 rimPower により、outColor を調整しています。

shader fresnel_shader(
    float rimPower = 1.0,
    output color outColor = 0
){
    float d = clamp(dot(normalize(N), -normalize(I)), 0.0, 1.0);
    outColor = color(pow(1.0 - d, rimPower));
}
Maya ArnoldのaiOslShaderでrimPowerを6.0に設定し、球体の白い輪郭を細く調整した結果
rimPowerを6.0にした場合

rimPower には0より大きい値を指定します。
1.0より大きくすると明るい輪郭が狭くなり、0.0より大きく1.0より小さい値にすると広くなります。

まとめ

今回は、OSL の標準関数とグローバル変数を使い、視線方向に応じて輪郭が明るくなるフレネル風マスクを作成しました。

  • 標準関数 normalize() や dot() でベクトルの正規化や内積を行う
  • グローバル変数 I, N からシェーディングポイントの情報を参照する

次回に続きます。

次回の記事: 【OSL入門】MayaでArnoldのシェーダーを書く③~テクスチャ~

コメント

タイトルとURLをコピーしました