WebGL - 3D im Browser

siehe auch WebGPU!
 Home
 WebGL Api Spickzettel
 WebGL Sicherheit

Tutorial
 0 : WebGL Browser
 1 : Das erste Dreieck
 2 : 3D-Mathematik
 3 : Farbe
 4 : Animation
 5 : Interaktion I
 6 : Texturen
 7 : Beleuchtung I
 8 : Interaktion II

Links
 WebGL Beispiele
 WebGL Frameworks
 ext. WebGL Tutorials


 Kontakt / Impressum
 webgl ([ät)] peter-strohm Punkt de

6 Texturen

>>>> Direkt zum Beispiel <<<<

6.1 Was sind Texturen ?

siehe auch http://de.wikipedia.org/wiki/Textur_(Computergrafik)

Ich werde es mit einer WebGL-praxisnahen Erklärung anstelle einer exakten Definition versuchen:

Texturen sind 2D-Grafiken, die auf Flächen (hier: Dreiecke) "aufgeklebt" werden um diesen eine schönere, detailiertere Oberfläche zu verleihen.

Hier das WebGL-Beispiel zu diesem Kapitel (aufbauend auf den vorangegangenen Kapiteln):
Da wir bis jetzt (vor allem wegen der einfachen Geometrie) mit einer Pyramide gearbeitet haben, habe ich auch dazu mehr oder weniger passend eine Pyramidentextur gesucht. Diese Maya-Pyramide (Chichén Itzá, Pyramide des Kukulcán) hat zwar weder ideale Pyramidenformen (Stufen an den Seiten, keine Spitze oben) noch ist sie günstig fotografiert um als Textur verwendet zu werden, aber das soll uns erstmal nicht stören.

texturierte Pyramide  Pyramidentextur - Texturpyramide
Bild 6.1 : Screenshot zum Beispiel 6

Bisher war in meiner WebGL-Beispielpyramide jedem Vertex ein Farbwert zugeordet. Zwischen den Vertices wurde die Farbe automatisch interpoliert (gemittelt).
Anhand des Beispiels wird sichbar, das auf die Geometrie der WebGL-Pyramide ein Teil des Fotos der echten Pyramide (texpyra.jpg) "geklebt" wird. Ich habe bewusst das Beispiel so gewählt, dass die Textur nicht 1:1 auf eine Rechteck-Geometrie gelegt wird, da so später der Zusammenhang zwischen Vertex- und Texturkoordinaten deutlicher wird.

6.2 Bastelarbeiten

Wie können wir WebGL vermitteln, welchen Teil der 2D-Grafik es wie und wohin auf die Oberfläche des 3D-Objektes aufbringen soll?
Dafür müssen wir zunächst einmal selbst wissen, welcher Teil verwendet werden soll. In diesem Beispiel wird nur ein Teil der Seitenansicht der Pyramide benötigt. Der Rasen, der Himmel, die Büsche im Hintergrund und die andere Pyramidenseite sollen in der Textur unserer WebGL-Pyramide nicht vorkommen.
Wir müssen in der 2D-Grafik (der jpg-Datei) die RELATIVEN Koordinaten derjenigen Punkte ermitteln, die später auf den Eckpunkten des WebGL-Dreiecks liegen sollen. Die folgenden beiden Bilder sollen das Prinzip veranschaulichen. Zum Ermitteln der Koordinaten kannst du ein beliebiges 2D-Bildbearbeitungsprogramm verwenden (ich nehme Paint Shop Pro 5.0).

absolute Bildkoordinaten der Textur relative Bildkoordinaten der Textur
Bild 6.2a : absolute Bildkoordinaten der Textur Bild 6.2b : relative Bildkoordinaten der Textur

Update: Leider passen die beiden beispielbilder nicht mehr 100%ig zum WebGL-Beispiel: WebGL verarbeitet spezifikationsgemäß ausschließlich Texturen, deren Kantenlängen einer 2er-Potenz entsprechen (4,8,16,..,256,.. etc. Pixel breit bzw. hoch). Mozilla FireFox ist an dieser Stelle noch tolerant, aber Google Chrome stellt Texturen mit anderen Kantenlängen nicht dar.

Grafikdateien für WebGL-Texturen MÜSSEN also immer Kantenlängen in 2er-Potenzen haben !

6.3 Texturen im Quellcode

Nachdem wir jetzt wissen, welche relativen Koordinaten des Bildes für die Textur verwendet werden sollen, müssen wir diese Informationen in den WebGL-Quellcode einbauen.
Die erste Änderung findet sich in der Funktion initBuffers() . Bis Kapitel 5 hatten wir hier die Vertex-Koordinaten und die zugehörigen Farb-Werte initialisiert. Jetzt ersetzen wir die Initialisierung des Farb-Buffers durch einen Texturkoordinaten-Buffer (Zeile 102-124):

102//TEXTURENKOORDINATEN:
103pyramideTextureCoordBufferID = gl.createBuffer();
104bindBuffer(gl.ARRAY_BUFFER, pyramideTextureCoordBufferID);
105 faPyramideTextureCoord = [
106/ Vorderseite
107  0.52, 0.05,
108  0.60, 0.62,
109  1.0, 0.62,
110  0.52, 0.05,
111  0.60, 0.62,
112  1.0, 0.62,
113  0.52, 0.05,
114  0.60, 0.62,
115  1.0, 0.62,
116  0.52, 0.05,
117  0.60, 0.62,
118  1.0, 0.62,
119];
120bufferData(gl.ARRAY_BUFFER, new Float32Array(faPyramideTextureCoord), gl.STATIC_DRAW);
121gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
122amideTextureCoordBufferID.itemSize = 2;
123amideTextureCoordBufferID.numItems = 12;
124


