Dieser Artikel erschien erstmals im PC Magazin 9/1999. Die Wieder­veröffentlichung erfolgt mit freundlicher Genehmigung der WEKA Media Publishing GmbH.

Demo-Programmierung unter Windows 95/98

Effektzauberei mit MP3

Für den weitverbreiteten MP3-Player Winamp schreiben Sie zwei Plugins und lernen nebenbei noch ein wenig OpenGL.

Carsten Dachsbacher/Nils Pipenbrinck

Musik aus dem Internet ist in: MP3-Dateien genießen große Popularität, ebenso Winamp, der Player schlechthin für dieses Dateiformat. Dank verschiedener Skins (Erscheinungs­bilder) können Sie das Aussehen dieses Programms individuell anpassen. Skins sind jedoch nicht der einzige Weg, Winamp ganz nach Ihrem Geschmack auszustatten. Sie können auch Plugins laden, die zum Beispiel grafische Effekte passend zur Musik zeigen. Dabei liefert Winamp alle Daten, das heißt die aktuelle Ausgangs­spannung des Verstärkers und die Amplitude der Frequenz­spektren. Das Plugin muß sich nur um die Darstellung kümmern.

Wie Sie solche Plugins ohne großen Aufwand selbst schreiben, zeigen wir Ihnen in dieser Ausgabe. Zuerst programmieren wir ein einfaches, aber eindrucks­volles Plugin unter DirectDraw. Im zweiten Plugin kommt zusätzlich OpenGL zum Einsatz.

Die Struktur eines Plugins

Die Visuali­sierungs-Plugins für Winamp sind keine gewöhnlichen Programme, sondern DLL-Dateien (Dynamic Link Libraries). Deren Aufbau ist in diesem Fall jedoch einfach. Jedes Plugin kann aus mehreren Modulen bestehen, wobei die Programmierer von Winamp mit Modul einen Grafikeffekt meinen.

Um eine reibungslose Zusammenarbeit mit dem MP3-Player zu gewährleisten, füllen Sie für jedes dieser Module eine Struktur aus. Darin steht der Name des Moduls, welche Daten es braucht und welche Routinen aufzurufen sind:


winampVisModule PCUModul =
{
	PluginName, // Modul-Name
	NULL, //* Fenster-Handle von Winamp
	NULL, //* DLL Instance Handle
	0, //* Sampling-Rate
	0, //* Anzahl der Channels (1=Mono, 2=Stereo)
	0, // Ausgabe-Latenz
	0, // Verzögerung der Grafik-Ausgabe
	0, // Keine Spektrum-Analyser-Daten anfordern
	2, // Stereo-Waveform-Daten anfordern
	{ 0, }, //* Spektrumdaten
	{ 0, }, //* Waveform-Daten
	config, // Konfigurations-Routine
	init, // Initialisierungs-Routine
	render, // Berechnungs-Routine
	quit // Beendigungsroutine
};
		

Die mit einem Sternchen (*) im Kommentar gekenn­zeichneten Felder füllt Winamp aus. Um die übrigen kümmern Sie sich selbst. Wichtig sind vor allem die letzten vier Felder mit den Programm­routinen.

Die config-Funktion wird aufgerufen, wenn der Benutzer bei der Auswahl Ihres Plugins den Button Configure drückt. Dort können Sie sich einen Dialog anzeigen lassen, über den der Anwender die Effekte nach seinen Wünschen anpassen kann.

Das Beispiel-Plugin kommt ohne einen solchen Dialog aus, es zeigt hier statt dessen eine kleine Infobox:


void config(struct
	winampVisModule *this_mod)
{
	MessageBox(this_mod->
		hwndParent,
		„PCU Winamp Plugin“,
		„About“, MB_OK);
}
		

Als Parameter wird immer ein Zeiger auf Ihre Modulstruktur übergeben. Dies wird wichtig, wenn Sie in Ihrem Plugin mehrere Module implementieren wollen, die alle den gleichen Konfigurations­dialog oder die gleiche Initialisierungs- und Beendigungs­routine benutzen.

Der Initialisierungscode


int init(struct winampVisModule *this_mod)
		

wird gleich nach dem Start des Plugins aufgerufen. Unser Plugin öffnet in dieser Routine ein einfaches Fenster und startet dann DirectDraw im Fullscreen-Modus.

