Sie sind auf Seite 1von 11

GLSL Shader in Videospielen - Die Rendering

Pipeline, Post Processing Eekte und Hardware


Theodor Frank
Gymnasium Dresden Klotzsche
2018
Dezember
Inhaltsverzeichnis
1 Einleitung 3
2 Was sind Shader? 3
3 Die Shader Pipeline 3
3.1 Die Shader Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . 3
3.2 Vertex-Shader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.3 Tessellation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.3.1 Tessellation Control Shader . . . . . . . . . . . . . . . . . 5
3.3.2 Primitve Generator . . . . . . . . . . . . . . . . . . . . . . 5
3.3.3 Tessellation Evaluation Shader . . . . . . . . . . . . . . . 6
3.4 Clipping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.5 Rasterization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.6 Fragment Shader . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.7 Fragment Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.8 Write Masking . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.9 Write to Framebuer . . . . . . . . . . . . . . . . . . . . . . . . . 7
4 Post-Processing Eekte 7
4.1 Lens Flares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
4.2 Bloom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5 Eigenleistung 8
5.1 Der Graustufen-Shader . . . . . . . . . . . . . . . . . . . . . . . . 8
5.2 Der Schwarz-Weiÿ-Shader . . . . . . . . . . . . . . . . . . . . . . 9
5.3 Der HSV-Shader . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1 Einleitung
Als Nvidia 2018 neue Grakkarten mit Raytracing veröentlicht hatte, wusste
niemand was das sein sollte. Als ich auf ein Video gestoÿen bin, welches diese
neue Technologie vorstellte, wollte ich mehr über das Thema Shader im allge-
meinen Wissen und stieÿ auf verschiedene Internetseiten und Bücher. MUSS
ICH NOCH MACHEN KEINPLAN

2 Was sind Shader?


Shader sind benutzer-denierte Programme, welche einige Prozesse des Ren-
derns modizieren. Früher waren Grakkarten nicht programmierbar, welche
vordenierte Algorithmen ausführten. Weil das für viele Programmierer, die ei-
gene visuelle Eekte machen wollten, zu beschränkend war, entschieden sich die
Grakkartenverkäufer mehrere Stationen der Rendering Pipeline zu önen und
programmierbar zu machen mit Sprachen wie GLSL.
Shader-Programme werden in die GPU geladen und von dieser ausgeführt,
diese Programme können wiederum aus einem oder mehreren einzelnen Sha-
dern bestehen, welche unterschiedliche Aufgaben erfüllen. Die wichtigsten zwei
Shader Programme sind Vertex-Shader, dieser wird für die geometrischen Be-
rechnung dynamischer Objekte benutzt und dem Fragment Shader, welcher für
die Berechnung der Pixelfarbe zuständig ist

3 Die Shader Pipeline


3.1 Die Shader Pipeline
Die Daten laufen durch viele verschiedene Stationen. Diese Shader Pipeline wird
dann im Besten Fall 60 mal pro Sekunde für jeden Pixel ausgeführt. Viele Schrit-
te sind programmierbar(blau), einige sind aber schon in ihrer Funktionsweise
vordeniniert(gelb).

3
3.2 Vertex-Shader
In diesem Schritt hat das ganze noch nichts mit Pixeln zu tun, sondern ist sehr
viel abstrakter. Hier sind nur Eckpunkt sogenannte Vertizes und Verbindungen
interessant. Man spricht von der Geometrie einer Szene. Der Vertex-Shader wird
für jeden einzelnen Vertize einmal ausgeführt. Er kann dann zum Beispiel zur
Manipulation der Position eines Vertizes verwendet werden. Dadurch kann der
komplette Eindruck verändert und verzehrt werden. Eine weitere Möglichkeit
zum Nutzen dieser Technik sind beispielsweise Wellen.

