www.rolandk.de
- Aktuelle Themen zu .Net -
Achtung: Hier handelt es sich um meine alte Seite.
Die aktuelle ist unter folgendem Link erreichbar: www.rolandk.de/wp/
Home Tutorials DirectX 11 Basics Chapter 6: Blending




















































Chapter 6: Blending
Sonntag, den 28. Oktober 2012 um 09:14 Uhr

 

 

Mixing colors

In diesem Kapitel schauen wir uns an, wie man mehrere Objekte übereinander zeichnet und dabei deren Farben vermischt. Doch wozu brauchen wir sowas überhaupt? Ein klassisches Beispiel dafür sind transparente Objekte. Anders, als man vielleicht als Einsteiger vermuten mag, wird sowas nicht mittels irgendeines Transparent-Flags erreicht, sondern mithilfe der in diesem Tutorial vorgestellten Technik "Blending". Zusätzlich zeige ich in diesem Artikel, wie man die Farben einer Textur innerhalb des Shaders abändern kann. In obigen Screenshot sehen wir ja sehr viele Sterne - jeder mit einer anderen Farbe. Es wird allerdings nur eine einzige Textur dafür verwendet, und zwar folgende:

 

 

Die Pixel dieser Textur speichern lediglich die Intensität eines Pixels - Schwarz ist somit komplett durchsichtig, Weiß komplett undurchsichtig, usw.. Im Shader werden diese Informationen schließlich mit der tatsächlichen Farbe verrechnet. 

Doch was verbirgt sich hinter dem Thema Blending überhaupt? Sobald Blending aktiviert ist, errechnet sich jeder neu gezeichnete Pixel aus folgender Formel:

 

output = sourcecolor * [sourceoption] [operation] destinationcolor * [destinationoption]

 

Das sieht auf den ersten Blick mehr als komisch aus, ist aber wie alles Andere dann doch nicht so schwer, wie es aussieht. Output ist die Farbe, auf welche der Pixel schließlich gesetzt wird. Nach dem Gleichheitszeichen folgt dann das Produkt sourcecolor * [sourceoption]. Sourcecolor ist die Farbe des Pixels, so wie sie vom Pixelshader zurückgegeben wurde. Den Wert für [sourceoption] kann man selber bestimmen. Dieser kann beispielsweise 1 sein (keine Änderung), 0 (Immer auf Schwarz setzen) oder auch der Alpha-Wert des Pixels. Bei [operation] handelt es sich nicht um einen Wert, sondern um ein Rechenzeichen. Hiermit kann also festgelegt werden, ob dort ein +, ein - oder etwas anders steht. Beim letzten Produkt destinationcolor * [destinationoption] verhält es sich wieder genauso, wie beim ersten, mit dem Unterschied, dass es sich diesmal um die Farbe des Pixels handelt, welcher schon auf dem Bildschirm zu sehen ist/wäre.

 

Der Shader

Im Prinzip ändern sich im Vergleich zum Shader des letzten Kapitels nur 2 Kleinigkeiten. Zum Einen benötigen wir eine neue Shaderkonstante. Diese soll die Farbe des Sterns enthalten:

  1. float4 StarColor;

Zum Anderen müssen wir diese Farbe noch mit dem Schwarzweiß-Ton aus der Textur vermischen. Das geschieht direkt im Pixelshader, und zwar indem wir einfach die Farbe aus der Textur mit der Farbe aus der neuen Konstanten multiplizieren. Warum multiplizieren? Auf der Grafikkarte wird eine Farbe als Vektor mit 4 Komponenten dargestellt. Jede Komponente hat einen Wertebereich von 0 bis 1. Bei Rot wäre beispielsweise 1 komplett Rot und 0 gar nichts. 0,5 wäre dann quasi dunkles Rot usw.. Aus diesem Grund kann man die Farben auch problemlos miteinander multiplizieren. Der Pixelshader sieht dann so aus:

  1. float4 PS( PS_IN input ) : SV_Target
  2. {
  3. return Texture.Sample(stateLinear, input.tex) * StarColor;
  4. }

Das war es dann auch schon, alles andere im Shader ist purer Standard.

 

Blending und Animation

Auf der Seite des C# Programms schaut die Sache schon etwas umfangreicher aus. Zunächst benötigen wir hier eine neue Klasse "Stars", welche das 3D-Objekt eines Sterns und Daten über Position und Farbe aller Sterne hält. Zusätzlich kümmert sich diese Klasse auch um das Rendering und um eine fließende Animation. Klingt jetzt nach viel Arbeit, ist es aber glücklicherweise nicht. Beginnen wir mit den Daten, welche wir für einen Stern benötigen - also Farbe und Position. Diese speichern wir mithilfe einer Subklasse, welche dann folgendermaßen aussieht:

  1. class StarInstance
  2. {
  3. public Vector4 Color;
  4. public float Angle;
  5. public float Distance;
  6. }

