レンズシェーダーについて


今回はSideFXのPAUL AMBROSIUSSEN氏が作成したレンズチュートリアルの紹介をしたいと思います。

 

今回のサンプルファイルです。
LensShader.zip

 

この記事はPAUL氏のオリジナルのチュートリアルを実際に検証して作成しています。チュートリアル上のタイトルは「LENS SHADERS FOR GAMEDEV」となっており、360度レンダリング、ライトマップのベイキング、Imposterツール用のテクスチャ作成、VR等の用途にレンズシェーダーが使えると説明されていますが、このスキルはゲームだけではなく、フィルム用途でも大いに役立つと思います。

 

レンズシェーダーの原理

レンダリングする際にカメラからレイを飛ばすのですが、この時スクリーンの座標がカメラスペースでX軸とY軸が -1 ~ 1 で表されます。

スクリーン上の各ピクセルをポイントで表すと下の図のようになります。カメラからこのポイントに対してレイを放ちます。

通常のレンダリングは下のGIFの様にカメラからスクリーンの各ピクセル向かってレイを投射することでピクセルの色を計算しています。

 

レンズシェーダーはこの「レイをどこからどこへ投射するか」を定義したものです。例えば Perspective と Orthographic のレイが放たれる方向を比較すると下のようになります。Orthographic ではレイはスクリーンと同じ方向に真っ直ぐ飛びますが、Perspective では放射状になっています。

 

このグリッド状のポイントを変形させてレイを飛ばす方向を自由にカスタマイズすることができればレンズの湾曲など、面白いレンダリング結果が得られるわけです。 CVEX でレンズシェーダーの作成、そしてカメラにレンズシェーダーを適用する事で、カメラのプライマリレイをコントロールする事ができます。

 

 

CVEX でレンズシェーダーの作成

レンズシェーダーを作るには、FIle > New Asset… から現れるNew Asset ウィンドウで、Operator Name と Operator Label に好みの名前を設定します。今回のサンプルではMyLensShaderという名前にしました。Network Type を CVEX Operator にして、 Operator Stype はVEX Type にします。Accept を押すと、Edit Operator Type Properties が現れ、この Code タブの中にCVEXコードを書いてレンズシェーダーを作ります。このCVEXシェーダーはSHOPレベルで呼び出すことができます。

レンズシェーダーをカメラに適用するには、Projection から Lens Shader を選び、Lens Shader で作ったCVEXシェーダーを指定します。

 

 

パラメーター一覧

レンズシェーダーを書く時に、以下のパラメーターが自動的にセットされます。なのでこれらのパラメーターを初期化したり、UI上のパラメーターで値を変更してもレンダリング時に自動的に値が上書きされてしまいます。

  • float x – X 座標の値 -1 to 1 (カメラ座標)
  • float y – Y 座標の値 -1 to 1 (カメラ座標)
  • float Time –サンプルタイム
  • float dofx – X 被写界深度のサンプル値
  • float dofy – Y 被写界深度のサンプル値
  • float aspect – 画像のアスペクトレシオ (x/y)

 

そして以下の3つの変数を出力します。レンズシェーダーを作るという事はこの P と I をどうコントロールするかという事です。

  • export vector P – レイの原点(カメラ座標)
  • export vector I – レイの方向(カメラ座標)
  • export int valid –サンプルが有効かどうか (0にすると飛ばしたレイのサンプル結果は破棄されます)

 

以下に様々なタイプのレンズシェーダーのコードを紹介しますが、コードの全てを説明することは出来ないので重要なポイントだけを説明します。またこのコードをコピーペーストすれば動作するはずです。

 

Orthographic

P の位置はそのままで、I でZ軸方向へ真っ直ぐレイを飛ばしています。P = set(x/zoom, y/(aspect*zoom), 0); という文で y の値を aspect で割っていますが、これは先ほど説明した通り、スクリーンは -1 ~ 1 で表されますが、レンダリングのサイズは横長になっているため普通にレンダリングすると押しつぶされた様な絵になってしまいます。これをaspect で割ることで正しい比率でレンダリングできるようにしています。また、x と y を zoom で割ることにより画角を変えることが出来ます。

