var: WebGL - Default-Einstellungen und Shader-Programme

Hallo miteinander!

Ich arbeite nun schon seit einiger Zeit an einem Framework für WebGL, und mein größtes Problem ist dabei im Moment, dass ich mich nicht recht entscheiden kann, wie ich am sinnvollsten die Default-Einstellungen festlegen soll für die Knoten meines Szenengraphen, beziehungsweise, welche Voreinstellungen ich hinsichtlich der Generierung der zugehörigen Shader-Programme idealerweise treffen sollte.

Also, für diejenigen, die mit der Funktionsweise von WebGL beziehungsweise der Grafikprogrammierung im Allgemeinen nicht so vertraut sind, hier mal eine kurze Zusammenfassung, worum es geht:

Auf die WebGL-Schnittstelle wird mittels JavaScript zugegriffen, aber die Shader-Programme, welche an die GPU übermittelt werden, sind in GLSL zu schreiben.

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.

Da die Shader-Programme jedoch zu dem Teil der Grafik-Pipeline gehören, den ich durch mein Framework zu abstrahieren versuche, generiere ich die Vertex- und Fragmentshader direkt in JavaScript, wobei es im Prinzip zwei mögliche Herangehensweisen gibt:

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, - womit wir bei den Knoten meines Szenengraphen wären!

Bei diesen Knoten handelt es sich um Funktions-Objekte und sie sind gewissermaßen die elementaren Bausteine, aus denen sich die 3D-Szene zusammensetzt, ähnlich den DOM-Elementen, und so wie man über die style-Eigenschaft die entsprechenden Element-Attribute ändern kann, kann man für jeden Knoten festlegen, ob und wenn ja welches 3D-Objekt gezeichnet werden soll, ob Farbe oder Textur verwendet werden soll, Transparenz, Beleuchtung, Animation, usw.

Auf diese Art erhält jeder Knoten ein eigenes Eigenschaftenprofil, welches dann die Grundlage für die Parameter liefert, nach denen das vom Knoten-Objekt verwendete Shader-Programm erstellt wird (wobei Code-intern natürlich erst einmal geprüft wird, ob nicht bereits ein entsprechend passendes Shader-Programm verfügbar ist, dass vom Knoten genutzt werden kann).

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.

Denn auch hier gibt es ja wieder die zwei Möglichkeiten: Entweder ich verlange, dass bei der Erstellung beziehungsweise Initialisierung des Knotens das Eigenschaftsprofil in Vorhersehung der weiteren Verwendung des Objektes vollständig angegeben und das Shader-Programm dementsprechend zum Beispiel unter Verwendung boolescher Variablen generiert wird, oder aber ich erlaube auch die nachträgliche Veränderung von Objekteigenschaften, - was dann aber natürlich die erneute Erstellung eines Shader-Programms erfordern würde - bei entsprechender Anzahl an nodes und bei 60fps unter Umständen nicht gerade die performanteste Lösung, aber im Vergleich eben deutlich intuitiver in der Handhabung...

Wie löse ich dieses Dilemma also auf?

Ich tendiere dazu, eventuelle Performance-Einbußen in Kauf zu nehmen und die Frage nach der Initialisierung des Knotens beziehungsweise des Zeitpunkts und der Bedingungen der Erstellung der Shader von der Benutzerschnittstelle einfach komplett auszunehmen, - aber sicher bin ich mir dabei nicht!

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...

Wozu würdet ihr mir raten?

Gruß,

