DirectX 10/11 Tutorial 2: Erster Kontakt mit DirectX 10

Hier können Artikel, Tutorials, Bücherrezensionen, Dokumente aller Art, Texturen, Sprites, Sounds, Musik und Modelle zur Verfügung gestellt bzw. verlinkt werden.
Forumsregeln
Möglichst sinnvolle Präfixe oder die Themensymbole nutzen.
Antworten
Gelöschter Benutzer
Beiträge: 92
Registriert: 26.02.2009, 22:09

DirectX 10/11 Tutorial 2: Erster Kontakt mit DirectX 10

Beitrag von Gelöschter Benutzer »

Teil 2 :): Sieht nach wenig aus, ist aber wohl der anstrengenste Codeteil für Einsteiger.

In diesem Teil geht es jetzt darum, ein Direct3D Device zu erzeugen und den Hintergrund mit einer Farbe zu löschen. Klingt nicht nach viel, ist aber viel Arbeit.

Technisches Wissen

Bevor wir anfangen Code zu schreiben, betrachten wir erst einmal, wie wir jetzt arbeiten müssen und welche Grundkomponenten es in DirectX gibt. Betrachten wir erst einmal den funktionalen Teil und schauen uns an, wie unser Programm strukturiert ist:
Ablauf des Programms
Ablauf des Programms
zfx_tut_02_ablauf.png (48.1 KiB) 9229 mal betrachtet

Hier sind drei grün hinterlegte neue Punkte auffällig. Zum einem müssen wir in der Initialisierungsroutine DirectX Initialisieren, zum anderen in der Hauptschleife einmal unser gewünschtes Ergebnis rendern (zu deutsch: Zeichnen) und am Ende unsere Objekte releasen (zerstören).

Wir fügen also drei Methoden hinzu: bool Initialize(HINSTANCE hInstance), void Render() und void Release(), welche in der Startfunktion einfach aufgerufen werden. In der Initialisierungsfunktion rufen wir dann AssignApplicationAndCreateWindow() und InitializeDirectX10() auf.

Die InitializeDirectX10()-Funktion initialisiert alles Wichtige für Direct3D 10 auf. Doch was ist wichtig?

Für unseren Zweck gibt es drei Interfaces, die wir bedienen werden:
InterfacenameDeklarationsnameBeschreibung
ID3D10DevicegDeviceDieses Interace verwaltet unseren Hauptkern von DirectX 10. Man könnte es als „das Arbeitspferd“ bezeichnen.
ID3D10RenderTargetViewgRenderTargetMan kann sich hier das Interface zum Monitorbild indirekt vorstellen, auf das wir malen.
IDXGISwapChaingSwapChainDieses Interface bestimmt so gesehen, wo unser Bild platziert wird und wann. Wir greifen hier ansatzweise auf die Hardware zu.
Um das Device und ein Swap Chain zu erzeugen, müssen wir eine Konfigurationstrutkur namens DXGI_SWAP_CHAIN_DESC füllen und die Funktion D3D10CreateDeviceAndSwapChain() aufrufen. Dann geht es daran, eine Textur (ein Bild) zu erzeugen und mit dem ein Render Target View zu erzeugen – also so gesehen eine „Zeichenoberflächenansicht“.
Haben wir das Render Target View (RTV) erzeugt, so binden wir es an das Device. Dann erstellen wir einen Viewport, der den Zeichnungsbereich des RTV bestimmt, und binden dieses auch an das Device. Dann ist alles initialisiert.

In der Render()-Funktion werden wir dem Device anordnen, den Hintergrund von zugewiesenen RTV zu löschen. Ist dies erledigt, so schicken wir dem SwapChain eine Anweisung, das Bild auf dem Monitor zu präsentieren.

Die Release()-Funktion räumt nach dem Verlassen der Hauptschleife alle Objekte auf. Hier ist zu beachten, dass man vom Vollbildmodus in den Fenstermodus wechselt vor dem Releasen des SwapChains.

Hier noch einmal ein Bild, um die Beziehungen zu den Interfaces zu verdeutlichen:
Beziehung der DirectX 10 Grundkomponenten
Beziehung der DirectX 10 Grundkomponenten
zfx_tut_02_dx_aufbau.png (14.14 KiB) 9234 mal betrachtet
Implementation