Der Initialisierungs­code ähnelt dem aus der Grafik­bibliothek, die Sie aus früheren Ausgaben von PC Underground kennen. Im Unterschied zur Grafik­bibliothek müssen Sie jetzt aber nicht dafür sorgen, daß Ihre Berechnungs­routine regelmäßig aufgerufen wird. Das erledigt Winamp für Sie.

Die Beendigungsroutine


void quit(struct winampVisModule *this_mod)
		

ist das nötige Gegenstück zur init-Funktion. Wenn das Plugin verlassen wird, beendet diese Funktion DirectDraw und schließt das erzeugte Fenster wieder.

Dazwischen erledigt die Render-Routine


int render(struct winampVisModule *this_mod)
		

die eigentliche Arbeit. Während die Musik läuft, wird sie regelmäßig von Winamp aus aufgerufen.

SECHS MOMENTAUFNAHMEN des ersten Winamp-Plugins zeigen farbenfrohe Zufallsmuster.
SECHS MOMENTAUFNAHMEN des ersten Winamp-Plugins zeigen farbenfrohe Zufallsmuster.

Um Ihre Ideen zur Visualisierung in Pixel umsetzen zu können, haben Sie hier Zugriff auf einige interessante Daten. Winamp füllt die Modulstruktur mit den Informationen, die Sie angefordert haben. So nutzen Sie entweder das Frequenz­spektrum der Musik oder die Sample-Daten – oder auch beides gleichzeitig, wenn Sie möchten.

Im Array waveformData finden Sie die jeweils aktuellen 576 Sample-Werte. Diese können Sie zur Anzeige eines Oszilloskops verwenden. Das Array spectrumData hingegen enthält das aktuelle Frequenz­spektrum und ist ebenfalls 576 Einträge lang. Die Baßanteile befinden sich dabei in den unteren Werten, während die höchste Frequenz bei Element 576 zu finden ist.

Da Sie möglicherweise Stereodaten vorliegen haben, sind die Arrays zwei­dimensional. Die Samples für den linken Kanal finden Sie in waveformData[0][i], die für den rechten in waveformData[1][i]. Das gleiche gilt analog für das Array spectrumData.

576 Sampling-Werte sind nicht besonders viel. Bei einer Wiedergabe­frequenz von 44 100 Hz, die Sie bei CD-Qualität erreichen, entsprechen die übergebenen Werte einem Zeitfenster von etwa 13 Millisekunden. Daher sollte auch Ihr Effekt nicht viel Rechenzeit kosten.

Ist Ihre Rendering-Funktion zu langsam, verpassen Sie einen Teil der Daten, und Ihr Plugin verliert an Genauigkeit. Dies ist zwar nicht sonderlich schlimm, aber Ihr Plugin kann dabei einen Teil seines optischen Reizes verlieren.

Die Schnittstelle zu Winamp

Jetzt informieren Sie Winamp darüber, welche Module Sie in Ihrem Plugin programmiert haben. Dafür brauchen Sie zwei Funktionen.
getModule ist eine sogenannte Callback-Funktion. Winamp wird sie mehrfach aufrufen und die DLL fragen, welche Module verfügbar sind. Da Sie zur Zeit nur ein Modul haben, fällt sie relativ einfach aus:


winampVisModule *getModule(int which)
{
	switch (which)
	{
		case 0: return &PCUModul;
		default: return NULL;
	}
}
		

Möchten Sie ein zusätzliches Modul programmieren, erweitern Sie lediglich das switch-Statement um den Fall 1. Das dritte Modul erhält die Nummer 2 usw.
• Die zweite Funktion ist sehr viel interessanter: Sie ist der Einsprungs­punkt der DLL. In etwa entspricht sie der main()-Funktion eines normalen C-Programms. Beim Laden von Winamp werden auch alle installierten Plugins geladen und diese Einsprung­routinen aufgerufen. Dabei geben diese den Namen des Plugins, die Versionsnummer und einen Pointer auf die getModule-Funktion zurück. Winamp fragt dann Informationen über die Module ab und läßt die Plugins bis zu ihrer Aktivierung erst einmal ruhen.


extern „C“ __declspec(dllexport)
	winampVisHeader *winampVisGetHeader()
{
	static winampVisHeader PluginHeader;
	//Felder der Header-Struktur ausfüllen
	PluginHeader.description = PluginName; // Name
	PluginHeader.version = VIS_HDRVER; // Version
	PluginHeader.getModule = getModule;
	// getModule-Funktion
	return &PluginHeader;
}
		

