HLSLで広角/魚眼レンズを作成するにはどうすればよいですか?


29

さまざまな四肢の広角レンズの効果を達成するために実装する必要がある概念は何ですか?

コンテンツパイプラインのさまざまな段階を参照する擬似コードと具体的な説明、およびソースコードからHLSLに渡す必要がある情報は非常に役立ちます。

また、広角レンズの実装と魚眼レンズの違いは何ですか?

回答:


37

広角レンズは、他の通常のレンズモデルと異なる動作をしてはなりません。それらは、より大きなFOV(あるD3DXMatrixPerspectiveFovLH意味では、DirectXを使用していると仮定しています)、またはより大きな左/右および下/上値(OpenGLのglFrustum意味)を持っています。

本当に面白い部分は魚眼レンズのモデリングにあると思います。ありますフィッシュアイはQuakeのそれはソースが付属して、あなたが勉強できること。

真の魚眼レンズ投影

ただし、魚眼レンズの投影は非常に非線形です。より一般的な(監視カメラに限られている私の知る限り)種類のレンズではM、空間内のポイントがユニット半球の表面に投影され、その表面がユニットディスクに平行投影されます。

           M
             x                 M: world position
              \                M': projection of M on the unit hemisphere
               \  ______       M": projection of M' on the unit disc (= the screen)
             M'_x'      `-.
             ,' |\         `.
            /   | \          \
           /    |  \          \
          |     |   \          |
__________|_____|____\_________|_________
                M"    O        1

より興味深い効果をもたらす可能性のある他の魚眼マッピングがあります。それはあなた次第です。

HLSLで魚眼効果を実装する2つの方法を見ることができます。

方法1:頂点シェーダーで投影を実行する

利点:コードを変更する必要はほとんどありません。フラグメントシェーダーは非常にシンプルです。のではなく:

...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...

あなたは次のようなことをします(おそらくかなり簡単にすることができます):

...
// This is optional, but it computes the z coordinate we will
// need later for Z sorting.
float4 out_Point = mul(in_Point, worldViewProjMatrix);

// This retrieves the world position so that we can project on
// the hemisphere. Normalise this vector and you get M'
float4 tmpPoint = mul(in_Point, worldViewMatrix);

// This computes the xy coordinates of M", which happen to
// be the same as M'.
out_Point.xy = tmpPoint.xy / length(tmpPoint.xyz);
...

欠点:レンダリングパイプライン全体が線形変換と考えられていたため、結果の投影は頂点に対して正確ですが、すべての変動はテクスチャ座標と同様に間違っており、三角形は歪んでいるように見えても三角形として表示されます。

回避策:より多くの三角形のサブディビジョンを使用して、洗練されたジオメトリをGPUに送信することにより、より良い近似値を取得することが許容される場合があります。これはジオメトリシェーダーでも実行できますが、このステップは頂点シェーダーの後に行われるため、ジオメトリシェーダーは独自の追加投影を実行する必要があるため、非常に複雑になります。

方法2:フラグメントシェーダーで投影を実行する

別の方法は、広角投影を使用してシーンをレンダリングし、その後、フルスクリーンフラグメントシェーダーを使用して画像を歪曲して魚眼効果を実現することです。

ポイントに魚眼画面のM座標(x,y)がある場合、それはで(x,y,z)半球の表面に座標があることを意味しz = sqrt(1-x*x-y*y)ます。つまり(ax,ay)thetaそのようなFOVでレンダリングされたシーン内の座標がありますa = 1/(z*tan(theta/2))。(ここでの数学については100%確信が持てませんが、今夜もう一度確認します)。

したがって、フラグメントシェーダーは次のようになります。

void main(in float4 in_Point : POSITION,
          uniform float u_Theta,
          uniform sampler2D u_RenderBuffer,
          out float4 out_FragColor : COLOR)
{
    z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y);
    float a = 1.0 / (z * tan(u_Theta * 0.5));
    out_FragColor = tex2D(u_RenderBuffer, (in_Point.xy - 0.5) * 2.0 * a);
}

利点:ピクセル精度に起因する歪みを除いて、歪みのない完璧な投影が得られます。

欠点:FOVが180度に到達できないため、シーン全体を物理的に表示できません。また、FOVが大きいほど、画像の中心の精度が低下します。これは、最高の精度が必要な場所です。

回避策:精度の低下は、いくつかのレンダリングパス(たとえば5)を実行することで改善でき、キューブマップの方法で投影を行います。別の非常に簡単な回避策は、目的のFOVに最終画像を単純にトリミングすることです-レンズ自体に180度のFOVがある場合でも、その一部のみをレンダリングしたい場合があります。これは「フルフレーム」フィッシュアイと呼ばれます(実際に画像をトリミングしているときに「フル」なものを取得したような印象を与えるため、ちょっと皮肉です)。

(注:これが有用であるが十分に明確ではない場合は、教えてください。これについてより詳細な記事を書きたいと思います)。


非常に便利です。もっと詳細な記事を心からお送りしたいと思います。
サーヤカロット

両方のアプローチを組み合わせて、より良い結果を得ることができますか?最初にすべてを表示するためにVSで投影し、次にPSで投影を解除し、再び投影して正しいUVとすべてを取得しますか?オリジナルに正しく投影を解除するには、PSにいくつかのパラメーターを送信する必要がある場合があります。
オンドレイペトルジルカ

3

はずz = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y)ですよね?

私のGLSL実装は次のとおりです。

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float fovTheta; // FOV's theta

// fisheye
void main (void)
{   
    vec2 uv = v_texCoord - 0.5;
    float z = sqrt(1.0 - uv.x * uv.x - uv.y * uv.y);
    float a = 1.0 / (z * tan(fovTheta * 0.5));
//  float a = (z * tan(fovTheta * 0.5)) / 1.0; // reverse lens
    gl_FragColor = texture2D(u_texture, (uv* a) + 0.5);
}

ちょっと@ジョシュ、どのようにfovThetaが計算されましたか?
トム

1
この回答は、書式を調整するために編集しただけです。@ bmanに直接対処したいと考えています。
ジョシュ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.