GLSL Partikel: Unterschied zwischen den Versionen
(Kategorie:Technik_oder_Algorithmus, Kategorie:Anleitung) |
(→Shader) |
||
(15 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) | |||
Zeile 2: | Zeile 2: | ||
So mal wieder was schreiben wo von ich keine Ahnung hab... (ironisch gemeint. Ich hab nur noch kein fertiges Testprogramm und es nirgends richtig dokumentiert) | So mal wieder was schreiben wo von ich keine Ahnung hab... (ironisch gemeint. Ich hab nur noch kein fertiges Testprogramm und es nirgends richtig dokumentiert) | ||
− | Sicher habt die meisten zumindest schon mal von Partikelsystemen | + | Sicher habt die meisten zumindest schon mal von Partikelsystemen gehört. Da die Leistung der Grafikkarten in den letzten Jahren immer größer wurde und auch die Flexibilität immer mehr zunahm, liegt es nahe ein Partikelsystem direkt in GLSL zu programmieren. Dadurch, dass keine Daten mehr zu der Grafikkarte transportiert werden müssen und die CPU von den Berechnungen entlastet wird, sollten um eine Größenordnung mehr Partikel möglich sein. |
− | Im | + | Im Gegensatz zu den klassischen Partikelsystemen wo alle Partikel unabhängig von einander sind, gibt es auch Partikelsysteme, bei denen Partikel mit einander verbunden sind. Dazu gehören unter anderem auch Seile und Kleidung. Der Übergang zu echten Physiksimulation ist fließend. Der wahrscheilich größte Unterschied ist, das ein Partikelsystem nur ein grafischer Effekt ist und keinen Einfluss auf die restliche Physik nimmt. |
==Grundlagen für die Implementierung== | ==Grundlagen für die Implementierung== | ||
− | + | Während es bei konventionellen Partikelsystemen am Sinnvollsten war, die berechneten Daten per VBO oder Vertexarray in die Grafikkarte zu laden. Müssen bei einem GLSL-Partikelsystem, alle Daten in Texturen gespeichert werden. Da die GPU in der Lage ist, mehr als die 10fache Menge an Partikeln zu verarbeiten und außerdem Positionen noch Physikdaten gespeichert werden müssen, sollte von Anfang an der Speicherkonsum auf der Grafikkarte mit eingeplant werden. | |
− | Die Anzahl der Komponenten sollte sich möglicherweise durch vier teilen lassen. Zudem sollte beim Speichern | + | Die Anzahl der Komponenten sollte sich möglicherweise durch vier teilen lassen. Zudem sollte beim Speichern statt float32 möglichst nur float16 genutzt werden. |
− | Ein Beispiel wäre ein einfaches Partikelsystem: Für drei Positionskomponenten, eine Zeitkomponente, drei Geschwindigkeitskomponenten und eine noch freie Komponente z.B. für Masse, werden 8 Werte benötigt. Bei einer Verwendung von float16 und 2^20 Partikeln, werden ganze 16MB GPU | + | Ein Beispiel wäre ein einfaches Partikelsystem: Für drei Positionskomponenten, eine Zeitkomponente, drei Geschwindigkeitskomponenten und eine noch freie Komponente z.B. für Masse, werden 8 Werte benötigt. Bei einer Verwendung von float16 und 2^20 Partikeln, werden ganze 16MB GPU RAM verbraucht. Da es nicht möglich oder sinnvoll ist in eine aktive Textur zu schreiben, wird eine zweite Textur benötigt, die die selbe Größe hat. So verbraucht unser Partikelsystem 32 MB GPU RAM. Da immer zwischen diesen zwei Buffern hin und her gerendert wird, spricht mach auch von einem Ping-Pong-Buffer. |
− | Damit keine getrennten Buffer für Position und Geschwindigkeit verwendet werden müssen, | + | Damit keine getrennten Buffer für Position und Geschwindigkeit verwendet werden müssen, sind Multiple Render Targets von Vorteil. Diese sind nicht schwerer zu verwenden als normale Framebufferobjekte. Eine Einschränkung ist, dass nur Render Targets mit gleichen Bitanzahlen kombiniert werden können. So lässt sich ein 1x float32 mit einem 2x float16 und einem RGBA8 Render target kombinieren. Ein RGBA_float16 Target z.B. lässt sich nur mit einem RG_float32 und sich selbst kombinieren. |
− | Weiterhin ist es sehr | + | Weiterhin ist es sehr sinnvoll einen float16 Frambuffer zu verwenden, da so nachträglich durch die Blende die Helligkeit des Bildes optimal angepasst werden kann (Siehe fehlendes HDR Tutorial....) |
− | Nun erst einmal ein | + | Nun erst einmal ein Programm Pseudocode um einen Überblick über die Initialisierung zu schaffen: |
Optional (3 Zeilen): | Optional (3 Zeilen): | ||
− | Eine RGB_Float16 Textur für HDR Rendering mit der | + | Eine RGB_Float16 Textur für HDR Rendering mit der OpenGL-Auflösung erzeugen. |
− | Ein | + | Ein Renderbufferobjekt für den Z-Buffer erstellen. |
− | Beides zu einem | + | Beides zu einem Framebufferobjekt hinzufügen. |
2x2 RGBA_Float16 Texturen mit 1024x1024 erstellen. | 2x2 RGBA_Float16 Texturen mit 1024x1024 erstellen. | ||
− | 2 mit | + | 2 mit Initialisierungsdaten füllen |
− | Ein weiteres | + | Ein weiteres Framebufferobjekt erzeugen und die zwei übrigen daran binden. |
− | Laden | + | Laden weiterer Texturen |
− | Shader | + | Shader laden |
− | + | Hauptschleife | |
Aktivieren des Partikelphysikshaders und des Rendertargets | Aktivieren des Partikelphysikshaders und des Rendertargets | ||
Rendern der Physikdaten in den zweiten Buffer | Rendern der Physikdaten in den zweiten Buffer | ||
Buffer tauschen. | Buffer tauschen. | ||
− | Die | + | Die gerenderten Daten in ein VBO übertragen oder später per Vertextexturefetch nutzen. |
− | + | Normales Rendertaget auswählen | |
Rest der Scene Rendern | Rest der Scene Rendern | ||
Partikel mit Partikelshader als Pointsprites rendern. | Partikel mit Partikelshader als Pointsprites rendern. | ||
Zeile 38: | Zeile 38: | ||
==Probleme bei der Implementation== | ==Probleme bei der Implementation== | ||
− | + | Während die Implementation der Framebufferobjekte sehr sauber ist und das Ping-Pong-Rendering an sich kein Problem darstellen sollte, macht das Nutzen der Pixeldaten als Vertexdaten richtig Stress. Die einzige halbwegs saubere Möglichkeit ist die Verwendung eines Pixelbufferobjektes. Jedoch hat dies den großen Nachteil, das es ein großer Speicherfresser ist, da alle Daten kopiert werden. Wenn unsere 2^20 Vertices auf Float32 aufgebläht werden, lösen sich weitere 32MB auf. Nun gibt es nur zwei sinnvolle Möglichkeiten: | |
− | Es wird ein relativ kleines PBO | + | Es wird ein relativ kleines PBO benutzt, welchen nur 2^16 Vertices abarbeitet, der Vorteil ist, dass einige Grafikkarten diese kleineren VBOs etwas schneller abarbeiten können. Ansonsten fallen zwar mehr Funktionsaufrufen an, dafür wird eine Menge RAM auf der Grafikkarte gespart. Dieser Weg bereitet einem besonders nette Überraschungen beim coden, da die Konzepte der FBOs irgendwie nicht zu den PBOs passen. |
− | Die zweite Möglichkeit erscheint auf dem erstem Blick noch sauberer, läuft jedoch nicht auf ATI Karten: Es wird ein kleines VBO mit Indexkoordinaten gebildet, welches einen kleinen Teil der Textur mit den Vertexdaten indiziert. Dieser Bereich lässt sich mit zwei Uniformvariablen verschieben, so das ohne kopieren der Daten alle Partikel | + | Die zweite Möglichkeit erscheint auf dem erstem Blick noch sauberer, läuft jedoch nicht auf ATI Karten: Es wird ein kleines VBO mit Indexkoordinaten gebildet, welches einen kleinen Teil der Textur mit den Vertexdaten indiziert. Dieser Bereich lässt sich mit zwei Uniformvariablen verschieben, so das ohne kopieren der Daten alle Partikel gerendert werden können. Möglicherweise gibt es hier eine weitere Beschränkung auf Float32-Texturen. |
− | In | + | In Zukunft sollte dieses Problem mit den "uerbuffers" oder "superbuffers" gelöst werden. Leider scheinen sich da Nvidia und ATI nicht einigen zu können. |
==Implementation per Texturlookup im Vertexshader== | ==Implementation per Texturlookup im Vertexshader== | ||
− | Auch wenn | + | Auch wenn Besitzer von ATI Karten jetzt dumm gucken, weil die Pixelshader 3.0 kompatiblen Karten den Texturelookup im Vertexshader nicht unterstützen, werde ich als erstes diesen Weg zeigen, da er am einfachsten ist. Um eventuelle Probleme mit Float16 zu umgehen wird hier durchgängig float32 benutzt. Die Partikelanzahl beträgt 2^16. Mehr Partikel sollten aber kein großes Problem sein. |
===Initialisierung=== | ===Initialisierung=== | ||
− | Für ein Partikelsystem welches mit 8 Floats pro Partikel auskommt. Kann man so die nötigen Texturen und Framebufferobjekte erzeugt werden. Zusätzlich wird noch ein VBO mit Vertexdaten notwendig, welches Texturkoordinaten für jeden Texel der Textur | + | Für ein Partikelsystem welches mit 8 Floats pro Partikel auskommt. Kann man so die nötigen Texturen und Framebufferobjekte erzeugt werden. Zusätzlich wird noch ein VBO mit Vertexdaten notwendig, welches Texturkoordinaten für jeden Texel der Textur enthält: |
<source lang="cpp"> | <source lang="cpp"> | ||
for (i=0;i<4;i++){ | for (i=0;i<4;i++){ | ||
Zeile 80: | Zeile 80: | ||
glBufferDataARB( GL_ARRAY_BUFFER_ARB, 65536 *2 * sizeof(float), data, GL_STATIC_DRAW_ARB ); | glBufferDataARB( GL_ARRAY_BUFFER_ARB, 65536 *2 * sizeof(float), data, GL_STATIC_DRAW_ARB ); | ||
</source> | </source> | ||
− | Anschließend sollten die Shader geladen werden. Benutzt für loadShader() eure eigene Funktion zum shader laden oder schaut im erstem GLSL Tutorial nach wie man einen | + | Anschließend sollten die Shader geladen werden. Benutzt für loadShader() eure eigene Funktion zum shader laden oder schaut im erstem GLSL Tutorial nach wie man einen Shaderloader schreibt. Anschließen sollten die Uniformvariablen der Texturen gesetzt werden. |
<source lang="cpp"> | <source lang="cpp"> | ||
Zeile 94: | Zeile 94: | ||
===Haupschleife=== | ===Haupschleife=== | ||
− | Damit das | + | Damit das Ping-Pong-Rendering leicht gesteuert werden kann sollte eine Variable immer zwischen dem Wert 0 und 1 hin und her wechseln. Als erstes der Teil, der die Partikelphysik in den Pingpongbuffer rendert: |
<source lang="cpp"> | <source lang="cpp"> | ||
pass = (pass+1)%2; | pass = (pass+1)%2; | ||
Zeile 118: | Zeile 118: | ||
− | Nun noch der Teil, der die Partikel als Points rendert. Gegebenfalls sind Poinsprites | + | Nun noch der Teil, der die Partikel als Points rendert. Gegebenfalls sind Poinsprites sinnvoller, zumal sie mit GLSL 1.2 noch ein wenig flexibler werden. |
<source lang="cpp"> | <source lang="cpp"> | ||
Zeile 138: | Zeile 138: | ||
Es werden zwei Vertex und Pixelshader gebraucht. | Es werden zwei Vertex und Pixelshader gebraucht. | ||
− | Der Partikelphysikvertexshader muss nur die Variablen an den Pixelshader weiter geben. | + | Der Partikelphysikvertexshader muss nur die Variablen an den Pixelshader weiter geben. Prinzipiell wäre es auch möglich die ModelViewProjectionMatrix zu umgehen, so dass die eventuell vorhandene Perspektive nicht stört. Auch die Texturkoordinaten ließen sich wegrationalisieren. Damit alles leicht verständlich bleibt hier die gewohnte Variante: |
− | <source lang=" | + | <source lang="glsl"> |
void main(void){ | void main(void){ | ||
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; | gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; | ||
gl_TexCoord[0] = gl_MultiTexCoord0; | gl_TexCoord[0] = gl_MultiTexCoord0; | ||
− | + | } | |
</source> | </source> | ||
− | Da die Berechnungen im Partikelsystem immer individuell sind, | + | Da die Berechnungen im Partikelsystem immer individuell sind, zeige ich hier nur wie die Ein- und Ausgabe funktioniert. Die Ausgabevariablen gl_FragData[] sind vec4-Vektoren. gl_FragData[0] entspricht dabei gl_FragColor. Hier ist ein Partikelphysikshader der im Prinzip nichts tut: |
− | <source lang=" | + | <source lang="glsl"> |
uniform sampler2D Texture0; | uniform sampler2D Texture0; | ||
uniform sampler2D Texture1; | uniform sampler2D Texture1; | ||
void main(){ | void main(){ | ||
− | + | vec4 data0 = texture2D(Texture0,vec2(gl_TexCoord[0])); | |
− | + | vec4 data1 = texture2D(Texture1,vec2(gl_TexCoord[0])); | |
− | } | + | |
+ | // ...Daten modifizieren... | ||
+ | |||
+ | gl_FragData[0] = data0; | ||
+ | gl_FragData[1] = data1; | ||
+ | } | ||
</source> | </source> | ||
− | Im Rendershader muss die | + | Im Rendershader muss die eigentliche Position (auch Normalvektoren und Farben) per Texturelookup ausgelesen werden. Um Bandbreite zu sparen sollten diese Variablen Werte möglichst eng zusammengepackt werden. Um Latenzen zu verstecken sollte man die Ergebnisse der Lookups nach Möglichkeit erst benutzen, wenn andere Dinge bereits berechnet wurden. |
− | <source lang=" | + | <source lang="glsl"> |
uniform sampler2D Texture0; | uniform sampler2D Texture0; | ||
Zeile 166: | Zeile 171: | ||
gl_Position = gl_ModelViewProjectionMatrix * look; | gl_Position = gl_ModelViewProjectionMatrix * look; | ||
gl_TexCoord[0] = gl_MultiTexCoord0; | gl_TexCoord[0] = gl_MultiTexCoord0; | ||
− | + | } | |
</source> | </source> | ||
− | Der Pixelshader zum rendern der Partikel | + | Der Pixelshader zum rendern der Partikel kann sehr einfach sein. Sollte jedoch später ein wenig mehr machen als nur weiße Punkte zu zeichnen: |
<source lang="cpp"> | <source lang="cpp"> | ||
void main(){ | void main(){ | ||
gl_FragColor = vec4(1.0,1.0,1.0,1.0); | gl_FragColor = vec4(1.0,1.0,1.0,1.0); | ||
− | + | } | |
</source> | </source> | ||
− | |||
==Links== | ==Links== | ||
* [[Shader]] : Shader Grundlagen | * [[Shader]] : Shader Grundlagen | ||
* [[Tutorial_glsl|Tutorial GLSL]] : Einführung in GLSL | * [[Tutorial_glsl|Tutorial GLSL]] : Einführung in GLSL | ||
− | * [[GLSL Partikel 2]] : aktuelleres GPU-Partikelsystem auf Basis von ShaderModel 4.0, Transform-Feedback und Geometryshader. | + | * [[GLSL Partikel 2]] : aktuelleres GPU-Partikelsystem auf Basis von ShaderModel 4.0, [[Transform-Feedback]] und [[Geometryshader]]. |
[[Kategorie:Technik_oder_Algorithmus]] | [[Kategorie:Technik_oder_Algorithmus]] | ||
[[Kategorie:Anleitung]] | [[Kategorie:Anleitung]] |
Aktuelle Version vom 18. März 2012, 16:35 Uhr
Inhaltsverzeichnis
Einleitung
So mal wieder was schreiben wo von ich keine Ahnung hab... (ironisch gemeint. Ich hab nur noch kein fertiges Testprogramm und es nirgends richtig dokumentiert)
Sicher habt die meisten zumindest schon mal von Partikelsystemen gehört. Da die Leistung der Grafikkarten in den letzten Jahren immer größer wurde und auch die Flexibilität immer mehr zunahm, liegt es nahe ein Partikelsystem direkt in GLSL zu programmieren. Dadurch, dass keine Daten mehr zu der Grafikkarte transportiert werden müssen und die CPU von den Berechnungen entlastet wird, sollten um eine Größenordnung mehr Partikel möglich sein.
Im Gegensatz zu den klassischen Partikelsystemen wo alle Partikel unabhängig von einander sind, gibt es auch Partikelsysteme, bei denen Partikel mit einander verbunden sind. Dazu gehören unter anderem auch Seile und Kleidung. Der Übergang zu echten Physiksimulation ist fließend. Der wahrscheilich größte Unterschied ist, das ein Partikelsystem nur ein grafischer Effekt ist und keinen Einfluss auf die restliche Physik nimmt.
Grundlagen für die Implementierung
Während es bei konventionellen Partikelsystemen am Sinnvollsten war, die berechneten Daten per VBO oder Vertexarray in die Grafikkarte zu laden. Müssen bei einem GLSL-Partikelsystem, alle Daten in Texturen gespeichert werden. Da die GPU in der Lage ist, mehr als die 10fache Menge an Partikeln zu verarbeiten und außerdem Positionen noch Physikdaten gespeichert werden müssen, sollte von Anfang an der Speicherkonsum auf der Grafikkarte mit eingeplant werden. Die Anzahl der Komponenten sollte sich möglicherweise durch vier teilen lassen. Zudem sollte beim Speichern statt float32 möglichst nur float16 genutzt werden. Ein Beispiel wäre ein einfaches Partikelsystem: Für drei Positionskomponenten, eine Zeitkomponente, drei Geschwindigkeitskomponenten und eine noch freie Komponente z.B. für Masse, werden 8 Werte benötigt. Bei einer Verwendung von float16 und 2^20 Partikeln, werden ganze 16MB GPU RAM verbraucht. Da es nicht möglich oder sinnvoll ist in eine aktive Textur zu schreiben, wird eine zweite Textur benötigt, die die selbe Größe hat. So verbraucht unser Partikelsystem 32 MB GPU RAM. Da immer zwischen diesen zwei Buffern hin und her gerendert wird, spricht mach auch von einem Ping-Pong-Buffer.
Damit keine getrennten Buffer für Position und Geschwindigkeit verwendet werden müssen, sind Multiple Render Targets von Vorteil. Diese sind nicht schwerer zu verwenden als normale Framebufferobjekte. Eine Einschränkung ist, dass nur Render Targets mit gleichen Bitanzahlen kombiniert werden können. So lässt sich ein 1x float32 mit einem 2x float16 und einem RGBA8 Render target kombinieren. Ein RGBA_float16 Target z.B. lässt sich nur mit einem RG_float32 und sich selbst kombinieren.
Weiterhin ist es sehr sinnvoll einen float16 Frambuffer zu verwenden, da so nachträglich durch die Blende die Helligkeit des Bildes optimal angepasst werden kann (Siehe fehlendes HDR Tutorial....)
Nun erst einmal ein Programm Pseudocode um einen Überblick über die Initialisierung zu schaffen:
Optional (3 Zeilen): Eine RGB_Float16 Textur für HDR Rendering mit der OpenGL-Auflösung erzeugen. Ein Renderbufferobjekt für den Z-Buffer erstellen. Beides zu einem Framebufferobjekt hinzufügen. 2x2 RGBA_Float16 Texturen mit 1024x1024 erstellen. 2 mit Initialisierungsdaten füllen Ein weiteres Framebufferobjekt erzeugen und die zwei übrigen daran binden. Laden weiterer Texturen Shader laden Hauptschleife Aktivieren des Partikelphysikshaders und des Rendertargets Rendern der Physikdaten in den zweiten Buffer Buffer tauschen. Die gerenderten Daten in ein VBO übertragen oder später per Vertextexturefetch nutzen. Normales Rendertaget auswählen Rest der Scene Rendern Partikel mit Partikelshader als Pointsprites rendern.
Probleme bei der Implementation
Während die Implementation der Framebufferobjekte sehr sauber ist und das Ping-Pong-Rendering an sich kein Problem darstellen sollte, macht das Nutzen der Pixeldaten als Vertexdaten richtig Stress. Die einzige halbwegs saubere Möglichkeit ist die Verwendung eines Pixelbufferobjektes. Jedoch hat dies den großen Nachteil, das es ein großer Speicherfresser ist, da alle Daten kopiert werden. Wenn unsere 2^20 Vertices auf Float32 aufgebläht werden, lösen sich weitere 32MB auf. Nun gibt es nur zwei sinnvolle Möglichkeiten:
Es wird ein relativ kleines PBO benutzt, welchen nur 2^16 Vertices abarbeitet, der Vorteil ist, dass einige Grafikkarten diese kleineren VBOs etwas schneller abarbeiten können. Ansonsten fallen zwar mehr Funktionsaufrufen an, dafür wird eine Menge RAM auf der Grafikkarte gespart. Dieser Weg bereitet einem besonders nette Überraschungen beim coden, da die Konzepte der FBOs irgendwie nicht zu den PBOs passen.
Die zweite Möglichkeit erscheint auf dem erstem Blick noch sauberer, läuft jedoch nicht auf ATI Karten: Es wird ein kleines VBO mit Indexkoordinaten gebildet, welches einen kleinen Teil der Textur mit den Vertexdaten indiziert. Dieser Bereich lässt sich mit zwei Uniformvariablen verschieben, so das ohne kopieren der Daten alle Partikel gerendert werden können. Möglicherweise gibt es hier eine weitere Beschränkung auf Float32-Texturen.
In Zukunft sollte dieses Problem mit den "uerbuffers" oder "superbuffers" gelöst werden. Leider scheinen sich da Nvidia und ATI nicht einigen zu können.
Implementation per Texturlookup im Vertexshader
Auch wenn Besitzer von ATI Karten jetzt dumm gucken, weil die Pixelshader 3.0 kompatiblen Karten den Texturelookup im Vertexshader nicht unterstützen, werde ich als erstes diesen Weg zeigen, da er am einfachsten ist. Um eventuelle Probleme mit Float16 zu umgehen wird hier durchgängig float32 benutzt. Die Partikelanzahl beträgt 2^16. Mehr Partikel sollten aber kein großes Problem sein.
Initialisierung
Für ein Partikelsystem welches mit 8 Floats pro Partikel auskommt. Kann man so die nötigen Texturen und Framebufferobjekte erzeugt werden. Zusätzlich wird noch ein VBO mit Vertexdaten notwendig, welches Texturkoordinaten für jeden Texel der Textur enthält:
for (i=0;i<4;i++){
glBindTexture (GL_TEXTURE_2D,partikelbuffer[i]);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D (GL_TEXTURE_2D, 0,GL_RGB32F_ARB , 256, 256, 0, GL_RGBA , GL_FLOAT, dummy[i]);
//dummy[] sollte Zeiger auf Speicherbereiche mit Initialisierungsdaten enthalten
//oder NULL enthalten.
}
glGenFramebuffersEXT (2, partikel_framebuffer);
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, partikel_framebuffer[0]);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D,partikelbuffer[2], 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT1_EXT,GL_TEXTURE_2D,partikelbuffer[3], 0);
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, partikel_framebuffer[1]);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D,partikelbuffer[0], 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT1_EXT,GL_TEXTURE_2D,partikelbuffer[1], 0);
for (i=0;i<256; i++ )for (j=0;j<256; j++ ){
data[i*2 + j* 512]= i/256.0;
data[i*2 + j* 512 +1]= j/256.0;}
glGenBuffersARB( 1, &vertex);
glBindBufferARB( GL_ARRAY_BUFFER_ARB, vertex );
glBufferDataARB( GL_ARRAY_BUFFER_ARB, 65536 *2 * sizeof(float), data, GL_STATIC_DRAW_ARB );
Anschließend sollten die Shader geladen werden. Benutzt für loadShader() eure eigene Funktion zum shader laden oder schaut im erstem GLSL Tutorial nach wie man einen Shaderloader schreibt. Anschließen sollten die Uniformvariablen der Texturen gesetzt werden.
partikelphy = loadShader("partikelphy.vert","partikelphy.frag");
glUseProgramObjectARB(partikelphy);
glUniform1iARB(glGetUniformLocationARB(partikelphy, "Texture0"),0);
glUniform1iARB(glGetUniformLocationARB(partikelphy, "Texture1"),1);
partikelren = loadShader("partikelren.vert","partikelren.frag");
glUseProgramObjectARB(partikelren);
glUniform1iARB(glGetUniformLocationARB(partikelren, "Texture0"),0);
Haupschleife
Damit das Ping-Pong-Rendering leicht gesteuert werden kann sollte eine Variable immer zwischen dem Wert 0 und 1 hin und her wechseln. Als erstes der Teil, der die Partikelphysik in den Pingpongbuffer rendert:
pass = (pass+1)%2;
glViewport(0, 0, 256,256);
glUseProgramObjectARB(partikelphy);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, partikelbuffer[pass * 2]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, partikelbuffer[ pass * 2 + 1]);
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, partikel_framebuffer[pass]);
glBegin(GL_QUADS);
glTexCoord2f(0,0); glVertex2f(0,0);
glTexCoord2f(0,1); glVertex2f(0,1);
glTexCoord2f(1,1); glVertex2f(1,1);
glTexCoord2f(1,0); glVertex2f(1,0);
glEnd();
glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0);
Nun noch der Teil, der die Partikel als Points rendert. Gegebenfalls sind Poinsprites sinnvoller, zumal sie mit GLSL 1.2 noch ein wenig flexibler werden.
glViewport(0, 0, screenwidth,screenheight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //nur wenn keine andere Scene gerendert wurde
glUseProgramObjectARB(partikelren);
glEnableClientState(GL_VERTEX_ARRAY);
glBindBufferARB( GL_ARRAY_BUFFER_ARB, vertex);
glVertexPointer(2, GL_FLOAT, 0 , (char *) NULL);
glDrawArrays(GL_POINTS, 0, 65536);
glDisableClientState(GL_VERTEX_ARRAY);
Shader
Es werden zwei Vertex und Pixelshader gebraucht.
Der Partikelphysikvertexshader muss nur die Variablen an den Pixelshader weiter geben. Prinzipiell wäre es auch möglich die ModelViewProjectionMatrix zu umgehen, so dass die eventuell vorhandene Perspektive nicht stört. Auch die Texturkoordinaten ließen sich wegrationalisieren. Damit alles leicht verständlich bleibt hier die gewohnte Variante:
void main(void){
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
}
Da die Berechnungen im Partikelsystem immer individuell sind, zeige ich hier nur wie die Ein- und Ausgabe funktioniert. Die Ausgabevariablen gl_FragData[] sind vec4-Vektoren. gl_FragData[0] entspricht dabei gl_FragColor. Hier ist ein Partikelphysikshader der im Prinzip nichts tut:
uniform sampler2D Texture0;
uniform sampler2D Texture1;
void main(){
vec4 data0 = texture2D(Texture0,vec2(gl_TexCoord[0]));
vec4 data1 = texture2D(Texture1,vec2(gl_TexCoord[0]));
// ...Daten modifizieren...
gl_FragData[0] = data0;
gl_FragData[1] = data1;
}
Im Rendershader muss die eigentliche Position (auch Normalvektoren und Farben) per Texturelookup ausgelesen werden. Um Bandbreite zu sparen sollten diese Variablen Werte möglichst eng zusammengepackt werden. Um Latenzen zu verstecken sollte man die Ergebnisse der Lookups nach Möglichkeit erst benutzen, wenn andere Dinge bereits berechnet wurden.
uniform sampler2D Texture0;
void main(void){
vec4 look = texture2D(Texture0, vec2(gl_Vertex));
look.w = 1.0;
gl_Position = gl_ModelViewProjectionMatrix * look;
gl_TexCoord[0] = gl_MultiTexCoord0;
}
Der Pixelshader zum rendern der Partikel kann sehr einfach sein. Sollte jedoch später ein wenig mehr machen als nur weiße Punkte zu zeichnen:
void main(){
gl_FragColor = vec4(1.0,1.0,1.0,1.0);
}
Links
- Shader : Shader Grundlagen
- Tutorial GLSL : Einführung in GLSL
- GLSL Partikel 2 : aktuelleres GPU-Partikelsystem auf Basis von ShaderModel 4.0, Transform-Feedback und Geometryshader.