17

シェーダーを作成して、影付きの円を描画することにより、ポイントスプライト上に球をレンダリングします。互いに近い球が正しく交差するように、深度コンポーネントと色を作成する必要があります。

私はJohnaHolwerdaによって書かれたものと同様のコードを使用しています:

void PS_ShowDepth(VS_OUTPUT input, out float4 color: COLOR0,out float depth : DEPTH)
{
   float dist = length (input.uv - float2 (0.5f, 0.5f)); //get the distance form the center of the point-sprite
   float alpha = saturate(sign (0.5f - dist));
   sphereDepth = cos (dist * 3.14159) * sphereThickness * particleSize; //calculate how thick the sphere should be; sphereThickness is a variable.

   depth = saturate (sphereDepth + input.color.w); //input.color.w represents the depth value of the pixel on the point-sprite
   color = float4 (depth.xxx ,alpha ); //or anything else you might need in future passes
}

そのリンクのビデオは、私が求めている効果の良いアイデアを提供します。ポイントスプライトに描かれた球は正しく交差します。説明のために以下の画像を追加しました。

ポイントスプライト自体の深さを正確に計算できます。ただし、球の厚さをピクセルで計算してスプライトの深さに追加し、最終的な深さの値を与えることを示すかどうかはわかりません。(上記のコードは、変数を計算するのではなく使用します。)

私はこれを数週間オンとオフで取り組んできましたが、それを理解していません-それは単純だと確信していますが、それは私の脳が小刻みに動くことはありません。

Direct3D 9のポイントスプライトサイズはピクセル単位で計算され、私のスプライトにはいくつかのサイズがあります-距離によるフォールオフ(頂点シェーダーでポイントサイズの計算に使用された古い固定関数パイプラインと同じアルゴリズムを実装しました)と、スプライトはを表します。

ピクセルシェーダーにあるデータ(スプライトの位置、スプライトの深さ、元のワールドスペースの半径、画面上のピクセルの半径、スプライトの中心からの問題のピクセルの正規化された距離)から深さの値に移動するにはどうすればよいですか?深さ座標 でのスプライトサイズと球の厚さの単純な部分解は問題ありません。これは、中心からの正規化された距離でスケーリングして、ピクセルでの球の厚さを取得できます。

SMの上限としてシェーダーモデル3でDirect3D9とHLSLを使用しています。

写真で

テクニックと私が問題を抱えているポイントを示すために:

2つのポイントスプライトから開始し、ピクセルシェーダーで、クリップを使用して円の境界の外側のフラグメントを削除し、それぞれに円を描画します。

ここに画像の説明を入力してください

結局、それらは平らな表面であるため、一方が他方の上にレンダリングされます。

次に、シェーダーをさらに高度にし、照明付きの球のように円を描きます。フラットスプライトは3Dに見えますが、幻想であるため、一方をもう一方の前に完全に配置して描画することに注意してください。フラットスプライトはまだフラットです。

ここに画像の説明を入力してください

(上記は簡単です。これは私が問題を抱えている最後のステップであり、どのように達成するかを尋ねています。)

これで、ピクセルシェーダーがカラー値のみを書き込む代わりに、深度も書き込む必要があります。