Damit Winamp diese Funktion in Ihrer DLL findet, muß sie mit einem bestimmten Namen exportiert werden. Die Anweisung extern „C“ sorgt dafür, daß Ihr C++-Compiler den Namen der Funktion nicht ändert. Bei C++ ist es nämlich in der Regel so, daß die Parameter und Rückgabetypen in den internen Namen codiert werden. Da es leider keinen allgemeinen Standard für diese Codierung gibt, programmieren Sie bei DLLs exportierte Funktionen im „C“-Standard.

Der Zusatz __declspec(dllexport) sorgt schließlich dafür, daß die Funktion in die sogenannte Exporttabelle der DLL aufgenommen wird. Lediglich exportierte Funktionen sind von außen zu sehen. Sie können dabei auch mehr als eine Routine exportieren.

Die Kommunikation zwischen den Plugins und Winamp erscheint am Anfang vielleicht etwas verwirrend – aber wenn Sie sich damit etwas näher beschäftigen, werden Sie schnell die Annehm­lichkeiten dieser Methode zu schätzen wissen.

Ein erstes Plugin

Als Einstieg in die Plugin-Programmierung wählen Sie zunächst einen einfachen Effekt. Damit Sie dabei nicht auf tolle optische Reize verzichten müssen, wenden Sie die sogenannte Movelist-Technik in einer verfeinerten Variante an.

Bei Movelists legen Sie – wie der Name schon sagt – eine Tabelle an, die für jedes Pixel eine neue Position angibt. Das ist noch nichts Neues. Daher erweitern Sie die Movelist so, daß sie mit höherer Genauigkeit arbeitet. Auch benutzen Sie keine Textur, sondern wenden die Movelist immer auf das vorherige Bild an. Diese sehr beein­druckenden Effekte, bei denen ein Ergebnis wieder in die Berechnung des nächsten einfließt, nennt man Feedbacks.

Für jeden Punkt auf dem Bildschirm brauchen Sie zwei Tabellen­einträge, die angeben, von welcher Position der Punkt kopiert werden soll. Wegen der erhöhten Genauigkeit speichern Sie diesen Wert in einem 32-Bit-Integer-Wert. Die oberen 16 Bit geben direkt einen Teil der Koordinate an, während die unteren 16 Bit festlegen, an welcher Position „zwischen“ den Pixeln kopiert werden soll. Sie können natürlich nicht zwischen zwei Speicher­stellen lesen, deshalb simulieren Sie dies mit Hilfe der bilinearen Interpolation.

Im Movelist-Array werden x- und y-Koordinate jeweils nacheinander abgelegt. Das ist sinnvoll, da Sie beide Koordinaten benötigen und alle Punkte des Bilds nacheinander berechnen. Der leicht vereinfachte Code zum Zeichnen des Feedbacks sieht so aus:

MEHRERE HINTEREINANDERGESCHALTETE Transformationen bilden einen Punkt von 3D nach 2D ab.
MEHRERE HINTEREINANDERGESCHALTETE Transformationen bilden einen Punkt von 3D nach 2D ab.

int lerp (int a, int b, int x)
// Lineare 16-Bit Interpolation
{
	return a + (((b - a) * x) >> 16);
}
		

Zunächst erfolgt die lineare Interpolation zweier Werte a und b. Die Funktion lerp liefert einen Wert zwischen a und b zurück. Wo genau das Ergebnis liegt, hängt von dem Wert x ab. Ist x gleich 0, bekommen Sie a. Ist x gleich 65536, erhalten Sie b zurück. Alle anderen Belegungen von x liefern Werte zwischen a und b.


long *source = movelist1;
for(int i = 0; i < width * height; i++)
{
	int x = *source++;
	int y = *source++;
	int offset = (x > 16) + width * ( y > 16);

	int a = lerp(buffer1[offset],
		buffer1[offset + 1], x & 0xffff);
	int b = lerp(buffer1[offset + width],
		buffer1[offset + 1 + width], x & 0xffff);
	buffer2[i] = lerp (a, b, y & 0xffff);
}
		

Dies ist die Hauptschleife des Movelist-Feedbacks. Darin lesen Sie zunächst die Quell­koordinaten x und y aus der Movelist aus. Die Pixeladresse offset berechnen Sie aus den oberen 16 Bit der Koordinaten.

