Ich versuche , meinen Kopf herum zu wickeln , wie Materialsysteme wie diese , diese umgesetzt werden. Diese leistungsstarken und benutzerfreundlichen grafischen Systeme scheinen relativ häufig zu sein, um Programmierern und Nicht-Programmierern das schnelle Erstellen von Shadern zu ermöglichen. Aufgrund meiner relativ begrenzten Erfahrung mit Grafikprogrammierung bin ich mir jedoch nicht ganz sicher, wie sie funktionieren.
Hintergrund:
Wenn ich zuvor einfache OpenGL-Rendering-Systeme programmiert habe , erstelle ich normalerweise eine Materialklasse, die Shader aus statischen GLSL-Dateien lädt, kompiliert und verknüpft, die ich manuell erstellt habe. Normalerweise erstelle ich diese Klasse auch als einfachen Wrapper für den Zugriff auf einheitliche GLSL-Variablen. Stellen Sie sich als einfaches Beispiel vor, ich habe einen einfachen Vertex-Shader und Fragment-Shader mit einer extra einheitlichen Texture2D zum Übergeben einer Textur. Meine Material-Klasse würde diese beiden Shader einfach laden und zu einem Material kompilieren und von diesem Punkt an eine einfache Schnittstelle zum Lesen / Schreiben der Texture2D-Uniform dieses Shaders bereitstellen.
Um dieses System ein wenig flexibler zu gestalten, schreibe ich es normalerweise so, dass ich versuchen kann, Uniformen eines beliebigen Namens / Typs zu übergeben [dh: SetUniform_Vec4 ("AmbientColor", colorVec4); Dies würde die AmbientColor-Uniform auf einen bestimmten 4d-Vektor namens "colorVec4" setzen, wenn diese Uniform im Material vorhanden ist.] .
class Material
{
private:
int shaderID;
string vertShaderPath;
string fragSahderPath;
void loadShaderFiles(); //load shaders from files at internal paths.
void buildMaterial(); //link, compile, buffer with OpenGL, etc.
public:
void SetGenericUniform( string uniformName, int param );
void SetGenericUniform( string uniformName, float param );
void SetGenericUniform( string uniformName, vec4 param );
//overrides for various types, etc...
int GetUniform( string uniformName );
float GetUniform( string uniformName );
vec4 GetUniform( string uniformName );
//etc...
//ctor, dtor, etc., omitted for clarity..
}
Dies funktioniert, aber es fühlt sich wie ein schlechtes System an, da der Kunde der Materialklasse nur im Glauben auf Uniformen zugreifen muss - der Benutzer muss sich der Uniformen in jedem Materialobjekt einigermaßen bewusst sein, weil er dazu gezwungen ist Übergeben Sie sie mit ihrem GLSL-Namen. Es ist keine große Sache, wenn nur 1-2 Personen mit dem System arbeiten, aber ich kann mir nicht vorstellen, dass dieses System überhaupt sehr gut skaliert werden kann, und bevor ich meinen nächsten Versuch unternehme, ein OpenGL-Rendering-System zu programmieren, möchte ich ein Level erstellen ein bisschen nach oben.
Frage:
Dort bin ich bisher und habe versucht zu untersuchen, wie andere Rendering-Engines mit ihren Materialsystemen umgehen.
Dieser knotenbasierte Ansatz ist großartig und scheint ein äußerst verbreitetes System zur Erstellung benutzerfreundlicher Materialsysteme in modernen Motoren und Werkzeugen zu sein. Soweit ich weiß, basieren sie auf einer Diagrammdatenstruktur, in der jeder Knoten einen Shader-Aspekt Ihres Materials darstellt und jeder Pfad eine Art Beziehung zwischen ihnen darstellt.
Soweit ich weiß, wäre die Implementierung eines solchen Systems eine so einfache MaterialNode-Klasse mit einer Vielzahl von Unterklassen (TextureNode, FloatNode, LerpNode usw.). Wo jede MaterialNode-Unterklasse MaterialConnections haben würde.
class MaterialConnection
{
MatNode_Out * fromNode;
MatNode_In * toNode;
}
class LerpNode : MaterialNode
{
MatNode_In x;
MatNode_In y;
MatNode_In alpha;
MatNode_Out result;
}
Das ist die Grundidee , aber ich bin mir ein bisschen unsicher, wie einige Aspekte dieses Systems funktionieren würden:
1.) Wenn Sie sich die verschiedenen 'Material Expressions' (Knoten) ansehen , die Unreal Engine 4 verwendet , werden Sie feststellen, dass sie jeweils über Eingangs- und Ausgangsverbindungen verschiedener Typen verfügen. Einige Knoten geben Floats aus, einige Ausgabevektoren2, einige Ausgabevektoren4 usw. Wie kann ich die obigen Knoten und Verbindungen verbessern, damit sie eine Vielzahl von Eingabe- und Ausgabetypen unterstützen können? Wäre es eine kluge Wahl, MatNode_Out mit MatNode_Out_Float und MatNode_Out_Vec4 (und so weiter) zu unterklassifizieren?
2.) Wie hängt diese Art von System mit GLSL-Shadern zusammen? Bei erneuter Betrachtung von UE4 (und ähnlich wie bei den anderen oben verlinkten Systemen) muss der Benutzer schließlich einen Materialknoten in einen großen Knoten mit verschiedenen Parametern einstecken, die Shader-Parameter darstellen (Grundfarbe, Metallizität, Glanz, Emissionsgrad usw.). . Meine ursprüngliche Annahme war, dass UE4 eine Art hartcodierten "Master-Shader" mit einer Vielzahl von Uniformen hatte und alles, was der Benutzer in seinem "Material" tut, einfach an den "Master-Shader" übergeben wird, wenn er seine Knoten in den "Master" einfügt. Hauptknoten '.
In der UE4-Dokumentation heißt es jedoch:
"Jeder Knoten enthält einen Ausschnitt aus HLSL-Code, der für die Ausführung einer bestimmten Aufgabe bestimmt ist. Dies bedeutet, dass Sie beim Erstellen eines Materials HLSL-Code durch visuelles Scripting erstellen."
Wenn das stimmt, generiert dieses System ein echtes Shader-Skript? Wie genau funktioniert das?