Wie angekündigt ist unsere Hauptfunktion nun etwas abgeändert:

Code: Alles auswählen

       // Initialisieren
	if(!Initialize(hInstance)) return 0;

	// Die Hauptschleife
	while(RunWindow())
	{
		Render();
	}

	// Programm verlassen
	Release();

	return 0;
In der Initialize()-Funktion werden beide genannten Funktion aufgerufen und auf Richtigkeit geprüft:

Code: Alles auswählen

bool Initialize(HINSTANCE hInstance)
{
	if(!AssignApplicationAndCreateWindow(hInstance)) return false;
	if(!InitializeDirectX10()) return false;
	return true;
}
Der große Clou bei der Initialisierung kommt bei der Erstellung des Device und des Swap Chains: Wir füllen die Konfigurationsstruktur und erstellen das Device. Die Bedeutung der Mitglieder von DXGI_SWAP_CHAIN_DESC sind unten in einer Tabelle aufgelistet:

Code: Alles auswählen

bool InitializeDirectX10()
{
	// Swap Chain Desc füllen (Konfiguration)
	DXGI_SWAP_CHAIN_DESC dsc = {0};
	dsc.BufferCount = 1;
	dsc.BufferDesc.Width = 1024;
	dsc.BufferDesc.Height = 768;
	dsc.BufferDesc.RefreshRate.Denominator = 1; // 60Hz
	dsc.BufferDesc.RefreshRate.Numerator = 60;	
	dsc.Windowed = WindowMode;
	dsc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 32Bit + Alpha
	dsc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;  // Als Render Target benutzen
	dsc.OutputWindow = hWnd; // Unser Fenster übergeben
	dsc.SampleDesc.Count = 1;	 // Qualität des Samplings = 1
	dsc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;	 // Pointertausch statt kopieren

	// Device erzeugen
	if(FAILED(D3D10CreateDeviceAndSwapChain(NULL /*Standardadapter*/,
				D3D10_DRIVER_TYPE_HARDWARE /* Hardware */,
				NULL /* Kein Softwaremodul */,
				0, // Keine Flags
				D3D10_SDK_VERSION, // SDK-Version benutzen
				&dsc, // Konfigurationsstrutktur
				&gSwapChain, // Swap Chain
				&gDevice))) // Device
		return false;

      ...
ElementnameBedeutung
BufferDesc {Width, Height, RefreshRate, Format, ScanlineOrdering, Scaling}Auflösung, Bildwiederholrate (Achtung: Denominator = 1), Bildformat (gleich), Zeichenmodus und Bildposition.
SampleDesc {Count, Quality}Beschreibung der Kantenglättung, Count ist ist größer als 0 und Quality ist etwas von 2^n "2 hoch n", 1 oder 0.
BufferUsageBeschreibung der Benutzung, in unserem Falle DXGI_USAGE_RENDER_TARGET_OUTPUT.
BufferCountAnzahl der BackBuffer, die "Zeichenfläche"
OutputWindowAusgabefenster
SwapEffectWie soll der Backbuffer kopiert werden. In unserem Falle Zeigertausch der Daten: DXGI_SWAP_EFFECT_DISCARD.
FlagsSonstige Einstellungskonstanten
Bleiben wir nun mal kurz bei den Datenformaten von DirectX stehen, denn es gibt wirklich sehr viele - die man aber gut interprätieren kann. Ein Format beginnt in Direct3D 10 aufwärts immer mit DXGI_FORMAT_ und ist eine Komponente von DXGI (dazu später mehr). Dann folgen kryptische Informationen wie R8G8B8A8, R32G32B32A32 etc. - diese Sagen etwas über die Datenform und den Datenplatz aus. Die Zahl gibt die Anzahl verwendeter Bits aus. Demnach heißt 8 = 1Byte. Davor stehen noch Buchstaben wir R, G, B, A: Das sind eventuell Farbkanäle - müssen als solche aber nicht benutzt sein wie wir später sehen werden.

Demnach ist R8G8B8A8 eine Komponente von Rot 1Byte, Grün 1Byte, Blau 1Byte und Alpha 1Byte. Bei Alpha handelt es sich um die Transparenz. Man kann dies alles als char[4] verstehen und ist auf dem Monitor intern tatsächlich unsigned char[4].

Dahinter stehen dann Informationen wie _UINT, _SINT, _FLOAT, _UNORM und _TYPELESS. UINT heißt unsigned int und ist je nach Bitgröße unsigned char, unsigned short oder unsigned int; _SINT ist es dann jeweils signed (Vorzeichenbehaftet); _UNORM ist als skalierte unsigned int zwischen 0 und 1 zu verstehen. _FLOAT ist dann eine Gleitkommazahl in 16Bit- oder 32Bit-Form und _TYPELESS ist Typenlos.

Wir werden DXGI_FORMAT_R8G8B8A8_UNORM verwenden, also: Jeweils 8 Bit pro Farbkomponente zwischen 0 und 1 mit Transparenz --> 32Bit + Alpha.

DXGI ist eine Bibliothek von DirectX, um auf die Hardware zuzugreifen. Zum Beispiel können wir damit prüfen, ob ein Adapter (Grafikkarte) eine Funktionalität für DirectX 10 überhaupt vorhanden ist oder oder ob überhaupt die gewünschte Auflösung verfügbar ist. Das ist jedoch nicht jetzt bestandteil unseres Wissens.

Danach initialisieren wir alles mittels D3D10CreateDeviceAndSwapChain(). Der erste Parameter erwartet einen Adapter (Grafikkarte) - lassen wir ihn NULL, so wählt Direct3D den Standardadapter. Der Zweite gibt an, wie Direct3D läuft. Wir können ihn im Hardwaremodus laufen lassen (siehe Quellcode) oder im Referenzmodus (Softwaremodus). In Direct3D 11 kommt noch der Warp-Modus hinzu - dort ist allerdings die Methode leicht verändert deklariert. Dann folgen Flags und eine Konstante zur benutzen SDK von Direct3D. Normalerweise setzt man hier D3D10_SDK_VERSION ein. Dann wird ein Zeiger auf unsere Konfigurationstruktur, unserem SwapChain und dem Device erwartet.

Die Definition FAILED() prüft, ob etwas in einer Direct3D-Funktion/-Methode schief gegangen ist von Rückgabetyp HRESULT her. Es gibt auch das Gegenstück SUCCEEDED() welches prüft, ob alles glatt ging. Geht an dieser Codestelle etwas schief, hat man
  • Kein Windows Vista / Seven
  • Keine Direct3D 10 fähige Grafikkarte
  • Oder eine Überforderung von den Einstellungen her konfiguriert
Sodann, können wir uns jetzt auf das Herstellen des RTVs konzentrieren. Wir werden hierzu das Interface ID3D10Texture2D kurz ankratzen und __uuidof() kennenlernen. Nach so langer Zeit allerdings wieder Quellcode:

Code: Alles auswählen

   ...

	// Render Target View erzeugen und dem Device zuordnen
	// Textur holen
	ID3D10Texture2D* BackBuffer = NULL;
	if(FAILED(gSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (void**)&BackBuffer)))
		return false;

	// Render Target View erstellen und zuordnen
	if(FAILED(gDevice->CreateRenderTargetView(BackBuffer, NULL, &gRenderTarget)))
		return false;
	BackBuffer->Release();
	gDevice->OMSetRenderTargets(1, &gRenderTarget, NULL);

   ...
