web-dev-qa-db-ja.com

パーティクルシステムのポイントスプライト

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

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

28
Javier Ramírez

ポイントスプライトは、実際にパーティクルシステムに適しています。ただし、VBOやGLSLとは何の関係もありません。つまり、完全に直交する機能です。ポイントスプライトを使用するかどうかに関係なく、ポイント、事前に作成されたスプライトなど、VBOを使用してジオメトリをアップロードする必要があります。また、このジオメトリを一連のシェーダー(常に最新のOpenGL)に配置する必要があります。もちろん)。

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

...
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つは、クリッピング動作です。ポイントはその中心座標でクリップされます(クリッピングはラスタライズの前に行われるため、ポイントが「拡大」される前に行われるため)。そのため、ポイントの中心が画面の外にある場合、表示領域に到達する可能性のある残りの部分は表示されないため、最悪の場合、ポイントが画面の半分から出ると、突然消えます。ただし、これは、ポイントスプライトが大きすぎる場合にのみ顕著になります(またはannyoing)。それらが非常に小さなパーティクルであり、いずれも数ピクセルをはるかに超えない場合、これはそれほど問題にはならず、パーティクルシステムはポイントスプライトの標準的なユースケースであると見なします。大きな看板に使用します。

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

uniform mat4 modelview;

layout(location = 0) in vec4 position;

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

次に、ジオメトリシェーダーが残りの作業を行います。ポイントの位置と一般的な[0,1]クワッドの4つのコーナーを組み合わせて、クリップ空間への変換を完了します。

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の代わりにカスタムtexCoordバリエーションを使用します。これは、実際のポイントを描画していないためです。


または、インスタンス化されたレンダリングを使用することもできます(おそらく、速度が遅いという評判のあるジオメトリシェーダーを覚えているので、より高速です)。このようにして、追加のVBOに1つの一般的な2Dクワッド(つまり、[0,1] -square)の頂点と、ポイント位置のみを含む古き良き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

そして、頂点シェーダーで、ポイントごとの位置を実際のコーナー/クワッド位置(その頂点のテクスチャ座標でもある)で組み立てます。

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;
}

これにより、ジオメトリシェーダーベースのアプローチと同じように、一貫したワールドスペースサイズで適切にクリップされたポイントスプライトが実現されます。実際に実際のポイントスプライトの画面空間のピクセルサイズを模倣したい場合は、計算量を増やす必要があります。しかし、これは練習問題として残されており、ポイントスプライトシェーダーからの世界から画面への変換の反対です。

95
Christian Rau