Shaders mit Shadertoy

Ich hat­te mich Anfang Dezem­ber 2024 ange­mel­det und ange­fan­gen, damit etwas zu pro­gram­mie­ren. Es erin­ner­te mich etwas an die Zeit vor so 15–20 Jah­ren, als ich mit dem script­ba­sier­ten Raytracing-Programm POV-Ray auch durch Schrei­ben von mathe­ma­tik­las­ti­gem Code Gra­fi­ken erzeug­te. Da dau­er­te ein Bild gut und ger­ne Stun­den bis Tage zum Berech­nen. Hier jedoch geht es dar­um, dass 60 Bil­der pro Sekun­de fer­tig werden.

Im Benut­zer­pro­fil gibt es mei­ne dort pro­gram­mier­ten Shader:
https://www.shadertoy.com/user/Svenofnine/sort=newest

Was ist Shadertoy?

Die Start­sei­te von Shader­toy ent­hält die jeweils bes­ten und neus­ten von Community-Mitgliedern pro­gram­mier­ten und geteil­ten Shader.

Shader­toy ist eine eng­lisch­spra­chi­ge Pro­gram­mier­um­ge­bung für soge­nann­te Shader. Rechts schreibt man ein Pro­gramm in der Spra­che GLSL, das dann direkt auf der Gra­fik­kar­te von der Shader-Kernen der GPU aus­ge­führt wird und links sieht man gleich, was es tut.

Für Pro­gram­mie­rer ist es wirk­lich wie ein Spiel: Schrei­be ein Pro­gramm, was in einer Sand­box läuft. Von außen bekommst du Wer­te für Auf­lö­sung, abge­lau­fe­ne Zeit und die Koor­di­na­te des zu berech­nen­den Frag­ments (Pixels) eines Frames. Die Funk­ti­on soll den Farb­wert die­ses Pixels berech­nen. Und nicht nur den: Die Gra­fik­kar­te führt die­se Funk­ti­on für jedes Pixel eines Frames gleich­zei­tig aus, bis alle Pixel berech­net wur­den und das Frame fer­tig ist. Das pas­siert im Ide­al­fall 60-mal in der Sekunde.

Also geht es um Bil­der­zeu­gung allein mit Mathe­ma­tik und dar­um, sei­ne Erfah­rung und den Code zu tei­len. Das macht wirk­lich Spaß, zumin­dest wenn man sich wie ich an Pro­gram­mie­rung und Mathe­ma­tik erfreu­en kann.

Doch wie errech­net man anhand der Pixel­ko­or­di­na­te eine Far­be und war­um und vor allem wie kön­nen alle Pixel gleich­zei­tig errech­net wer­den? Dazu habe ich unter https://www.shadertoy.com/view/XXKcR1 ein ein­fach gehal­te­nes Bei­spiel erstellt:

 

Dreh- und Angel­punkt eines Shaders ist die mainImage()-Funktion, wel­che von der Host-Anwendung (hier: Brow­ser mit Java­script im HTML-Canvas) mit jeder mög­li­chen Pixel­ko­or­di­na­te (=Frag­ment) auf­ruft und die Far­be, die sie zurück­er­hält, auf den Bild­schirm bringt. Dabei bekommt jeder Kern der Gra­fik­kar­te eine ande­re Pixel­ko­or­di­na­te zuge­teilt, so dass der Pro­zess par­al­lel abläuft.

Als Pro­gram­mie­rer sitzt man qua­si in die­ser Funk­ti­on mai­nI­mage() drin. Neben den Koor­di­na­ten XY bekommt man von außen noch Lese­zu­griff auf soge­nann­te Uniform-Variablen für Auf­lö­sung, Zeit, Framenum­mer, Maus­po­si­ti­on von der Host-Anwendung, wel­che für alle Pixel eines Frames gleich sind.

Die­ses Bei­spiel soll zei­gen, wie man nun inner­halb die­ser Black Box mit Mathe­ma­tik For­men defi­niert und die­se im ein­fachs­ten Fall dar­stellt, indem man nur ent­schei­det, ob eine Pixel­ko­or­di­na­te auf der Form liegt oder nicht. Die ein­fachs­te Form dafür ist mei­ner Mei­nung nach ein Kreis. Die For­mel in Zei­le 12 errech­net dafür den Abstand zwi­schen Pixel­ko­or­di­na­te und Kreis­mit­te. Ist er klei­ner als der Radi­us, wird 1 zurück­ge­lie­fert, ansons­ten 0.