Hier deklarieren wir dieses Interface als Zeiger und lassen uns die Textur, den BackBuffer, vom SwapChain an das Interface mittels IDXGISwapChain::GetBuffer() übergeben. __uuidof() ermittelt hierbei die Klassen-GUID des Interfaces und ist Microsoft Visual Studio speziefisch. Es gibt hierbei die GUID (Global Unique Identifier) der Klasse zurück - eine globale Identifikationsnummer.

ID3D10Texture2D ist unser Erstkontakt mit einem Texturinterface. Es beinhaltet eine Textur (in diesem Fall einen BackBuffer). Wie wir damit erweitert arbeiten, das wird später Thema sein. Hier "missbrauchen" wir dieses Interface, um vom SwapChain einen Zeiger zur Textur für den Monitor zu bekommen und überreichen diesen Zeiger von der Textur dem RTV und damit dem Device.

Ist dies geglückt, so können wir das RTV mittels ID3D10Device::CreateRenderTargetView() erstellen. Hier wird als erstes ein Zeiger zum Backbuffer erwartet. Als zweites kann man eine Beschreibung des RTVs einfügen - was wir nicht müssen. Dann geben wir den Zeiger des Zeigers des RTVs an. Das Texturobjekt können wir danach schon getrost aufräumen lassen.

