Jak najlepiej narysować pełnoekranowy quad w OpenGL 3.2?

Robię Ray casting W fragment shader. Mogę wymyślić kilka sposobów na narysowanie w tym celu pełnoekranowego quada. Albo narysuj kwadrat w przestrzeni klipu z macierzą projekcji ustawioną na macierz tożsamości, albo użyj shadera geometry, aby przekształcić punkt w pasek trójkąta. Pierwszy używa trybu natychmiastowego, przestarzałego w OpenGL 3.2. Ten ostatni używam z nowości, ale nadal używa trybu natychmiastowego, aby narysować punkt.

 22
Author: May Oakes, 2010-04-06

6 answers

Możesz wysłać dwa trójkąty tworząc kwadrat, z atrybutami wierzchołków ustawionymi odpowiednio na -1 / 1.

Nie trzeba ich mnożyć z żadną macierzą w cieniowaniu wierzchołków/fragmentów.

Oto kilka próbek kodu, proste jak jest:)

Vertex Shader:

const vec2 madd=vec2(0.5,0.5);
attribute vec2 vertexIn;
varying vec2 textureCoord;
void main() {
   textureCoord = vertexIn.xy*madd+madd; // scale vertex attribute to [0-1] range
   gl_Position = vec4(vertexIn.xy,0.0,1.0);
}

Fragment Shader:

varying vec2 textureCoord;
void main() {
   vec4 color1 = texture2D(t,textureCoord);
   gl_FragColor = color1;
}
 19
Author: AdilYalcin,
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
2010-04-07 10:21:47

Do wyświetlenia pełnoekranowego shadera Quad geometry można użyć:

#version 330 core

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

out vec2 texcoord;

void main() 
{
    gl_Position = vec4( 1.0, 1.0, 0.5, 1.0 );
    texcoord = vec2( 1.0, 1.0 );
    EmitVertex();

    gl_Position = vec4(-1.0, 1.0, 0.5, 1.0 );
    texcoord = vec2( 0.0, 1.0 ); 
    EmitVertex();

    gl_Position = vec4( 1.0,-1.0, 0.5, 1.0 );
    texcoord = vec2( 1.0, 0.0 ); 
    EmitVertex();

    gl_Position = vec4(-1.0,-1.0, 0.5, 1.0 );
    texcoord = vec2( 0.0, 0.0 ); 
    EmitVertex();

    EndPrimitive(); 
}

Vertex shader jest po prostu pusty:

#version 330 core

void main()
{
}

Aby użyć tego shadera, możesz użyć polecenia dummy draw z pustym VBO:

glDrawArrays(GL_POINTS, 0, 1);
 13
Author: Dimitry Leonov,
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
2012-02-18 16:47:09

Nie trzeba używać shadera geometry, VBO ani żadnej pamięci.

Vertex shader może wygenerować kwadrat.

layout(location = 0) out vec2 uv;

void main() 
{
    float x = float(((uint(gl_VertexID) + 2u) / 3u)%2u); 
    float y = float(((uint(gl_VertexID) + 1u) / 3u)%2u); 

    gl_Position = vec4(-1.0f + x*2.0f, -1.0f+y*2.0f, 0.0f, 1.0f);
    uv = vec2(x, y);
}

Zwiąż puste VAO. Wyślij wywołanie losowania dla 6 wierzchołków.

 10
Author: d3m4nz3,
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
2018-08-01 03:53:11

Będę argumentować, że najskuteczniejszym podejściem będzie rysowanie pojedynczego "pełnoekranowego" trójkąta. Aby trójkąt pokrywał Pełny ekran, musi być większy niż rzeczywisty widok. W NDC (a także spacji klipów, jeśli ustawimy w=1), viewport będzie zawsze kwadratem [-1,1]. Aby trójkąt całkowicie pokrył ten obszar, musimy mieć dwa boki, aby były dwa razy dłuższe od prostokąta wizjera, tak aby trzecia strona przekroczyła krawędź wizjera, stąd może na przykład używać następujących koordiatów (w kolejności zgodnej z ruchem wskazówek zegara): (-1,-1), (3,-1), (-1,3).