4
3.3 Tessellation
Die Tessellation besteht technisch aus zwei getrennten Shadern, dem Tessella-
tion Control Shader und dem Tessellation Evaluation Shader. Auÿerdem noch
aus einem Fixed-Function Tessellator. Die Aufgabe des Tessellator ist es Körper
in kleinere Flächen, sogenannten Patches, zu unterteilen(Drei- oder Vierecke).
Durch die Tessellation kann eine Menge Rechenleistung der GPU gespart wer-
den, indem nur die Objekte mit vollem Detailgrad und Polygon zahl gerendert
werden, welche sich auch nah an der Kamera benden. Eine moderne Grakkar-
te schat zwar problemlos 2 Millionen Dreiecke bei einer hohen Framerate zu
rendern, auch wenn jedes Dreieck nur einen Pixel groÿ ist, aber wir versuchen in
diesem Schritt Dreiecke auf hoher Distanz zusammenzufassen. Je weiter weg ein
Objekt ist desto undetalierter wird das Objekt. Dieser Eekt ist besonders bei
der Berechnung von Terrain interessant. Bevor die Tessellation in OpenGL 4.x
eingführt wurde, benutzte man die Level of Detail (LOD) Technik. Bei dieser
Methode hat man mehrere Models mit einer festen Anzahl an unterschiedlichen
Detailstufen und Polygonen vorgerendert und werden in der Entfernung ausge-
tauscht. Mit der Tessellation ist es möglich ein Model zu animieren und dann
die kleinstmögliche Polygonzahl in welcher das Model dargestellt werden kann.

3.3.1 Tessellation Control Shader


Der Tessellation Control Shader wird auf eine Gruppe Vertizes angewandt,
welche Control Points genannt werden. Diese haben keine einfache polygona-
le Form, wie Dreiecke oder Vierecke, sondern sie denieren eine geometrische
Oberäche Um aus diesen Punkten eine runde Oberäche zu berechnen, wird
die Bézier-kurve anwenden. Mit dessen Hilfe, kann man den Eekt auch auf sich
bewegende Objekte übertragen.

Der Tessellation Control Shader, hat die Möglichkeit Control Points hinzu-
zufügen oder zu löschen. Diese werden zu einer Sammlung von Zahlen, welche
Tessellation Levels genannt werden. Damit wird hier ein Tessellation Level of
Detail generiert, welche sich in ihrer Polygonanzahl unterscheiden.

3.3.2 Primitve Generator


Nachdem Tessellation Control Shader folgt der Primitive Generator, welcher
schon eine vordenierte Funktion besitzt. In diesem Schritt werden nicht wirk-
lich die Output-Patches vom Tessellation Control Shader unterteilt, Der Primit-
ve Generator hat noch nicht einmal Zugri auf diese. Stattdessen nimmt er die
Tessellation Levels und unterteilt sie in Domain's. Die Domain kann entweder
ein normalisiertes (also ein Vektor mit den Werten zwischen 0 und 1) Qua-
drat aus 2D Koordinaten oder ein gleichseitiges Dreieck sein, welches von 3D

5
baryzentrischen Koordinaten (die Lage von Punkten, welche in Bezug auf die
gegenbene Streckenkonstellation) deniert ist. Der Primitive Generator benutzt
die Tessellation Levels und generiert auf Basis deren Wert eine Sammlung von
Punkten innerhalb des Dreiecks. Diese werden dann in die Pipeline gesendet um
als Punkte rasterisiert zu werden.

3.3.3 Tessellation Evaluation Shader


Der Tessellation Evaluation Shader wird vom Primitive Generator für jede ba-
ryzentrische Koordinate ausgeführt die Funktion ist es für jedem dieser Punkte
ein Vertize zu generieren. Sobald der Tessellation Evaluation Shader Zugri auf
die Patches hat kann dieser verschiedene Sachen abfragen, wie Position und mit
dessen Hilfe werden dann Vertizes generiert. Nachdem der Primitive generator
den Tessellation Evaluation Shader für drei baryzentrischen Koordinaten für
ein kleines Dreieck ausgeführt hat, nimmt dieser sich die drei Vertizes, welche
vom Tessellation Evaluation Shader generiert wurden und sendet diese dann als
komplettes Dreieck für die Rasterization.

3.4 Clipping
In diesem Schritt werden Teile weggeschnitten, welche im Ausgabebild nicht
sichtbar sind, dadurch kann wieder sehr viel Leistung gespart werden. Das Clip-
ping wird für jedes Primitive durchgeführt, bevor es zur Rasterization geschickt
wird.

3.5 Rasterization
Die Rasterization erhält alle Geometrie Informationen und berechnet aus diesen
wiederum Fragments. Es werden über die Flächen viele Kleine Quadrate gelegt.
Jeder Quadratmittelpunkt, welcher innerhalb der Fläche liegt, wird in einem Set
aus Fragments gespeichert. Fragments sind potentielle Pixel, welche noch einige

6
Tests durchlaufen müssen, bevor sie Pixel werden. Sie speichern auÿerdem sehr
viel mehr Informationen,wie Position, Seite des dazugehörigen Primitive oder
Farben, als Pixel.

3.6 Fragment Shader