Ist dies geglückt, koppeln wir das RTV mittels ID3D10Device::OMSetRenderTargets an das Device an. Als erstes möchte es die Anzahl der RTVs wissen, dann einen Zeiger zu den RTVs (hier & da Einzelelement) und als drittes kommt ein Depth Stencil View (DSV) [Z-Buffer] ins Spiel - den wir jedoch erstmal vernachlässigen werden.

OM heißt bei der Methode übrigens Output Merger. Es gibt noch mehr. Hier ganz kurz in einer Liste was was ist:
AbkürzungBedeutung
IAInput-Assembler-Stage: Unsere Rohdaten für Direct3D - so zum Beispiel Objektpunkte etc.
VSVertex-Shader-Stage: Unser Renderer für die Objektpunkte
GSGeometry-Shader-Stage: Unser "Geometrie-Former"
SOStream-Ouput-Stage: Die Rückgabedaten des "Geometrie-Formers"
RSRasterizer-Stage: Wie etwas gezeichnet werden soll
PSPixel-Shader-Stage: Wie die Pixel gerendert werden
OMOutput-Merger-State: Wie (wo) etwas ausgegeben wird.
Anmerkung: Ich habe hier vieles umschrieben. Wie es sich mit der Shader Function Pipeline verhält, werden wir später kennenlernen.

Was wir jetzt noch zu guter letzt zum Initialisieren machen müssen, ist einfach einen Viewport (VP) zu erstellen und an Direct3D zu binden. Dieser VP bestimmt unseren Zeichenbereich in die Breite, Höhe und Tiefe. Dabei gibt es jeweils einen Minimalwert (Offset) und eine dazu passende Breite. Hier die Struktur und alles kompakt in Kommentaren erklärt:

Code: Alles auswählen

   // View Port erzeugen und festlegen
	D3D10_VIEWPORT vp = {0};
	vp.TopLeftX = 0;	// Obere X-Ecke in px
	vp.TopLeftY = 0;	// Obere Y-Ecke in px
	vp.Width = 1024;	// Breite der Zeichenfläche
	vp.Height = 768;	// Höhe der Zeichenfläche
	vp.MinDepth = 0.0f;	// Minimaler Tiefenwert
	vp.MaxDepth = 1.0f;	// Maximaler Tiefenwert

	// Viewport festlegen
	gDevice->RSSetViewports(1, &vp);

   return true;
}
Dabei ist in ID3D10Device::RSSetViewports() der erste Parameter die Anzahl der Viewport und der Zweie ein Zeiger auf ein Array von Viewports. Die Anzahl der Viewports sollte mit denen der Render Targets übereinstimmen.

Das war es zur Initialisierung.

Das Rendering wird wirklich einfacher. Wir werden hier einfach ID3D10Device::ClearRenderTargetView() ausführen, um den Hintergrund mit einer Farbe zu löschen und einmal IDXGISwapChain::Present() um das Bild zu präsentieren.

Code: Alles auswählen

void Render()
{
	// Backbuffer mit Rot löschen:
	gDevice->ClearRenderTargetView(gRenderTarget, D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f));
	gSwapChain->Present(1, 0);
}
Der erste Parameter von ClearRTV() erwartet das zu löschende RTV und eine Farbe in float[4]. Hier gibt es das nette D3DXCOLOR(float Rot, float Grün, float Blau, float Transparenz) um eine Farbe erstellen zu lassen. 0 ist hierbei keine Gewichtung und 1 die Volle.