Nie musimy się też martwić texcoordami. Aby uzyskać standardowy znormalizowany zakres [0,1] przechodzący przez widzialny viewport, wystarczy, że odpowiednie texcoordy dla wierzchołków tiwce będą tak duże, a interpolacja barycentryczna da dokładnie takie same wyniki dla dowolnego piksela viewportu, jak przy użyciu quada.

To podejście można oczywiście połączyć z bez atrybutów rendering zgodnie z sugestią w odpowiedź demanze :

out vec2 texcoords; // texcoords are in the normalized [0,1] range for the viewport-filling quad part of the triangle
void main() {
        vec2 vertices[3]=vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1, 3));
        gl_Position = vec4(vertices[gl_VertexID],0,1);
        textcoords = 0.5 * gl_Position.xy + vec2(0.5);
}

Dlaczego pojedynczy trójkąt będzie bardziej efektywny?

To jest nie o jednym zachowanym wywołaniu shadera wierzchołków i o jednym mniej trójkątnym do obsługi na froncie. Najbardziej znaczącym efektem użycia pojedynczego trianlge będzie to, że istnieje mniej wywołań shder fragmentów)

Prawdziwe układy graficzne alwyas wywołują fragment shader dla bloków o rozmiarze 2x2 pikseli, gdy tylko pojedynczy piksel prymitywu wpada do takiego bloku. Jest to niezbędne do obliczenia funkcji pochodnych przestrzeni okien (są one również niezbędne do próbkowania tekstur, zobacz to pytanie ).

Jeśli primitive nie obejmuje wszystkich 4 pikseli w tym bloku, Pozostałe wywołania shadera fragmentów nie wykonają żadnej użytecznej pracy (poza dostarczeniem danych do obliczeń pochodnych) i będą tak zwanymi wywołaniami pomocniczymi (co może być nawet queried via the gl_HelperInvocation funkcja GLSL . Zobacz również artykuł na blogu Fabiana "Ryga" Giesena Po Więcej Szczegółów.

Jeśli wyrenderujesz kwadrat z dwoma trójkątami, oba będą miały jedną krawędź biegnącą po przekątnej przez wizjer, a na obu trójkątach wygenerujesz wiele bezużytecznych wywołań pomocniczych na krawędzi. Efekt będzie najgorszy dla idealnie kwadratowego widoku (współczynnik proporcji 1). Jeśli narysujesz pojedynczy trójkąt, nie będzie takiej przekątnej krawędzi (leży poza viewport i w ogóle nie będzie dotyczył rasteryzatora), więc nie będzie dodatkowych wywołań helperów.

Chwileczkę, jeśli trójkąt rozciąga się przez granice viewportu, czy nie zostanie przycięty i faktycznie umieścić więcej pracy na GPU?

Jeśli przeczytasz materiały podręcznika o rurociągach graficznych (a nawet specyfikacji GL), możesz odnieść takie wrażenie. Jednak prawdziwe GPU używają różnych metod, takich jak przycinanie Opaski ochronnej . Nie wejdę do szczegóły tutaj (to byłby temat sam w sobie, spójrz na świetny artykuł na blogu Fabiana "Ryga" Giesena ), ale ogólna idea jest taka, że rasteryzator będzie produkował fragmenty tylko dla Pikseli wewnątrz viewportu (lub scissor rect) w każdym razie, bez względu na to, czy leży całkowicie w nim, czy nie, więc możemy po prostu rzucać w niego większymi trójkątami, jeśli oba poniższe są prawdziwe: {11]}

  • A) trójkąt rozszerza tylko płaszczyzny obcinania 2D góra/dół/lewo/prawo (jako w przeciwieństwie do z-wymiarów bliskich / dalekich)

  • B) rzeczywiste współrzędne wierzchołków (i wszystkie pośrednie wyniki obliczeń, które rasteryzator może na nich wykonywać) są reprezentowane w wewnętrznych formatach danych, których używa rasteryzator sprzętowy GPU. Rasteryzator będzie używał typów danych o stałych punktach o szerokości specyficznej dla implementacji, podczas gdy wierzchołki są 32-bitowymi jednoprzecinkowymi pływakami. (To jest bascially co określa rozmiar Straży-band)

Nasz tiranlge jest tylko współczynnik 3 większy od viewportu, dzięki czemu możemy być pewni, że nie ma potrzeby jego klipowania.