void SpherePS (...any parameters...
    out float4 oBackBuffer : COLOR0,
    out float oDepth : DEPTH0 <- now also writing depth
   )
{

球の間の距離が半径よりも小さい場合、球が交差することに注意してください。

ここに画像の説明を入力してください

この最後のステップを達成するために、正しい深度値を計算するにはどうすればよいですか?

編集/メモ

何人かの人々は、実際の球は遠近法のために歪むだろうとコメントしました。それは特に画面の端に見えるかもしれません、それで私は別のテクニックを使うべきです。まず、それを指摘してくれてありがとう、それは必ずしも明白ではなく、将来の読者にとって良いことです!次に、私の目的は、遠近法が正しい球をレンダリングすることではなく、数百万のデータポイントを高速にレンダリングすることです。視覚的には、球のようなオブジェクトはフラットスプライトよりも見栄えがよく、空間位置もよく表示されると思います。わずかな歪みや歪みの欠如は問題ではありません。デモビデオを見れば、それがどのように便利なビジュアルツールであるかを見ることができます。単純なハードウェアで生成されたポイントスプライトと比較して三角形の数が多いため、実際の球メッシュをレンダリングしたくありません。私は本当にポイントスプライトの手法を使用したいと思っています。正しい深度値を計算するために、現存するデモ手法を拡張したいだけです。デモでは、それがどのように導出されたかのソースなしで変数として渡されました。

4

3 に答える 3

10

昨日、解決策を思いつきました。これはうまく機能し、スプライトに描かれた球体の望ましい結果を生成し、シーン内の他のオブジェクトや球体と交差する正しい深度値を備えています。必要以上に効率的ではない可能性があり (たとえば、スプライトごとに 2 つの頂点を計算して投影する)、数学的に完全に正しくない可能性があります (ショートカットが必要です) が、視覚的には良好な結果が得られます。

テクニック

「球」の深さを書き出すには、深さ座標で球の半径を計算する必要があります。つまり、球の半分の厚さです。この量は、球の中心からどれだけ離れているかによって、球の各ピクセルを書き出すときにスケーリングできます。

深さ座標の半径を計算するには:

  • 頂点シェーダー:投影されていないシーン座標で、目から球の中心 (つまり、ポイント スプライトを表す頂点) を通って光線を放ち、球の半径を追加します。これにより、球の表面にあるポイントが得られます。スプライト頂点と新しい球面頂点の両方を投影し、それぞれの深さ (z/w) を計算します。異なるのは、必要な深さの値です。

  • ピクセル シェーダー:clip円を描画するには、円の外側にピクセルを描画しないように使用して、スプライトの中心からの正規化された距離を既に計算しています。正規化 (0-1) されているため、これに球の深さ (半径の深さの値、つまり球の中心のピクセル) を掛けて、フラット スプライト自体の深さに加算します。これにより、球の中心で深さが最も厚くなり、球の表面に沿って 0 とエッジになります。(必要な精度に応じて、余弦を使用して曲線の厚さを取得します。線形は完全に見栄えの良い結果をもたらすことがわかりました。)

コード

私のエフェクトは会社用であるため、これは完全なコードではありませんが、ここのコードは実際のエフェクト ファイルから不要なものや独自のものを省略して書き直したものであり、テクニックを実証するのに十分なはずです。

頂点シェーダー

void SphereVS(float4 vPos // Input vertex,
   float fPointRadius, // Radius of circle / sphere in world coords
   out float fDXScale, // Result of DirectX algorithm to scale the sprite size
   out float fDepth, // Flat sprite depth
   out float4 oPos : POSITION0, // Projected sprite position
   out float fDiameter : PSIZE, // Sprite size in pixels (DX point sprites are sized in px)
   out float fSphereRadiusDepth : TEXCOORDn // Radius of the sphere in depth coords
{
    ...
   // Normal projection
   oPos = mul(vPos, g_mWorldViewProj);

   // DX depth (of the flat billboarded point sprite)
   fDepth = oPos.z / oPos.w;

   // Also scale the sprite size - DX specifies a point sprite's size in pixels.
   // One (old) algorithm is in http://msdn.microsoft.com/en-us/library/windows/desktop/bb147281(v=vs.85).aspx
   fDXScale = ...;
   fDiameter = fDXScale * fPointRadius;

   // Finally, the key: what's the depth coord to use for the thickness of the sphere?
   fSphereRadiusDepth = CalculateSphereDepth(vPos, fPointRadius, fDepth, fDXScale);

   ...
}

すべて標準的なものですが、使用方法を示すために含めています。

重要な方法と質問への答えは次のとおりです。

float CalculateSphereDepth(float4 vPos, float fPointRadius, float fSphereCenterDepth, float fDXScale) {
   // Calculate sphere depth.  Do this by calculating a point on the
   // far side of the sphere, ie cast a ray from the eye, through the
   // point sprite vertex (the sphere center) and extend it by the radius
   // of the sphere
   // The difference in depths between the sphere center and the sphere
   // edge is then used to write out sphere 'depth' on the sprite.
   float4 vRayDir = vPos - g_vecEyePos;
   float fLength = length(vRayDir);
   vRayDir = normalize(vRayDir);
   fLength = fLength + vPointRadius; // Distance from eye through sphere center to edge of sphere

   float4 oSphereEdgePos = g_vecEyePos + (fLength * vRayDir); // Point on the edge of the sphere
   oSphereEdgePos.w = 1.0;
   oSphereEdgePos = mul(oSphereEdgePos, g_mWorldViewProj); // Project it

   // DX depth calculation of the projected sphere-edge point
   const float fSphereEdgeDepth = oSphereEdgePos.z / oSphereEdgePos.w;
   float fSphereRadiusDepth = fSphereCenterDepth - fSphereEdgeDepth; // Difference between center and edge of sphere
   fSphereRadiusDepth *= fDXScale; // Account for sphere scaling

   return fSphereRadiusDepth;
}

ピクセル シェーダー

void SpherePS(
   ...
    float fSpriteDepth : TEXCOORD0,
    float fSphereRadiusDepth : TEXCOORD1,
    out float4 oFragment : COLOR0,
    out float fSphereDepth : DEPTH0
   )
{
   float fCircleDist = ...; // See example code in the question
   // 0-1 value from the center of the sprite, use clip to form the sprite into a circle
   clip(fCircleDist);    

   fSphereDepth = fSpriteDepth + (fCircleDist * fSphereRadiusDepth);

   // And calculate a pixel color
   oFragment = ...; // Add lighting etc here
}

このコードは照明などを省略しています。ピクセルがスプライトの中心からどれだけ離れているかを計算するには ( を取得するため) 、既に円を描いたfCircleDist質問 (' ' を計算します) のサンプル コードを参照してください。float dist = ...

最終結果は...

結果

ポイント スプライトを使用した球体の交差

出来上がり、球を描くポイント スプライト。

ノート

  • スプライトのスケーリング アルゴリズムでは、深度もスケーリングする必要がある場合があります。その線が正しいかどうかはわかりません。
  • 数学的に完全に正しいわけではありませんが (近道を取ります)、ご覧のとおり、結果は視覚的に正しいです
  • 数百万のスプライトを使用しても、レンダリング速度は良好です (VMWare Fusion でエミュレートされた Direct3D デバイスで、300 万のスプライトでフレームあたり 10 ミリ秒未満)。
于 2012-09-18T13:47:30.610 に答える
2

最初の大きな間違いは、実際の 3D 球体が遠近法 3D 投影では円に投影されないことです。

これは非常に直感的ではありませんが、いくつかの写真を見てください。特に視野が広く、球が中心から外れています。

第 2 に、最初はポイント スプライトを使用しないことをお勧めします。特に最初のポイントを考えると、必要以上に難しくなる可能性があります。球の周りに寛大なバウンディング クワッドを描画し、そこから移動するだけです。

シェーダーでは、スクリーン空間の位置を入力として持つ必要があります。それから、ビュー変換、および射影行列から、目の空間の線を得ることができます。この線を目の空間 (レイ トレーシング) で球体と交差させ、目の空間の交点を取得し、それを変換して画面空間に戻す必要があります。次に、深さとして 1/w を出力します。私は少し酔っていて怠け者で、とにかくあなたが本当にやりたいことだとは思わないので、ここであなたのために計算をしていません. ただし、これは線形代数の優れた演習になるので、試してみるとよいでしょう。:)

おそらく実行しようとしている効果は深度スプライトと呼ばれ、通常、正投影とテクスチャに格納されたスプライトの深度でのみ使用されます。たとえば、アルファチャンネルに色とともに深度を保存し、 eye.z+(storeddepth-.5)*depthofsprite を出力するだけです。

于 2012-09-14T05:51:32.930 に答える
1

通常、球は円に投影されません。これが解決策です。

この技法は と呼ばれspherical billboardsます。詳細な説明は、この論文で見つけることができます: Spherical Billboards and their Application to Rendering Explosions

ポイント スプライトをクワッドとして描画し、深度テクスチャをサンプリングして、ピクセルごとの Z 値と現在の Z 座標の間の距離を見つけます。サンプリングされた Z 値と現在の Z の間の距離は、ピクセルの不透明度に影響を与え、下にあるジオメトリと交差するときにピクセルが球のように見えるようにします。この論文の著者は、不透明度を計算するために次のコードを提案しています。

float Opacity(float3 P, float3 Q, float r, float2 scr)
{
   float alpha = 0;
   float d = length(P.xy - Q.xy);
   if(d < r) {
      float w = sqrt(r*r - d*d);
      float F = P.z - w;
      float B = P.z + w;
      float Zs = tex2D(Depth, scr);
      float ds = min(Zs, B) - max(f, F);
      alpha = 1 - exp(-tau * (1-d/r) * ds);
   }
   return alpha;
}

これにより、ビルボードがシーン ジオメトリと鋭く交差するのを防ぐことができます。

ポイント スプライト パイプラインを制御するのが難しい場合 (DirectX ではなく OpenGL についてしか言えません)、GPU で高速化されたビルボードを使用することをお勧めします。パーティクルの中心に一致する 4 つの等しい 3D 頂点を指定します。次に、それらを頂点シェーダーの適切なビルボード コーナーに移動します。つまり、次のようになります。

if ( idx == 0 ) ParticlePos += (-X - Y);
if ( idx == 1 ) ParticlePos += (+X - Y);
if ( idx == 2 ) ParticlePos += (+X + Y);
if ( idx == 3 ) ParticlePos += (-X + Y);

これは最新の GPU パイプラインにより適したものであり、粗いものは縮退していない透視投影で機能します。

于 2012-09-16T19:17:10.037 に答える