cvex
MyLensShader(
    //Inputs
    float x = 0;
    float y = 0;
    float Time = 0;
    float dofx = 0;
    float dofy = 0;
    float aspect = 1;
   
    //Outputs
    export vector P = 0;
    export vector I = 0;
    export int valid = 1;
   
    // Custom
    float zoom = 1;
    )
{

    P = set(x/zoom, y/(aspect*zoom), 0);
    I = set(0,0,1);
}

 

 

Perspective

Orthographic と似ていますが、異なるのはレイの原点が (0,0,0) に位置し、法線が放射状に伸びている点です。法線のZ軸でレンズの曲面を表す事ができるようになっています。

cvex
MyLensShader(
    //Inputs
    float x = 0;
    float y = 0;
    float Time = 0;
    float dofx = 0;
    float dofy = 0;
    float aspect = 1;
   
    //Outputs
    export vector P = 0;
    export vector I = 0;
    export int valid = 1;
   
    // Custom
    float zoom = 1;
    float curvature = 0.0;
    )
{

    P = set(0, 0, 0);
    I = set(x/zoom,y/(zoom*aspect),1+(1-2*(x*x+y*y)) * curvature);
}

 

curvature のパラメーターを変えていくと下のように画像が歪んでいき、魚眼レンズのようになっていくのが分かるかと思います。

レンズの曲面を表した 1+(1-2*(x*x+y*y)) * curvature の部分をSOPで表してみると下のようになります。(P はビジュアライズのために原点には置いていません。)

 

Polar

レイの原点を(0,0,0)に置き、法線を放射状にすることでシーン全体をレンダリングし、lat-long 画像を作ります。PI という変数を読み込むために、math.h を含めています。

#include “math.h”

cvex
MyLensShader(
    //Inputs
    float x = 0;
    float y = 0;
    float Time = 0;
    float dofx = 0;
    float dofy = 0;
    float aspect = 1;
   
    //Outputs
    export vector P = 0;
    export vector I = 0;
    export int valid = 1;    
    )
{

    float   xa = -PI*x;
    float   ya = (0.5*PI)*y;
    float   sx = sin(xa);
    float   cx = cos(xa);
    float   sy = sin(ya);
    float   cy = cos(ya);

    P = set(0, 0, 0);
    I = set(cx*cy, sy, sx*cy);
}

 

これをSOP上で確認してみると、スクリーンを表すGrid が Sphere の形状に変形していくのが分かるかと思います。(P はビジュアライズのために原点には置いていません)

 

 

Cylindrical

シリンダー状にレンダリングします。P は Y軸をそのあままに、X と Z 軸を 0 にして、法線を放射状になるようにします。

#include “math.h”

cvex
MyLensShader(
    //Inputs
    float x = 0;
    float y = 0;
    float Time = 0;
    float dofx = 0;
    float dofy = 0;
    float aspect = 1;
   
    //Outputs
    export vector P = 0;
    export vector I = 0;
    export int valid = 1;
   
    // Custom
    float cylinder_amount = 1;
    )
{

    float offset = 2 * PI * cylinder_amount;
    float HalfPI = 0.5*PI;
    float theta = fit(x, -1, 1, 0, offset);
   
    P = set(0, y, 0);
    I = set(cos(theta – (offset*0.5) + HalfPI), 0, sin(theta – (offset*0.5) + HalfPI));
}

 

P と I をSOP上で表したものが下のGIFになります。スクリーン用のGridがシリンダー形状になるのが分かります。

 

Texture Atlas

