OSLの次の一歩のためのメモ

早いものでアドベントカレンダーも間近の季節になりました。

大昔BlenderでOSLを使ってみる一歩一歩なんてエントリーを書いたこともあったので、ちょっとOSLについてメモのようなものをまとめておこうと思いました。

ちなみにBlenderのバージョンはこれを書いた時点での安定版である2.79bを基にしています。

情報源

まずは個人的によく参照する情報源です。

BlenderマニュアルのOpen Shading Languageの項

Docs » Render » Cycles Renderer » Nodes » Open Shading Language

言わずと知れたBlender公式マニュアルのOSLの項目です。最小限で全部英語ではあるものの、Script Nodeの使い方、簡単な例、Shaderノードに相当するclosureのリスト、 getattribute("アトリビュート名", return_var) で取得できるもののリスト、それからシェーダー中からレイを飛ばす trace 関数まで説明されています。

Open Shading Language のGitHub

OSLの公式リポジトリです。最新のSpecificationがあります。
言語自体の仕様は全てここに書かれています。
また、ありがたいことにKaz-Kitaguchi氏によるSpecificationの非公式日本語訳というものもあります。参考にさせていただきましょう。

CyclesのOSL関連の実装部分のソース

blender_source/intern/cycles/kernel/osl/

いうまでもなくBlenderおよびCyclesはオープンソースなので実装部分のソースを見ることができます。
ダウンロードページのOS自動判別のダウンロードボタンの下の『macOS,Linux,and other versions』などと書かれたプルダウンからソースのアーカイブを落とすことができます。
このアーカイブを展開したものをblender_sourceとして、CyclesでのOSLの実装部分のソースが上記のパスになります。特に重要なのはosl_service.cppです。ここに大抵のOSLの関数に対応するCyclesの実装が書いてあります。ファイルの上の方にアトリビュートが定数で並んでいますが、ファイル内を検索してみると実は非対応などというものもあったりします。

マテリアルノードのOSL実装

blender_source/intern/cycles/kernel/shaders

この中にはCyclesのノードを全てOSLで実装したものがあります。OSLモードで使われているものでしょう。 ちょっとしたテクニックから、Blender固有の事情になるUVや頂点カラーが複数ある場合の取得方まで意外と参考になったりします。 余談ですがoslutil.hにある float wireframe(string edge_type, float line_width, int raster) などはいかにもOSLを生かしたものという感じで面白いです。

Tips

小ネタをいくつか。

大域変数

Specificationの6.5 Global variablesで説明されている大域変数の表です。 何が定義されてるんだっけとよく参照するので書き出してみます。

変数 大雑把な説明
point P 位置
vector I 入射ベクトル
normal N スムースシェーディングなどが適用された法線
normal Ng Pの表面上の本当の法線
float u, v 面上の2D座標で表したP
vector dPdu, dPdv Pの表面上の接ベクトル
point Ps ライティングしようとしている位置
float time 今の時間
float dtime このサンプルがカバーしている時間
vector dPdtime 単位時間でのPの移動量
closure color Ci 入射輝度

これらについては使ったことがなくて本当に入ってくるかは知らないものもあります。 OSLの仕様上はこれらの変数には書き換え可能なものもあるのですが、Cyclesでは基本的に読み取り専用と考えておけば良かったような気がします(要出典)

getattribute()

BlenderのScript NodeのページにOSLで取得できるアトリビュートの一覧があると上に書きました。 しかしどんな型の変数で受け取れば良いのかが書かれていません。 大方想像はつくと思いますが、せっかくなので上記のマテリアルノードのOSL実装の中から探して並べてみました。

name 受け取る型
geom:generated point
geom:uv point
geom:dupli_generated point
geom:dupli_uv point
geom:trianglevertices point[3]
geom:numpolyvertices int(※現状は必ず3)
geom:polyvertices point[N]
geom:name string
geom:is_curve float
geom:curve_intercept float
geom:curve_thickness float
geom:curve_tangent_normal normal
object:location point
object:index float
object:random float
material:index float
particle:index float
particle:age float
particle:lifetime float
particle:location point
particle:size float
particle:velocity vector
particle:angular_velocity vector
path:ray_length float
path:ray_depth int
path:diffuse_depth int
path:glossy_depth int
path:transparent_depth int
path:transmission_depth int
"UV Mapの名前" point
"Vertex Colorの名前" color

object:indexmaterial:indexがfloatだったりと注意したいところがいくつかありそうです。

ちなみに探してみるとここに上がっていないものも取得する方法が見つかったりします。

trace()getmessage()

OSLはシェーダーの中でレイを飛ばすtrace(point position, vector direction, ...)と、そのレイがヒットした先の情報を取得するgetmessage("trace", "attribute名", attributeに合った型の変数)という強力な機能があります。

ただ少し制限があり、仕様では存在するtraceの"shade"と"traceset"の2つのオプションは無効になっています。 またそれに伴ってレイのヒット先からは取得できないアトリビュートがいくつかあります。 ドキュメントに書いてあるようにライティングに使うのではなく、周囲の調査に使ってアンビエントオクルージョンのように使えるかもね、というものです。

とはいえアイデア次第です。

関数の引数は参照扱い

最後にちょっとした仕様のことを。

Specification の 6.4.1 Function calls に、OSLはC言語風ですが関数に渡される引数は全て参照扱いです、と書いてあります。 わかる人にはこの解説で十分ですが、超訳して言うと、引数を再利用する形で処理を書くと呼び出し元の値も変更されますということです。

// 例えばこんな関数を書くと
color makeRed(color c) {
    c = color(1.0, 0.0, 0.0);
    return c;
}

void someProcess() {
    color white = color(1.0, 1.0, 1.0);
    color red = makeRed(white);    // <- これでwhiteは(1.0, 0.0, 0.0)になる
}

知っていれば複数の値を変更したい時などに便利な仕様ですが、知らないとびっくりします(笑

まあそもそもモダンな言語では引数は変更不可能だったりするのでこういう書き方は悪習の類ですよね…