Im Fragment Shader können Informationen der Fragments verändert werden.
Man kann z.B Farbinformationen verändern etc. Anwendung sind beispielsweise
Spiegelungen, Schattierungen, Lens Flares, HSV oder sogar die Schärfentiefe.

3.7 Fragment Tests


Hier können Fragments getestet und unwichtige Fragments gelöscht werden,
dieses Feature kann durch zwei wege ausgeführt werden. Einmal durch eine
Optimierung um Rechenleistung zu sparen oder durch den Nutzer.

3.8 Write Masking


Der Write Masking Schritt ltert am Ende nochmal die Informationen, wie
Farbe oder Tiefe, und kann diesen erlauben bzw. verbieten auf den Framebuer
beschrieben zu werden.

3.9 Write to Framebuer


Jetzt werden die Fragments auf den Framebuer, also das endgültige Bild ge-
schrieben und der Nutzer sieht dieses Bild auf seinem Bildschirm.

4 Post-Processing Eekte
Mithilfe der Shader Pipeline ist es nun möglich verschiedene Stationen in ihrer
Funktionsweise zu manipulieren und dadurch Eekte wie Lens Flares, Depth of
Field, Screen Space Reection, God Rays oder auch Bloom zu erschaen. Einige
davon werde ich theoretisch erklären.

4.1 Lens Flares


Lens Flares sind in der Realität Spiegelungen von Lichtquellen in der Kame-
ralinse. Um diesen Eekt zu simulieren Wird die 2D Koordinate der Lichtquelle

7
genommen und von dort aus ein Strahl gezogen. Nun werden in bestimmten
Abständen Texturen auf diesen Strahl gehangen. Schlussendlich wird noch die
Stärke der Texturen im Bezug auf die Entfernung zu der Lichtquelle reguliert.
Dies ist die einfachste Möglichkeit Lens Flares zu simulieren alternativ ist es
aber auch möglich diese Texturen generieren zu lassen.

4.2 Bloom
5 Eigenleistung
Als meine Eigenleistung habe ich selbst zwei kleine Fragment Shader geschrie-
ben. Der eine Shader ist einer, welcher das Bild in Graustufen rendert. Der
andere ist ein HSV-shader, mithilfe man den Farbton, die Helligkeit und die
Sättigung des Bildes regulieren. Ich habe meine Shader für das Spiel Minecraft
geschrieben, weil das Importieren in diesem Spiel besonders leicht ist und die
GLSL-Shader Mod einige Daten mehr ausgibt. Für einen funktionierenden Sha-
der braucht man immer zwei gleich heiÿende Dateien mit zwei unterschiedlichen
Endungen, damit der Shader richtig erkannt wird. Die eine Endung ist .vsh für
den Vertex-shader und die andere .fsh für den Fragment-shader. In der Sha-
derdatei muss immer angegeben werden, welche Version man von GLSL nutzt.
In meinem Fall habe ich immer #version 120 angegeben. Jede Datei braucht
auch ein "void main () ". Alles was im void main steht wird initalisiert und
zur Berechnung des Bildes weitergegeben. Am Ende werden Endinformation
des Fragment Shaders in die Variable gl_Fragdatagespeichert . gl_Fragdata
ist eine voreingebaute Variable, welche ihren Wert in der Shader Pipeline wei-
tergibt.
Beim Vertex Shader heiÿt die dazugehörige Variable gl_MultiTexCoord0.

5.1 Der Graustufen-Shader


Um diesen Eekt umzusetzen und die Farben der Texturen zu editieren, muss
man zuerst die Informationen der Texturen in einen Uniform laden. Ein uni-
form ist hierbei ein read-only Datentyp, welcher aus der Shadermod exportiert

8
wird. Die RGB und Alpha Informationen der Textur habe ich danach in einen
Vierervektor gespeichert. Aus dem Vierervektor Color, habe ich aus den RGB In-
formationen das Arithmetische Mittel gebildet und in der oat äve"gespeichert.
Nun werden die RGB Elemente des Vektors color mit der gemittelten Zahl
überschrieben und Alpha wird auf Null gesetzt. Nach diesem Schritt wird die
editierte Farbe in die Variable gl_FragData geschrieben.

#v e r s i o n 120

uniform sampler2D t e x t u r e ;

void main ( ) {

vec4 c o l o r = texture2D ( t e x t u r e ) . rgba ;

f l o a t ave = ( c o l o r . r + c o l o r . g + c o l o r . b ) / 3 ;

c o l o r = vec4 ( ave , ave , ave , 0 . 0 ) ;

gl_FragData [ 0 ] = vec4 ( c o l o r . rgba ) ;


}