Die Farbe bilden wir hier so ab, wie sie von der Grafikkarte auch verarbeitet wird, also als Vektor mit 4 Komponenten. Die Position eines Sterns ergibt sich aus dem aktuellen Winkel und der Entfernung zum Mittelpunkt. 

So, machen wir weiter mit den Membervariablen der Klasse Stars:

  1. private const int STARS_COUNT = 50;
  2.  
  3. private List m_stars;
  4. private Random m_randomizer;
  5. private DX11.Buffer m_vertexBuffer;
  6. private DX11.InputLayout m_vertexLayout;
  7. private DX11.Effect m_effect;
  8. private DX11.EffectTechnique m_effectTechnique;
  9. private DX11.EffectPass m_effectPass;
  10. private DX11.EffectMatrixVariable m_transformVariable;
  11. private DX11.EffectVectorVariable m_colorVariable;
  12. private DX11.Texture2D m_texture;
  13. private DX11.EffectResourceVariable m_resourceVariable;
  14. private DX11.ShaderResourceView m_textureView;
  15. private DX11.BlendState m_blendState;

Ganz oben haben wir zunächst eine Konstante. Die speichert einfach nur die Anzahl der Sterne, die wir später anzeigen und animieren wollen. Weiter geht's mit einer Liste von StarInstance Objekten, welche Informationen über alle Sterne hält. Danach kommt erst mal Standard, ich denke, dazu brauch ich nichts mehr schreiben. Allerdings ist der letzte Member vom Typ BlendState noch neu. Ein BlendState hält Informationen über den aktuellen Blending-Status der Hardware. An diesem Objekt wird also konfiguriert, ob und wie die gezeichneten Farben mit dem bereits auf dem Bildschirm vorhandenen vermischt werden sollen. 

Als nächstes kümmern wir uns um die LoadResources Methode. Hier legen wir wieder zu erst die benötigten Vertices fest. Für unseren Stern benötigen wir eigentlich bloß ein flaches Quadrat (Die Vertex-Struktur ist übrigens dieselbe, wie bei Kapitel 4):

  1. Vertex[] vertices = new Vertex[]
  2. {
  3. new Vertex(new Vector3(-1f, -1f, 0f), new Vector2(0, 0)),
  4. new Vertex(new Vector3(-1f, 1f, 0f), new Vector2(0, 1f)),
  5. new Vertex(new Vector3(1f, 1f, 0f), new Vector2(1f, 1f)),
  6. new Vertex(new Vector3(1f, 1f, 0f), new Vector2(1f, 1f)),
  7. new Vertex(new Vector3(1f, -1f, 0f), new Vector2(1f, 0)),
  8. new Vertex(new Vector3(-1f, -1f, 0f), new Vector2(0, 0))
  9. };

Als nächsten folgt dann Bekanntes: Den VertexBuffer erzeugen, den Shader laden, die Textur laden und das InputLayout erzeugen. Einzig beim Laden des Shaders müssen wir auch unsere neue Shaderkonstante berücksichtigen, welche wir als Vektor betrachten:

  1. m_colorVariable = m_effect.GetVariableByName("StarColor").AsVector();

Soweit noch nicht viel Neues. Jetzt müssen wir uns noch um das Blending kümmern. Hierzu müssen wir ein BlendState Objekt erzeugen. Doch bevor wir das tun können, müssen wir eine BlendStateDescription Instanz mit allen nötigen Daten füllen - wichtig, alle Member müssen hier gesetzt werden, andernfalls gibt es später eine Exception. Das ganze sieht dann so aus:

  1. DX11.BlendStateDescription blendDesc = new DX11.BlendStateDescription();
  2. blendDesc.RenderTargets[0].BlendOperation = DX11.BlendOperation.Add;
  3. blendDesc.RenderTargets[0].BlendOperationAlpha = DX11.BlendOperation.Add;
  4. blendDesc.RenderTargets[0].SourceBlendAlpha = DX11.BlendOption.Zero;
  5. blendDesc.RenderTargets[0].DestinationBlendAlpha = DX11.BlendOption.Zero;
  6. blendDesc.RenderTargets[0].SourceBlend = DX11.BlendOption.SourceColor;
  7. blendDesc.RenderTargets[0].DestinationBlend = DX11.BlendOption.One;
  8. blendDesc.AlphaToCoverageEnable = false;
  9. blendDesc.RenderTargets[0].BlendEnable = true;
  10. blendDesc.RenderTargets[0].RenderTargetWriteMask = DX11.ColorWriteMaskFlags.All;
  11. m_blendState = DX11.BlendState.FromDescription(device, blendDesc);