Der ermit­tel­te Wert wird dann mit allen drei Kom­po­nen­ten der Kreis­far­be mul­ti­pli­ziert und der Pixel­far­be zuge­wie­sen (Zei­le 18).

Das macht ein Shader: Pixel­ko­or­di­na­te rein, Pixel­far­be raus. Die Pixel­far­be zu ermit­teln ist sel­ten so eine ein­fa­che Wenn-Dann-Entscheidung, son­dern wird bestimmt auf künst­le­risch gestal­te­ten Wegen vom Betrach­ter aus durch beweg­li­che Distanz­fel­der, Ober­flä­chen mit Tex­tu­ren und Refle­xio­nen in ver­zerr­ten Räu­men. Das kann alles sein, was sich berech­nen lässt. Das Rech­nen mit Vek­to­ren, Matrit­zen und Fließ­kom­ma­zah­len sind in den Gra­fik­pro­zes­so­ren mit ihren vie­len Shader-Kernen nativ ver­drah­tet. Die war­ten drauf!

Quellen für den Anfang

Das »Book Of Shaders« setzt hier an und erklärt in ein­fa­cher Spra­che und Hand­zeich­nun­gen, wie das Gan­ze im Grun­de funk­tio­niert.
Youtube-Video »Lean to Paint with Mathe­ma­tics« von Nut­zer iq ist eine gute Ein­füh­rung in das Tool Shader­toy und Nut­zer FabriceNeyret2 zeigt auf sei­nem Blog Shader­toy Unof­fi­ci­al, was damit alles mög­lich ist. Vor­weg: Gra­fisch ist alles möglich!

Nach­dem etwa eine Woche ver­gan­gen war, ließ mich das The­ma nicht mehr los und es krib­bel­te in den Fin­gern. Das Erzeu­gen von Bil­dern und Ani­ma­tio­nen mit Mathe­ma­tik und Pro­gramm­code war ja schon 2003 mit POV-Ray künst­le­ri­sches Werk­zeug. Anders als mit dem Ray­tra­cer POV-Ray geht es bei Shadern um Echt­zeit­gra­fik, also es müs­sen 60 Bil­der pro Sekun­de ent­ste­hen, nicht eins in Stun­den oder Tagen. Bei­des behält aber trotz­dem in ihrer Ver­schie­den­heit sei­ne Daseinsberechtigung.

Bei den Pro­gram­mier­spra­chen, mit wel­chen ich mich bis­her beschäf­tigt habe, gab es immer Refe­ren­zen der Funk­tio­nen, Varia­blen und Ope­ra­to­ren. Bei Shader­toy hat­te es eine Wei­le gedau­ert, bis ich auf die­ses klei­ne Fra­ge­zei­chen rechts unten am Rand des Text­edi­tors ent­deck­te. Klickt man da drauf, gibt es direkt von Shader­toy das Wich­tigs­te auf einer Seite.

Oben am Edi­tor gibt es die »Shader Inputs«. Das sind soge­nann­te uni­for­me Varia­blen, wel­che für alle Pixel eines Frames kon­stant sind und zur lesen­den Ver­fü­gung stehen.

Es gibt neben den bekann­ten Daten­ty­pen float (Fließkomma-Zahl) und int (Ganz­zahl) noch ver­schie­de­ne Vektor‑, Matrix- und Buffer-Datentypen, die auch auf der Hard­ware der Gra­fik­kar­te unter­stützt werden.

Die­ser Blog-Post ist kein Tuto­ri­al, weil ich da ja selbst ganz am Anfang ste­he und es vie­le Res­sour­cen dafür gibt. Ich ver­su­che, soweit es geht, mei­nen Code zu kom­men­tie­ren und Fra­gen zu beantworten.

Hier sind noch wei­te­re Links zum Thema:

 
Viel­leicht sieht man sich ja beim krea­ti­ven »Shadern«.