5.2 Der Schwarz-Weiÿ-Shader


Auf den Graustufen Shader aufbauend, habe ich versucht einen Ëchten Schwarz-
Weiÿ-Shaderÿu bauen. Dieser Shader kann nur entweder Schwarz oder Weiÿ
ausgeben. Der Grenzwert kann durch das CUT_VALUE deniert werden. Hier
wird die gemittelte Zahl der RGB Informationen mit dem CUT_VALUE vergli-
chen und wenn diese geringer ist, wird in den Vektor color Schwarz bzw. wenn
sie gröÿer ist weiÿ geladen. Der Vierervektor color wird anschlieÿend wieder in
gl_FragData[0] geschrieben.

9
#v e r s i o n 120

uniform sampler2D t e x t u r e ;
// S e t t i n g s −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
#d e f i n e CUT_VALUE 0 . 5
// S e t t i n g s −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

void main ( ) {

vec4 c o l o r = texture2D ( t e x t u r e ) . rgba ;

f l o a t ave = ( c o l o r . r + c o l o r . g + c o l o r . b ) / 3 ;

i f ( ave <= CUT_VALUE){


c o l o r = vec4 ( 0 , 0 , 0 , 0 . 0 ) ; // black
} else {
c o l o r = vec4 ( 2 5 5 , 255 , 2 5 5 , 0 . 0 ) ; // white
}

gl_FragData [ 0 ] = vec4 ( c o l o r . rgba ) ;

5.3 Der HSV-Shader


Dieser Shader kann zum Colorgraden benutzt werden. Er regelt den Farbwert
(engl. Hue), die Farbsättigung (engl. Saturation) und den Hellwert (engl. Va-
lue). Die Umrechnung vom RGB zum HSV Farbraum hab ich com Sam Hocevar,
welcher diesen Code veröentlichte. Es wird hier in einem Dreiervektor color,
die Farbinformationen der Textur geladen und mithilfe von dem Code von Sam
Hocevar in den Dreiervektor hsvColor umgerechnet. hsvColor beinhaltet die glei-
chen Informationen nur sind diese im Format des HSV Farbraum. Nun werden
die Informationen mit den Variablen HUE, SAT bzw VAL multipliziert und an-
schlieÿend wieder in den RGB Farbraum umgerechnet. Die dadurch erhaltenen
Informationen werden wieder in gl_FragData geschrieben.

#v e r s i o n 120

// S e t t i n g s −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

10
#d e f i n e HUE 1 2 . 5 //−Hue
#d e f i n e SAT 1 //− S a t u r a t i o n
#d e f i n e VAL 1 //− Value

// S e t t i n g s −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−

uniform sampler2D t e x t u r e ;

// by Sam Hocevar
vec3 rgb2hsv ( vec3 c ) {
vec4 K = vec4 ( 0 . 0 , − 1.0 / 3 . 0 , 2 . 0 / 3 . 0 , − 1.0);
vec4 p = mix ( vec4 ( c . bg , K. wz ) , vec4 ( c . gb , K. xy ) , s t e p ( c . b , c . g ) ) ;
vec4 q = mix ( vec4 ( p . xyw , c . r ) , vec4 ( c . r , p . yzx ) , s t e p ( p . x , c . r ) ) ;
f l o a t d = q . x − min ( q . w, q . y ) ;
f l o a t e = 1 . 0 e − 10;
r e t u r n vec3 ( abs ( q . z + ( q .w − q . y ) / ( 6 . 0 ∗ d + e ) ) , d / ( q . x + e ) , q . x )
}
vec3 hsv2rgb ( vec3 c ) {
vec4 K = vec4 ( 1 . 0 , 2 . 0 / 3 . 0 , 1 . 0 / 3 . 0 , 3 . 0 ) ;
vec3 p = abs ( f r a c t ( c . xxx + K. xyz ) ∗ 6 . 0 − K.www) ;
r e t u r n c . z ∗ mix (K. xxx , clamp ( p − K. xxx , 0 . 0 , 1 . 0 ) , c . y ) ;
}

void main ( ) {

vec3 c o l o r = texture2D ( t e x t u r e ) . rgb ;


vec3 hsvColor = rgb2hsv ( c o l o r ) ;

hsvColor . x ∗= HUE;
hsvColor . y ∗= SAT;
hsvColor . z ∗= VAL;

c o l o r = hsv2rgb ( hsvColor ) ;

gl_FragData [ 0 ] = vec4 ( c o l o r , 0 . 0 ) ;

11