Texture Atlas は Game Development Tool の Imposter ツールで使えるテクスチャをレンズシェーダーを通してレンダリングできるようにしたものです。通常であれば異なる角度から複数のカメラを使ってレンダリングした物をつなぎ合わせるのですが、レンズシェーダーを使えばレンダリングを1回しただけで、後からつなぎ合わせる作業は必要ありません。Texture Atlas を使うときはレンダリングしたいジオメトリとカメラを原点(0,0,0)に置き、カメラの回転を(0,90,0)とします。レンダリング画像は正方形で2のn乗のサイズにします。

オリジナルのチュートリアルのコードをコピーペーストするとエラーが出てコンパイル出来ないので、以下の文で付いていた vector キーワードを削除しました。

P = swizzle(ImpostorVector(Input), 0, 2, 1);

また、I が反対の方向を向いているので、法線を反転したものを I に代入しています。

I = -N;

 

#include “math.h”

cvex
MyLensShader(
    //Inputs
    float x = 0;
    float y = 0;
    float Time = 0;
    float dofx = 0;
    float dofy = 0;
    float aspect = 1;
   
    //Outputs
    export vector P = 0;
    export vector I = 0;
    export int valid = 1;
   
    // Custom
    float xy_size = 6;   
    float camera_width = 0.2;   
    float camera_zoom = 10;   
    )
{

    int nFrames = int(xy_size * xy_size);  
        
   
    float fx = fit(x, -1, 1, 0, 1) * float(xy_size);   
    float fy = (1 – fit(y, -1, 1, 1, 0)) * float(xy_size);   
    float floorx = clamp(float(xy_size) – trunc(fx) – 1.0, 0, xy_size-1);   
    float floory = clamp(float(xy_size) – trunc(fy) – 1.0, 0, xy_size-1);   
    float fracx = (frac(fx) * 2.0 – 1) * camera_width;   
    float fracy = (frac(fy) * 2.0 – 1) * camera_width;
   
    vector ImpostorVector(vector2 Coord){   
        vector2 CoordModify = Coord;   
        CoordModify.x *= 2.0;   
        CoordModify.x -= 1.0;   
        CoordModify.y *= 2.0;   
        CoordModify.y -= 1.0;
   
        vector ImpostorRenderVector;   
        vector2 HemiOct = set(CoordModify.x + CoordModify.y, CoordModify.x – CoordModify.y);
   
        HemiOct.x *= 0.5;   
        HemiOct.y *= 0.5;
   
        ImpostorRenderVector = set(HemiOct.x, HemiOct.y, 0);   
        ImpostorRenderVector.z = 1.0 – dot(abs(HemiOct), {1.0, 1.0});
   
        return normalize(ImpostorRenderVector);   
    }
   
    vector2 Input = set(float(int(floory)) / float(xy_size-1), float(int(floorx)) / float(xy_size-1));   
    P = swizzle(ImpostorVector(Input), 0, 2, 1);   
    vector N = normalize(P);   
    vector XDir = normalize(cross(set(0,1,0), N));   
    vector2 Coord = set(floorx, floory);
   
    if (xy_size%2==1) {   
        if(Coord == set(ceil(float(xy_size-1)/2.0), ceil(float(xy_size-1)/2.0)))   
            XDir = set(-1,0,0);
   
    }
   
    vector YDir = normalize(cross(XDir, N));
   
    P += ((XDir*fracx) + (YDir*-fracy));
    P =  (P * camera_zoom);
    I = -N;

}

 

このP と I をSOP上でビジュアライズした物が以下のGIFになります。グリッド がさらに細かいグリッドに分割されて、それぞれの位置に移動していく様子が分かります。

 

ASAD Lens

様々なタイプのレンズシェーダーのコードを紹介しましたが、Houdini にはデフォルトで ASAD Lens というレンズシェーダーがあり、Perspective、Polar、Cylinder はこのノードを使って行うことが出来ます。Type Property… を開くことでコードを見ることが出来るので内部でどんな事をしているのか勉強することができます。それぞれのプロジェクション方法のコードはチュートリアルで紹介されていた物と同じものとなっています。