Sprity punktowe dla układu cząstek

Czy point sprites to najlepszy wybór do budowy układu cząstek?

Czy point sprites jest obecny w nowszych wersjach OpenGL i sterownikach najnowszych kart graficznych? A może powinienem to zrobić za pomocą vbo i glsl?

Author: genpfault, 2013-07-01

1 answers

Sprity punktowe są rzeczywiście dobrze dostosowane do układów cząstek. Ale nie mają nic wspólnego z VBOs i GLSL, co oznacza, że są całkowicie ortogonalną funkcją. Bez względu na to, czy używasz sprite 'ów punktowych, czy nie, zawsze musisz użyć VBO do przesyłania geometrii, czy to tylko punkty, gotowe sprite' y lub cokolwiek innego, i zawsze musisz umieścić tę geometrię przez zestaw shaderów (w nowoczesnym OpenGL oczywiście).

To powiedziawszy, sprity punktowe są bardzo dobrze wspierane w nowoczesnych OpenGL, tylko nie tak automatycznie jak w starym podejściu do stałej funkcji. To, co nie jest obsługiwane, to funkcje tłumienia punktów, które pozwalają skalować rozmiar punktu na podstawie jego odległości od kamery, musisz to zrobić ręcznie wewnątrz modułu cieniowania wierzchołków. W ten sam sposób musisz ręcznie teksturować punkt w odpowiednim shaderze fragmentów, używając specjalnej zmiennej wejściowej gl_PointCoord (która mówi, gdzie w [0,1] - kwadracie całego punktu znajduje się bieżący fragment). Na przykład a podstawowy punktowy rurociąg sprite ' a może wyglądać tak:

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

Vertex shader:

uniform mat4 mvp;

layout(location = 0) in vec4 position;

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

Fragment shader:

uniform sampler2D tex;

layout(location = 0) out vec4 color;

void main()
{
    color = texture(tex, gl_PointCoord);
}
I to wszystko. Oczywiście shadery te po prostu robią najbardziej podstawowe rysowanie teksturowanych sprite' ów, ale są punktem wyjścia dla dalszych funkcji. Na przykład, aby obliczyć rozmiar sprite ' a na podstawie jego odległości od kamery (być może w celu nadania mu stałej wielkości świata-przestrzeni), musisz glEnable(GL_PROGRAM_POINT_SIZE) i zapisać do specjalnej zmiennej wyjściowej gl_PointSize w wierzchołku shader:
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;
}

To sprawi, że wszystkie sprite ' y punktowe będą miały ten sam rozmiar przestrzeni świata (a tym samym inny rozmiar przestrzeni ekranu w pikselach).


Ale point Sprite, mimo że nadal jest doskonale wspierany w nowoczesnym OpenGL, ma swoje wady. Jedną z największych wad jest ich zachowanie przycinania. Punkty są przycinane w ich współrzędnej Środkowej (ponieważ przycinanie odbywa się przed rasteryzacją, a więc zanim punkt zostanie "powiększony"). Więc jeśli środek punktu jest poza ekranem reszta, która może nadal sięgać do obszaru oglądania, nie jest wyświetlana, więc w najgorszym przypadku, gdy punkt jest w połowie drogi z ekranu, nagle zniknie. Jest to jednak zauważalne (lub denerwujące) tylko wtedy, gdy sprity punktowe są zbyt duże. Jeśli są to bardzo małe cząstki, które i tak nie pokrywają dużo więcej niż kilka pikseli, to nie będzie to wielki problem i nadal uważałbym systemy cząstek za kanoniczne zastosowanie dla punktowych spritów, po prostu nie używaj je na duże billboardy.

Ale jeśli jest to problem, to nowoczesny OpenGL oferuje wiele innych sposobów implementowania punktów sprite 'ów, oprócz naiwnego sposobu wstępnego budowania wszystkich sprite' ów jako pojedynczych quadów na CPU. Nadal możesz renderować je jako bufor pełen punktów (a więc w sposób, w jaki mogą one pochodzić z twojego silnika cząstek opartego na GPU). Aby wygenerować geometrię quad, możesz użyć modułu geometry shader, który pozwala wygenerować quad z jednego punktu. Najpierw wykonujesz tylko transformację modelview wewnątrz shadera wierzchołków:

uniform mat4 modelview;

layout(location = 0) in vec4 position;

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

Następnie Shader geometry wykonuje resztę pracy. Łączy położenie punktu z 4 kątami ogólnymi [0,1] - kwadrat i kończy przekształcenie w Przestrzeń spinową:

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

W cieniowaniu fragmentów można oczywiście użyć niestandardowego texCoord zmiennego zamiast gl_PointCoord do teksturowania, ponieważ nie rysujemy już rzeczywistych punktów.


Lub inna możliwość (a może szybsze, bo pamiętam, że shadery geometrii miały reputację powolnego) byłoby użycie renderingu z instancjami. W ten sposób masz dodatkowe VBO zawierające wierzchołki tylko jeden ogólny kwadrat 2D (tj. [0,1]-kwadrat) i twoje stare dobre VBO zawierające tylko pozycje punktowe. Następnie rysuje się ten pojedynczy kwadrat wiele razy (instancja), jednocześnie pozyskując pozycje poszczególnych instancji z punktu 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

I w cieniu wierzchołka wtedy układanie położenia punktu z rzeczywistą narożną / czworokątną pozycją (która jest również współrzędną tekstury tego wierzchołka):

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

Osiąga to samo, co podejście oparte na shaderach geometrii, odpowiednio przycięte sprity punktowe o spójnej wielkości świat-przestrzeń. Jeśli naprawdę chcesz naśladować rozmiar piksela ekranu rzeczywistego punktu, musisz włożyć w to więcej wysiłku obliczeniowego. Ale to jest pozostawione jako ćwiczenie i byłoby całkiem uciążliwe dla transformacja świata na ekran z shadera the point sprite.

 101
Author: Christian Rau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-02 14:32:07