2013-07-01 35 views
23

パーティクルシステムを構築するにはポイントスプライトが最適ですか?パーティクルシステムのポイントスプライト

新しいバージョンのOpenGLと最新のグラフィックスカードのドライバにポイントスプライトがありますか?または、私はvboとglslを使ってそれを行うべきですか?

答えて

86

ポイントスプライトは確かに粒子システムによく適しています。しかし、彼らはVBOやGLSLとは関係がありません。つまり、それらは完全に直交した機能です。ポイントスプライトを使用するかどうかにかかわらず、ジオメトリのアップロードには常にVBOを使用する必要があります。ポイントやプリペア済みのスプライトなどは常に必要です。このジオメトリは常にシェーダのセットに入れなければなりません(最新のOpenGLもちろん)。

最新のOpenGLではポイントスプライトが非常にうまくサポートされていますが、これは古い固定機能アプローチのように自動的にはサポートされません。サポートされていない点は、ポイントのサイズをカメラとの距離に基づいてスケーリングできる点減衰機能です。頂点シェーダ内で手動で行う必要があります。同じように、特別な入力変数gl_PointCoord(現在のフラグメントがある点全体の[0,1]正方形のどこにあるか)を使用して、適切なフラグメントシェーダでポイントのテクスチャを手動で行う必要があります。例えば、基本的なポイントスプライトのパイプラインはこのようになります:

... 
glPointSize(whatever);    //specify size of points in pixels 
glDrawArrays(GL_POINTS, 0, count); //draw the points 

頂点シェーダ:

uniform mat4 mvp; 

layout(location = 0) in vec4 position; 

void main() 
{ 
    gl_Position = mvp * position; 
} 

フラグメントシェーダ:

uniform sampler2D tex; 

layout(location = 0) out vec4 color; 

void main() 
{ 
    color = texture(tex, gl_PointCoord); 
} 

そして、それがすべてです。もちろん、これらのシェーダはテクスチャ付きスプライトの最も基本的な描画を行いますが、さらなる機能の出発点です。たとえば、カメラとの距離に基づいてスプライトのサイズを計算するには、glEnable(GL_PROGRAM_POINT_SIZE)を指定し、頂点シェーダの特別な出力変数gl_PointSizeに書き込む必要があります。

uniform mat4 modelview; 
uniform mat4 projection; 
uniform vec2 screenSize; 
uniform float spriteSize; 

layout(location = 0) in vec4 position; 

void main() 
{ 
    vec4 eyePos = modelview * position; 
    vec4 projVoxel = projection * vec4(spriteSize,spriteSize,eyePos.z,eyePos.w); 
    vec2 projSize = screenSize * projVoxel.xy/projVoxel.w; 
    gl_PointSize = 0.25 * (projSize.x+projSize.y); 
    gl_Position = projection * eyePos; 
} 

これは、すべてのポイントスプライトが同じワールドスペースサイズ(したがって、ピクセル単位で異なるスクリーンスペースサイズ)を持つようにします。


最新のOpenGLでは完全にサポートされていますが、ポイントスプライトには欠点があります。最大の欠点の1つは、クリッピング動作です。点は中心座標で切り取られます(切り抜きはラスタライズ前、したがって点が拡大される前に行われるためです)。したがって、ポイントの中心が画面の外側にある場合でも、残りの部分は表示領域に表示されません。最悪の場合、ポイントが画面外に出ると、突然消えます。ただし、ポイントスプライトが大きすぎる場合は、これは目立つ(または抹消する)だけです。いずれにせよ、それほど多くのピクセルをカバーしていない非常に小さな粒子の場合、これはあまり問題にならず、パーティクルシステムはポイントスプライトの正規のユースケースとみなされます。大きな看板のためにそれらを使用してください。

これが問題であれば、現代のOpenGLは、CPU上の個々の四角形としてすべてのスプライトを事前に構築する素朴な方法以外に、ポイントスプライトを実装する他の多くの方法を提供します。あなたはまだそれらをポイントでいっぱいのバッファとしてレンダリングすることができます(そしてGPUベースのパーティクルエンジンから出てくる可能性が高い)。実際にクワッドジオメトリを生成するには、ジオメトリシェーダを使用します。これにより、シングルポイントからクワッドを生成できます。まず、頂点シェーダ内のモデルビュー変換のみを行います。

uniform mat4 modelview; 

layout(location = 0) in vec4 position; 

void main() 
{ 
    gl_Position = modelview * position; 
} 

ジオメトリシェーダは残りの作業を行います。これは、一般的な[0,1] -quadの4角の点位置を組み合わせて、クリップ空間への変換を完了:あなたは、その後、もちろんのではなく、様々なカスタムtexCoordを使用することになり、フラグメントシェーダで

const vec2 corners[4] = { 
    vec2(0.0, 1.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(1.0, 0.0) }; 

layout(points) in; 
layout(triangle_strip, max_vertices = 4) out; 

uniform mat4 projection; 
uniform float spriteSize; 

out vec2 texCoord; 

void main() 
{ 
    for(int i=0; i<4; ++i) 
    { 
     vec4 eyePos = gl_in[0].gl_Position;   //start with point position 
     eyePos.xy += spriteSize * (corners[i] - vec2(0.5)); //add corner position 
     gl_Position = projection * eyePos;    //complete transformation 
     texCoord = corners[i];       //use corner as texCoord 
     EmitVertex(); 
    } 
} 

テクスチャリングのためのgl_PointCoordの、私たちはもはや実際の点を描いていないからです。


それとも別の可能性(そしておそらくより速く、私が遅いという評判を持つジオメトリシェーダを覚えているので)がインスタンス化レンダリングを使用することです。このようにして、の頂点を含む追加のVBOと、ただ1つの一般的な2Dクワッド(すなわち[0,1]正方形)と、ポイントの位置だけを含む古いVBOがあります。あなたはその後、やっていることは、点VBOから個々のインスタンスのポジションを調達しながら、この単一のクワッドを複数回(インスタンス化)を描画です:

glVertexAttribPointer(0, ...points...); 
glVertexAttribPointer(1, ...quad...); 
glVertexAttribDivisor(0, 1);   //advance only once per instance 
... 
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, count); //draw #count quads 

そして、頂点シェーダに、あなたは、実際にポイントごとの位置を組み立てます(また、テクスチャその頂点の座標である)がCK /クワッド位置:

uniform mat4 modelview; 
uniform mat4 projection; 
uniform float spriteSize; 

layout(location = 0) in vec4 position; 
layout(location = 1) in vec2 corner; 

out vec2 texCoord; 

void main() 
{ 
    vec4 eyePos = modelview * position;   //transform to eye-space 
    eyePos.xy += spriteSize * (corner - vec2(0.5)); //add corner position 
    gl_Position = projection * eyePos;    //complete transformation 
    texCoord = corner; 
} 

これは一貫したワールド空間サイズのジオメトリシェーダーベースのアプローチ、適切にクリッピングポイントスプライトと同じ達成します。実際のポイントスプライトのスクリーンスペースのピクセルサイズを実際に模倣したい場合は、計算にもっと多くの労力をかける必要があります。しかし、これは練習問題として残されており、ポイントスプライトシェーダからのワールドツースクリーン変換の問題にはかなり似ています。

+5

うわー!何の答え! :-D – Joe

+0

あなたの助けと偉大な説明をありがとう、私の友人:-) –

+0

それで 'glDrawArrays(GL_POINTS、...)'を使うと、私はポイントスプライトをレンダリングしていますか? – BRabbit27

関連する問題