Hack and Play

プログラミングやCG、ゲーム、コンピュータのネタを投稿していくブログ。不定期更新。

よく分かるかもしれないOculus Riftについて(2) - Oculus Rift向けのポストプロセスの部分について

2013年07月06日 | CG関連

はじめに

Oculus Rift向けの公式に対応している開発環境はUnity Proですが、今はTrial版も出ているため、触っておられる方も多いと思います。
しかし私は、はじめからこのソフトは使うことを考えてはいませんでした。それでもなんとか、見れるようになりましたので、解説をして行きたいと思います。

魚眼モデルスクリーンについて

人間の目は、光学レンズとしての能力に忠実であり、Oculus Riftもその性質を生かして設計されています。 まず、魚眼モデルでは、人間の目に対して、半球状の投影スクリーンを投影している状態と同じです。Oculus Riftは平面モデルの液晶を持っていますが、これをレンズでうまく加工して目の前に半球状の投影スクリーンを置いているように見せかけています。 

 

 

極座標系への変換

図中の青い部分はCGでの撮像面、つまり映像そのものであり、赤い部分は魚眼モデルでの撮像面となります。カメラは本来、赤い部分の撮像面が平面に射影するため、光学的な歪みを持っていますが、レンズの性質やソフトウェアでこの歪みを無くす処理がされています。Oculus Riftでは、この歪みの状態を再現する必要があります。  撮像面の大きさをwとする時、計算するための撮像面をw=2uとおき、魚眼モデルの撮像面位置に対応する平面モデルの位置を計算します。

平面モデルの単位である1uは直交座標系、魚眼モデルの単位である1ucは極座標系のため、対応点を見つけるには座標変換をする必要があります。図中の点pは3次元空間上の点、点p'はCG映像や写真で撮像された面での対応点になります。OculusRiftでは、p''が対応する点となります。

 

ここでの解説は、p''の位置に対応するp'を求めるための方法について解説します。まずはfocal legnthを計算します。ここでの焦点距離の定義は、カメラ光学と同様に焦点から撮像面までのことを差します。

focal lengthが1、視野角の角度をθとする時、撮像面の大きさはTan(θ/2)となります。Tanの性質上、90°は求められないため、算出できる視野角は1°~189°までとなります。ここで、撮像面の大きさを単位数(1u)にした場合、focal lengthは Cot(θ/2)*u = 1/Tan(θ/2)*uで求めることができます。これによって、1uに対する比率としてfocal lengthを得ることができます。

1ucは画面中心からの視野の最大角の単位(ラジアン)となり、視野角をθとすると、1uc = θ/2となります。つまり、半球上の極座標点 p''は -θ/2<=p<=θ/2の区間で表すことができます。

よってp''のに対応するp'の位置はp'= f * Tan((θ/2) * p'')となります。視野角を110°として実数に直すと、f * Tan(55° * p'')になります。p''が1ucの時、Tan(55°) * f = 1uとなり、これにより、半球型の撮像面上の点から平面型の撮像面での対応点が求まります。

 

 

撮像面の変形

また、魚眼モデルの座標系は極座標系なので、常に焦点から撮像面までの距離を一定にする必要があります。これには撮像面を変形させる必要があります。
水平部分(y軸の値が0)では先ほどのモデルが通用しますが、ここでもレンズモデルが極座標系なので、極座標系から直交座標系に変換した時に樽型の歪みが生じます。
極座標系では画像中心からの距離と角度によって表されるため、p’’(θ,l)の極座標系の対応点をp’(x,y)とおくと、x^2+y^2 = l^2となり、p’=(cos(θ)*l,sin(θ)*l)で計算する事ができます。ただし、これは水平視野角、垂直視野角がいずれも180°の時になりますので、OculusRiftでは水平視野角が120°、垂直視野角が90°であり、計算が複雑になってしまいます。

樽型に歪ませる関数として、厳密に極座標系から直交座標系の式を導入する必要がない場合は、簡略式で済ませてしまいます。ただし、この方法はxとyがそれぞれ-1から1までの範囲を取るようにプロジェクション変換をする必要があります。