Ale czy warto?

Cóż, oszczędności na wywołaniach fragment shader są realne (zwłaszcza jeśli masz złożony fragment shader), ale ogólny efekt może być ledwo mierzalny w rzeczywistym scenariuszu. Z drugiej strony, podejście nie jest bardziej skomplikowane niż korzystanie z pełnoekranowego quad, i wykorzystuje mniej danych , więc nawet jeśli nie może zrobić ogromny różnica, to nie będzie bolało, więc dlaczego nie używać go?

Czy takie podejście może być stosowane do wszelkiego rodzaju prostokątów wyrównanych do osi, a nie tylko pełnoekranowych?

Teoretycznie można to połączyć z testem nożycowym, aby narysować jakiś prostokąt wyrównany osiami arbitrażu (a test nożycowy będzie bardzo skuteczny, ponieważ ogranicza tylko, które fragmenty są produkowane w pierwszej kolejności, nie jest to prawdziwy "test" w HW, który odrzuca fragmenty). Wymaga to jednak zmiany parametry nożycowe dla każdego prostokąta, który chcesz narysować, co oznacza wiele zmian stanu i ogranicza cię do pojedynczego prostokąta na wywołanie rysowania, więc robienie tego nie będzie dobrym pomysłem w większości scenariuszy.

 9
Author: derhass,
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
2020-01-14 18:18:30

Poniższy tekst pochodzi z funkcji draw klasy, która rysuje tekstury fbo na wyrównany kwadrat ekranu.

Gl.glUseProgram(shad);      

Gl.glBindBuffer(Gl.GL_ARRAY_BUFFER, vbo);           
Gl.glEnableVertexAttribArray(0);
Gl.glEnableVertexAttribArray(1);
Gl.glVertexAttribPointer(0, 3, Gl.GL_FLOAT, Gl.GL_FALSE, 0, voff);
Gl.glVertexAttribPointer(1, 2, Gl.GL_FLOAT, Gl.GL_FALSE, 0, coff);  

Gl.glActiveTexture(Gl.GL_TEXTURE0);
Gl.glBindTexture(Gl.GL_TEXTURE_2D, fboc);
Gl.glUniform1i(tileLoc, 0);

Gl.glDrawArrays(Gl.GL_QUADS, 0, 4);

Gl.glBindTexture(Gl.GL_TEXTURE_2D, 0);
Gl.glBindBuffer(Gl.GL_ARRAY_BUFFER, 0); 

Gl.glUseProgram(0); 

Sam quad i coordy dostajemy od:

private float[] v=new float[]{  -1.0f, -1.0f, 0.0f,
                                1.0f, -1.0f, 0.0f,
                                1.0f, 1.0f, 0.0f,
                                -1.0f, 1.0f, 0.0f,

                                0.0f, 0.0f,
                                1.0f, 0.0f,
                                1.0f, 1.0f,
                                0.0f, 1.0f
};
Oprawę i ustawienie vbo pozostawiam tobie.

The Vertex shader:

#version 330

layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 coord;

out vec2 coords;

void main() {
    coords=coord.st;
    gl_Position=vec4(pos, 1.0);
}

Ponieważ pozycja jest surowa, tzn. nie pomnożona przez jakąkolwiek macierz, to -1, -1::1, 1 quada pasuje do punktu widzenia. Poszukaj tutoriala Alfonse ' a podlinkowanego do któregokolwiek z jego postów na openGL.org.

 2
Author: ste3e,
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
2017-09-29 21:30:16

Jest to podobne do odpowiedzi demanze , ale ja bym argumentował, że jest to łatwiejsze do zrozumienia. Również jest to rysowane tylko z 4 wierzchołkami za pomocą TRIANGLE_STRIP.

#version 300 es
out vec2 textureCoords;

void main() {
    const vec2 positions[4] = vec2[](
        vec2(-1, -1),
        vec2(+1, -1),
        vec2(-1, +1),
        vec2(+1, +1)
    );
    const vec2 coords[4] = vec2[](
        vec2(0, 0),
        vec2(1, 0),
        vec2(0, 1),
        vec2(1, 1)
    );

    textureCoords = coords[gl_VertexID];
    gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
}
 2
Author: Magnus,
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
2020-01-14 16:59:34