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.
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).
|
|
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:
| 103 | pyramideTextureCoordBufferID = gl.createBuffer();
| 104 | bindBuffer(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 | ];
| 120 | bufferData(gl.ARRAY_BUFFER, new Float32Array(faPyramideTextureCoord), gl.STATIC_DRAW);
| 121 | gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
| 122 | amideTextureCoordBufferID.itemSize = 2;
| 123 | amideTextureCoordBufferID.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.
126 | function 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.
|