Wie du siehst, werden für jede Seite der Pyramide drei Texturkoordinatenpaare angegeben. (Die Zahlenwerte haben wir in 6.3 ermittelt!). Da wir für alle vier Seiten der Pyramide den gleichen Texturausschnitt verwenden, wiederholen sich die Koordinaten. Die weitere Initialisierung des Buffers entspricht der Initialisierung des Vertex-Buffers.

Um das Bild (=die jpg-Datei) Verwenden zu können, habe wird die Funktion initTexture() (Zeile 126-141) verwendet.

126function initTexture(sFilename) {
127  myTexture = gl.createTexture();
128  myTexture.image = new Image();
129  myTexture.image.onload = function() {
130    gl.bindTexture(gl.TEXTURE_2D, myTexture);
131    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
132    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, myTexture.image);
133    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
134    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
135    gl.bindTexture(gl.TEXTURE_2D, null);
136 
137 drawScene();
138  }
139
140  myTexture.image.src = sFilename;
141}


InitTexture() wird von WebGLStart() aufgerufen und bekommt den Namen der jpg-Datei übergeben. Beachte vor allem die eingebettete Funktion myImage.onload. Diese wird automatisch ausgeführt, sobald der Ladevorgang abgeschlossen ist. Erst wenn das der Fall ist, kann die Textur an WebGL übergeben werden und für folgende Rendervorgänge gebunden werden.
Nach Ende des Ladevorgangs wird (vorsichtshalber) noch drawScene() aufgerufen. Da wir in diese Beispiel keine kontinuierliche Animation verwenden, wird die Szene nur neu gezeichnet, wenn der User das Objekt mit Maus oder Tastur dreht - und darauf wollen wir ja nicht warten wenn die Textur geladen ist.

Jetzt müssen wir noch in drawScene WebGL mitteilen, dass (bzw. welche) Textur verwendet werden soll:

164    gl.activeTexture(gl.TEXTURE0);
165    gl.bindTexture(gl.TEXTURE_2D, myTexture);


Da die Schnittstelle zwischen Javascript und den Shadern somit geändert wurde (Textur statt Farbwerte) müssen wir noch in glpsutilskap6.js eine kleine Änderung gegenüber glpsutilskap5.js einfügen:
In initShaders() (Zeile 157-158) wird die Verknüpfung zum Texture-Koordinaten-Attribut hergestellt. Das war's schon :-)

6.4 Texturen in den Shadern

Im Vertex-Shader wird das Attribut aVertexColor durch aTextureCoord ersetzt. Dessen Inhalt wird 1:1 vom Vertex- an den Fragment-Shader weitergegeben: die neu eingeführte varying Variable vTextureCoord wird im Fragmentshader als Stützpunkte für die Interpolation der einzelnen Texturkoordinaten verwendet. Die Funktion texture2D(..) ist Teil von WebGL und nimmt uns diese Interpolationsberechnung ab.

Hier sind die beiden Shader im Quellcode:

10<script id="shader-fs" type="x-shader/x-fragment">
11  //this ifdef is a temporary work-around for the (upcoming) strict shader validator
12  #ifdef GL_ES
13  precision highp float;
14  #endif
15  varying vec2 vTextureCoord;
16  
17  uniform sampler2D uSampler;
18
19  void main(void)
20  {
21    gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
22  }
23</script>
24
25<script id="shader-vs" type="x-shader/x-vertex">
26  attribute vec3 aVertexPosition;
27  attribute vec2 aTextureCoord;
28
29  uniform mat4 uMVMatrix;
30  uniform mat4 uPMatrix;
31  varying vec2 vTextureCoord;
32  void main(void)
33  {
34    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
35    vTextureCoord = aTextureCoord;
36  }
37</script>


Das waren dann auch schon alle Code-Anpassungen die zur Verwendung von Texturen in WebGL erforderlich sind. Das ist natürlich nur die einfachste Variante; mit Texturen sind noch viiiele weitere tolle Dinge möglich:

6.5 Was geht sonst noch mit Texturen ?

In dem einfachen Beispiel in diesem Kapitel wird nur eine einzige 2D-Textur verwendet. Analog dazu können natürlich auch mehrere Textur-Dateien für mehrere Objekte verwendet werden.

Texturfilter können in texParameteri konfiguriert werden. Hierdurch lässt sich die Darstellung der Texturen (insbesondere bei Bewegung) optimieren.

Aus Performance-Gründen werden in professionellen Anwendung für die gleiche Oberfläche Texturen in mehreren Auflösungen verwendet. Je weiter die virtuelle Kamera von dem texturierten Objekt entfernt ist, desto geringer muss die Texturauflösung sein. Anschaulich: Für Objekte, die am Horizont der 3D-Szene gerade noch sichtbar sind, wird keine hochauflösende Textur benötigt.

Texturen können auch mit den Normalenvektoren verknüpft werden (Bumpmapping). Dadurch kann der 3D-Effekt bei entsprechendem Lichteinfall verstärkt werden.

<< Kapitel 5 <<    >> Startseite <<   ^ Seitenanfang ^     >> Kapitel 7 >>
Fehler? Kommentare? webgl ([ät)] peter-strohm Punkt de