Bei Blending gibt es grundsätzlich 2 Arten, das normale Blending und das AlphaBlending. AlphaBlending benötigen wir erst einmal nicht, somit setzen wir diese Member auf Standardwerte. Für uns interessant sind die Member BlendOperation, SourceBlend und DestinationBlend. Wie diese funktionieren, ist bereits weiter oben beschrieben. Allerdings noch zur Vollständigkeit: Dem Member SourceBlend habe ich absichtlich den Wert BlendOption.SourceColor zugewiesen, um die Helligkeit etwas abzuschwächen. Selbstverständlich sollte hier normalerweise BlendOption.One zugewiesen werden. Zusätzlich müssen wir noch eine Schreibmaske setzen (legt fest, welche Farbkomponenten geschrieben werden dürfen) und das Blending überhaupt aktivieren (SetBlendEnable). In der letzten Zeile wird schließlich das BlendState Objekt erzeugt. 

Unsere letzte Aufgabe in der LoadResources Methode ist das Anlegen der Sterne selbst. Hierzu erzeugen und füllen wir eine Liste mit StarInstance Objekten. Jeden Stern belegen wir mit jeweils einer anderen Position und einer zufälligen Farbe:

  1. m_stars = new List();
  2. m_randomizer =new Random(Environment.TickCount);
  3. for (int loop = 1; loop <= STARS_COUNT; loop++)
  4. {
  5. StarInstance newInstance = new StarInstance();
  6. newInstance.Angle = 0f;
  7. newInstance.Distance = ((float)loop / (float)STARS_COUNT) * 5f;
  8. newInstance.Color = new Vector4(
  9. m_randomizer.Next(128, 255) / 255f,
  10. m_randomizer.Next(128, 255) / 255f,
  11. m_randomizer.Next(128, 255) / 255f,
  12. 1f);
  13. m_stars.Add(newInstance);
  14. }

Zum Abschluss müssen wir das alles in der Render Methode noch zeichnen. Diesmal sieht das etwas komplizierter aus, als in den letzten Kapiteln:

  1. DX11.DeviceContext deviceContext = device.ImmediateContext;
  2.  
  3. deviceContext.InputAssembler.InputLayout = m_vertexLayout;
  4. deviceContext.InputAssembler.PrimitiveTopology = DX11.PrimitiveTopology.TriangleList;
  5. deviceContext.InputAssembler.SetVertexBuffers(
  6. 0,
  7. new DX11.VertexBufferBinding(m_vertexBuffer, Marshal.SizeOf(typeof(Vertex)), 0));
  8.  
  9. m_resourceVariable.SetResource(m_textureView);
  10. deviceContext.OutputMerger.BlendState = m_blendState;
  11.  
  12. for(int loop=0 ; loop < STARS_COUNT; loop++)
  13. {
  14. StarInstance actStar = m_stars[loop];
  15.  
  16. m_colorVariable.Set(actStar.Color);
  17. m_transformVariable.SetMatrix(
  18. (world * Matrix.Translation(actStar.Distance, 0f, 0f) *
  19. Matrix.RotationZ(actStar.Angle)) * viewProj);
  20.  
  21. m_effectPass.Apply(deviceContext);
  22. deviceContext.Draw(6, 0);
  23.  
  24. actStar.Angle += ((float)loop / (float)STARS_COUNT * 2f % 1f) / 57f;
  25. if (actStar.Angle > (float)Math.PI * 2f) { actStar.Angle = actStar.Angle - (float)Math.PI * 2f; }
  26.  
  27. actStar.Distance = actStar.Distance - 0.01f;
  28. if (actStar.Distance < 0f)
  29. {
  30. actStar.Distance = 5.0f;
  31. actStar.Color = new Vector4(
  32. m_randomizer.Next(128, 255) / 255f,
  33. m_randomizer.Next(128, 255) / 255f,
  34. m_randomizer.Next(128, 255) / 255f,
  35. 1f);
  36. }
  37. }

Wirklich neu hier ist eigentlich nur das Setzen des BlendStates kurz vor der for-Schleife. Alles andere Neue bezieht sich mehr auf die Animation. Ich denke, dazu brauch ich nicht viel schreiben. 

Zum Abschluss dieses Kapitels ist noch zu sagen, dass der Depth-Buffer deaktiviert sein muss. Im Prinzip heißt dass lediglich, dass man im Hauptfenster den DepthBuffer gar nicht erst initialisieren braucht.

 

Kommentar hinzufügen

Ihr Name:
Kommentar: