340 likes | 410 Views
Elemente der 3D-Grafikprogrammierung. Render - Pipeline. Aufgabe eines Grafikpakets wie DirectX: mit Grafikkarte eingehende Grafikdaten, Primitive, in Pixel auf dem Bildschirm transformieren Prozess aus mehreren Verarbeitungsschritten: Fixed Function Pipeline: 1.) Vorverarbeitung:
E N D
Render - Pipeline • Aufgabe eines Grafikpakets wie DirectX: mit Grafikkarte eingehende Grafikdaten, Primitive, in Pixel auf dem Bildschirm transformieren • Prozess aus mehreren Verarbeitungsschritten: • Fixed Function Pipeline: 1.) Vorverarbeitung: • Tesselieren: komplexe Geometrieelemente in einfache Geometrieelemente (Grafikprimitive) zerlegen • Um weitere Operationen zu vereinheitlichen • Z.B. Meshes immer aus Dreiecken 2.) Tranformation und Beleuchtung • Geometrische Umrechnung der Eingangsdaten wird durchgeführt • Teilschritte: • World – Transformation • View-Transformation • Perspektivische Transformation
3.) Clipping: Abschneiden von Teilen, die außerhalb des Blickfeldes liegen • Backface Culling: Entfernen von Rückseiten • Beide Prozesse automatisch, aber Programmierer kann durch Setzen von Render-States eingreifen -> Culling abschalten: auch Rückseiten der Objekte erzeugt 4.) Texturierung • Texturen bzw Bilder werden auf Grafikprimitive aufgebracht • entsprechend Lichtverhältnissen eingefärbt 5.) Ergebnisse zusammenführen • Farbwerte der Pixel für Bildschirmausgabe berechnen • Anhand Tiefen-Information (Z-Buffer) wird entschieden, welche Pixel im Vordergrund liegen • Übereinander liegende Pixel werden verschnitten
Fixed Function Pipeline: geeignet für feste Strukturen • Organische Strukturen wie Gesichter, Pflanzen, Wasser mit Verformungen oder Farbenspiel -> Berechnung aller geometrischen und farblichen Übergänge im Anwendungsprogramm nötig, dann Übergabe an Grafikkarte -> dazu „programmierbare“ Render-Pipeline: • Geometrische und farbliche Transformationen nah an der Hardware durchgeführt • Vertexshader: zur geometrischen Transformation • Pixelshader: zur Texturierung • Shader: kleine Programme, die bei Bedarf in Grafikkarte geladen werden um Vertex- oder Pixelberechnungen durchzuführen • Vorrangig dann, wenn spezielle geometrische pder farbliche Effekte erzielt werden sollen
7.2 Vertices und Vertexbuffer • Interne Darstellung eines 3D-Modells: Datenstruktur, die performant gerendert werden kann • maximale Geschwindigkeit in Render – Pipeline: am besten Arrays mit Aneinanderreihung von Geometriedaten, die als Ganzes in Grafikkarte geladen und mit einfachen Algorithmen verarbeitet werden können • Verzicht auf Kopien -> es darf nur einen Originaldatensatz geben, auf dem dann sowohl Anwendungsprogramm als auch Grafiksystem arbeiten kann • In DirectX: Vertexbuffer sind auf Verwendung an Schnittstelle zwischen Grafiksystem und Anwendungsprogramm hin optimiert
Vertexbuffer: • reservierter Speicherbereich • Informationen über Knoten (Vertices) eines 3D Modells sind hier abgelegt • Programm kann mehrere Vertexbuffer anlegen und unabhängig voneinander verwenden • in Vertexbuffer steht Aneinanderreihung von Vertices • Vertices: • Eckpunkte oder Knoten eines dreidimensionalen Mesh • Hat Positionsdaten und ggf. weitere Daten über den Knoten wie Farbwerte, Texturkoordinaten • Möglichst performant: schlichte, starre Struktur für Vertex -> einfachste Möglichkeit: immer gleiche Struktur, kann auch enorme Speichervergeudung bedeuten • Daher flexibles Vertex - Format
7.2.1 Das Flexible Vertex-Format • Flexibles Vertex-Format (FVF): zur Speicherung von Vertices in Vertexbuffer verwendetes Datenformat wird festgelegt • Für jedes Attribut bestimmter Datentyp • Position des Vertex durch Vektor D3DXVECTOR3 beschrieben, • Farbe durch RGBA-Wert D3DCOLOR • Zusätzlich ist verbindliche Reihenfolge der Attribute festgelegt -> Gesamtstruktur mit allen Feldern ist festgelegt, nicht verwendete Felder werden gelöscht
Vertexformat mit Positionsangabe, Normale, diffusem Farbwert: # define MEINVERTEXFORMAT (D3DFVF_XYZ I D3DFVF_NORMAL I D3DFVF_DIFFUSE) struct meinvertex { D3DXVECTOR3 pos; D3DXVECTOR3 normale; D3DCOLOR color; }; • Symbolische Konstante MEINVERTEXFORMAT verwendet man später zur Reservierung von Vertexbuffer • Datenstruktur meinvertex verwendet als Overlay, wenn man von Vertexbuffer schreiben oder lesen will • Deklaration des Vertexformats -> dieses wird später mit Funktion SetFVF dem Device bekannt gegeben • Bei Deklaration von MEINVERTEXFORMAT kommt es nicht auf die Reihenfolge einzelner Flags an • die Reihenfolge der Felder in der Datenstruktur meinvertex muss sich jedoch streng an Reihenfolge der Attribute in der Tabelle orientieren -> bei abweichender Reihenfolge erhält man später falsche Werte durch unpassendes Overlay
Umfassendere Methode zur Definition von Vertexformaten: • Array mit Feldbeschreibungen anlegen D3DVERTEXELEMENT9 • Diesen an Funktion CreateVertexDeclaration übergeben um Vertexdeklaration zu erzeugen • SetVertexDeclaration setzt Deklaration im Device -> Feldbeschreibungen D3DVERTEXELEMENT9 entsprechen in etwa symbolischen Konstanten -> Aufruf von CreateVertexDeclaration entspricht Montage der symbolischen Konstanten durch Oder – Verbindung -> SetVertexDeclaration entspricht Funktion SetFVF -> Vertexdeklaration verwendet man im Zusammenhang mit Vertex- und Pixelshadern, wenn man selbst definierte Formate in der Renderpipeline verwenden will
7.2.2 Verwendung von Vertexbuffern • Vertexbuffern anlegen für ausgewähltes Vertexformat: Position, Normale, Farbe wird für Vertices benötigt • Vertexbuffer bereitstellen über Memberfunktion CreateVertexBuffer aus Interface des DirectX-Device (IDirect3DDevice9) HRESULT CreateVertexBuffer (UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9 **ppVertexBuffer, HANDLE pHandle) -> Länge des Buffers in Bytes, Verwendungsart, flexibles Vertexformat, Memorypool, aus dem Speicher bereitgestellt wird, Pointer für erzeugten Buffer, reservierter Parameter, muss NULL sein • Als Länge übergibt man Anzahl gewünschter Vertices x Größe der Datenstruktur für einen Vertex, etwa sizeof(meinvertex) • FVF-Parameter sollte zu Vertexstruktur passende Bitmaske, etwa MEINVERTEXFORMAT enthalten • im Parameter ppVertexBuffer erhalten wir Funktionsergebnis, d.h. Vertexbuffer
Konkretes Beispiel für Anforderung eines Buffers für 1000 Vertices LPDIRECT3DVERTEXBUFFER9 vertexbuffer; device->CreateVertexBuffer (1000*sizeof(meinvertex), 0, MEINVERTEXFORMAT, D3DPOOL_MANAGED, &vertexbuffer, NULL); • Device als Zeiger auf zuvor initialisiertes DirectX-Device • Zugriff auf Vertexbuffer über Interface (LPDIRECT3DVERTEXBUFFER9) • Buffer kann in anderem Adressraum als dem unseres Prozesses liegen und damit konkurrierend von anderen Prozessen verwendet werden • Daher mit Funktion Lock für exklusiven Zugriff ganz oder teilweise sperren ->
HRESULT Lock (UINT OffsetToLock, UINT SizeToLock, VOID **ppbData, DWORD Flags) • Offset, ab dem gesperrt wird; Anzahl Bytes, die gesperrt werden; unspezifizierter Zeiger, der nach Aufruf auf den Buffer zeigt; Flags, die Art des gewünschten Locks bestimmen • Bei erfolgreichem Aufruf erhalten wir in ppbData einen Zeiger auf reservierten Vertexbuffer • konkret: meinvertex *pv; vertexbuffer->Lock(0, 0, (void**)&pv, 0); • Wenn SizeToLock auf 0, wird der gesamte Vertexbuffer gesperrt • Nach Funktionsaufruf finden wir in pv einen Zeiger auf Vertexbuffer
Struktur meinvertex dient dann als Overlay zum Lesen/Schreiben in Vertexbuffer for (int i=0; i<1000; i++) { pv[i].pos = D3DXVECTOR3 (1, 1, 1); pv[i].normale = D3DXVECTOR3 (0, 1, 0); pv[i].color = D3DCOLOR_ARGB (255, 255, 255, 0); } • Nach Zugriff Freigabe des Vertexbuffer für andere Prozesse über Funktion Unlock: HRESULT Unlock (VOID) • Freigabe über Aufruf von Release – Funktion: vertexbuffer -> Release();
7.3 Grafikprimitive7.3.1 Grafikprimitive in DirectX • Grafikprimitive: einfache geometrische Struktur, aus der komplexere Strukturen aufgebaut werden können • DirectX kennt Punkte, Linien, Dreiecke als Primitive -> alle räumlichen Modelle werden in Dreiecke aufgelöst >>Triangulierung<< • Vertexbuffer kann Punktliste, Linienliste, Linienzug, Dreiecksliste, Dreiecksstreifen oder Dreiecksfächer enthalten
Punktliste: D3DPT_POINTLIST • Vertexbuffer enthält einzelne Punkte • Linienliste: D3DPT_LINELIST • Zwei aufeinanderfolgende Vertices werden als Anfangs- bzw Endpunkt einer Linie aufgefasst • Anzahl der Vertices muss größer als 1 und gerade sein • Linienzüge: D3DPT_LINESTRIP • Können ebenfalls im Vertexbuffer modelliert werden • Punkte, Linien, Linienzüge eher selten, wichtiger sind Dreiecke -> flexibelste Form dazu Dreiecksliste D3DPT_TRIANGLELIST • Je drei aufeinanderfolgende Vertices bilden Dreieck • Dreiecke haben Vorder- und Rückseite -> nur Vorderseite wird gerendert • Vorderseite: Windungsrichtung der im Vertexbuffer aufeinanderfolgenden Eckpunkte verläuft im Uhrzeigersinn • Ausblenden der Rückseite: Backface - Culling
Culling stellt man mit Funktion SetRenderState um device->SetRenderState ( D3DRS_CULLMODE, D3DCULLL_CCW); • Im obigen Beispiel wird Culling so eingestellt, dass Rückseiten, deren Windungsrichtung gegen Uhrzeigersinn (CCW) verläuft, nicht dargestellt werden • Anzahl zur Beschreibung geometrischer Strukturen verwendete Vertices verringern: • Dreiecksstreifen (D3DPT_TRIANGLESTRIP) • Erste drei Vertices bilden Dreick, jeder weitere Vertex bildet mit seinen zwei Vorgängern das nächste Dreieck • Wenn Backface-Culling aktiviert: erstes Dreieck im eingestellten Cullingmode gerendert, bei jedem weiteren Dreieck wird Wichtungsrichtung umgekehrt • Dreiecksfächer (D3DPT_TRIANGLEFAN): drei erste Vertices ein Dreieck, jeder weitere Vertex mit Vorgänger und erstem Vertex • Culling für alle Dreiecke gleich
7.3.2 Rendern von Primitiven • Vertex-Format festlegen -> Vertexbuffer allokieren -> Vertexbuffer mit Primitiven füllen -> rendern • Vertexbuffer als Eingabequelle der Render-Pipeline unseres Devices festlegen: mit Funktion SetStreamSource HRESULT SetStreamSource (UINT StreamNumber, IDirect3DVertexBuffer9 *pStreamData, UINT OffsetInBytes, UINT Stride) • Nummer des Streams, Vertexbuffer als Quelle, Offset zum Startpunkt im Vertexbuffer, Größe des Datensatzes für einen Vertex in Bytes • StreamNumber ist logische Nummer -> mehrere Vertexbuffer als Streamsource festlegen, sofern man jedes Mal eine andere Nummer wählt • Beispiel: device->SetStreamSource (0, vertexbuffer, 0, sizeof(meinvertex));
Device mitteilen, welches Vertexformat man im Vertexbuffer verwendet: HRESULT SetFVF (DWORD FVF) -> flexibles Vertex – Format • Als Parameter definiertes Vertexformat übergeben • Beispiel: device-> SetFVF(MEINVERTEXFORMAT); • Funktion DrawPrimitive rendert Vertexbuffer HRESULT DrawPrimitive (D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) • Art der Primitive, Startindex im Vertexbuffer, Anzahl der zu rendernden Primitiven • idR kompletten Vertexbuffer rendern und Startindex auf 0 setzen, aber auch andere Startpunkte sind möglich • Beispiel: Ab dem 10ten Vertex im Buffer 100 Dreiecke rendern: device->DrawPrimitive (D3DPT_TRIANGLELIST, 10, 100);
7.3.3 Beispiele mit Dreiecken und Linien • Schritt: Festlegen des Vertexformats • Für jeden Vertex Position und Farbwert im Vertexbuffer speichern # define MEINVERTEXFORMAT (D3DFVF_XYZ I D3DFVF_DIFFUSE) struct meinvertex { D3DXVECTOR3 pos; D3DCOLOR color; }; • Liste von Dreiecken mit Umrandung erstellen • Dazu Dreiecksliste und Linienliste rendern • Voraussetzung: Device (LPDIRECT3DDEVICE9) wurde erfolgreich initialisiert • Funktionalität zum Erstellen und rendern konzipieren wir als eine in sich geschlossene Klasse (trianglelist), in die von außen nur ein Zeiger auf Device eingeht
class trianglelist { private: LPDIRECT3DDEVICE9 device; LPDIRECT3DVERTEXBUFFER9 dreieckbuffer; LPDIRECT3DVERTEXBUFFER9 linienbuffer; public: trianglelist(); ~trianglelist(); void create(LPDIRECT3DDEVICE9 dev); void setup(); void render(); }; • Zwei Vertexbuffer: der erste (dreieckbuffer) soll Eckpunkte der Dreiecke, der zweite (linienbuffer) Eckpunkte der Umrandung aufnehmen • Fehlende Arbeitsschritte werden in öffentlichen Methoden create, setup, render abgehandelt
Konstruktor und Destruktor der Klasse: trianglelist::trianglelist() { dreieckbuffer = 0; linienbuffer = 0; } trianglelist::~trianglelist() { if (dreieckbuffer) dreieckbuffer -> Release(); if (linienbuffer) linienbuffer-> Release(); } • Konstruktor initialisiert Bufferzeiger • Destruktor gibt Buffer frei
2. Schritt: Allokieren des Vertexbuffers • Zwei Vertexbuffer: erste soll 2 Dreiecke = 6 Vertices aufnehmen, zweite soll 6 Randlinien = 12 Vertices aufnehmen • Daraus ergibt sich erforderlicher Speicherplatz void trianglelist::create (LPDIRECT3DDEVICE9 dev) { device = dev; device -> CreateVertexBuffer (6*sizeof(meinvertex),0, MEINVERTEXFORMAT, D3DPOOL_MANAGED, &dreieckbuffer, NULL); device->CreateVertexBuffer (12*sizeof(meinvertex),0, MEINVERTEXFORMAT, D3DPOOL_MANAGED, &linienbuffer, NULL); } - System legt beide Buffer an, kein direkter Zugriff -> bei Zugriff auf Inhalt zuvor Funktion Lock aufrufen
3. Schritt: Befüllen des Vertexbuffers • Zunächst beide Vertexbuffer für Zugriff durch unser Programm reservieren void trianglelist::setup() { meinvertex *dv, *lv; int i; dreieckbuffer -> Lock (0, 0, (void**)&dv, 0); linienbuffer ->Lock(0,0, (void**)&lv, 0); /* Zeiger auf jeweilige Bufferinhalte werden in Variablen dv und lv übertragen */ dv[o].pos = D3DXVECTOR3 (0, 0, 0); dv[1].pos = D3DXVECTOR3 (2, 1, 0); dv[2].pos = D3DXVECTOR3 (2, 0, 0); // … bis dv[5] /* Koordinatenwerte für sechs Eckpunkte der beiden Dreiecke eintragen dv[0] bis dv[5] in Dreiecks- bzw Linienpuffer */
dv[0].color = D3DCOLOR_ARGB (255, 80, 80, 80); dv[1].color = D3DCOLOR_ARGB (255, 160, 160, 160); // .. Bis dv[5] /* für Eckpunkte der Dreiecke unterschiedliche Farbwerte */ lv[0].pos = dv[0].pos; // 12 Eckpunkte der 6 Randlinien lv[1].pos = dv[1].pos; lv[2].pos = dv[1].pos; lv[3].pos = dv[2].pos; lv[4].pos = dv[2].pos; lv[5].pos = dv[0].pos; lv[6].pos = dv[3].pos; lv[7].pos = dv[4].pos; lv[8].pos = dv[4].pos; lv[9].pos = dv[5].pos; lv[10].pos = dv[5].pos; lv[11].pos = dv[3].pos;
for (i = 0; i<12; i++) lv[i].color=D3DCOLOR_ARGB(255, 0, 0, 0); // Eckpunkte der Randlinien schwarz dreieckbuffer->Unlock(); linienbuffer->Unlock(); } // Vertexbuffer freigeben - Beide Buffer sind jetzt bereit zum rendern
4. Schritt: Rendern der Primitiven im Vertexbuffer • Rendern besteht aus wenigen Funktionsaufrufen • Zunächst mit Funktion SetFVF richtiges Vertexformat setzen • Danach für jeden Buffer Funktionen SetStreamSource und DrawPrimitive aufrufen void trianglelist::render() { device->SetRenderState (D3DRS_AMBIENT, 0xffffff); // Beleuchtung: Ambient Licht einschalten device->SetRenderState (D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_COLOR1); // Farbwerte aus diffusen Farbkomponente des Vertex verwenden device ->SetFVF (MEINVERTEXFORMAT); device->SetStreamSource (0, dreieckbuffer, 0 sizeof(meinvertex)); device->DrawPrimitive (D3DPT_TRIANGLELIST, 0, 2); devide->SetStreamSource(0, linienbuffer, 0 sizeof(meinvertex)); device->DrawPrimitive(D3DPT_LINELIST, 0, 6); } • Aufruf der Methoden create und setup sowie regelmäßiges Rendern ergibt gewünschtes Bild
Unterschiedliche Farbtöne an den Ecken der Dreiecke führen zu Farbverläufen • Ecken dv[2] und dv[3] haben gleiche Position, unterscheiden sich aber in Farbe -> da Vertices zu unterschiedlichen Dreiecken gehören, hätten wir auf keines von beiden verzichten können • Wenn alle Dreiecke in der gleichen Farbe und ohne Farbverläufe dargestellt werden sollen, wäre Farbinfo für jeden Vertex Speicherplatzvergeudung • Stattdessen Material in gewünschter Farbe anlegen: D3DMATERIAL9 material; ZeroMemory (&material, sizeof (material)); Material.Ambient = D3DXCOLOR (255, 255, 0, 0); • Material in render-Funktion auswählen und durch SetRenderState festlegen, dass Material verwendet werden soll: device->SetMaterial (&material); device->SetRenderState (D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL);
7.3.4 Beispiel mit Punktlisten (Partikelsysteme) • Dreiecke oder Linien als am häufigsten genutzte Primitive • Punktlisten zur Realisation von Partikelsystemen = 3-dimensionale Punktwolken um etwa Rauch oder Schnee zu implementieren • verwenden gleiches Vertexformat wie zuvor define MEINVERTEXFORMAT (D3DFVF_XYZ I D3DFVF_DIFFUSE) struct meinvertex { D3DXVECTOR3 pos; D3DCOLOR color; };
Jeder Punkt /Partikel hat eigene Position, kann eigenständig bewegt werden, hat eigene Farbe • Beispiel: Kugeloberfläche als Wolke aus einzelnen Punkten zusammensetzen -> größer werden lassen - > zerstäuben class partikelsystem { private: LPDIRECT3DDEVICE9 device; LPDIRECT3DVERTEXBUFFER9 partikelbuffer; int anzahl; float radius; public: partikelsystem (); ~partikelsystem(); void create( LPDIRECT3DDEVICE9 dev, int anz); void setup(); void blowup(); void render (); void lookatme (…} };
Create – Methode: void partikelsystem::create(LPDIRECT3DDEVICE9 dev, int anz) { device = dev; anzahl = anz; device ->CreateVertexBuffer (anzahl*sizeof(meinvertex), 0, MEINVERTEXFORMAT, D3DPOOL_MANAGED, &partikelbuffer, NULL); } • Anwender kann wählen, wie viele Partikel er haben will ->entsprechend wird Vertexbuffer bereit gestellt
Setup – Funktion: Anfangspositionen einzelner Partikel in Buffer eingetragen void partikelsystem::setup() { int i; meinvertex *pv; srand (123); radius = 0.1f; partikelbuffer->Lock (0, 0, (void**)&pv, 0); for (i=0;i < anzahl; i++) { pv[i].pos.x = ((float)rand())/RAND_MAX – 0.5f; // für jedes Partikel durch Zufalls- pv[i].pos.y = ((float)rand())/RAND_MAX – 0.5f; // Zufallskoordinaten ein // Punkt in Würfel pv[i].pos.z = ((float)rand())/RAND_MAX – 0.5f; // um den Ursprung bestimmt D3DXVec3Normalize (&pv[i].pos, &pv[i].pos); // Normalisierung des Punktes pv[i].color = D3DCOLOR_ARGB (255, (int) (128*(pv[i].pos.x +1)), (int) (128*(pv[i].pos.y +1)), (int) (128*(pv[i].pos.z +1))); // Auswahl eines Farbwertes für Partikel pv[i].pos = radius * pv[i].pos; // Sphäre auf Anfangsgröße verkleinern } partikelbuffer -> Unlock(); }
Funktion blowup: Aufblasen der Sphäre, bei jedem Aufruf um 10% bis Maximalwert des Radius 10 • Radius 10 erreicht -> Funktion setup setzt Sphäre in Ausgangszustand void partikelsystem::blowup() { int i; meinvertex *pv; radius *= 1.1f; if (radius <10) { partikelbuffer ->Lock (0, 0, (void**)&pv, 0); for (i = 0; i < anzahl; i++) pv[i].pos = 1.1f * pv[i].pos; partikelbuffer->Unlock(); } else setup (); }
Render – Funktion nimmt Beleuchtungseinstellungen vor und bringt Vertexbuffer zur Darstellung void partikelsystem::render () { device->SetRenderState (D3DRS_AMBIENT, 0xffffff); device->SetRenderState (D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_COLOR1); device->SetFVF (MEINVERTEXFORMAT); device ->SetStreamSource (0, partikelbuffer, 0, sizeof (meinvertex)); device -> DrawPrimitive (D3DPT_POINTLIST, 0, anzahl); } • Effekt des Zerbröselns: die dem Auge näher liegenden Partikel größer und weiter entfernt liegende Partikel kleiner -> Pointscaling D3DRS_POINTSCALEENABLE aktivieren
Skalierung für Partikel einschalten und Konstanten A, B und C setzen: inline DWORD FtoDW (FLOAT f) { return *((DWORD*) &f);} void partikelsystem::render () { device ->SetRenderState (D3DRS_POINTSCALEENABLE, TRUE); device ->SetRenderState (D3DRS_POINTSIZE, FtoDW(0.1f)); device->SetRenderState (D3DRS_POINTSCALE_A, FtoDW (0.0f)); device->SetRenderState (D3DRS_POINTSCALE_B, FtoDW (0.0f)); device ->SetRenderState (D3DRS_POINTSCALE_C, FtoDW (1.00f)); device ->SetRenderState (D3DRS_AMBIENT, 0xffffff); device ->SetRenderState (D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_COLOR1); device ->SetFVF (MEINVERTEXFORMAT); device ->SetStreamSource (0, partikelbuffer, 0, sizeof (meinvertex)); device -> DrawPrimitive (D3DPT_POINTLIST, 0, anzahl); } • Beim Setzen der Konstanten - Hilfsfunktion FtoDW: Funktion SetRenderState akzeptiert nur DWORD, aber eine Gleitkommazahl übergeben -> kopiert float in Speicherbereich, der für DWORD vorgesehen ist