Dann interpolieren Sie zwischen den Pixeln des Bilds in buffer1. Da Sie eine zwei­dimensionale bilineare Interpolation brauchen, rufen Sie die lerp-Funktion mehrfach auf. Das interpolierte Ergebnis schreiben Sie schließlich in das neue Bild bei buffer2.

Mit diesen wenigen Zeilen Code können Sie jetzt Bilder um Bruchteile von Pixeln verschieben, drehen, verzerren, vergrößern und stauchen. Das Resultat hängt nur davon ab, was Sie in Ihre Movelist schreiben.

Die Effekt-Movelist

Bei Feedbacks sollten Sie die Bewegung nicht zu schnell laufen lassen. Nur so kommt der Effekt voll zur Geltung. Als kleine Anregung hier eine kombinierte Rotation und Vergrößerung:


double sinval = sin(0.01) * 1.01;
double cosval = cos(0.01) * 1.01;
		

Hier berechnen Sie die Rotationswerte vor. Der Winkel beträgt 0,01 rad, was in etwa 0,6 Grad entspricht. Die Multiplikation mit 1,01 sorgt für einen Zoom-Wert von einem Prozent.


long* dest = movelist1;
for(int py = 0; py < height; py++)
	for(int px = 0; px < width; px++)
	{
		double x = (double)(px - (width / 2)) / (width / 2);
		double y = (double)(line - (height / 2)) / (height / 2);
		

Diese Zeilen skalieren die Koordinaten px und py in den Bereich von -1 bis 1. Dadurch werden die Berechnungen unabhängig von der Breite und Höhe der Movelist.


double xx = x * cosval - y * sinval + 1.0;
double yy = y * cosval + x * sinval + 1.0;
xx = Clamp(xx * width /2.0, width - 1, 1);
yy = Clamp(yy * height /2.0, height - 1, 1);

Der Punkt wird nun mit den vorberechneten Werten xx und yy rotiert und danach wieder auf Bildgröße skaliert. Die Funktion Clamp sorgt dafür, daß die rotierten Werte nicht den Bildbereich verlassen. Sonst würde Ihre Feedback-Routine beim Auslesen der Pixel unweigerlich abstürzen.


*dest++ = (long)(xx * 65536.0);
*dest++ = (long)(yy * 65536.0);
}
		

Zuletzt schreiben Sie die Koordinaten in die Movelist. Zuerst kommt die x-, dann die y-Koordinate. Die Multiplikation mit der Konstanten 65536 sorgt für die Aufteilung in eine 16-Bit-Koordinate und in eine 16-Bit-Subkoordinate. Das Schreiben der Koordinaten mit dem Befehl *dest++= sieht etwas ungewöhnlich aus. Es funktioniert, weil dest ein Pointer ist. Nachdem der Wert an die entsprechende Adresse geschrieben wurde, erhöht die Operation ++ den Pointer, der dann auf das nächste Element zeigt. Bei dieser Vorgehensweise sparen Sie eine Variable, und der Compiler kann möglicherweise effizienteren Code erzeugen.

Im Beispielcode haben wir noch etwas mehr Aufwand getrieben, um den Effekt wilder zu gestalten. Wie Sie bemerken werden, haben wir einfach mehrere Rotationen übereinander­gelegt.

Wenn Sie Ihr Plugin so starten, sehen Sie noch nichts. Denn es fehlt noch der Code, der die Sample- oder Spektrumdaten benutzt, um dem Feedback brauchbare Bilder zu liefern. Zeichnen Sie einfach die Samples als Wellenform im Kreis über das aktuelle Bild. Dies können Sie ähnlich wie die Funktion Movelist_Draw() machen.

Wir haben es uns nicht nehmen lassen, noch einige Extras in das Plugin einzubauen. So können Sie das Aussehen mit den Cursor-Tasten verändern. Das Bild auf S. 212 zeigt einige psychedelisch wirkende Schnappschüsse des Plugins.

Einführung in OpenGL

OpenGL (Open Graphics Library) ist ein Standard der Computer­industrie für 3D-Grafik. Er stammt ursprünglich von der internen Grafik­bibliothek von Silicon Graphics (SGI) und wird jetzt von SGI, Microsoft, IBM, Intel und DEC weiter­entwickelt. Die Vorteile von OpenGL sind die genaue Spezifikation des Standards – er arbeitet gleichermaßen unter Betriebs­systemen wie Windows, Unix sowie Linux – und die Unterstützung durch 3D-Hardware.

