Nun mal angenommen, man wollte nur eine einzelne Szene rendern, dann würde man den für diesen konkreten Anwendungsfall erforderlichen GLSL-Code einfach innerhalb des HTML-Dokumentes in ein
<script>
-Element schreiben, selbiges mit irgendeinem Fantasienamen als Typenbezeichnung ausstatten, damit es vom Browser beim parsen ignoriert wird, und das Script dann mittels JavaScript manuell auslesen, um es dann an die WebGL-Schnittstelle weiterzureichen.
Der ganze <script>-Kram ist in diesem Zusammenhang nur nebensächlich. Wichtig ist eben, dass Shader als Strings an die WebGL-Schnittstelle übergeben werden müssen.
Entweder man erstellt ein ellenlanges Shader-Programm, welches alle Eventualitäten abdeckt, das also beispielsweise sowohl Farbe als auch Textur verarbeitet und auch alle möglichen Wünsche hinsichtlich der Beleuchtung der Szene berücksichtigt - was aber natürlich extrem unperformant wäre, oder - wofür ich mich entschieden habe - man erstellt die Shader-Programme je nach tatsächlichem Bedarf
Es gibt noch weitere Möglichkeiten: Du kannst beispielsweise statt einem monolithischem Shader auch modular vorgehen und diverse kleine Shader programmieren. Bei Three.js benutzt man einen hybriden Ansatz: Man hat mehrere Shader, aber man kann sie zusätzlich über JavaScript parametresieren.
Mein Problem ist nun, dass ich mir nicht sicher bin, was aus Usability- und auch aus Performance-Sicht die beste Herangehensweise ist, um dem Umstand zu begegnen, dass sich das Anforderungsprofil an die Shader in einer animierten Szene durchaus verändern kann.
Shader sind in der WebGL-Rendering-Pipeline soweit ich weiß starr und können nicht mehr zur Laufzeit verändert werden. Du kannst Shader höchstens nachladen, das ist vermutlich eine sehr teure Operation un solltest du nicht bei jedem Rendering Call machen.
Wie löse ich dieses Dilemma also auf?
Welche Einflüsse können denn während einer laufenden Animation dazu führen, dass der Shader ausgetauscht werden muss? Kann man dieses Problem vielleicht schon auf Shader-Ebene lösen?
Andererseits könnte man als Kompromiss vielleicht zwar davon absehen, die Initialisierung der Knoten beziehungsweise der Shader zu formalisieren, jedoch trotzdem die Möglichkeit einräumen, bereits bei der Erstellung der Knoten auch prinzipiell einander ausschließende Einstellungen wie etwa Farbe/Textur oder Licht/kein Licht zuzulassen und über boolesche Variablen entsprechende Schalter einzubauen...
Du solltest bei deinem API-Design auf jeden Fall darauf achten, dass keine Optionen gesetzt werden können, die sich gegenseitig ausschließen. Das kann zu inkonsistenten Programmzuständen und zu schwer vorhersehbarem (weil undefiniertem) Verhalten führen. Boolsche Variablen sind außerdem nicht die beste Lösung, um gegenseitigen Ausschluss (mutual exclusion) zu realisieren, denn damit kannst du ja immer nur zwei Optionen unterscheiden.
Wozu würdet ihr mir raten?
Es kommt sehr auf deine Bibliothek an, und welchen Abstraktionsgrad du damit leisten willst. Wenn du eine High-Level-Bibliothek entwickelst, dann kann es in der Regel nicht schaden, auch Schnittstellen auf einem niedrigerem Niveau anzubieten, so kannst du deine Bibliothek erweiterbar gestalten. Wenn du dagegen eine Low-Level-Bibliothek schaffen möchtest, dann solltest du diese Abstraktion über die Shader vielleicht ganz außen vor lassen.