var

  1. Hallo miteinander!

    Ich habe nochmal darüber nachgedacht und glaube, dass es zwar theoretisch Programm-Konstrukte geben mag, bei denen eine Shader-Generierung on-the-fly unperformant wäre, aber in der absoluten Mehrzahl der Fälle dürfte es wohl kein Problem darstellen.

    Die in der Praxis wohl wahrscheinlichsten Veränderungen von Objekteigenschaften bei Animationen dürften nämlich ohnehin nicht das Vorliegen der Eigenschaft selbst, sondern nur die zugewiesenen Werte betreffen, wie beispielsweise Position, Beleuchtungsstärke und -richtung, usw., und das können die Shader-Programme ja unproblematisch handhaben.

    Und selbst wenn die Neuerstellung eines Shader-Programms zur Laufzeit erforderlich wird, so dürfte es dennoch unwahrscheinlich sein, dass dies öfter als ein paar mal geschieht, zumal der alte Zustand, sprich, das zuvor genutzte Shader-Programm, ja nicht erneut erstellt werden muss, sondern einfach gespeichert und bei Bedarf mit gl.useProgram( ) wieder angewendet werden kann.

    Ich denke, ich werde die Benutzer, wie zuvor schon angedacht, mit der ganzen Shader-Problematik also überhaupt nicht behelligen, so dass sie einfach wie in CSS die gewünschten Eigenschaften für die erstellten node-Objekte festlegen können, unabhängig davon, ob das vor oder während der drawing-time geschieht.

    Denn nur für ein paar absolute Grenzfälle den Code aufzublähen und die Benutzung komplizierter zu machen ist wohl keine gute Wahl und schließlich ist mein Framework ja auch so konzipiert, dass die automatische Shader-Generierung vom Benutzer deaktiviert und stattdessen eigene GLSL-Sources eingebunden werden können...

    Damit dürfte allen gedient sein. ;-)

    Gruß,

    var

  2. 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.

    1. Hallo 1unitedpower

      Erstmal danke für die Antwort!

      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.

      Jep, so ist es. - Ich wollte die Geschichte nur ein wenig ausschmücken. ;-)

      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.

      Naja, das mit dem monolithischen Shader, wie du es so schön genannt hast, ist natürlich in der Praxis vollkommen unrealistisch, ich habe es nur erwähnt, um Uneingeweihten den theoretischen Rahmen der Angelegenheit etwas zu verdeutlichen, beziehungsweise um eben diese "Option" von vorneherein aus den Überlegungen auszuschließen, nur für den Fall, dass jemand dies als Lösungsansatz in Betracht ziehen würde.

      Was den modularen Ansatz angeht, das ist keine weitere Option, sondern im Prinzip genau das, was ich mit tatsächlichem Bedarf meinte. Das habe ich wohl schlecht erklärt!

      Also die Sache funktioniert bei mir eigentlich genau wie HTML und CSS, das heißt, man erstellt Elemente in etwa nach dem Schema

      var earth = scene.createNode({'type' : 'regular', 'parent' : 'root', 'geometry' : 'sphere'});
      

      und weist dann diesen Elementen Eigenschaften zu, entweder direkt bei der Initialisierung wie oben, oder nachträglich einzeln

      earth.setNodeName('earth');
      

      oder zusammen:

      earth.setProperties({
      
        'position' : [0.0, -2.8, -10.2],
        'scale' : [1.3, 1.3, 1.3],
        'color' : true,
        'color-type' : 'uniform',
        'color-rgb' : [20, 130, 210],
        'alpha' : 1.0,
        'lighting' : true,
        'light-model' : 'phong',
        'light-position' : [2.4, 8.0, -4.7] // etc...
      
      });
      

      Jedes node-Objekt gewinnt dadurch ein mehr oder weniger individuelles Eigenschaftenprofil und je nach dem, welche Eigenschaften zugewiesen wurden, wird dann automatisch ein exakt passendes Shader-Programm generiert (sofern ein solches nicht bereits auf dem Stack liegt), welches dann von dem Elementknoten verwendet wird.

      Man könnte hier also von maximaler Modularisierung sprechen! ;-)

      Dabei sei hinsichtlich der Problemstellung noch erwähnt, dass die Werte, die wie oben gesehen für jeden Knoten festgelegt werden, nicht zwingend und in jedem Fall vor Initialisierung des Shaders bestimmt werden müssen: Zwar gibt es Eigenschaften - wie etwa die Wahl des Beleuchtungs-Modells - die für die Shader konstitutiv sind, aber andere Werte, wie beispielsweise die Position usw. können (und müssen) natürlich zur Laufzeit an den Shader übermittelt werden.

      Darüber hinaus bestimmt der Benutzer hier über den Grad der Einflussnahme weitestgehend selbst, das heißt, wenn er zum Beispiel nur {'lighting' : true} angibt, ohne Typ-, Farb- oder Positionsangabe, dann wird halt die Szene einfach entlang der Y-Achse mit weißem Licht von oben angeleuchtet...

      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.

      Das ist richtig. Ein Programm in WebGL (bestehend aus Vertexshader und Fragmentshader) muss kompiliert und gelinkt werden, bevor es verwendet werden kann. Eine nachträgliche Änderung ist dann nicht mehr möglich. Man kann das Programm höchstens löschen, Modifikationen am Source-String vornehmen und dann die ganze Prozedur wiederholen. Und ja, das ist eine teure Operation! - Deshalb meine anfänglichen Bedenken hinsichtlich der Shader-Generierung zur Laufzeit (die aber aus Gründen, auf die ich weiter unten noch eingehen werde, nicht so problematisch ist, wie zunächst angenommen).

      Welche Einflüsse können denn während einer laufenden Animation dazu führen, dass der Shader ausgetauscht werden muss?

      Naja, wie bereits erklärt, werden die Shader exakt nach den Eigenschaftenprofilen der einzelnen Elemente erstellt, das heißt, sie repräsentieren den genauen Status eines Knotens zu einem bestimmten Zeitpunkt. Aber es wäre natürlich ohne weiteres denkbar, dass jemand mit meiner Bibliothek ein Programm schreibt, bei dem sich bestimmte Objekt-Eigenschaften, die für den Shader konstitutiv sind, verändern, wie beispielsweise die Anzahl der Lichtquellen oder gleich das ganze Beleuchtungsmodell, - ich meine, da kommen doch recht viele Szenarien in Frage...

      Kann man dieses Problem vielleicht schon auf Shader-Ebene lösen?

      Hierzu mal ein kleines Beispiel. Ein sehr einfacher Fragmentshader könnte so aussehen:

      precision highp float;
      
      uniform float uAlpha;
      uniform vec3 uColor;
      
      uniform vec3 uLightDirection;
      uniform vec3 uAmbientColor;
      uniform vec3 uLightColor;
      
      varying vec3 vTransformedNormal;
      
      void main (void) {
      
        vec3 reflectedLight = max(dot(vTransformedNormal, uLightDirection), 0.0);
      
        vec3 lightWeighting = uAmbientColor + reflectedLight * uLightColor;
      
        gl_FragColor = vec4(uColor * lightWeighting, uAlpha);
      
      }
      

      Oder aber so:

      precision highp float;
      
      uniform float uAlpha;
      uniform vec3 uColor;
      
      uniform vec3 uLightDirection;
      uniform vec3 uLightPosition;
      
      uniform bool uUseDirectionalLighting;
      
      uniform vec3 uAmbientColor;
      uniform vec3 uLightColor;
      
      varying vec4 vPosition;
      varying vec3 vTransformedNormal;
      
      void main (void) {
      
        vec3 reflectedLight;
      
        if (uUseDirectionalLighting) {
      
          reflectedLight = max(dot(vTransformedNormal, uLightDirection), 0.0);
      
        } else {
      
          vec3 lightDirection = normalize(uLightPosition - vPosition.xyz);
      
          reflectedLight = max(dot(normalize(vTransformedNormal), lightDirection), 0.0);
      
        }
      
        vec3 lightWeighting = uAmbientColor + reflectedLight * uLightColor;
      
        gl_FragColor = vec4(uColor * lightWeighting, uAlpha);
      
      }
      

      Während der Shader im ersten Fall nur directional-lighting berechnen kann, kann der zweite Shader sowohl directional- als auch position-lighting darstellen, je nach dem, ob zur Laufzeit an die uniform uUseDirectionalLighting der Wert true oder false übergeben wurde, und dieses Spiel kann man natürlich nahezu beliebig weiter treiben (oder bestimmte Komplexe gar gleich in externe Funktionen außerhalb von main ausgliedern).

      Das Problem ist hierbei aber natürlich, dass branching eigentlich unerwünscht weil unperformant ist, insbesondere bei Shadern!

      Und das ist eben das Dilemma: Entweder ich gestatte (oder fordere), dass alle Eigenschafts-Zustände, die im Lebenszyklus eines node-Objektes vorkommen sollen, bereits bei der Initialisierung des Shaders festgelegt werden, damit dieser die entsprechende Funktionalität bereitstellen kann, was dann zwangsläufig zu ineffizientem GLSL-Code führt, oder aber ich erlaube es, auch solche Objekteigenschaften zur Laufzeit dynamisch anzupassen, die für den Shader konstitutiv sind, wobei dann aber freilich jedesmal ein neues Shaderprogramm generiert werden muss, - was dann auch wieder Ressourcen kostet. (Zumindest in dem Fall, dass ein solches Programm nicht bereits existiert.)

      Warum Letzteres trotzdem die weniger schlechte Alternative ist, habe ich in meinem Nachtrag schon angedeutet, aber es kann sicher nicht schaden, meinen Gedankengang nocheinmal etwas zu erläutern...

      Ich denke, bezüglich der Lösung des Problems müssen wir uns folgendes klar machen:

      Shader werden auf der GPU per-Vertex ausgeführt, das heißt, Programme wie in dem Beispiel weiter oben werden unter Umständen, je nach dem, wie komplex das darzustellende 3D-Objekt aufgebaut ist, zigtausende Male aufgerufen, um ein Bild zu rendern!

      Demgegenüber ist die Erstellung eines Shaderprogramms ein singuläres Ereignis, welches zudem unabhängig von der GPU auf dem Hauptprozessor berechnet wird, so dass der Flaschenhals hier meiner Ansicht nach eindeutig bei den Shadern liegt.

      Auch ist zu berücksichtigen, dass WebGL es erlaubt, eine beliebige Anzahl solcher Programme zu linken, so dass Shader, die einmal erstellt wurden, nahezu ohne Performanceeinbußen wiederverwendet werden können.

      Und schließlich gibt es zwar eine beachtliche Anzahl an Kombinationsmöglichkeiten hinsichtlich der Komponenten aus denen die Shader aufgebaut sind, aber bezogen auf ein einzelnes Objekt ist in der Praxis davon auszugehen, dass nur eine überschaubare Menge an Veränderungen zur Laufzeit in Betracht kommen werden die eine Neuerstellung des Shaders erfordern, und sich diese Menge aufgrund der Wiederverwendbarkeit der Programme mit jeder Änderung um 1 reduziert. ;-)

      Aus genannten Gründen wird es also wohl das Beste sein, an meinem Konzept prinzipiell festzuhalten und auch die Veränderung von Shader-relevanten Objekteigenschaften zur Laufzeit zuzulassen, wobei man allerdings darüber nachdenken sollte, dem Benutzer hier einen Entscheidungsspielraum zuzugestehen, schon alleine aufgrund der kaum Überschaubaren Menge potentieller Use-Cases.

      Ich denke hier an drei Möglichkeiten:

      Erstens wäre es natürlich möglich, jedem Knoten-Objekt mehr als ein Set an Eigenschaften anzuhängen, so dass man schon vor der Laufzeit alternative Shaderprogramme erstellen könnte.

      Zweitens könnte ich es so einrichten, dass multioptionale Shader durch entsprechende Parametrisierung zumindest möglich sind, wobei ich - wie du in deinem Post richtig angemerkt hast - dabei allerdings sehr streng darauf achten muss, dass es nicht zu inkonsistenten Programmzuständen kommt.

      Und sollte das alles im Einzelfall nicht zu den gewünschten Ergebnissen führen, bliebe dem Benutzer schließlich noch die Möglichkeit, ein eigenes Shaderprogramm zu verwenden.

      Das scheint mir jedenfalls im Moment die beste Lösung zu sein...

      Beste Grüße,

      var

  3. Hallo miteinander!

    Ich hätte mal eine Frage zum <canvas> Element, und zwar welche Elternelemente hierfür zulässig sind.

    Der Referenz im Wiki nach darf das canvas „in allen Textstrukturierungselementen, in denen Inhalt eingebettet werden darf“ vorkommen, aber welche Elemente sind das konkret?

    Ich möchte nämlich den Benutzern meiner Bibliothek die Wahl lassen, ob sie den WebGL-Kontext an ein bereits bestehendes canvas knüpfen wollen, oder ob sie ein Elternelement bestimmen und das canvas von der scene-Funktion erstellen lassen wollen, nach dem Schema:

    var myCanvas = document.getElementById('myCanvas');
    
    var myScene = new scene({ 'canvas' : myCanvas });
    

    oder

    var myDiv = document.getElementById('myDiv');
    
    var myScene = new scene({ 'parent' : myDiv });
    

    Im ersten Fall ist die Eingabeüberprüfung ja recht einfach mit param.canvas.tagName === 'CANVAS' zu bewerkstelligen, aber außer param.parent.nodeType === 1 zur Überprüfung, ob es sich um einen Elementknoten handelt, wüsste ich nicht, wie ich die Eingabe eines canvas-Elternelementes validieren soll!

    Hat jemand eine Idee, wie ich das am besten anstelle?

    Dank und Gruß,

    var

    1. Hallo miteinander!

      Naja, im Prinzip könnte man die Sache natürlich auch von der anderen Seite her angehen und sagen:

      Wenn bei der Initialisierung der scene-Funktion kein <canvas> übergeben wurde, wird eben automatisch eins erstellt und dieses ist dann über scene.canvasElement oder so abrufbar.

      So könnte ich mir die Frage nach der Validierung sparen und diese Zugriffsoption sollte es ja ohnehin geben, da die scene-Funktion nicht dafür da ist, irgendwelche Layout- bzw. Stylefragen zu regeln...

      Außer der Anbindung des WebGL-Kontextes ist das canvas zudem nur insoweit von Interesse, als dass ich eine auto-resize-Funktion bereitstelle, aber dafür muss ich nicht am DOM rumpfuschen, da lese ich einfach mit getComputedStyle( )[ ] die tatsächlichen Abmessungen aus und setze dann den viewport des Kontextes.

      Naja, interessant wär's trotzdem gewesen. ;-)

      Gruß,

      var

    2. @@var

      Der Referenz im Wiki nach darf das canvas „in allen Textstrukturierungselementen, in denen Inhalt eingebettet werden darf“ vorkommen, aber welche Elemente sind das konkret?

      Kuckst du nicht in Sekundärliteratur, sondern in die Referenz. Click.

      Im Inhaltsverzeichnis nach „canvas“ gesucht. Click.

      “Contexts in which this element can be used: Where embedded content is expected.” Click.

      Hochscrollen zur Überschrift. 3.2.4.1.6: Die Anzahl der Hierarchie-Ebenen lässt vermuten, dass weiter oben noch brauchbare Informationen zu finden sind. Weiter hochscrollen … bis zum Diagramm unter 3.2.4.1

      Embedded content ist also echte Teilmenge von phrasing content. canvas kann also Kind aller Elemente sein, die phrasing content als Inhaltsmodell haben.

      LLAP 🖖

      --
      „Talente finden Lösungen, Genies entdecken Probleme.“ (Hans Krailsheimer)
      1. Hallo Gunnar!

        Kuckst du nicht in Sekundärliteratur, sondern in die Referenz. Click.

        Habe ich gemacht...

        Im Inhaltsverzeichnis nach „canvas“ gesucht. Click.

        ...das auch...

        “Contexts in which this element can be used: Where embedded content is expected.” Click.

        ...und das!

        Hochscrollen zur Überschrift. 3.2.4.1.6: Die Anzahl der Hierarchie-Ebenen lässt vermuten, dass weiter oben noch brauchbare Informationen zu finden sind. Weiter hochscrollen … bis zum Diagramm unter 3.2.4.1

        Das nicht! ;-)

        Embedded content ist also echte Teilmenge von phrasing content. canvas kann also Kind aller Elemente sein, die phrasing content als Inhaltsmodell haben.

        Hmmhh, phrasing content, also wenn ich das richtig verstehe, finde ich das durchaus erstaunlich:

        <canvas> als Kindelement von <b>, <img> oder <br>!?

        HTML scheint hinsichtlich innerer Logik weniger strikt zu sein, als ich vermutet habe...

        Danke für den Tip!

        Gruß,

        var

        1. @@var

          Embedded content ist also echte Teilmenge von phrasing content. canvas kann also Kind aller Elemente sein, die phrasing content als Inhaltsmodell haben.

          Hmmhh, phrasing content, also wenn ich das richtig verstehe,

          Tust du nicht.

          <canvas> als Kindelement von <b>, <img> oder <br>!?

          Nein. Die Auflistung sind die Elemente, die selbst phrasing content sind; nicht die, die phrasing content als Inhaltsmodell haben.

          img und br dürfen natürlich keinen Inhalt haben.

          LLAP 🖖

          --
          „Talente finden Lösungen, Genies entdecken Probleme.“ (Hans Krailsheimer)
          1. @@Gunnar

            Hmmhh, phrasing content, also wenn ich das richtig verstehe,

            Tust du nicht.

            Hätte mich auch stark gewundert!

            [...] Die Auflistung sind die Elemente, die selbst phrasing content sind; nicht die, die phrasing content als Inhaltsmodell haben.

            Ich nehme an, falls du die richtige Auflistung zur Hand hättest, hättest du sie verlinkt. Naja, da werd ich selbst nochmal nachschauen...

            img und br dürfen natürlich keinen Inhalt haben.

            Davon war ich bisher auch ausgegangen. ;-)

            Nochmals Dank und Gruß,

            var

      2. Hallo,

        Embedded content ist also echte Teilmenge von phrasing content. canvas kann also Kind aller Elemente sein, die phrasing content als Inhaltsmodell haben.

        Das ist m.E. keine zulässige Schlussfolgerung.

        Gruß
        Kalk

        1. Tach,

          Embedded content ist also echte Teilmenge von phrasing content. canvas kann also Kind aller Elemente sein, die phrasing content als Inhaltsmodell haben.

          Das ist m.E. keine zulässige Schlussfolgerung.

          dann solltest du deine Logik nochmal überprüfen. Elemente von A haben die Eigenschaft a, und B ist Teilmenge von A, daraus folgt Elemente von B haben die Eigenschaft a, weil jedes Element von B ist auch Element von A.

          mfg
          Woodfighter

          1. Hallo,

            dann solltest du deine Logik nochmal überprüfen.

            Oh, ok. Ich hatte den Teil mit dem "Inhaltsmodell" fehlinterpretiert.

            Gruß
            Kalk

  4. Hallo miteinander!

    Bislang hatte ich es immer so gehalten, dass ich die Voreinstellungen in meinem Code immer (nur) an der Stelle notiert habe, wo sie gebraucht werden, also in etwa nach dem Schema:

    var node = function (param) {
    
      // ...
    
      var blending = param.blending || false;
    
      // ...
    
      var uniformColor = param.uniformColor || [1.0, 1.0, 1.0];
    
      // ...
    
    }
    

    Allerdings Frage ich mich, ob es nicht besser wäre, die Defaults in einem separaten Funktionsobjekt zu speichern, also z.B.

    var nodeDefault = {
    
      'parent' : 'root',
      'role' : 'abstract',
    
      // ...
    
      'blending' : false,
    
      // ...
    
      'uniformColor' : [1.0, 1.0, 1.0],
    
      // ...
    
    }
    

    um dann an entsprechender Stelle darauf Bezug zu nehmen

    var node = function (param) {
    
      // ...
    
      var uniformColor = param.uniformColor || nodeDefault.uniformColor;
    
      // ...
    
    }
    

    Das würde zwar mehr Code beudeuten, aber für die Übersichtlichkeit und Wartbarkeit desselben wäre es wohl eine Verbesserung, wenn man die Voreinstellungen nicht mehr über hunderte Zeilen versteut suchen muss, sondern sie an zentraler Stelle aufzubewahrt werden, oder?

    Statt nodeDefaults könnte man dann natürlich auch gleich sceneDefaults erstellen

    var sceneDefault = {
    
      // ...
    
      'autoResize' : true,
    
      // ...
    
      'node' : {
    
          'parent' : 'root',
          'role' : 'abstract',
    
          // ...
    
      }
    
    }
    

    Daher meine Frage an die erfahrenen Programmierer unter euch: Was meint ihr, welche Vorgehensweise hier zu empfehlen sei?

    PS: Die gleiche Frage könnte man natürlich auch hinsichtlich der Behandlung von Fehlern stellen, sprich, sollte soweit möglich nicht auch Validierung und Reaktion zentralisiert werden?

    Gruß,

    var