Es ist ganz einfach, mit OpenGL beeindruckende 3D-Grafiken zu programmieren. Dabei lernen Sie die Funktionen kennen, die Sie für ein Winamp-Plugin brauchen.

OpenGL stellt Ihnen Funktionen zum Zeichnen von Primitiven, also von Punkten, Linien und Polygonen, zur Verfügung. Es gibt auch Support-Routinen, mit denen Sie Kurven, Bézier-Oberflächen oder Text darstellen können. Die Polygon­primitive können Sie dabei mit Texture Mapping und Schattierung ausstatten.

Sobald Sie eine 3D-Szene aus Primitiven aufgebaut haben, definieren Sie Beleuchtungs­effekte, das Blickfeld und Spezialeffekte wie Nebel oder Transparenz. OpenGL erledigt dann den Rest für Sie: die Schattierung, das perspek­tivische Rendering, das „Wegwerfen“ der nicht sichtbaren Polygone (Hidden Surface Removal) und das Clipping. Wenn Sie das Blickfeld oder die Beleuchtung ändern oder die definierten Objekte bewegen, berechnet OpenGL alles für Sie neu.

OpenGL ist als eine sogenannte State Machine implementiert: Das heißt, daß ein festgelegter Zustand (zum Beispiel eine Farbe) so lange aktuell ist, bis Sie ihn wieder ändern. Solange also beispielsweise die aktuelle Zeichenfarbe Rot ist, erhalten alle definierten Primitive diese gesetzte Farbe. Zudem ist OpenGL dafür konzipiert, in einem Client-Server-Modell zu arbeiten. Client und Server können natürlich auch in einem Rechner vereint sein, so wie es bei uns der Fall ist.

OpenGL-Programmierung

Zuerst einmal bringen Sie OpenGL dazu, in ein Windows-Fenster zu rendern. Da OpenGL plattform­unabhängig ist, stellt Microsoft die Befehle dazu zur Verfügung. Dabei registrieren Sie wie bei einem normalen Windows-Programm eine Fensterklasse, öffnen ein Fenster und erzeugen mit folgenden Befehlen einen sogenannten OpenGL-Kontext:


// Pixelformat des Windows-Bildschirms lesen
int FormatIndex = ChoosePixelFormat(
	WindowDC, &FormatDescriptor);
SetPixelFormat(WindowDC, FormatIndex,
	&FormatDescriptor);
// OpenGL Kontext erzeugen
WindowRC = wglCreateContext(WindowDC);
wglMakeCurrent(WindowDC, WindowRC);
		
DAS ZWEITE PLUGIN VERWENDET verschiedene Polygon-Primitive, hier mit ihren Bezeichnungen in OpenGL.
DAS ZWEITE PLUGIN VERWENDET verschiedene Polygon-Primitive, hier mit ihren Bezeichnungen in OpenGL.

Das ist schon alles. Diese Zeilen dienen nur als Beispiel, die vollständige Routine finden Sie im Quelltext des OpenGL-Plugins.

Nun können Sie schon mit der Beschreibung der 3D-Szene beginnen. Im Bild auf S. 213 sehen Sie, welche Trans­formationen ein Punkt im Raum – gegeben durch (x,y,z) – durchläuft, bis er die Bildschirm­koordinaten (x,y) erhält. Beachten Sie dabei, daß x und y im Raum und im Fenster verschieden sind.

Alle Trans­formationen in OpenGL beschreiben Sie mit Matrizen. Sie können Ihr Mathematikbuch aber ruhig in der Ecke lassen, denn Matrizen werden hier abstrakt behandelt.

Stellen Sie sich eine Matrix einfach als ein mathematisches Gebilde vor, das eine Verschiebung, Drehung oder Skalierung darstellt. Verschiedene Matrizen können Sie aufeinander anwenden und so eine einzige Matrix errechnen, die alle dabei durchgeführten Trans­formationen enthält.

Für das OpenGL-Plugin beschreiben Sie zunächst eine Projektions­matrix, die sowohl die Perspektiv- als auch die Viewport-Transformation beschreibt. Bei der Perspektiv­projektion kann es sich zum Beispiel um eine Zentral- oder Orthogonal­projektion handeln. Mit der Bezeichnung Viewport ist das Fenster gemeint – die entsprechende Transformation bildet also die einzelnen Punkte auf die Bildebene ab.

