|
| Im zweiten Teil dieses Artikels wurde eine beleuchtete Szene mit einem
dreidimensionalen Dreieck dargestellt, das sich um die eigene Achse gedreht
hat. Die Farbe der einzelnen Pixel dieses Dreiecks wurde ausgehend von den
Eckpunkten über die Pixel interpoliert. Für viele Grafiken sind solche
Farbverläufe nur sehr selten relevant. Wesentlich häufiger ist es sinnvoll,
Bilder auf den Flächen der Dreiecke abzubilden.
Dadurch können Dreiecke erst wirklich als die Oberfläche eines realen
Gegenstandes dargestellt werden. Ziegelsteine einer Mauer sind ein gutes
Beispiel dafür. In Direct3D können Grafiken durch ein spezielles Feature mit
der Oberfläche eines Dreiecks verknüpft werden. Dieses Feature wird Textur
genannt.
Der englische Begriff Texture lässt sich mit Beschaffenheit, Konsistenz oder
Struktur übersetzen. Texturen sind Bilder, die an ein oder mehrere Dreiecke
geheftet werden. Richtig eingesetzt, können viele Objekte mithilfe von Texturen
durch sehr wenige Dreiecke dargestellt werden. Das wirkt sich in den meisten
Fällen sehr positiv auf die Geschwindigkeit der Bilddarstellung aus.
Das
Textur-Koordinatensystem
Um zu verstehen, wie Texturen einem oder mehreren Dreiecken zugeordnet werden
können, muss ein weiteres Koordinatensystem eingeführt werden. Es ist
zweidimensional und heißt Textur-Koordinatensystem. Um seine Achsen nicht mit
denen der anderen Koordinatensysteme zu verwechseln, werden diese mit den
Buchstaben u und v benannt.
Abbildung 1 zeigt ein Porträt. In
Abbildung 2 sehen Sie, wie dieses Bild auf ein Dreieck abgebildet
werden kann. Beachten Sie bitte, dass das Bild um 90 Grad gedreht ist. Da es
auf eine dreieckige Fläche projiziert wird, wurde eine Hälfte des Bildes nicht
dargestellt.
Um die Ausgabe wie in
Abbildung 2 zu realisieren, ist ein weiteres Vertexformat notwendig. Es
heißt PositionedNormalTextured. Ähnlich wie bei dem im vorangegangenen Teil
dieses Artikels erklärten Format PositionedNormal sind zur Definition eines
PositionedNormalTextured- Vertex eine dreidimensionale Position und ein
Normalenvektor notwendig. Darüber hinaus müssen auch noch u- und v-Werte
angegeben werden, die das Textur-Koordinatensystem definieren.
Textur-Koordinaten beziehen sich auf eine Position im Originalbild. Die
Koordinaten (0,0) beziehen sich auf die obere linke Ecke des Bildes. Das
Koordinatensystem ist automatisch so skaliert, dass der Wert u = 1 der Breite
und v = 1 der Höhe des Bildes entspricht.
Die Koordinaten (0,1) beschreiben also den Punkt in der rechten oberen Ecke,
während mit den Koordinaten (1,0) die linke untere Ecke des Bildes gemeint ist.
Damit das Bild in
Abbildung 1 um 90 Grad gedreht im Dreieck dargestellt wird, müssen die
Koordinaten u und v also so angegeben werden, wie es in
Abbildung 3 zu sehen ist.
Die in dem Bild angegebenen Koordinaten der einzelnen Vertizes sind Textur-
Koordinaten. Durch die Textur-Koordinaten (1,0) im rechten unteren Vertex des
Dreiecks wurde diesem Vertex die rechte obere Ecke des Bildes zugeordnet. Der
linke untere Vertex des Dreiecks hat die Koordinaten (1,1).
Daher wird in der linken unteren Ecke des Dreiecks die rechte untere Ecke des
Bildes dargestellt. Die Textur-Koordinaten im oberen Vertex des Dreiecks sind
(0,1). An dieser Ecke wird also die linke untere Ecke des Bildes dargestellt.
Durch diese Koordinaten wird die Drehung des Bildes um 90 Grad bewirkt.
Ähnlich wie Farbwerte und Normalenvektoren werden auch Textur-Koordinaten
gleichmäßig über das Dreieck hinweg interpoliert. Der Bildpunkt in der Mitte
der unteren Linie des Dreiecks entspricht daher dem Bildpunkt in der Mitte des
rechten Bildrandes.
Texturen im Einsatz
Die bisher besprochene Theorie genügt, um die Beispielanwendung so zu
implementieren, dass das Dreieck nun mit dem Urlaubsfoto unseres Protagonisten
als Textur dargestellt wird. Dazu wird erst einmal die Methode
InitializeGraphics angepasst. Diese Methode wurde schon in früheren Versionen
der Beispielanwendung eingeführt.
private Texture texture;
protected bool InitializeGraphics()
{
PresentParameters pres = new PresentParameters();
pres.Windowed = true;
pres.SwapEffect = SwapEffect.Discard;
device = new Device(0,
DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing,
pres);
texture = CreateOverlayTexture(device);
vertices = CreateVertexBuffer(device);
return true;
}
Die in InitializeGraphics aufgerufene Methode
CreateOverlayTexture ist wie folgt eingearbeitet:
protected Texture CreateOverlayTexture( Device device) {
Texture t = TextureLoader.FromFile(device, “texture.bmp”);
return t;
}
Der Aufruf TextureLoader.FromFile erzeugt aus einer Datei ein Objekt vom Typ
Microsoft.DirectX.Direct3D.Texture und gibt dieses zurück. Um den Typ
TextureLoader verwenden zu können, muss zusätzlich die Assembly Microsoft.
Direct3.Direct3DX referenziert werden. In dieser Assembly befinden sich
zahlreiche Typen, die für die Entwicklung von Direct3D hilfreich sind.
Für die Ausgabe des Dreiecks muss zusätzlich zu dem Texture-Objekt ein
VertexBuffer erzeugt werden.
protected VertexBuffer CreateVertexBuffer( Device device) {
VertexBuffer buf = new VertexBuffer(
typeof(CustomVertex.TransformedTextured), 3,
device, 0,
CustomVertex.TransformedTextured.Format,
Pool.Default);
PopulateVertexBuffer(buf); return buf;
}
protected void PopulateVertexBuffer( VertexBuffer vertices) {
CustomVertex.TransformedTextured[] verts =
(CustomVertex.TransformedTextured[])
vertices.Lock(0, 0);
int i = 0;
verts[i++] = new CustomVertex.TransformedTextured(
// Vertex position
Width / 2, Height / 4, 0.5F,
// rhw (advanced)
1,
// texture coordinates
0, 1);
verts[i++] =
new CustomVertex.TransformedTextured(
Width * 3 / 4, Height * 3 / 4, 0.5F,
1,
1, 0);
verts[i++] = new
CustomVertex.TransformedTextured(
Width / 4 , Height * 3 / 4, 0.5F,
1,
1, 1);
vertices.Unlock();
}
Wenn Sie die ersten beiden Teile dieser Serie noch in Erinnerung haben, wird
Ihnen vieles von dem hier gezeigten Code bekannt vorkommen. Einzig der Typ der
Vertizes hat sich verändert. Um das Beispiel so einfach wie möglich zu machen
werden hier transformierte Koordinaten (zweidimensionale Fensterkoordinaten)
für die Position des Dreiecks verwendet. Daher ist es auch nicht notwendig, mit
Welt- und Ansichtskoordinaten und deren Transformation zu arbeiten.
Der Konstruktor des TransformedTextured- Vertexes erhält neben den üblichen
Angaben wie den x- und y-Koordinaten für die Position auch die u- und
v-Koordinaten des Textur-Koordinatensystems. Mit diesen Vertizes kann das
Dreieck so angezeigt werden, wie es in
Abbildung 4 zu sehen ist.
Dazu ist es noch notwendig, Direct3D mitzuteilen, dass das Porträtfoto als Bild
für Textur-Ausgaben verwendet werden soll. Der folgende Code zeigt, wie das
umgesetzt werden kann.
protected void Render() {
device.Clear(ClearFlags.Target, Color.Black, 1.0F, 0);
device.BeginScene();
device.SetTexture(0, texture);
device.VertexFormat = CustomVertex.TransformedTextured.Format;
device.SetStreamSource(0, vertices, 0);
device.DrawPrimitives( PrimitiveType.TriangleList, 0, 1);
device.EndScene();
device.Present();
}
Der Aufruf der Methode SetTexture legt hier fest, welches Texture-Objekt für
Ausgaben über Vertizes mit Textur-Information verwendet werden soll.
Als zweites Argument wird das in CreateOverlayTexture erzeugte Texture- Objekt
übergeben. Es kann mehr als ein aktives Textur-Objekt gleichzeitig geben. Diese
können durch den ersten Parameter der Methode SetTexture unterschieden werden.
Da die Beispielanwendung nur mit einem Textur-Objekt arbeitet, wird hier der
Wert 0 übergeben.
Listing 1 enthält den vollständigen Code der Beispielanwendung.
Materialien
Bisher wurden zwei Möglichkeiten aufgezeigt, um Objekte und deren Dreiecke
farbig anzuzeigen. Es kann ein Vertex-Format verwendet werden, das
Farbinformationen hat, oder es kann mithilfe eines Textur- Objektes eine Bitmap
auf einem Dreieck abgelegt werden.
Es gibt jedoch noch eine weitere wichtige Möglichkeit. Diese wird Material
genannt. Ein Material ist eine Sammlung von Einstellungen eines
Device-Objektes, mit denen die Ausgabe und damit die Darstellung von Dreiecken
beeinflusst werden kann. Diese Einstellungen werden in einem Wertetyp namens
Material zusammengefasst. In einem Device-Objekt gibt es eine Eigenschaft von
diesem Wertetyp, die ebenfalls Material heißt. Der folgende Code zeigt, wie Sie
mit dem Material-Objekt arbeiten können.
protected void SetupMaterials() {
Material mat = new Material();
// Set the properties of the material
// (Code omitted for the moment)
device.Material = mat;
}
Der Wertetyp Material hat nur einige wenige Einstellungen. Zu diesen gehören
vier unterschiedliche Farbwerte: Diffuse, Ambient, Emissive und Specular.
Ähnlich wie bei Lichtquellen wird mit der Eigenschaft Diffuse die ursprüngliche
Farbe des Materials angegeben. Die Eigenschaft Ambient hängt mit dem
Umgebungslicht von Direct3D zusammen. Wie unlängst besprochen, gibt es
unterschiedliche Arten von Lichtquellen in Direct3D. Während die drei
Lichtquellen Point Light, Spot Light und Directional Light das Licht in
bestimmte Richtungen ausstrahlen, ist das Ambient Light überall in gleicher
Weise vorhanden. Mit der Eigenschaft Ambient des Materials kann die Farbe
festgelegt werden, die ein dargestelltes Objekt annimmt, wenn es mit Ambient
Light interagiert. Meist ist es sinnvoll, diese Eigenschaft auf den gleichen
Wert zu setzen wie die Eigenschaft Diffuse.
Die Eigenschaft Emissive gibt die Farbe an, die ein Objekt von Natur aus
emittiert, unabhängig von dem Licht, das auf das Objekt fällt. Diese
Eigenschaft ist auch dann wichtig, wenn es kein Licht gibt. Sie lässt ein
Objekt glühend erscheinen. Ein Objekt strahlt durch diese Eigenschaft jedoch
selbst kein Licht aus. Benachbarte Objekte werden dadurch also nicht heller
dargestellt.
Deshalb wird die Eigenschaft Emissive auch nur in einigen Spezialfällen – zum
Beispiel zur Darstellung der Sonne oder einer Glühbirne – verwendet. In den
meisten Fällen sollte diese Eigenschaft auf schwarz gesetzt werden.
Specular
und SpecularSharpness
Mit der Eigenschaft Specular kann ein Effekt berücksichtigt werden, der in der
Realität so oft vorkommt, dass er kaum noch wahrgenommen wird.
Dieser Effekt entsteht durch Unregelmäßigkeiten in der Oberflächenstruktur
eines Materials und ist als heller Fleck auf der Oberfläche eines Objektes
sichtbar. Denken Sie an ein Bild mit einem Apfel. Der Apfel selbst ist grün,
aber an der Stelle, an der besonders viel Licht auf den Apfel fällt, erscheint
ein heller Fleck.
Wenn die Eigenschaft Specular auf die Farbe weiß gesetzt wird, erscheint dieser
Fleck weiß – vorausgesetzt, das einfallende Licht ist auch weiß. Glänzende
Materialien wie zum Beispiel viele Kunststoffe können auf diese Weise gut
dargestellt werden.
Wenn stattdessen eine Farbe verwendet wird, die näher an der Farbe des
dargestellten Objektes liegt, erscheint der Fleck weniger hell. Solche
Farbwerte werden häufiger zur Darstellung von metallischen Gegenständen
verwendet.
Abbildung 5
zeigt einen Zylinder, bei dem die Eigenschaft Specular auf weiß gesetzt wurde,
während die Eigenschaft in
Abbildung 6 auf einen Wert gesetzt wurde, der näher an der Objektfarbe
liegt. Dieser Effekt kann durch Reflexion – ein weiteres Feature von Direct3D,
das hier allerdings nicht besprochen wird – noch mehr verstärkt werden.
Im Zusammenhang mit der Specular- Farbe ist auch die Eigenschaft Specular-
Sharpness des Wertetyps Material von Bedeutung. Mit dieser kann die Ausdehnung
des hellen Flecks beeinflusst werden. Je kleiner der Wert, desto größer wird
der helle Fleck.
In Abbildung 7
wird der Zylinder mit einem SpecularSharpness-Wert von 50 dargestellt, während
der Wert in Abbildung
8 auf 10 gesetzt wurde. Wenn ein niedriger SpecularSharpness-Wert
zusammen mit einer dunklen Specular-Farbe verwendet wird, erscheint die
Oberfläche des dargestellten Objektes matt. Wird stattdessen ein hoher
SpecularSharpness- Wert und eine helle Specular-Farbe verwendet, erscheint die
Oberfläche des Objektes glänzend.
Genauso wie bei der Berechnung der auf ein Objekt fallenden Lichtmenge werden
auch bei der Berechnung dieses hellen Flecks die so genannten
Oberflächennormalen verwendet, also die Vektoren, die im rechten Winkel zur
Oberfläche stehen. Demzufolge sind auch die Beschränkungen gleich.
Je mehr Dreiecke verwendet werden, desto gleichmäßiger erscheint der helle
Fleck, aber desto aufwändiger und langsamer wird die Darstellung. Um die
Berechnung des hellen Flecks nur dann durchzuführen, wenn sie tatsächlich
notwendig ist, gibt es die Eigenschaft Device.RenderState.SpecularEnable. Hat
diese Eigenschaft den Wert false, werden die Eigenschaften Material.Specular
und Material.SpecularSharpness nicht berücksichtigt und der helle Fleck wird
nicht berechnet.
Mit diesen Informationen kann die Methode SetupMaterials wie folgt
vervollständigt werden:
protected void SetupMaterials() {
Material mat = new Material();
// Set the properties of the material
// The object itself will be blue
mat.Diffuse = Color.Blue;
// We want it to look slightly dull,
// so maybe a grey wide
highlight mat.Specular = Color.LightGray;
mat.SpecularSharpness = 15.0F;
device.Material = mat;
// Very important –
// without this there is no specularity
device.RenderState.SpecularEnable = true;
}
Nach dem Aufruf dieser Methode werden alle Dreiecke blau und etwas glänzend
dargestellt.
Wie viele andere Einstellungen auch hat die Eigenschaft Material des Device-
Objektes globalen Charakter. Wenn in einer Szene also einige Objekte mit blauem
Material und einige mit gelbem Material dargestellt werden sollen, dann muss
die Material-Einstellung des Device-Objektes zunächst passend für die blauen
Dreiecke eingestellt und es müssen die blauen Dreiecke gezeichnet werden.
Danach muss die Material-Einstellung dem gelben Material entsprechend verändert
und es müssen die gelben Dreiecke gezeichnet werden. Um solche Probleme zu
vermeiden, arbeitet die Beispielanwendung in
Listing 2 nur mit einem Objekt und einem Material. Das dargestellte
Objekt ist hier ein Zylinder, weil die Auswirkung der Eigenschaften Specular
und SpecularSharpness bei runden Objekten besser zu erkennen ist.
Fazit
Mit Texturen und Materialien bietet die Bibliothek Managed Direct3D zwei
Techniken, mit denen Objekte in dreidimensionalen Grafiken wirklichkeitsnah
dargestellt werden können. Sinnvoll eingesetzt, ermöglichen es diese beiden
Techniken, die Qualität der dargestellten Szenen wesentlich zu verbessern, ohne
dass die Zahl der zur Darstellung verwendeten Dreiecke massiv erhöht werden
muss.
Das kann sich in vielen Fällen auch positiv auf die Geschwindigkeit der Ausgabe
auswirken, denn die Berechnung vieler einzelner Dreiecke ist häufig wesentlich
aufwändiger als das Interpolieren von Texturkoordinaten und Normalenvektoren
über eine geringere Zahl von Dreiecken.
Ich habe versucht, Ihnen in diesem dreiteiligen Workshop die Verwendung von
Direct3D zusammen mit Windows Forms näher zu bringen. Der Einsatz dieser
Grafikschnittstelle zusammen mit .NET ist möglich.
Allerdings ist die Vorgehensweise anders, als man sie von GDI+ her kennt.
Ungewohnt ist zum Beispiel sicher die so genannte Game Loop, die schon im
ersten Teil vorgestellt worden ist.
Gespannt darf man auf die weitere Entwicklung von Direct3D in Bezug auf NET
sein. Schließlich dürfte eine komplette Integration der Grafikbibliothek in NET
nicht nur im Sinne der Spieleentwickler sein.
|
 |
 |