IDXGISwapChain::Present() zeigt unser Bild dann an. Der erste Parameter ist ein Divisor zur Präsentationsrate. Wenn wir 60Hz haben und einen Divisor von 1, sind das 60 Bilder die Sekunde. Bei 2 sind es 30 Bilder und so weiter. Bei 0 wird das Bild ohen Warten angezeigt - was dazu führt, dass man sieht, wie das Bild auf dem Monitor entsteht.

Das Releasen (Aufräumen) ist relativ simpel. Wir müssen nur darauf achten, dass wir die SwapChain vorher wieder auf Fenstermodus zurücksetzen:

Code: Alles auswählen

void ReleaseDirectX10()
{
	if(gRenderTarget) gRenderTarget->Release(); // Render Target releasen

	gSwapChain->SetFullscreenState(false, NULL); // Wichtig bei Vollbild
	if(gSwapChain) gSwapChain->Release(); // SwapChain releasen

	if(gDevice) gDevice->Release(); // Device releasen
}

void Release()
{
	ReleaseDirectX10();
}
Das war es auch alles. Nun ein Bild vom Ergebnis:
zfx_tut_02_screen.png
zfx_tut_02_screen.png (836 Bytes) 9231 mal betrachtet
Hier der gesamte Quellcode zum Programm: Siehe nächster Post!

Folgende Bibliotheken werden benötigt: Windows-Standardbibliothek, d3d10.lib, d3dx10.lib und dxguid.lib

Zusammenfassung

Dieser Teil des Tutorials ist so gesehen ein wirklich harter, aber unvermeidbarer, Einstieg. Wer bis hier ohne Probleme weiß, was eigentlich passiert, der ist auf dem besten Wege Direct3D zu verstehen. Hier sollte man auch wirklich üben, bis man alles verstanden hat.

Die nächsten Tutorials werden wieder deutlich einfacher für den Einsteiger und man wird deutlich mehr Ergebnisse sehen als "nur" ein blank gelöschter Hintergrund.

Auf jeden Fall hat man schon eine Menge geschafft, wenn man auch nicht viel sieht.

Aufgaben

1. Ändere die Hintergrundfarbe zu einem Olive!
2. Spiele ein bisschen mit der Konfigurationsstruktur!
3. Lass die Hintergrundfarbe sich mit der Zeit ändern in Sinusform mit je unterschiedlichen Zeitkopplungen!
Zuletzt geändert von Gelöschter Benutzer am 18.03.2009, 16:53, insgesamt 2-mal geändert.
Gelöschter Benutzer
Beiträge: 92
Registriert: 26.02.2009, 22:09

Re: DirectX 10/11 Tutorial 2: Erster Kontakt mit DirectX 10

Beitrag von Gelöschter Benutzer »

Hier nun der Quellcode. Das Forum meinte, es könne mehr als drei Dateianhänge nicht zulasten nehmen ;)!

Anmerkung von Steffen Engel:Ich moechte keinen neuen Beitrag anfangen, deshalb hier rein, kannst meinen Kommentar ja wieder loeschen. Ich habe das Limit auf fuenf erhoeht.
Dateianhänge
ZFXTutorial2_FirstContact.zip
Der Programmcode
(34.94 KiB) 617-mal heruntergeladen
Benutzeravatar
Krishty
Establishment
Beiträge: 8237
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: DirectX 10/11 Tutorial 2: Erster Kontakt mit DirectX 10

Beitrag von Krishty »

Was mir immer wieder auffällt: Du verwechselst überall (selbst im Titel der Hilfedatei!) DirectX mit Direct3D … ich korrigiere sowas ja nur ungern weil es sich so nach Klugsch*****erei anhört, aber gerade bei Tutorials ist es wichtig, damit Neulinge es von Anfang an richtig machen.
Es kann doch auch nicht so schwer sein, fängt doch jede/s Direct3D-Funktion/-Objekt mit „D3D“ an ;)