Die in dieser Matrix gespeicherten Transforma­tionen stellen quasi das Kameramodell dar. Nun teilen Sie OpenGL mit, daß Sie die Projektions­matrix bearbeiten möchten:


glMatrixMode(GL_PROJECTION);
		

Danach laden Sie die Einheitsmatrix, das neutrale Element beim Arbeiten mit Matrizen:


glLoadIdentity();
		

Egal, ob Sie diese Matrix auf eine andere Matrix oder einen Vektor anwenden, Sie erhalten als Ergebnis immer wieder den unveränderten Operanden zurück.

Die Kamera- bzw. die Perspektiv- und die Viewport-Transformation legen Sie am einfachsten mit einem Befehl aus der Glut Library fest. Dabei handelt es sich um eine Sammlung von Highlevel-Befehlen, die sehr komfortable OpenGL-Funktionen enthalten.


gluPerspective(45.0f, 1.33f, 1.0f, 1000.0f);
glViewport(0, 0, SCREEN_X, SCREEN_Y);
		

Die erste Zeile definiert eine Kamera mit einem Öffnungswinkel von 45 Grad. Der zweite Parameter der Funktion gluPerspective beschreibt den Aspect-Ratio-Wert, also das Verhältnis von der Breite zur Höhe des Bildschirms. Zuletzt übergeben Sie noch die gewünschten Entfernungs-Clipping-Ebenen mit Abstandswerten von 1 und 1000.

Dank des folgenden Befehls glViewport weiß OpenGL, wie groß das Fenster ist. Jetzt verschaffen Sie der Kamera noch etwas Abstand von der Projektions­ebene, auf die die Primitive projiziert werden:


glTranslatef(0.0f, 0.0f, -30.0f);
		

Das war die Definition der Kamera.

Es gibt noch ein paar zusätzliche Initialisierungs­aufrufe, die Sie einmalig beim Start des Programms einsetzen:

Nun legen Sie die Objekte der Szenerie fest. Dazu wählen Sie die Modelview-Matrix, die die Bewegung und Drehung eines Objektes bestimmt. Danach laden Sie wieder die Identität, also die Einheitsmatrix:


glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
		

Für eine Verschiebung, Skalierung oder Drehung stehen dann folgende drei Befehle zur Verfügung:


glTranslatef(float x,float y, float z)
glScalef(float x,float y, float z)
glRotatef(float drehwinkel, float achse_x,
	float achse_y, float achse_z)
		

Dabei spielt die Reihenfolge der Trans­formationen durchaus eine Rolle: Je nachdem, ob Sie ein Objekt zum Beispiel vor oder nach einer Drehung verschieben, erhalten Sie ein anderes Ergebnis.

Nachdem Sie die Transformation festgelegt haben, übermitteln Sie die Polygon-Primitive, aus denen Sie Ihre Objekte zusammensetzen, an OpenGL. Eine Übersicht der in diesem Artikel verwendeten Primitive zeigt das Bild auf S. 214. Die Zahlen an den Eckpunkten deuten die Reihenfolge an, in der Sie die Punkte übergeben, um die Primitive zu zeichnen.

Ein Primitiv wie ein Dreieck beginnen Sie mit dem Befehl:


glBegin(GL_TRIANGLE);
		

Bevor Sie die Eckpunkte übergeben, wählen Sie noch die Farben, Oberflächen­normalen und Texture-Mapping-Koordinaten aus. Denken Sie daran, daß Sie es mit einer State Machine zu tun haben – alle Zustände wie Farben, Normalen und Koordinaten gelten für alle Eckpunkte, solange Sie sie nicht ändern:


glNormal3f(0, -1, 0);
glTexCoord2d(0.0, 0.0);
glVertex3f(1.0, 2.0, 1.0);
glTexCoord2d(1.0, 0.0);
glVertex3f(3.0, 2.0, 1.0);
glTexCoord2d(1.0, 1.0);
glVertex3f(1.0, 1.0, 0.0);
glEnd();
		

Bei den Primitiven, die eine unbestimmte Anzahl von Eckpunkten (Vertices) enthalten können, übergeben Sie so viele Punkte, wie Sie wollen.

Texture Mapping in OpenGL

