Wie in einem anderen Thema schon angekündigt habe ich eine kurze Einführung zum Thema "Shader und PureBasic" geschrieben. Sie deckt natürlich nicht alles ab, da das Thema ziemlich komplex ist und ich neben dem Studium kaum Zeit hierfür finde. So wird HLSL nicht allzu genau erklärt, aber die Zusammenhänge zwischen den Material-Skripte, PureBasic und OGRE sollten daraus ersichtlicher werden. Am Ende wird man ein Programm haben, welches durch einen Shader eine animierte Textur auf der Oberfläche eines Würfels generiert. Einige Erklärungen habe ich in die Kommentare innerhalb der Quelltexte geschrieben, da dort die Zusammenhänge besser ersichtlich sind.
Download Version
Einführend: Wie funktionieren Shader?
Es gibt drei Arten von Shadern: Geometrie-, Vertex- und Fragmentshader. Geometrieshader erzeugen globale Geometriekomplexe und können beispielsweise zum Instanzieren von Modellen verwendet werden. Auf sie werden wir in diesem Tutorial nicht genauer eingehen. Vertexshader betrachten immer nur einen Eckpunkt (Vertex) in der Szene und können keine neuen Eckpunkte oder Flächen entstehen lassen. Sie sind sozusagen ein Callback, der alle Vertexattribute (Position, Texturkoordinaten, Normalen, Tangenten, Materialeigenschaften, ...) entgegennimmt und dann etwas relativ Entwickler-abhängiges zurückgibt. Diese Rückgabe wird ganz allgemein zwischen den Eckpunkten durch baryzentrische Koordinaten interpoliert und es entstehen sogenannte Fragmente. Ein Fragment beschreibt einen Bildpunkt hinsichtlich mehrerer Aspekte, nicht nur die Farbe. Diese werden dem Fragmentshader übergeben und es wird eigentlich wieder ein Fragment ausgegeben (bestehend aus Tiefenwert und Farbe), doch wir beschränken uns auf die Farbe. Ursprünglich konnten nur Fragmentshader auf Texturen zugreifen, aber mittlerweile können Vertexshader das auch. Shader ersetzen die Fixed-Function Pipeline. D.h. die Beleuchtung, Nebel, materielle Einflüsse, ... werden nurnoch über die Shader berechnet. Will man teile der Fixed-Function Pipeline darin haben, so muss man diese selbst berechnen.
Shader in PureBasic
PureBasic hat leider keine eigenen Funktionen Shader direkt in ein Material einzubinden. Jedoch gibt es ja noch die Material-Skripte (Endung: ".material"; Kann mit einem Texteditor geöffnet werden). Hierzu empfehle ich das OGRE-Manual. Einige Modelleditoren erstellen auch schon ein Grundgerüst für ein OGRE-Material-Skript. Das Modell muss auf jeden Fall mit dem Material-Skript in Verbindung gebracht werden. Dies geschieht über den Namen des Materials. Nicht der Dateiname ist wichtig, sondern der Name in der Definition des Materials:
Dieser Name steht dann auch irgendwo in der Modelldatei (Endung: ".mesh") und sofern man dem Entity kein anderes Material zuweist und es mit CreateEntity(..., ..., #PB_Material_None) erstellt wird dieses Material verwendet. Vor PB 4.60 gab es aber keine Möglichkeit einem Entity ein anderes Material zuzuweisen, welches in einem Skript definiert wurde, sondern lediglich die PureBasic Standardmaterialien. Seit PB 4.60 gibt es jedoch GetScriptMaterial(), welches hierbei Abhilfe schaffen soll.material Materialname
{
...
}
Zum ersten Test habe ich einen einfachen Würfel mit einem Material und einer Textur mit Milkshape3D erstellt. Man sollte darauf achten "Export Materials" anzukreuzen beim Exportieren ins OGRE Mesh Format.
Mein Material heißt "CubeMaterial". Die ".mesh" und ".material" Datei packen wir in unser 3D-Archiv, welches wir im PureBasic Code bestimmen (Ich nehme den Unterordner "data/"). Das anfängliche Material-Skript sollte ungefähr so aussehen:
Code: Alles auswählen
material CubeMaterial
{
technique
{
pass
{
ambient 0.2 0.2 0.2 1
diffuse 0.8 0.8 0.8 1
texture_unit
{
texture font.png -1
}
}
}
}
PureBasic Code
Wenn wir nun das ganze mit PureBasic OGRE anzeigen wollen brauchen wir einen Code ähnlich diesem:
Code: Alles auswählen
; Einfaches Check-Makro
Macro Check(Status, Error, EndProgram = 1)
If Not Status
MessageRequester("ERROR", Error)
CompilerIf EndProgram
End
CompilerEndIf
EndIf
EndMacro
; Initialisiere die 3D Engine, die Sprite-Library und die Tastatursteuerung
Check(InitEngine3D(), "InitEngine3D failed.")
Check(InitSprite(), "InitSprite failed.")
Check(InitKeyboard(), "InitKeyboard failed.")
; Wir nehmen den ersten Desktop zur Bestimmung der Auflösung
Check(ExamineDesktops(), "ExamineDesktops failed.")
; Der Unterordner "data" soll all unsere Modelle, Texturen, Material-Skripte, ... enthalten.
Add3DArchive("data", #PB_3DArchive_FileSystem)
; Öffne den Screen
Check(OpenScreen(DesktopWidth(0), DesktopHeight(0), 32, "Shadertest"), "OpenScreen failed.")
; Lade alle Material-Skripte
Parse3DScripts()
; Lade den Würfel
Check(LoadMesh(0, "Cube.mesh"), "LoadMesh failed")
; Erzeuge ein Entity von dem Würfel und nimm das entsprechende Materialskript
Check(CreateEntity(0, MeshID(0), #PB_Material_None), "CreateEntity failed")
; Hier bitte eigene Werte verwenden, dass der Würfel korrekt angezeigt wird
ScaleEntity(0, 0.1, 0.1, 0.1)
RotateEntity(0, 90.0, 0.0, 180.0)
; Erstelle eine Kamera
Check(CreateCamera(0, 0, 0, 100, 100), "CreateCamera failed")
; Verschiebe sie, aber lasse sie zur Nullposition schauen
CameraLocate(0, -5, 5, -10)
CameraLookAt(0, 0, 0, 0)
; Hauptschleife
Repeat
ClearScreen(0)
RenderWorld() ; rendert alles
FlipBuffers()
; Prüfe auf Tastatureingaben
ExamineKeyboard()
Until KeyboardPushed(#PB_Key_Escape)
End
Material-Skript bearbeiten
Um einen HLSL Shader mit dem Material zu verknüpfen muss das Material-Skript etwas angepasst werden. Zunächst deklarieren wir den Vertexshader völlig unabhängig vom Material an sich. Deshalb muss dieser auch außerhalb vom Material-Block stehen. Wir starten mit dem Block-Schlüsselwort "vertex_program", gefolgt von einem Namen des Shaders und dem Typ. In diesem Block muss auf jeden Fall der Dateiname (Schlüsselwort: "source") des Shaders stehen, der Einsprungspunkt (Schlüsselwort: "entry_point", nur HLSL) und die Shaderversion (Schlüsselwort: "target"). Später werden wir im Shaderquelltext allerdings noch die finale Transformationsmatrix benötigen. Sonst bleiben die Vertizen genau so wie im Modell gespeichert und rühren sich nie vom Fleck, egal welche Befehle (ScaleEntity, MoveEntity, RotateEntity, EntityLocate, CameraLocate, ...) verwendet werden um das Objekt zu bewegen. Aus diesem Grund definieren wir mit dem Block-Schlüsselwort "default_params" und dem Schlüsselwort "param_named_auto" darin noch einen Parameter für den Shader, der automatisch von OGRE ausgefüllt wird. Die Parameter, die dort definiert werden könnte man auch durch PureBasic beeinflussen, doch bis zum heutigen Tag fehlen immernoch die nötigen Funktionen dazu.
Code: Alles auswählen
// Vertexshader
// Name: cubeTestVS
// Typ: hlsl
vertex_program cubeTestVS hlsl
{
// Datei: cubetest_vs.hlsl enthält den Quelltext des Vertexshaders
source cubetest_vs.hlsl
// Funktion, die zuerst aufgerufen wird: main
entry_point main
// Shaderversion
target vs_3_0
default_params
{
// Vertexshader sind auch für die korrekte Transformation der Vertizen zuständig.
// Aus diesem Grund benötigen wir die WorldViewProj-Matrix.
param_named_auto worldViewProj worldviewproj_matrix
}
}
Code: Alles auswählen
// Fragmentshader
// Name: cubeTestPS
// Typ: hlsl
fragment_program cubeTestPS hlsl
{
// Datei: cubetest_ps.hlsl enthält den Quelltext des Fragmentshaders
source cubetest_ps.hlsl
// Funktion, die zuerst aufgerufen wird:
entry_point main
// Shaderversion
target ps_3_0
default_params
{
// Die Variable currentTime ist im Shader und enthält den OGRE eigenen Zeitwert
param_named_auto currentTime time
}
}
Code: Alles auswählen
// Vertexshader
// Name: cubeTestVS
// Typ: hlsl
vertex_program cubeTestVS hlsl
{
// Datei: cubetest_vs.hlsl enthält den Quelltext des Vertexshaders
source cubetest_vs.hlsl
// Funktion, die zuerst aufgerufen wird: main
entry_point main
// Shaderversion
target vs_3_0
default_params
{
// Vertexshader sind auch für die korrekte Transformation der Vertizen zuständig.
// Aus diesem Grund benötigen wir die WorldViewProj-Matrix.
param_named_auto worldViewProj worldviewproj_matrix
}
}
// Fragmentshader
// Name: cubeTestPS
// Typ: hlsl
fragment_program cubeTestPS hlsl
{
// Datei: cubetest_ps.hlsl enthält den Quelltext des Fragmentshaders
source cubetest_ps.hlsl
// Funktion, die zuerst aufgerufen wird:
entry_point main
// Shaderversion
target ps_3_0
default_params
{
// Die Variable currentTime ist im Shader und enthält den OGRE eigenen Zeitwert
param_named_auto currentTime time
}
}
material CubeMaterial
{
technique
{
pass
{
ambient 0.2 0.2 0.2 1
diffuse 0.8 0.8 0.8 1
texture_unit
{
texture font.png -1
}
// Ein Verweis auf die 2 Shaderprogramme:
vertex_program_ref cubeTestVS
{
}
fragment_program_ref cubeTestPS
{
}
}
}
}
Vertex-Shader:
Zu HLSL gibt es genügend eigene Tutorials für den Einstieg. Die wichtigste Information ist: die Vertexattributeingabe erfolgt durch Parameter der Einsprungsfunktion und Konstanten (Uniforms), die vom Material-Skript oder später vllt. auch PureBasic Code gesetzt werden können, müssen als solche mit dem "uniform" Schlüsselwort definiert werden. Die Datentypen umfassen Skalare (float, int, ...), Vektoren (float2, float3, ..., auch Farbwerte sind als Vektoren gegeben mit dem komponentenweisen Definitionsbereich 0.0 bis 1.0), Matrizen (float3x3, ...) und Texturen (sampler). Alles, was der Vertexshader zurückgibt wird automatisch auf der Polygonoberfläche interpoliert und an den Fragmentshader übergeben (vgl. Phong vs. Gouraud Schattierung). Die Rückgabe erfolgt über das TEXCOORD0..n Register in HLSL (vgl. GLSL über varyings). Außerdem muss auf jeden Fall die projizierte Position des aktuellen Vertex zurückgegeben werden.
Zunächst benötigen wir eine Struktur, die den Aufbau unseres Rückgabewerts aus der "main" Einsprungsfunktion definiert:
Code: Alles auswählen
struct VS_RESULT {
float2 texCoord: TEXCOORD0;
float4 pos: POSITION;
};
Code: Alles auswählen
VS_RESULT main(float4 pos: POSITION,
float2 texCoord0: TEXCOORD0
)
und genau so ("worldViewProj") muss die Variable auch im Shader heißen. Uniform-Variablen werden außerhalb von Funktionen deklariert. Wir schreiben alsoparam_named_auto worldViewProj worldviewproj_matrix
Code: Alles auswählen
uniform float4x4 worldViewProj;
Code: Alles auswählen
struct VS_RESULT {
float2 texCoord: TEXCOORD0;
float4 pos: POSITION;
};
uniform float4x4 worldViewProj;
VS_RESULT main(float4 pos: POSITION,
float2 texCoord0: TEXCOORD0
)
{
VS_RESULT result;
result.pos = mul(worldViewProj, pos);
result.texCoord = texCoord0;
return result;
}
Fragment-Shader:
Da nach einfachen Zeichnen-Befehlen gefragt wurde muss ich leider etwas Vektor-Rechnung (z.B. Lotfußpunkt zwischen Punkt und Gerade) Voraussetzen. Zunächst definieren wir unsere Konstanten. Neben unserer Zeitangabe finden wir hier auch die Zeichensatztextur:
Code: Alles auswählen
sampler fontTexture: register(s0);
uniform float currentTime;
Die dazugehörige Funktion lautet also
Code: Alles auswählen
float4 drawChar(float2 texCoord, int character, float2 coords, float2 size)
{
// Wir setzen den Standard-Rückgabewert auf Schwarz.
float4 result = float4(0.0f, 0.0f, 0.0f, 1.0f);
// Das Argument texCoord ist die Texturkoordinate unseres aktuellen Fragments,
// coords die Position unseres Zeichens auf dem Objekt
// und size die größe des darzustellenden Zeichens.
// Nun wird texCoord relativ zu dem Bereich gesehen:
float2 normalizedTexCoord = (texCoord - coords) / size;
// nur wenn normalizedTexCoord im Bereich 0..1 liegt ist das Fragment im Bereich des darzustellenden Zeichens auf dem Objekt.
if(normalizedTexCoord.x >= 0.0f && normalizedTexCoord.y >= 0.0f && normalizedTexCoord.x < 1.0f && normalizedTexCoord.y < 1.0f)
{
// wir haben einen Zeichensatz, der bei 32 (Leerzeichen) anfängt. Daher subtrahiere erstmal die 32 von dem Zeichen
character -= 32;
// finde die entsprechende Texturkoordinate auf unserem Zeichensatz
float2 charCoord;
// 32 Zeichen pro Zeile, 3 Zeilen insgesamt
charCoord.y = float(character / 32);
charCoord.x = float(character) - charCoord.y * 32.0f;
charCoord += normalizedTexCoord;
charCoord *= float2(1.0f / 32.0f, 1.0f / 3.0f);
// gebe den Texturwert zurück
result = tex2D(fontTexture, charCoord);
}
return result;
}
Code: Alles auswählen
float4 drawCircle(float2 texCoord, float2 center, float radius)
{
float4 result = float4(0.0f, 0.0f, 0.0f, 1.0f);
// Abstand Texturkoordinate vom Kreiszentrum < Kreisradius?
if(length(texCoord - center) <= radius)
{
result = float4(1.0f, 1.0f, 1.0f, 1.0f);
}
return result;
}
Code: Alles auswählen
float4 drawLine(float2 texCoord, float2 start, float2 end, float width)
{
float4 result = float4(0.0f, 0.0f, 0.0f, 1.0f);
// Richtung der Geraden
float2 dir = end - start;
// Verschiebe alles, sodass texCoord auf dem Ursprung liegt
start -= texCoord;
// Berechne das Lambda der Geradengleichung v = start + lambda * dir, sodass v der [url=http://mathworld.wolfram.com/PerpendicularFoot.html]Lotfußpunkt[/url] ist.
// Es soll aber immer auf der Geraden bleiben, deshalb darf lambda nur im Bereich 0..1 bleiben.
float lambda = clamp(-1.0f * (start.x*dir.x + start.y*dir.y) / (dir.x * dir.x + dir.y * dir.y), 0.0f, 1.0f);
// Wenn der Abstand des Punktes zu unserer texCoord (nun der Ursprung) kleiner als die Geradenbreite ist, gebe Weiß zurück.
if(length(start + lambda * dir) <= width)
{
result = float4(1.0f, 1.0f, 1.0f, 1.0f);
}
return result;
}
Code: Alles auswählen
// Eingabe: Fragment-Texturkoordinate und Fragment-Position
float4 main(float2 texCoord: TEXCOORD0,
float4 pos: POSITION
) : COLOR0
{
float weight;
// Hintergrundfarbe: Dunkelblau.
float4 result = float4(0.0f, 0.0f, 0.1f, 1.0f);
// Wir addieren zunächst die Farbwerte des Kreises, den wir zentriert Zeichnen wollen multipliziert mit Gelb,
// d.h. wenn drawCircle Weiß zurückgibt wird 100% Gelb daraus und bei Schwarz bleibt es Schwarz.
result += drawCircle(texCoord, float2(0.5f, 0.5f), 0.3f) * float4(1.0f, 1.0f, 0.0f, 1.0f);
// Für unsere Linie wollen wir nicht die Farbe multipliziert mit Blau addieren,
// sonst würde Gelb vom Kreis + Blau von der Linie = Weiß ergeben. Wir wollen
// die Linie komplett in Blau haben.
// Deshalb nehmen wir die Länge des Farbvektors (außer alpha) als Gewichtung.
// Das Anhängsel .xyz erstellt aus dem float4 einfach einen float3 Vektor mit
// den x, y und z Komponenten des ursprünglichen float4 Vektors und schließt
// somit den Alpha-Wert aus.
// Die Linie soll im Zentrum anfangen und durch den currentTime-Wert rotieren.
weight = length(drawLine(texCoord, float2(0.5f, 0.5f), float2(0.5f, 0.5f) + float2(cos(currentTime), sin(currentTime)) * 0.5f, 0.01f).xyz) * 0.5f;
// Wir Gewichten nun Dunkelblau mit weight und den alten result-Wert mit (1.0 - weight)
result = weight * float4(0.0f, 0.0f, 0.4f, 1.0f) + (1.0 - weight) * result;
// Da wir wissen, dass sich der Text nicht überschneidet, addieren wir
// einfach die einzelnen Werte von den jeweiligen Zeichen an ihrer
// jeweiligen Position:
float4 text = float4(0.0f, 0.0f, 0.0f, 1.0f);
float2 charSize = float2(0.05f, 0.05f);
texCoord -= float2(0.2f, 0.2f);
text += drawChar(texCoord, 'H', float2(0.00f, 0.0f), charSize);
text += drawChar(texCoord, 'a', float2(0.05f, 0.0f), charSize);
text += drawChar(texCoord, 'l', float2(0.10f, 0.0f), charSize);
text += drawChar(texCoord, 'l', float2(0.15f, 0.0f), charSize);
text += drawChar(texCoord, 'o', float2(0.20f, 0.0f), charSize);
text += drawChar(texCoord, ',', float2(0.25f, 0.0f), charSize);
text += drawChar(texCoord, 'P', float2(0.35f, 0.0f), charSize);
text += drawChar(texCoord, 'B', float2(0.40f, 0.0f), charSize);
text += drawChar(texCoord, '!', float2(0.45f, 0.0f), charSize);
// Erstelle wieder einen Gewichtungswert aus text.xyz wie bei der Linie auch:
weight = length(text.xyz) * 0.5f;
// Und gewichte exakt wie bei der Linie, nur mit Rot statt Dunkelblau:
result = weight * float4(1.0f, 0.0f, 0.0f, 1.0f) + (1.0 - weight) * result;
// Gebe den Farbwert zurück
return result;
}
Code: Alles auswählen
sampler fontTexture: register(s0);
uniform float currentTime;
float4 drawChar(float2 texCoord, int character, float2 coords, float2 size)
{
// Wir setzen den Standard-Rückgabewert auf Schwarz.
float4 result = float4(0.0f, 0.0f, 0.0f, 1.0f);
// Das Argument texCoord ist die Texturkoordinate unseres aktuellen Fragments,
// coords die Position unseres Zeichens auf dem Objekt
// und size die größe des darzustellenden Zeichens.
// Nun wird texCoord relativ zu dem Bereich gesehen:
float2 normalizedTexCoord = (texCoord - coords) / size;
// nur wenn normalizedTexCoord im Bereich 0..1 liegt ist das Fragment im Bereich des darzustellenden Zeichens auf dem Objekt.
if(normalizedTexCoord.x >= 0.0f && normalizedTexCoord.y >= 0.0f && normalizedTexCoord.x < 1.0f && normalizedTexCoord.y < 1.0f)
{
// wir haben einen Zeichensatz, der bei 32 (Leerzeichen) anfängt. Daher subtrahiere erstmal die 32 von dem Zeichen
character -= 32;
// finde die entsprechende Texturkoordinate auf unserem Zeichensatz
float2 charCoord;
// 32 Zeichen pro Zeile, 3 Zeilen insgesamt
charCoord.y = float(character / 32);
charCoord.x = float(character) - charCoord.y * 32.0f;
charCoord += normalizedTexCoord;
charCoord *= float2(1.0f / 32.0f, 1.0f / 3.0f);
// gebe den Texturwert zurück
result = tex2D(fontTexture, charCoord);
}
return result;
}
float4 drawCircle(float2 texCoord, float2 center, float radius)
{
float4 result = float4(0.0f, 0.0f, 0.0f, 1.0f);
// Abstand Texturkoordinate vom Kreiszentrum < Kreisradius?
if(length(texCoord - center) <= radius)
{
result = float4(1.0f, 1.0f, 1.0f, 1.0f);
}
return result;
}
float4 drawLine(float2 texCoord, float2 start, float2 end, float width)
{
float4 result = float4(0.0f, 0.0f, 0.0f, 1.0f);
// Richtung der Geraden
float2 dir = end - start;
// Verschiebe alles, sodass texCoord auf dem Ursprung liegt
start -= texCoord;
// Berechne das Lambda der Geradengleichung v = start + lambda * dir, sodass v der [url=http://mathworld.wolfram.com/PerpendicularFoot.html]Lotfußpunkt[/url] ist.
// Es soll aber immer auf der Geraden bleiben, deshalb darf lambda nur im Bereich 0..1 bleiben.
float lambda = clamp(-1.0f * (start.x*dir.x + start.y*dir.y) / (dir.x * dir.x + dir.y * dir.y), 0.0f, 1.0f);
// Wenn der Abstand des Punktes zu unserer texCoord (nun der Ursprung) kleiner als die Geradenbreite ist, gebe Weiß zurück.
if(length(start + lambda * dir) <= width)
{
result = float4(1.0f, 1.0f, 1.0f, 1.0f);
}
return result;
}
// Eingabe: Fragment-Texturkoordinate und Fragment-Position
float4 main(float2 texCoord: TEXCOORD0,
float4 pos: POSITION
) : COLOR0
{
float weight;
// Hintergrundfarbe: Schwarz.
float4 result = float4(0.0f, 0.0f, 0.1f, 1.0f);
// Wir addieren zunächst die Farbwerte des Kreises, den wir zentriert Zeichnen wollen multipliziert mit Gelb,
// d.h. wenn drawCircle Weiß zurückgibt wird 100% Gelb daraus und bei Schwarz bleibt es Schwarz.
result += drawCircle(texCoord, float2(0.5f, 0.5f), 0.3f) * float4(1.0f, 1.0f, 0.0f, 1.0f);
// Für unsere Linie wollen wir nicht die Farbe multipliziert mit Blau addieren,
// sonst würde Gelb vom Kreis + Blau von der Linie = Weiß ergeben. Wir wollen
// die Linie komplett in Blau haben.
// Deshalb nehmen wir die Länge des Farbvektors (außer alpha) als Gewichtung.
// Das Anhängsel .xyz erstellt aus dem float4 einfach einen float3 Vektor mit
// den x, y und z Komponenten des ursprünglichen float4 Vektors und schließt
// somit den Alpha-Wert aus.
// Die Linie soll im Zentrum anfangen und durch den currentTime-Wert rotieren.
weight = length(drawLine(texCoord, float2(0.5f, 0.5f), float2(0.5f, 0.5f) + float2(cos(currentTime), sin(currentTime)) * 0.5f, 0.01f).xyz) * 0.5f;
// Wir Gewichten nun Dunkelblau mit weight und den alten result-Wert mit (1.0 - weight)
result = weight * float4(0.0f, 0.0f, 0.4f, 1.0f) + (1.0 - weight) * result;
// Da wir wissen, dass sich der Text nicht überschneidet, addieren wir
// einfach die einzelnen Werte von den jeweiligen Zeichen an ihrer
// jeweiligen Position:
float4 text = float4(0.0f, 0.0f, 0.0f, 1.0f);
float2 charSize = float2(0.05f, 0.05f);
texCoord -= float2(0.2f, 0.2f);
text += drawChar(texCoord, 'H', float2(0.00f, 0.0f), charSize);
text += drawChar(texCoord, 'a', float2(0.05f, 0.0f), charSize);
text += drawChar(texCoord, 'l', float2(0.10f, 0.0f), charSize);
text += drawChar(texCoord, 'l', float2(0.15f, 0.0f), charSize);
text += drawChar(texCoord, 'o', float2(0.20f, 0.0f), charSize);
text += drawChar(texCoord, ',', float2(0.25f, 0.0f), charSize);
text += drawChar(texCoord, 'P', float2(0.35f, 0.0f), charSize);
text += drawChar(texCoord, 'B', float2(0.40f, 0.0f), charSize);
text += drawChar(texCoord, '!', float2(0.45f, 0.0f), charSize);
// Erstelle wieder einen Gewichtungswert aus text.xyz wie bei der Linie auch:
weight = length(text.xyz) * 0.5f;
// Und gewichte exakt wie bei der Linie, nur mit Rot statt Dunkelblau:
result = weight * float4(1.0f, 0.0f, 0.0f, 1.0f) + (1.0 - weight) * result;
// Gebe den Farbwert zurück
return result;
}
Ergebnis
Erweitertes Thema: Kommunikation zwischen PureBasic und dem Shader
Da PureBasic keine direkten Funktionen zur Verfügung stellt um die Uniform-Variablen zu ändern müssen wir leider einen Umweg über sowas wie Lichtquellen gehen.
Änderungen im PureBasic Code
Wir werden dem Shader die Zeitangabe über die X Position einer Lichtquelle übergeben. Hierzu benötigen wir erstmal eine Lichquelle, die irgendwo am Anfang erstellt wird.
Code: Alles auswählen
; Erstelle eine Lichtquelle zur Kommunikation mit dem Shader (Zeit wird als X-Position übergeben):
Define Time.f
CreateLight(0, 0, Time, 0.0, 0.0)
Code: Alles auswählen
; Hauptschleife
Repeat
ClearScreen(0)
RenderWorld() ; rendert alles
FlipBuffers()
; Prüfe auf Tastatureingaben
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Left)
Time - 0.025
LightLocate(0, Time, 0.0, 0.0)
ElseIf KeyboardPushed(#PB_Key_Right)
Time + 0.025
LightLocate(0, Time, 0.0, 0.0)
EndIf
Until KeyboardPushed(#PB_Key_Escape)
Änderungen im Material
Die einzige Änderung die wir im Material nun benötigen ist die Quelle für den Wert unserer currentTime Shadervariable:
Code: Alles auswählen
param_named_auto currentTime light_position 0
Änderungen im Shader-Quelltext
Die Variable "currentTime" muss nun als float4 deklariert werden, da die Lichtposition nunmal ein Vektor ist:
Code: Alles auswählen
uniform float4 currentTime;
Code: Alles auswählen
weight = length(drawLine(texCoord, float2(0.5f, 0.5f), float2(0.5f, 0.5f) + float2(cos(currentTime.x), sin(currentTime.x)) * 0.5f, 0.01f).xyz) * 0.5f;
-------------------------------------------------------------
Links zum Thema:
HLSL Dokumentation - http://msdn.microsoft.com/en-us/library ... S.85).aspx (14.08.2011)
Writing HLSL Shaders in DirectX9 - http://msdn.microsoft.com/en-us/library ... s.85).aspx (14.08.2011)
OgreManual Inhaltsverzeichnis - http://www.ogre3d.org/docs/manual/manua ... C_Contents (09.08.2011)
OgreManual Material-Skripte - http://www.ogre3d.org/docs/manual/manual_14.html#SEC23 (09.08.2011)
OgreManual Declaring Vertex/Fragment Shaders - http://www.ogre3d.org/docs/manual/manual_18.html#SEC104 (09.08.2011)
Baryzentrische Koordinaten - http://math.fau.edu/Yiu/barycentricpaper.pdf (09.08.2011)
Lotfußpunkt - http://mathworld.wolfram.com/PerpendicularFoot.html (14.08.2011)