Die zweite Hausaufgabe ist ziemlich kritisch:
Einerseits sollte die Auflösung unbedingt an die Ausmaße des Fensters gebunden sein, was sicher keinem Neuling von Anfang an klar ist. Du hast doch bereits globale Variablen mit den entsprechenden Werten, also benutz sie auch!
Zum anderen ist es bei einem einfarbigen Hintergrund schlicht unmöglich zu beurteilen, ob sich die Auflösung nun geändert hat oder nicht … es sieht ja immernoch aus wie vorher. Es gibt nichts, was verpixelt oder verschwommen sein könnte, wenn man was falsch macht. Wahrscheinlich ändern 90% ausschließlich die Auflösung des Back-Buffers (vergessen also die des Fensters und des Viewports) und wundern sich, warum sich nichts tut. Genau darum ist das ja der härteste Teil: Man macht einen Haufen komplizierter Dinge, die ohne etwas, was man auf dem Bildschirm darstellt, quasi Null Auswirkungen haben.

Gruß, Ky
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: DirectX 10/11 Tutorial 2: Erster Kontakt mit DirectX 10

Beitrag von Aramis »

Na du legst ja so richtig los :-)

Was mir beim groben Durchgucken aufgefallen ist:
  • 'Hier sind drei neue Punkte grün auffällig'
  • interprätieren
  • __uuidof() - MSVC-spezifisch. Ist D3D10 natürlich im Normalfall sowieso, aber ein Hinweis darauf wäre aber imho angebracht. Ich bin immer für eine klare Trennung zwischen Sprache und compilerspezifischen Erweiterungen, damit grade bei Einsteigern keine entsprechenden Missverständnisse, die schlussendlich nur zu wenig portablem Code führen, entstehen.
  • GUID - Begriff wird nicht definiert
  • Viele Fachausdrücke ('OM'), die jedoch nicht erklärt werden oder explizit als 'gegeben' hingenommen werden sollen. Kann man nicht jetzt noch auf die verzichten und sie dann in späteren Tutorialversionen, wenn das notwendige Hintergrundwissen gereift ist, einführen?
  • 'Wer wissen will, was alles demnächst kommt (ohne Mathematik)'. Ich weiß nicht ob ich dich da richtig verstehe .. klickverbot's Arbeit handelt einzig und alleine von den Geheimnissen der 3D-Mathematik. Ergo: Paper - Mathematik = 0 :D
  • ID3D10Texture2D - wird einfach so in den Raum geworfen und verwendet. Was ist eine Textur? Wo ist der Zusammenhang zu unserem Backbuffer?
Bitte sieh dies ausschließlich als konstruktive Kritik an. Ich finde die Idee eine D3D10-Tutorials generell gut, zumal das Forum hier durchaus ein bisschen weniger Leere vertragen kann :-)

Alex
Gelöschter Benutzer
Beiträge: 92
Registriert: 26.02.2009, 22:09

Re: DirectX 10/11 Tutorial 2: Erster Kontakt mit DirectX 10

Beitrag von Gelöschter Benutzer »

Ich werde die Änderungen morgen einbauen. Auf einige Begriffe gehe ich doch noch mal ein und das DirectX- und Direct3D-Benennungsproblem wird auch gelöst. Auf die ID3D10Texture2D-Klasse werde ich in im Zusammenhang BackBuffer nochmal eingehen. Nun ja, das mit der Mathematik werde ich auch nochmal in ein Extratutorial verschieben um die Multiplikationsreihenfolgen von Matrizen etc. zu zeigen.

Die zweite Aufgabe nehme ich imho raus. So kompliziert war es nicht gedacht sondern eher "Mein Monitor kann nur 1024x768 und die SwapChain verträgt trotzdem 1600x1200 in der Konfiguration.".

Aber aus Kritik wird man ja besser ;) !
klickverbot
Establishment
Beiträge: 191
Registriert: 01.03.2009, 19:22
Echter Name: David N.

Re: DirectX 10/11 Tutorial 2: Erster Kontakt mit DirectX 10

Beitrag von klickverbot »

Alexander Gessler hat geschrieben: 'Wer wissen will, was alles demnächst kommt (ohne Mathematik)'. Ich weiß nicht ob ich dich da richtig verstehe .. klickverbot's Arbeit handelt einzig und alleine von den Geheimnissen der 3D-Mathematik. Ergo: Paper - Mathematik = 0 :D
Das stimmt. Der nichtmathematische Teil der Arbeit beschränkt sich auf die absoluten Grundlagen, also den Teil, den die Mathematiker brauchen, um zu verstehen, was ich so von mir gebe. :)
Antworten