Um Ihre Objekte mit Texturen zu versehen, genügen in OpenGL wenige Programmzeilen. Aktivieren Sie mit dem Befehl glEnable das Texture Mapping. Dann legen Sie fest, daß Texture-Mapping-Koordinaten größer als 1,0 eine Wiederholung (Kachelung) der Textur bedeuten:


glEnable(GL_TEXTURE_2D);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		

Wählen Sie über eine Nummer die Textur aus, deren Zustand Sie verändern möchten:


glBindTexture(GL_TEXTURE_2D, int Nummer);
		

Bei einer Größenänderung kann OpenGL die Textur entweder per bilinearer Interpolation oder per Mipmapping anpassen. Wie sich das Texture Mapping hier verhalten soll, wählen Sie – jeweils separat für die Vergrößerung und die Verkleinerung – über das Kommando glTexParameteri.

Danach übergeben Sie der Funktion glTexImage2D die Texturdaten:


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, maxFilter);
void glTexImage2D(GL_TEXTURE_2D, 0, 4, 256, 256,0, GL_BGRA_EXT,
	GL_UNSIGNED_BYTE, void *texturedaten);
		

In diesem Beispiel übergeben Sie eine 256 x 256 Pixel große Textur mit 4 Byte Farbtiefe, wobei die Konstante GL_BGRA_EXT jeweils 8 Bit für den Blau-, Rot-, Grün- und den Alphakanal reserviert. Der Zeiger texturedaten verweist auf die geladene Textur im Speicher.

Das war schon alles, um OpenGL Texturen zu entlocken. Doch im Moment sehen diese Texturen noch sehr blaß aus...

Lichtquellen in OpenGL

EINE STIMMUNGSVOLLE UND DIFFUSE BELEUCHTUNG sorgt beim zweiten Plugin für glitzernde Lichteffekte.
EINE STIMMUNGSVOLLE UND DIFFUSE BELEUCHTUNG sorgt beim zweiten Plugin für glitzernde Lichteffekte.

Die richtige Beleuchtung nimmt entscheidenden Einfluß auf die Wirkung einer Szene. In OpenGL legen Sie zunächst fest, wie die Oberflächen auf die Lichtquelle reagieren sollen. Zum Beispiel können Sie für die Vorderseiten der Polygone eine stimmungsvolle und diffuse Beleuchtung einschalten. Die Vorder- und Rückseite bestimmen Sie durch die Reihenfolge der Eckpunkte – sind diese im Uhrzeigersinn angeordnet, sehen Sie das Polygon von vorne.

Mit glEnable aktivieren Sie die Beleuchtungs­berechnung:


glEnable(GL_LIGHTING);
GLfloat Intensität[4] = {1.0, 1.0, 1.0, 1.0};
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, Intensität);
		

Wählen Sie nun eine Lichtquelle. OpenGL stellt Ihnen dabei – je nach Implementierung – mindestens acht verschiedene zur Verfügung. Danach legen Sie die Lichtfarbe sowie die Position und Richtung des Lichts fest:


glEnable(GL_LIGHT0);
GLfloat light_diffuse[]= { 1.0, 1.0, 1.0, 1.0};
GLfloat light_ambient[]= { 0.1, 0.1, 0.1, 1.0};

glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);

glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light_direction);
		

Das ist alles, was Sie für ein respektables OpenGL-Programm benötigen. Das Bild auf S. 218 zeigt das fertige Plugin im Einsatz.

Am besten experi­mentieren Sie ein bißchen mit dem Quellcode und verändern ein paar Parameter. So bekommen Sie mehr Gefühl für die Wahl der passenden Transformations­matrix und für eine optimale Beleuchtung.

Möchten Sie sich in OpenGL vertiefen, lohnt sich ein Besuch der Homepage von Silicon Graphics unter www.sgi.com. Hier stoßen Sie auf jede Menge Sourcecodes, Tips und Tutorials. So zum Beispiel auch auf der Seite des Silicon-Graphics-Mitarbeiters Mark Kilgard.

Wenn Sie die Plugins ausprobieren wollen, kopieren Sie die jeweilige DLL-Datei und für das OpenGL-Programm noch die raw- und tga-Dateien in das Unter­verzeichnis Plugin von Winamp. Die aktuelle Version des MP3-Players Winamp erhalten Sie unter www.winamp.com. Dort finden Sie auch verschiedene Benutzer­oberflächen in unterschiedlichem Design und viele Plugins.