x’ = cos(y*垂直視野角(ラジアン)*歪曲強度 )* x;
y’ = cos(x*水平視野角(ラジアン)*歪曲強度 )* y;

cos(0)=1、cos(180°)=0なので、式を見るように、 yの値が大きいときはxの値が小さくなるようになり、xの値が大きいときはyの値が小さくなるように計算されます。
ただし、厳密的に証明している訳ではないので、歪曲強度のパラメータをいじって調整する必要があります。

この二つの方法により、擬似的ですがOculusRiftのポストプロセッシングを再現する事ができます。

GPUによる処理コード(GLSL)

最後にQuartzComposer上で書いたGLSLコードを紹介します。中には解説に登場しなかった変数も出てきますが、dist、distTX、distTY、は1から調整し、xshiftは0、xscale、yscaleは1から、offsetxも0からで調整していけば具合が良いと思います。参考になれば幸いです。

textureはレンダリングしたイメージ(水平視野角110°、垂直視野角90°以上でレンダリングしたもの)をバインドし、VertexShaderのメッシュは-1から1にスケーリングされたグリッドを使っています。分割されていればされているほど綺麗にレンズ状になります。

 

----ここからVertex Shader----
const float PI = 3.14159265358979323846264; //Constant Pi
uniform float fovX; //Field of View X
uniform float fovY; //Field of View Y
uniform float dist; //lens distortion(1.0~)
uniform float distTX; //another distortion(~1.0) horizontal texture magnification
uniform float distTY; //another distortion(~1.0) vertical texture magnification
uniform float xshift; //horizontal gaze shifthing(default value is 0)
uniform float xscale; //horizontal scale
uniform float yscale; //vertical scale
uniform float offsetx;//horizontal offset(a margin between both images)

//float fovX = 55.0; //Field of View X
//float fovY = 45.0; //Field of View Y
//float dist = 1.4; //lens distortion(min value is 1.0)
void main()
{
    //focal legnth = 1.0/2.0*cot(fov/180.0*PI) = 0.5/tan(fov/180.0*PI)
    //float focalX = 0.35010376910485486; //110(55) Degree focal length
    //float focalY = 0.5; //90(45) Degree focal length
    float focalX = 0.5/tan(fovX/180.0*PI);
    float focalY = 0.5/tan(fovY/180.0*PI);
    //vec4 pos = gl_Vertex * gl_ModelViewProjectionMatrix;
    vec4 pos = gl_Vertex;
    
    vec4 tpos = gl_TextureMatrix[0] * gl_MultiTexCoord0;
    //vec4 tpos = gl_MultiTexCoord0;
    
    float px = pos.x;
    float py = pos.y;
    
    float tx = (tpos.x - 0.5)*2.0;
    float ty = (tpos.y - 0.5)*2.0;
    
    float dx = cos(dist*py*fovY/180.0*PI)*px;
    float dy = cos(dist*px*fovX/180.0*PI+xshift/180.0*PI)*py;
    
    float ox = focalX*tan(distTX*tx*fovX/180.0*PI);
    float oy = focalY*tan(distTY*ty*fovY/180.0*PI);
    
    gl_Position.x = dx*xscale+0.5+offsetx;
    gl_Position.y = dy*2.0*yscale;
    gl_Position.z = 0.0;
    //gl_Position = pos;
    
    gl_TexCoord[0].x = ox+0.5;
    gl_TexCoord[0].y = oy+0.5;
    //gl_TexCoord[0] = tpos;
        
    //Forward current color and texture coordinates after applying texture matrix
    gl_FrontColor = gl_Color;
}
----ここまでVertex Shader----
----ここからPixel Shader----
uniform sampler2D texture;
void main()
{
    //Multiply color by texture
    gl_FragColor = gl_Color * texture2D(texture, gl_TexCoord[0].xy);
}
----ここまでPixel Shader----

ほか、上記のコードを使った参考動画


最新の画像もっと見る

コメントを投稿

ブログ作成者から承認されるまでコメントは反映されません。